Skip to content

Commit

Permalink
Add support for ignore directives (#13)
Browse files Browse the repository at this point in the history
* Allow ignoring specific checks for certain objects by adding specific annotations.
  • Loading branch information
viswajithiii authored Oct 23, 2020
1 parent 4eb4607 commit 372bdaf
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 4 deletions.
9 changes: 9 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,12 @@ The list of supported check templates, along with their metadata, can be found [
All checks in `kube-linter` are defined by referencing a check template, passing parameters to it, and adding additional
check specific metadata (like check name and description). Users can configure custom checks the same way built-in checks
are configured, and add them to the config file. The built-in checks are specified [here](../internal/builtinchecks).

### Ignoring violations for specific cases

To ignore violations for specific objects, users can add an annotation with the key
`kube-linter.io/ignore-<check-name>`. We strongly encourage adding an explanation as the value for the annotation.
For example, to ignore the check "privileged" for a specific deployment, you can add an annotation like:
`kube-linter.io/ignore-privileged: "This deployment needs to run as privileged because it needs kernel access"`.

To ignore _all_ checks for a specific object, you can use the special annotation key `kube-linter.io/ignore-all`.
27 changes: 27 additions & 0 deletions internal/ignore/ignore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ignore

import (
"golang.stackrox.io/kube-linter/internal/stringutils"
)

const (
// AnnotationKeyPrefix is the prefix for annotations for kube-linter check ignores.
AnnotationKeyPrefix = "kube-linter.io/ignore/"

// AllAnnotationKey is used to ignore all checks for a given object.
AllAnnotationKey = "kube-linter.io/ignore-all"
)

// ObjectForCheck returns whether to ignore the given object for the passed check name.
func ObjectForCheck(annotations map[string]string, checkName string) bool {
for k := range annotations {
if k == AllAnnotationKey {
return true
}
key := k
if stringutils.ConsumePrefix(&key, AnnotationKeyPrefix) && key == checkName {
return true
}
}
return false
}
77 changes: 77 additions & 0 deletions internal/ignore/ignore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package ignore

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestObjectForCheck(t *testing.T) {
for _, testCase := range []struct {
annotations map[string]string
checkName string
shouldIgnore bool
}{
{
annotations: nil,
checkName: "some-check",
},
{
annotations: map[string]string{},
checkName: "some-check",
},
{
annotations: map[string]string{
"random-unrelated": "blah",
"kube-linter.io/ignore/some-check": "Not applicable",
},
checkName: "some-check",
shouldIgnore: true,
},
{
annotations: map[string]string{
"random-unrelated": "blah",
"kube-linter.io/ignore/some-check": "Not applicable",
},
checkName: "some-check-2",
},
{
annotations: map[string]string{
"random-unrelated": "blah",
"kube-linter.io/ignore/some-check": "Not applicable",
},
checkName: "other-check",
},
{
annotations: map[string]string{
"random-unrelated": "blah",
"kube-linter.io/ignore/some-check": "Not applicable",
"kube-linter.io/ignore/other-check": "Not applicable",
},
checkName: "other-check",
shouldIgnore: true,
},
{
annotations: map[string]string{
"random-unrelated": "blah",
"kube-linter.io/ignore-all": "Too much of a mess",
},
checkName: "other-check",
shouldIgnore: true,
},
{
annotations: map[string]string{
"random-unrelated": "blah",
"kube-linter.io/ignore-all": "Too much of a mess",
},
checkName: "some-other-check",
shouldIgnore: true,
},
} {
c := testCase
t.Run(fmt.Sprintf("%+v", c), func(t *testing.T) {
assert.Equal(t, c.shouldIgnore, ObjectForCheck(c.annotations, c.checkName))
})
}
}
9 changes: 9 additions & 0 deletions internal/instantiatedcheck/instantiated_check.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package instantiatedcheck

import (
"regexp"

"github.com/pkg/errors"
"golang.stackrox.io/kube-linter/internal/check"
"golang.stackrox.io/kube-linter/internal/errorhelpers"
Expand All @@ -16,13 +18,20 @@ type InstantiatedCheck struct {
Matcher objectkinds.Matcher
}

var (
validCheckNameRegex = regexp.MustCompile(`^[a-zA-Z0-9-_]+$`)
)

// ValidateAndInstantiate validates the check, and creates an instantiated check if the check
// is valid.
func ValidateAndInstantiate(c *check.Check) (*InstantiatedCheck, error) {
validationErrs := errorhelpers.NewErrorList("validating check")
if c.Name == "" {
validationErrs.AddString("no name specified")
}
if !validCheckNameRegex.MatchString(c.Name) {
validationErrs.AddStringf("invalid name %s, must match regex %s", c.Name, validCheckNameRegex.String())
}
template, found := templates.Get(c.Template)
if !found {
validationErrs.AddStringf("template %q not found", c.Template)
Expand Down
11 changes: 8 additions & 3 deletions internal/lintcontext/create_contexts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package lintcontext
import (
"os"
"path/filepath"
"sort"
"strings"

"github.com/pkg/errors"
Expand Down Expand Up @@ -71,11 +72,15 @@ func CreateContexts(filesOrDirs ...string) ([]*LintContext, error) {
if err != nil {
return nil, errors.Wrapf(err, "loading from path %q", fileOrDir)
}

}
dirs := make([]string, 0, len(contextsByDir))
for dir := range contextsByDir {
dirs = append(dirs, dir)
}
sort.Strings(dirs)
var contexts []*LintContext
for _, context := range contextsByDir {
contexts = append(contexts, context)
for _, dir := range dirs {
contexts = append(contexts, contextsByDir[dir])
}
return contexts, nil
}
5 changes: 4 additions & 1 deletion internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/pkg/errors"
"golang.stackrox.io/kube-linter/internal/checkregistry"
"golang.stackrox.io/kube-linter/internal/diagnostic"
"golang.stackrox.io/kube-linter/internal/ignore"
"golang.stackrox.io/kube-linter/internal/instantiatedcheck"
"golang.stackrox.io/kube-linter/internal/lintcontext"
)
Expand All @@ -15,7 +16,6 @@ type Result struct {

// Run runs the linter on the given context, with the given config.
func Run(lintCtxs []*lintcontext.LintContext, registry checkregistry.CheckRegistry, checks []string) (Result, error) {

instantiatedChecks := make([]*instantiatedcheck.InstantiatedCheck, 0, len(checks))
for _, checkName := range checks {
instantiatedCheck := registry.Load(checkName)
Expand All @@ -32,6 +32,9 @@ func Run(lintCtxs []*lintcontext.LintContext, registry checkregistry.CheckRegist
if !check.Matcher.Matches(obj.K8sObject.GetObjectKind().GroupVersionKind()) {
continue
}
if ignore.ObjectForCheck(obj.K8sObject.GetAnnotations(), check.Name) {
continue
}
diagnostics := check.Func(lintCtx, obj)
for _, d := range diagnostics {
result.Reports = append(result.Reports, diagnostic.WithContext{
Expand Down

0 comments on commit 372bdaf

Please sign in to comment.