diff --git a/CHANGELOG.md b/CHANGELOG.md index e1e243da..40817857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # x.x.x (next release) +* Added the ability to exempt a particular controller from a particular check. # 0.6.0 * Fixed webhook support in Kubernetes 1.16 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39ff4703..ee58dc8c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Issues, whether bugs, tasks, or feature requests are essential for keeping Polar This project adheres to a [code of conduct](CODE_OF_CONDUCT.md). Please review this document before contributing to this project. ## Sign the CLA -Before you can contribute, you will need to sign the [Contributor License Agreement](https://cla-assistant.io/fairwinds/polaris). +Before you can contribute, you will need to sign the [Contributor License Agreement](https://cla-assistant.io/fairwindsops/polaris). ## Project Structure diff --git a/docs/usage.md b/docs/usage.md index 610f63e0..7e59be0f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -62,7 +62,7 @@ There are additional examples in the [checks folder](/checks). ### Exemptions Exemptions can be added two ways: by annotating a controller, or editing the Polaris config. -To exempt a controller via annotations, use the annotation `polaris.fairwinds.com/exempt=true`, e.g. +To exempt a controller from all checks via annotations, use the annotation `polaris.fairwinds.com/exempt=true`, e.g. ``` kubectl annotate deployment my-deployment polaris.fairwinds.com/exempt=true ``` @@ -76,6 +76,11 @@ exemptions: - hostNetworkSet ``` +To exempt a controller from a particular check via annotations, use an annotation in the form of `polaris.fairwinds.com/-exempt=true`, e.g. +``` +kubectl annotate deployment my-deployment polaris.fairwinds.com/cpuRequestsMissing-exempt=true +``` + # Installing There are several ways to install and use Polaris. Below outline ways to install using `kubectl`, `helm` and `local binary`. diff --git a/pkg/validator/container_test.go b/pkg/validator/container_test.go index 60997cb1..c298586b 100644 --- a/pkg/validator/container_test.go +++ b/pkg/validator/container_test.go @@ -63,10 +63,14 @@ func getEmptyController(name string) controllers.Interface { } func testValidate(t *testing.T, container *corev1.Container, resourceConf *string, controllerName string, expectedErrors []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) { + testValidateWithController(t, container, resourceConf, getEmptyController(controllerName), expectedErrors, expectedWarnings, expectedSuccesses) +} + +func testValidateWithController(t *testing.T, container *corev1.Container, resourceConf *string, controller controllers.Interface, expectedErrors []ResultMessage, expectedWarnings []ResultMessage, expectedSuccesses []ResultMessage) { parsedConf, err := conf.Parse([]byte(*resourceConf)) assert.NoError(t, err, "Expected no error when parsing config") - results, err := applyContainerSchemaChecks(&parsedConf, getEmptyController(controllerName), container, false) + results, err := applyContainerSchemaChecks(&parsedConf, controller, container, false) if err != nil { panic(err) } @@ -1146,3 +1150,52 @@ func TestValidateResourcesExemption(t *testing.T) { testValidate(t, &container, &disallowExemptionsConf, "foo", expectedErrors, expectedWarnings, expectedSuccesses) } + +func TestValidateResourcesEmptyContainerCPURequestsExempt(t *testing.T) { + container := corev1.Container{ + Name: "Empty", + } + + expectedWarnings := []ResultMessage{ + { + ID: "memoryRequestsMissing", + Success: false, + Severity: "warning", + Message: "Memory requests should be set", + Category: "Resources", + }, + } + + expectedErrors := []ResultMessage{ + { + ID: "cpuLimitsMissing", + Success: false, + Severity: "error", + Message: "CPU limits should be set", + Category: "Resources", + }, + { + ID: "memoryLimitsMissing", + Success: false, + Severity: "error", + Message: "Memory limits should be set", + Category: "Resources", + }, + } + + expectedSuccesses := []ResultMessage{} + + controller := controllers.NewDeploymentController(appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Annotations: map[string]string { + "polaris.fairwinds.com/cpuRequestsMissing-exempt": "true", // Exempt this controller from cpuRequestsMissing + "polaris.fairwinds.com/memoryRequestsMissing-exempt": "truthy", // Don't actually exempt this controller from memoryRequestsMissing + } , + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{}, + }, + }) + testValidateWithController(t, &container, &resourceConfMinimal, controller, expectedErrors, expectedWarnings, expectedSuccesses) +} \ No newline at end of file diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go index a18f19df..1d5033f5 100644 --- a/pkg/validator/schema.go +++ b/pkg/validator/schema.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "sort" + "strings" packr "github.com/gobuffalo/packr/v2" corev1 "k8s.io/api/core/v1" @@ -105,10 +106,19 @@ func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bo return result } +func getExemptKey(checkID string) string { + return fmt.Sprintf("polaris.fairwinds.com/%s-exempt", checkID) +} + func applyPodSchemaChecks(conf *config.Configuration, controller controllers.Interface) (ResultSet, error) { results := ResultSet{} checkIDs := getSortedKeys(conf.Checks) + objectAnnotations := controller.GetObjectMeta().Annotations for _, checkID := range checkIDs { + exemptValue := objectAnnotations[getExemptKey(checkID)] + if strings.ToLower(exemptValue) == "true" { + continue + } check, err := resolveCheck(conf, checkID, controller, config.TargetPod, false) if err != nil { return nil, err @@ -130,7 +140,12 @@ func applyPodSchemaChecks(conf *config.Configuration, controller controllers.Int func applyContainerSchemaChecks(conf *config.Configuration, controller controllers.Interface, container *corev1.Container, isInit bool) (ResultSet, error) { results := ResultSet{} checkIDs := getSortedKeys(conf.Checks) + objectAnnotations := controller.GetObjectMeta().Annotations for _, checkID := range checkIDs { + exemptValue := objectAnnotations[getExemptKey(checkID)] + if strings.ToLower(exemptValue) == "true" { + continue + } check, err := resolveCheck(conf, checkID, controller, config.TargetContainer, isInit) if err != nil { return nil, err diff --git a/pkg/validator/schema_test.go b/pkg/validator/schema_test.go index 0db08d2b..3005558e 100644 --- a/pkg/validator/schema_test.go +++ b/pkg/validator/schema_test.go @@ -259,4 +259,4 @@ func TestValidateCustomCheckExemptions(t *testing.T) { }, } testValidate(t, &container, &customCheckExemptions, "notexempt", expectedErrors, expectedWarnings, expectedSuccesses) -} +} \ No newline at end of file