skip health checks for jobs, cronjobs, and initContainers (#216)

This commit is contained in:
Robert Brennan
2019-11-06 13:31:17 -05:00
committed by GitHub
parent 152c876067
commit 22ab851681
9 changed files with 192 additions and 80 deletions

View File

@@ -38,7 +38,7 @@ type ContainerValidation struct {
// relevant podSpec in order to check certain aspects of a containerSpec.
// Perhaps there is a more ideal solution instead of attaching a parent
// podSpec to every container Validation struct...
func ValidateContainer(container *corev1.Container, parentPodResult *PodResult, controllerName string, conf *config.Configuration, isInit bool) ContainerResult {
func ValidateContainer(container *corev1.Container, parentPodResult *PodResult, conf *config.Configuration, controllerName string, controllerType config.SupportedController, isInit bool) ContainerResult {
cv := ContainerValidation{
Container: container,
ResourceValidation: &ResourceValidation{},
@@ -58,7 +58,9 @@ func ValidateContainer(container *corev1.Container, parentPodResult *PodResult,
}
cv.validateResources(conf, controllerName)
cv.validateHealthChecks(conf, controllerName)
if !isInit && controllerType != config.Jobs && controllerType != config.CronJobs {
cv.validateHealthChecks(conf, controllerName)
}
cv.validateImage(conf, controllerName)
cv.validateNetworking(conf, controllerName)
cv.validateSecurity(conf, controllerName)
@@ -138,12 +140,8 @@ func (cv *ContainerValidation) validateResourceRange(id, resourceName string, ra
cv.addError(fmt.Sprintf(messages.ResourceAmountTooLowFailure, resourceName, errorBelow.String()), category, id)
} else if warnBelow != nil && warnBelow.MilliValue() > res.MilliValue() {
cv.addWarning(fmt.Sprintf(messages.ResourceAmountTooLowFailure, resourceName, warnBelow.String()), category, id)
} else {
if warnAbove != nil || warnBelow != nil || errorAbove != nil || errorBelow != nil {
cv.addSuccess(fmt.Sprintf(messages.ResourceAmountSuccess, resourceName), category, id)
} else {
cv.addSuccess(fmt.Sprintf(messages.ResourcePresentSuccess, resourceName), category, id)
}
} else if errorAbove != nil && warnAbove != nil && errorBelow != nil && warnBelow != nil {
cv.addSuccess(fmt.Sprintf(messages.ResourceAmountSuccess, resourceName), category, id)
}
}

View File

@@ -285,10 +285,10 @@ func testValidateResources(t *testing.T, container *corev1.Container, resourceCo
cv.validateResources(&parsedConf, controllerName)
assert.Len(t, cv.Warnings, len(*expectedWarnings))
assert.ElementsMatch(t, cv.Warnings, *expectedWarnings)
assert.ElementsMatch(t, *expectedWarnings, cv.Warnings)
assert.Len(t, cv.Errors, len(*expectedErrors))
assert.ElementsMatch(t, cv.Errors, *expectedErrors)
assert.ElementsMatch(t, *expectedErrors, cv.Errors)
}
func TestValidateHealthChecks(t *testing.T) {

View File

@@ -24,10 +24,11 @@ import (
// ValidateController validates a single controller, returns a ControllerResult.
func ValidateController(conf conf.Configuration, controller controller.Interface) ControllerResult {
controllerType := controller.GetType()
pod := controller.GetPodSpec()
podResult := ValidatePod(conf, controller.GetName(), pod)
podResult := ValidatePod(conf, pod, controller.GetName(), controllerType)
return ControllerResult{
Type: controller.GetType().String(),
Type: controllerType.String(),
Name: controller.GetName(),
PodResult: podResult,
}

View File

@@ -0,0 +1,136 @@
// Copyright 2019 FairwindsOps Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package validator
import (
"testing"
conf "github.com/fairwindsops/polaris/pkg/config"
controller "github.com/fairwindsops/polaris/pkg/validator/controllers"
corev1 "k8s.io/api/core/v1"
"github.com/fairwindsops/polaris/test"
"github.com/stretchr/testify/assert"
)
func TestValidateController(t *testing.T) {
c := conf.Configuration{
Security: conf.Security{
HostIPCSet: conf.SeverityError,
HostPIDSet: conf.SeverityError,
},
}
deployment := controller.NewDeploymentController(test.MockDeploy())
expectedSum := ResultSummary{
Totals: CountSummary{
Successes: uint(2),
Warnings: uint(0),
Errors: uint(0),
},
ByCategory: make(map[string]*CountSummary),
}
expectedSum.ByCategory["Security"] = &CountSummary{
Successes: uint(2),
Warnings: uint(0),
Errors: uint(0),
}
expectedMessages := []*ResultMessage{
{ID: "hostIPCSet", Message: "Host IPC is not configured", Type: "success", Category: "Security"},
{ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"},
}
actualResult := ValidateController(c, deployment)
assert.Equal(t, "Deployments", actualResult.Type)
assert.Equal(t, 1, len(actualResult.PodResult.ContainerResults), "should be equal")
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)
assert.EqualValues(t, expectedMessages, actualResult.PodResult.Messages)
}
func TestSkipHealthChecks(t *testing.T) {
c := conf.Configuration{
HealthChecks: conf.HealthChecks{
ReadinessProbeMissing: conf.SeverityError,
LivenessProbeMissing: conf.SeverityWarning,
},
ControllersToScan: []conf.SupportedController{
conf.Deployments,
conf.StatefulSets,
conf.DaemonSets,
conf.Jobs,
conf.CronJobs,
conf.ReplicationControllers,
},
}
deploymentBase := test.MockDeploy()
deploymentBase.Spec.Template.Spec.InitContainers = []corev1.Container{test.MockContainer("test")}
deployment := controller.NewDeploymentController(deploymentBase)
expectedSum := ResultSummary{
Totals: CountSummary{
Successes: uint(0),
Warnings: uint(1),
Errors: uint(1),
},
ByCategory: make(map[string]*CountSummary),
}
expectedSum.ByCategory["Health Checks"] = &CountSummary{
Successes: uint(0),
Warnings: uint(1),
Errors: uint(1),
}
expectedMessages := []*ResultMessage{
{ID: "readinessProbeMissing", Message: "Readiness probe should be configured", Type: "error", Category: "Health Checks"},
{ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Type: "warning", Category: "Health Checks"},
}
actualResult := ValidateController(c, deployment)
assert.Equal(t, "Deployments", actualResult.Type)
assert.Equal(t, 2, len(actualResult.PodResult.ContainerResults), "should be equal")
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)
assert.EqualValues(t, []*ResultMessage{}, actualResult.PodResult.ContainerResults[0].Messages)
assert.EqualValues(t, expectedMessages, actualResult.PodResult.ContainerResults[1].Messages)
job := controller.NewJobController(test.MockJob())
expectedSum = ResultSummary{
Totals: CountSummary{
Successes: uint(0),
Warnings: uint(0),
Errors: uint(0),
},
ByCategory: make(map[string]*CountSummary),
}
expectedMessages = []*ResultMessage{}
actualResult = ValidateController(c, job)
assert.Equal(t, "Jobs", actualResult.Type)
assert.Equal(t, 1, len(actualResult.PodResult.ContainerResults), "should be equal")
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)
assert.EqualValues(t, expectedMessages, actualResult.PodResult.ContainerResults[0].Messages)
cronjob := controller.NewCronJobController(test.MockCronJob())
expectedSum = ResultSummary{
Totals: CountSummary{
Successes: uint(0),
Warnings: uint(0),
Errors: uint(0),
},
ByCategory: make(map[string]*CountSummary),
}
expectedMessages = []*ResultMessage{}
actualResult = ValidateController(c, cronjob)
assert.Equal(t, "CronJobs", actualResult.Type)
assert.Equal(t, 1, len(actualResult.PodResult.ContainerResults), "should be equal")
assert.EqualValues(t, &expectedSum, actualResult.PodResult.Summary)
assert.EqualValues(t, expectedMessages, actualResult.PodResult.ContainerResults[0].Messages)
}

View File

@@ -33,21 +33,16 @@ func TestGetTemplateData(t *testing.T) {
// TODO: split out the logic for calculating summaries into another set of tests
sum := ResultSummary{
Totals: CountSummary{
Successes: uint(24),
Warnings: uint(6),
Errors: uint(6),
Successes: uint(0),
Warnings: uint(4),
Errors: uint(4),
},
ByCategory: CategorySummary{},
}
sum.ByCategory["Health Checks"] = &CountSummary{
Successes: uint(0),
Warnings: uint(6),
Errors: uint(6),
}
sum.ByCategory["Resources"] = &CountSummary{
Successes: uint(24),
Warnings: uint(0),
Errors: uint(0),
Warnings: uint(4),
Errors: uint(4),
}
actualAudit, err := RunAudit(c, resources)
@@ -60,10 +55,10 @@ func TestGetTemplateData(t *testing.T) {
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].DeploymentResults[0].PodResult.ContainerResults), "should be equal")
assert.Equal(t, 6, len(actualAudit.NamespacedResults["test"].DeploymentResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
assert.Equal(t, 2, len(actualAudit.NamespacedResults["test"].DeploymentResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults), "should be equal")
assert.Equal(t, 1, len(actualAudit.NamespacedResults["test"].StatefulSetResults[0].PodResult.ContainerResults), "should be equal")
assert.Equal(t, 6, len(actualAudit.NamespacedResults["test"].StatefulSetResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
assert.Equal(t, 2, len(actualAudit.NamespacedResults["test"].StatefulSetResults[0].PodResult.ContainerResults[0].Messages), "should be equal")
}

View File

@@ -27,7 +27,7 @@ type PodValidation struct {
}
// ValidatePod validates that each pod conforms to the Polaris config, returns a ResourceResult.
func ValidatePod(conf config.Configuration, controllerName string, pod *corev1.PodSpec) PodResult {
func ValidatePod(conf config.Configuration, pod *corev1.PodSpec, controllerName string, controllerType config.SupportedController) PodResult {
pv := PodValidation{
Pod: pod,
ResourceValidation: &ResourceValidation{},
@@ -43,8 +43,8 @@ func ValidatePod(conf config.Configuration, controllerName string, pod *corev1.P
podSpec: *pod,
}
pv.validateContainers(pod.InitContainers, &pRes, controllerName, &conf, true)
pv.validateContainers(pod.Containers, &pRes, controllerName, &conf, false)
pv.validateContainers(pod.InitContainers, &pRes, &conf, controllerName, controllerType, true)
pv.validateContainers(pod.Containers, &pRes, &conf, controllerName, controllerType, false)
for _, cRes := range pRes.ContainerResults {
pRes.Summary.appendResults(*cRes.Summary)
@@ -53,9 +53,9 @@ func ValidatePod(conf config.Configuration, controllerName string, pod *corev1.P
return pRes
}
func (pv *PodValidation) validateContainers(containers []corev1.Container, pRes *PodResult, controllerName string, conf *config.Configuration, isInit bool) {
func (pv *PodValidation) validateContainers(containers []corev1.Container, pRes *PodResult, conf *config.Configuration, controllerName string, controllerType config.SupportedController, isInit bool) {
for _, container := range containers {
cRes := ValidateContainer(&container, pRes, controllerName, conf, isInit)
cRes := ValidateContainer(&container, pRes, conf, controllerName, controllerType, isInit)
pRes.ContainerResults = append(pRes.ContainerResults, cRes)
}
}

View File

@@ -40,7 +40,7 @@ func TestValidatePod(t *testing.T) {
expectedSum := ResultSummary{
Totals: CountSummary{
Successes: uint(8),
Successes: uint(4),
Warnings: uint(0),
Errors: uint(0),
},
@@ -51,11 +51,6 @@ func TestValidatePod(t *testing.T) {
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Resources"] = &CountSummary{
Successes: uint(4),
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Security"] = &CountSummary{
Successes: uint(2),
Warnings: uint(0),
@@ -68,7 +63,7 @@ func TestValidatePod(t *testing.T) {
{ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"},
}
actualPodResult := ValidatePod(c, "", &pod.Spec)
actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments)
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)
@@ -94,7 +89,7 @@ func TestInvalidIPCPod(t *testing.T) {
expectedSum := ResultSummary{
Totals: CountSummary{
Successes: uint(7),
Successes: uint(3),
Warnings: uint(0),
Errors: uint(1),
},
@@ -105,11 +100,6 @@ func TestInvalidIPCPod(t *testing.T) {
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Resources"] = &CountSummary{
Successes: uint(4),
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Security"] = &CountSummary{
Successes: uint(1),
Warnings: uint(0),
@@ -121,7 +111,7 @@ func TestInvalidIPCPod(t *testing.T) {
{ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"},
}
actualPodResult := ValidatePod(c, "", &pod.Spec)
actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments)
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)
@@ -147,7 +137,7 @@ func TestInvalidNeworkPod(t *testing.T) {
expectedSum := ResultSummary{
Totals: CountSummary{
Successes: uint(7),
Successes: uint(3),
Warnings: uint(1),
Errors: uint(0),
},
@@ -159,12 +149,6 @@ func TestInvalidNeworkPod(t *testing.T) {
Errors: uint(0),
}
expectedSum.ByCategory["Resources"] = &CountSummary{
Successes: uint(4),
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Security"] = &CountSummary{
Successes: uint(2),
Warnings: uint(0),
@@ -177,7 +161,8 @@ func TestInvalidNeworkPod(t *testing.T) {
{ID: "hostPIDSet", Message: "Host PID is not configured", Type: "success", Category: "Security"},
}
actualPodResult := ValidatePod(c, "", &pod.Spec)
actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments)
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)
assert.EqualValues(t, expectedMessages, actualPodResult.Messages)
@@ -202,7 +187,7 @@ func TestInvalidPIDPod(t *testing.T) {
expectedSum := ResultSummary{
Totals: CountSummary{
Successes: uint(7),
Successes: uint(3),
Warnings: uint(0),
Errors: uint(1),
},
@@ -213,11 +198,6 @@ func TestInvalidPIDPod(t *testing.T) {
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Resources"] = &CountSummary{
Successes: uint(4),
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Security"] = &CountSummary{
Successes: uint(1),
Warnings: uint(0),
@@ -230,7 +210,7 @@ func TestInvalidPIDPod(t *testing.T) {
{ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"},
}
actualPodResult := ValidatePod(c, "", &pod.Spec)
actualPodResult := ValidatePod(c, &pod.Spec, "", conf.Deployments)
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)
@@ -262,7 +242,7 @@ func TestExemption(t *testing.T) {
expectedSum := ResultSummary{
Totals: CountSummary{
Successes: uint(7),
Successes: uint(3),
Warnings: uint(0),
Errors: uint(0),
},
@@ -273,11 +253,6 @@ func TestExemption(t *testing.T) {
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Resources"] = &CountSummary{
Successes: uint(4),
Warnings: uint(0),
Errors: uint(0),
}
expectedSum.ByCategory["Security"] = &CountSummary{
Successes: uint(1),
Warnings: uint(0),
@@ -288,7 +263,7 @@ func TestExemption(t *testing.T) {
{ID: "hostNetworkSet", Message: "Host network is not configured", Type: "success", Category: "Networking"},
}
actualPodResult := ValidatePod(c, "foo", &pod.Spec)
actualPodResult := ValidatePod(c, &pod.Spec, "foo", conf.Deployments)
assert.Equal(t, 1, len(actualPodResult.ContainerResults), "should be equal")
assert.EqualValues(t, &expectedSum, actualPodResult.Summary)

View File

@@ -94,7 +94,7 @@ func (v *Validator) Handle(ctx context.Context, req types.Request) types.Respons
if req.AdmissionRequest.Kind.Kind == "Pod" {
pod := corev1.Pod{}
err = v.decoder.Decode(req, &pod)
podResult = validator.ValidatePod(v.Config, "", &pod.Spec)
podResult = validator.ValidatePod(v.Config, &pod.Spec, "", config.Unsupported)
} else {
var controller controllers.Interface
if yes := v.Config.CheckIfKindIsConfiguredForValidation(req.AdmissionRequest.Kind.Kind); !yes {

View File

@@ -11,7 +11,8 @@ import (
"k8s.io/client-go/kubernetes/fake"
)
func mockContainer(name string) corev1.Container {
// MockContainer creates a container object
func MockContainer(name string) corev1.Container {
c := corev1.Container{
Name: name,
}
@@ -20,7 +21,7 @@ func mockContainer(name string) corev1.Container {
// MockPod creates a pod object.
func MockPod() corev1.PodTemplateSpec {
c1 := mockContainer("test")
c1 := MockContainer("test")
p := corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
@@ -31,7 +32,8 @@ func MockPod() corev1.PodTemplateSpec {
return p
}
func mockDeploy() appsv1.Deployment {
// MockDeploy creates a Deployment object.
func MockDeploy() appsv1.Deployment {
p := MockPod()
d := appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
@@ -41,7 +43,8 @@ func mockDeploy() appsv1.Deployment {
return d
}
func mockStatefulSet() appsv1.StatefulSet {
// MockStatefulSet creates a StatefulSet object.
func MockStatefulSet() appsv1.StatefulSet {
p := MockPod()
s := appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
@@ -51,7 +54,8 @@ func mockStatefulSet() appsv1.StatefulSet {
return s
}
func mockDaemonSet() appsv1.DaemonSet {
// MockDaemonSet creates a DaemonSet object.
func MockDaemonSet() appsv1.DaemonSet {
return appsv1.DaemonSet{
Spec: appsv1.DaemonSetSpec{
Template: MockPod(),
@@ -59,7 +63,8 @@ func mockDaemonSet() appsv1.DaemonSet {
}
}
func mockJob() batchv1.Job {
// MockJob creates a Job object.
func MockJob() batchv1.Job {
return batchv1.Job{
Spec: batchv1.JobSpec{
Template: MockPod(),
@@ -67,7 +72,8 @@ func mockJob() batchv1.Job {
}
}
func mockCronJob() batchv1beta1.CronJob {
// MockCronJob creates a CronJob object.
func MockCronJob() batchv1beta1.CronJob {
return batchv1beta1.CronJob{
Spec: batchv1beta1.CronJobSpec{
JobTemplate: batchv1beta1.JobTemplateSpec{
@@ -79,7 +85,8 @@ func mockCronJob() batchv1beta1.CronJob {
}
}
func mockReplicationController() corev1.ReplicationController {
// MockReplicationController creates a ReplicationController object.
func MockReplicationController() corev1.ReplicationController {
p := MockPod()
return corev1.ReplicationController{
Spec: corev1.ReplicationControllerSpec{
@@ -95,32 +102,32 @@ func SetupTestAPI() kubernetes.Interface {
// SetupAddControllers creates mock controllers and adds them to the test clientset.
func SetupAddControllers(k kubernetes.Interface, namespace string) kubernetes.Interface {
d1 := mockDeploy()
d1 := MockDeploy()
if _, err := k.AppsV1().Deployments(namespace).Create(&d1); err != nil {
fmt.Println(err)
}
s1 := mockStatefulSet()
s1 := MockStatefulSet()
if _, err := k.AppsV1().StatefulSets(namespace).Create(&s1); err != nil {
fmt.Println(err)
}
ds1 := mockDaemonSet()
ds1 := MockDaemonSet()
if _, err := k.AppsV1().DaemonSets(namespace).Create(&ds1); err != nil {
fmt.Println(err)
}
j1 := mockJob()
j1 := MockJob()
if _, err := k.BatchV1().Jobs(namespace).Create(&j1); err != nil {
fmt.Println(err)
}
cj1 := mockCronJob()
cj1 := MockCronJob()
if _, err := k.BatchV1beta1().CronJobs(namespace).Create(&cj1); err != nil {
fmt.Println(err)
}
rc1 := mockReplicationController()
rc1 := MockReplicationController()
if _, err := k.CoreV1().ReplicationControllers(namespace).Create(&rc1); err != nil {
fmt.Println(err)
}