Repository: FairwindsOps/polaris
Pull request #239 (2020-01-28 13:33:44)

Exclude individual checks from a controller
Added the ability to exclude a controller from individual checks via annotation.


Patch:
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/<check>-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/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


Test patch:
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_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