mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-05-14 13:16:54 +00:00
Merge pull request #10 from reactiveops/jg/add-config-probes-tags
add image and healthcheck config and checks
This commit is contained in:
@@ -17,6 +17,9 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- run: dep ensure
|
||||
- run: go get -u github.com/golang/lint/golint
|
||||
- run: go list ./... | grep -v vendor | xargs golint
|
||||
- run: go list ./... | grep -v vendor | xargs go vet
|
||||
- run: go test ./pkg/... -coverprofile cover.out
|
||||
|
||||
build:
|
||||
|
||||
@@ -77,28 +77,15 @@ data:
|
||||
memory:
|
||||
min: 10m
|
||||
max: 2000M
|
||||
ingresses:
|
||||
whitelist:
|
||||
- '*.example.com'
|
||||
prevent_overlaps: true
|
||||
health_checks:
|
||||
healthChecks:
|
||||
readiness:
|
||||
require: true
|
||||
liveness:
|
||||
require: true
|
||||
images:
|
||||
require_tag: true
|
||||
repos:
|
||||
whitelist:
|
||||
- gcr.io
|
||||
namespaces:
|
||||
require_labels: true
|
||||
security_context:
|
||||
capabilities:
|
||||
whitelist:
|
||||
- 'CAP_SYS_ADMIN'
|
||||
prevent_privileged: true
|
||||
read_only_file_system: true
|
||||
tagRequired: true
|
||||
whitelistRepos:
|
||||
- gcr.io
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
|
||||
@@ -5,28 +5,35 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
// ResourceMinMax sets a range for a min and max setting for a resource.
|
||||
type ResourceMinMax struct {
|
||||
Min *resource.Quantity
|
||||
Max *resource.Quantity
|
||||
}
|
||||
|
||||
// ResourceList maps the resource name to a range on min and max values.
|
||||
type ResourceList map[corev1.ResourceName]ResourceMinMax
|
||||
|
||||
// RequestsAndLimits contains config for resource requests and limits.
|
||||
type RequestsAndLimits struct {
|
||||
Requests ResourceList
|
||||
Limits ResourceList
|
||||
}
|
||||
|
||||
// Configuration contains all of the config for the validation checks.
|
||||
type Configuration struct {
|
||||
Resources RequestsAndLimits
|
||||
Resources RequestsAndLimits
|
||||
HealthChecks Probes
|
||||
Images Images
|
||||
}
|
||||
|
||||
// ParseFile parses config from a file
|
||||
// ParseFile parses config from a file.
|
||||
func ParseFile(path string) (Configuration, error) {
|
||||
rawBytes, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -35,7 +42,7 @@ func ParseFile(path string) (Configuration, error) {
|
||||
return Parse(rawBytes)
|
||||
}
|
||||
|
||||
// Parse parses config from a byte array
|
||||
// Parse parses config from a byte array.
|
||||
func Parse(rawBytes []byte) (Configuration, error) {
|
||||
reader := bytes.NewReader(rawBytes)
|
||||
conf := Configuration{}
|
||||
@@ -49,3 +56,20 @@ func Parse(rawBytes []byte) (Configuration, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Probes contains config for the readiness and liveness probes.
|
||||
type Probes struct {
|
||||
Readiness ResourceRequire
|
||||
Liveness ResourceRequire
|
||||
}
|
||||
|
||||
// ResourceRequire indicates if this resource should be validated.
|
||||
type ResourceRequire struct {
|
||||
Require bool
|
||||
}
|
||||
|
||||
// Images contains the config for images.
|
||||
type Images struct {
|
||||
TagRequired bool
|
||||
WhitelistRepos []string
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
var resourceConfInvalid1 = `test`
|
||||
|
||||
var resourceConfYaml1 = `---
|
||||
var resourceConfYAML1 = `---
|
||||
resources:
|
||||
requests:
|
||||
cpu:
|
||||
@@ -41,7 +41,7 @@ resources:
|
||||
max: 4G
|
||||
`
|
||||
|
||||
var resourceConfJson1 = `{
|
||||
var resourceConfJSON1 = `{
|
||||
"resources": {
|
||||
"requests": {
|
||||
"cpu": {
|
||||
@@ -72,7 +72,7 @@ func TestParseError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseYaml(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(resourceConfYaml1))
|
||||
parsedConf, err := Parse([]byte(resourceConfYAML1))
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
requests := parsedConf.Resources.Requests
|
||||
@@ -89,7 +89,7 @@ func TestParseYaml(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseJson(t *testing.T) {
|
||||
parsedConf, err := Parse([]byte(resourceConfJson1))
|
||||
parsedConf, err := Parse([]byte(resourceConfJSON1))
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
requests := parsedConf.Resources.Requests
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
conf "github.com/reactiveops/fairwinds/pkg/config"
|
||||
"github.com/reactiveops/fairwinds/pkg/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -33,8 +35,8 @@ func validateContainer(conf conf.Configuration, container corev1.Container) Cont
|
||||
}
|
||||
|
||||
cv.validateResources(conf.Resources)
|
||||
// cv.validateHealthChecks(conf.HealthChecks)
|
||||
// cv.validateTags(conf.Image)
|
||||
cv.validateHealthChecks(conf.HealthChecks)
|
||||
cv.validateImage(conf.Images)
|
||||
|
||||
return cv
|
||||
}
|
||||
@@ -65,24 +67,23 @@ func (cv *ContainerValidation) withinRange(resourceName string, expectedRange co
|
||||
}
|
||||
}
|
||||
|
||||
// func probes(conf conf.ResourceRequestsAndLimits, c corev1.Container, results types.ContainerResults) types.ContainerResults {
|
||||
// if c.ReadinessProbe == nil {
|
||||
// results.AddFailure("Readiness Probe", "placeholder", "placeholder")
|
||||
// }
|
||||
func (cv *ContainerValidation) validateHealthChecks(conf conf.Probes) {
|
||||
if conf.Readiness.Require && cv.Container.ReadinessProbe == nil {
|
||||
cv.addFailure("readiness", "probe needs to be configured", "nil")
|
||||
}
|
||||
if conf.Liveness.Require && cv.Container.LivenessProbe == nil {
|
||||
cv.addFailure("liveness", "probe needs to be configured", "nil")
|
||||
}
|
||||
}
|
||||
|
||||
// if c.LivenessProbe == nil {
|
||||
// results.AddFailure("Liveness Probe", "placeholder", "placeholder")
|
||||
// }
|
||||
// return results
|
||||
// }
|
||||
|
||||
// func tag(conf conf.ResourceRequestsAndLimits, c corev1.Container, results types.ContainerResults) types.ContainerResults {
|
||||
// img := strings.Split(c.Image, ":")
|
||||
// if len(img) == 1 || img[1] == "latest" {
|
||||
// results.AddFailure("Image Tag", "not latest", "latest")
|
||||
// }
|
||||
// return results
|
||||
// }
|
||||
func (cv *ContainerValidation) validateImage(conf conf.Images) {
|
||||
if conf.TagRequired {
|
||||
img := strings.Split(cv.Container.Image, ":")
|
||||
if len(img) == 1 || img[1] == "latest" {
|
||||
cv.addFailure("Image Tag", "not latest", "latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func hostPort(conf conf.ResourceRequestsAndLimits, c corev1.Container, results types.ContainerResults) types.ContainerResults {
|
||||
// for _, port := range c.Ports {
|
||||
|
||||
@@ -165,3 +165,86 @@ func testValidateResources(t *testing.T, container *corev1.Container, resourceCo
|
||||
assert.Len(t, cv.Failures, len(*expectedFailures))
|
||||
assert.ElementsMatch(t, cv.Failures, *expectedFailures)
|
||||
}
|
||||
|
||||
func TestValidateHealthChecks(t *testing.T) {
|
||||
|
||||
// Test setup.
|
||||
p1 := conf.Probes{}
|
||||
p2 := conf.Probes{
|
||||
Readiness: conf.ResourceRequire{Require: false},
|
||||
Liveness: conf.ResourceRequire{Require: false},
|
||||
}
|
||||
p3 := conf.Probes{
|
||||
Readiness: conf.ResourceRequire{Require: true},
|
||||
Liveness: conf.ResourceRequire{Require: true},
|
||||
}
|
||||
|
||||
probe := corev1.Probe{}
|
||||
cv1 := ContainerValidation{Container: corev1.Container{Name: ""}}
|
||||
cv2 := ContainerValidation{Container: corev1.Container{Name: "", LivenessProbe: &probe, ReadinessProbe: &probe}}
|
||||
|
||||
l := types.Failure{Name: "liveness", Expected: "probe needs to be configured", Actual: "nil"}
|
||||
r := types.Failure{Name: "readiness", Expected: "probe needs to be configured", Actual: "nil"}
|
||||
f1 := []types.Failure{}
|
||||
f2 := []types.Failure{r, l}
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
probes conf.Probes
|
||||
cv ContainerValidation
|
||||
expected []types.Failure
|
||||
}{
|
||||
{name: "probes not configured", probes: p1, cv: cv1, expected: f1},
|
||||
{name: "probes not required", probes: p2, cv: cv1, expected: f1},
|
||||
{name: "probes required & configured", probes: p3, cv: cv2, expected: f1},
|
||||
{name: "probes required & not configured", probes: p3, cv: cv1, expected: f2},
|
||||
{name: "probes configured, but not required", probes: p2, cv: cv2, expected: f1},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.cv.validateHealthChecks(tt.probes)
|
||||
assert.Len(t, tt.cv.Failures, len(tt.expected))
|
||||
assert.ElementsMatch(t, tt.cv.Failures, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateImage(t *testing.T) {
|
||||
|
||||
// Test setup.
|
||||
i1 := conf.Images{}
|
||||
i2 := conf.Images{TagRequired: false}
|
||||
i3 := conf.Images{TagRequired: true}
|
||||
|
||||
cv1 := ContainerValidation{Container: corev1.Container{Name: ""}}
|
||||
cv2 := ContainerValidation{Container: corev1.Container{Name: "", Image: "test:tag"}}
|
||||
cv3 := ContainerValidation{Container: corev1.Container{Name: "", Image: "test:latest"}}
|
||||
cv4 := ContainerValidation{Container: corev1.Container{Name: "", Image: "test"}}
|
||||
|
||||
f := types.Failure{Name: "Image Tag", Expected: "not latest", Actual: "latest"}
|
||||
f1 := []types.Failure{}
|
||||
f2 := []types.Failure{f}
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
image conf.Images
|
||||
cv ContainerValidation
|
||||
expected []types.Failure
|
||||
}{
|
||||
{name: "image not configured", image: i1, cv: cv1, expected: f1},
|
||||
{name: "image not required", image: i2, cv: cv1, expected: f1},
|
||||
{name: "image tag required and configured", image: i3, cv: cv2, expected: f1},
|
||||
{name: "image tag required, but not configured", image: i3, cv: cv1, expected: f2},
|
||||
{name: "image tag required, but is latest", image: i3, cv: cv3, expected: f2},
|
||||
{name: "image tag required, but is empty", image: i3, cv: cv4, expected: f2},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.cv.validateImage(tt.image)
|
||||
assert.Len(t, tt.cv.Failures, len(tt.expected))
|
||||
assert.ElementsMatch(t, tt.cv.Failures, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user