Filter diagnostics based on enabled checks while writing diagnostics to stdout

varsha/versions
Varsha Varadarajan 2019-07-02 16:06:24 -04:00 committed by Varsha Varadarajan
parent 5f62173e0b
commit 98cd5d2b7a
29 changed files with 165 additions and 81 deletions

View File

@ -54,9 +54,9 @@ func (fq *fullyQualifiedImageCheck) Run(objects *kube.Objects) ([]checks.Diagnos
var diagnostics []checks.Diagnostic
for _, pod := range objects.Pods.Items {
d := checkImage(pod.Spec.Containers, pod)
d := fq.checkImage(pod.Spec.Containers, pod)
diagnostics = append(diagnostics, d...)
d = checkImage(pod.Spec.InitContainers, pod)
d = fq.checkImage(pod.Spec.InitContainers, pod)
diagnostics = append(diagnostics, d...)
}
@ -65,12 +65,13 @@ func (fq *fullyQualifiedImageCheck) Run(objects *kube.Objects) ([]checks.Diagnos
// checkImage checks if the image name is fully qualified
// Adds a warning if the container does not use a fully qualified image name
func checkImage(containers []corev1.Container, pod corev1.Pod) []checks.Diagnostic {
func (fq *fullyQualifiedImageCheck) checkImage(containers []corev1.Container, pod corev1.Pod) []checks.Diagnostic {
var diagnostics []checks.Diagnostic
for _, container := range containers {
value, err := reference.ParseAnyReference(container.Image)
if err != nil {
d := checks.Diagnostic{
Check: fq.Name(),
Severity: checks.Error,
Message: fmt.Sprintf("Malformed image name for container '%s'", container.Name),
Kind: checks.Pod,
@ -81,6 +82,7 @@ func checkImage(containers []corev1.Container, pod corev1.Pod) []checks.Diagnost
} else {
if value.String() != container.Image {
d := checks.Diagnostic{
Check: fq.Name(),
Severity: checks.Warning,
Message: fmt.Sprintf("Use fully qualified image for container '%s'", container.Name),
Kind: checks.Pod,

View File

@ -41,6 +41,7 @@ func TestFullyQualifiedImageCheckRegistration(t *testing.T) {
func TestFullyQualifiedImageWarning(t *testing.T) {
const message = "Use fully qualified image for container 'bar'"
const severity = checks.Warning
const name = "fully-qualified-image"
tests := []struct {
name string
@ -60,7 +61,7 @@ func TestFullyQualifiedImageWarning(t *testing.T) {
{
name: "pod with container image - busybox:latest",
objs: container("busybox:latest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - k8s.gcr.io/busybox",
@ -70,7 +71,7 @@ func TestFullyQualifiedImageWarning(t *testing.T) {
{
name: "pod with container image - busybox",
objs: container("busybox"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - test:5000/repo/image@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
@ -80,7 +81,7 @@ func TestFullyQualifiedImageWarning(t *testing.T) {
{
name: "pod with container image - repo/image@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
objs: container("repo/image@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - test:5000/repo/image:ignore-tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
@ -90,7 +91,7 @@ func TestFullyQualifiedImageWarning(t *testing.T) {
{
name: "pod with container image - repo/image:ignore-tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
objs: container("repo/image:ignore-tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - k8s.gcr.io/busybox:latest",
@ -100,7 +101,7 @@ func TestFullyQualifiedImageWarning(t *testing.T) {
{
name: "pod with container image - busybox:latest",
objs: initContainer("busybox:latest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - k8s.gcr.io/busybox",
@ -110,7 +111,7 @@ func TestFullyQualifiedImageWarning(t *testing.T) {
{
name: "pod with container image - busybox",
objs: initContainer("busybox"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - test:5000/repo/image@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
@ -120,7 +121,7 @@ func TestFullyQualifiedImageWarning(t *testing.T) {
{
name: "pod with container image - repo/image@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
objs: initContainer("repo/image@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - test:5000/repo/image:ignore-tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
@ -130,7 +131,7 @@ func TestFullyQualifiedImageWarning(t *testing.T) {
{
name: "pod with container image - repo/image:ignore-tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
objs: initContainer("repo/image:ignore-tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
}
@ -148,6 +149,8 @@ func TestFullyQualifiedImageWarning(t *testing.T) {
func TestMalformedImageError(t *testing.T) {
const message = "Malformed image name for container 'bar'"
const severity = checks.Error
const name = "fully-qualified-image"
tests := []struct {
name string
objs *kube.Objects
@ -156,12 +159,12 @@ func TestMalformedImageError(t *testing.T) {
{
name: "container with image : test:5000/repo/image@sha256:digest",
objs: container("test:5000/repo/image@sha256:digest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "init container with image : test:5000/repo/image@sha256:digest",
objs: initContainer("test:5000/repo/image@sha256:digest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
}
fullyQualifiedImageCheck := fullyQualifiedImageCheck{}

View File

@ -71,9 +71,10 @@ func initContainer(image string) *kube.Objects {
return objs
}
func issues(severity checks.Severity, message string, kind checks.Kind) []checks.Diagnostic {
func issues(severity checks.Severity, message string, kind checks.Kind, check string) []checks.Diagnostic {
d := []checks.Diagnostic{
{
Check: check,
Severity: severity,
Message: message,
Kind: kind,

View File

@ -55,6 +55,7 @@ func (h *hostPathCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error)
pod := pod
if volume.VolumeSource.HostPath != nil {
d := checks.Diagnostic{
Check: h.Name(),
Severity: checks.Error,
Message: fmt.Sprintf("Avoid using hostpath for volume '%s'.", volume.Name),
Kind: checks.Pod,

View File

@ -69,6 +69,7 @@ func TestHostpathVolumeError(t *testing.T) {
}),
expected: []checks.Diagnostic{
{
Check: "hostpath-volume",
Severity: checks.Error,
Message: "Avoid using hostpath for volume 'bar'.",
Kind: checks.Pod,

View File

@ -54,8 +54,8 @@ func (l *latestTagCheck) Description() string {
func (l *latestTagCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
var diagnostics []checks.Diagnostic
for _, pod := range objects.Pods.Items {
diagnostics = append(diagnostics, checkTags(pod.Spec.Containers, pod)...)
diagnostics = append(diagnostics, checkTags(pod.Spec.InitContainers, pod)...)
diagnostics = append(diagnostics, l.checkTags(pod.Spec.Containers, pod)...)
diagnostics = append(diagnostics, l.checkTags(pod.Spec.InitContainers, pod)...)
}
return diagnostics, nil
@ -63,13 +63,14 @@ func (l *latestTagCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error)
// checkTags checks if the image name conforms to pattern `image:latest` or `image`
// Adds a warning if it finds any image that uses the latest tag
func checkTags(containers []corev1.Container, pod corev1.Pod) []checks.Diagnostic {
func (l *latestTagCheck) checkTags(containers []corev1.Container, pod corev1.Pod) []checks.Diagnostic {
var diagnostics []checks.Diagnostic
for _, container := range containers {
namedRef, _ := reference.ParseNormalizedNamed(container.Image)
tagNameOnly := reference.TagNameOnly(namedRef)
if strings.HasSuffix(tagNameOnly.String(), ":latest") {
d := checks.Diagnostic{
Check: l.Name(),
Severity: checks.Warning,
Message: fmt.Sprintf("Avoid using latest tag for container '%s'", container.Name),
Kind: checks.Pod,

View File

@ -41,6 +41,7 @@ func TestLatestTagCheckRegistration(t *testing.T) {
func TestLatestTagWarning(t *testing.T) {
const message = "Avoid using latest tag for container 'bar'"
const severity = checks.Warning
const name = "latest-tag"
tests := []struct {
name string
@ -55,32 +56,32 @@ func TestLatestTagWarning(t *testing.T) {
{
name: "pod with container image - k8s.gcr.io/busybox:latest",
objs: container("k8s.gcr.io/busybox:latest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - busybox:latest",
objs: container("busybox:latest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - k8s.gcr.io/busybox",
objs: container("k8s.gcr.io/busybox"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - busybox",
objs: container("busybox"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - private:5000/busybox",
objs: container("private:5000/repo/busybox"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - private:5000/busybox:latest",
objs: container("private:5000/repo/busybox:latest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
@ -106,32 +107,32 @@ func TestLatestTagWarning(t *testing.T) {
{
name: "pod with init container image - k8s.gcr.io/busybox:latest",
objs: initContainer("k8s.gcr.io/busybox:latest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with init container image - busybox:latest",
objs: initContainer("busybox:latest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with init container image - k8s.gcr.io/busybox",
objs: initContainer("k8s.gcr.io/busybox"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with init container image - busybox",
objs: initContainer("busybox"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - private:5000/busybox",
objs: container("private:5000/repo/busybox"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - private:5000/busybox:latest",
objs: container("private:5000/repo/busybox:latest"),
expected: issues(severity, message, checks.Pod),
expected: issues(severity, message, checks.Pod, name),
},
{
name: "pod with container image - test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",

View File

@ -50,6 +50,7 @@ func (alert *alert) SetDiagnostics(d []checks.Diagnostic) {
// warn adds warnings for k8s objects that should not be in the default namespace
func (alert *alert) warn(k8stype checks.Kind, itemMeta metav1.ObjectMeta) {
d := checks.Diagnostic{
Check: "default-namespace",
Severity: checks.Warning,
Message: "Avoid using the default namespace",
Kind: k8stype,

View File

@ -41,17 +41,17 @@ func TestNamespaceCheckRegistration(t *testing.T) {
}
func TestNamespaceWarning(t *testing.T) {
namespace := defaultNamespaceCheck{}
tests := []struct {
name string
objs *kube.Objects
expected []checks.Diagnostic
}{
{"no objects in cluster", empty(), nil},
{"user created objects in default namespace", userCreatedObjects(), errors()},
{"user created objects in default namespace", userCreatedObjects(), errors(namespace)},
}
namespace := defaultNamespaceCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := namespace.Run(test.objs)
@ -88,7 +88,7 @@ func userCreatedObjects() *kube.Objects {
return objs
}
func errors() []checks.Diagnostic {
func errors(n defaultNamespaceCheck) []checks.Diagnostic {
objs := userCreatedObjects()
pod := objs.Pods.Items[0]
template := objs.PodTemplates.Items[0]
@ -100,6 +100,7 @@ func errors() []checks.Diagnostic {
d := []checks.Diagnostic{
{
Check: n.Name(),
Severity: checks.Warning,
Message: "Avoid using the default namespace",
Kind: checks.Pod,
@ -107,6 +108,7 @@ func errors() []checks.Diagnostic {
Owners: pod.ObjectMeta.GetOwnerReferences(),
},
{
Check: n.Name(),
Severity: checks.Warning,
Message: "Avoid using the default namespace",
Kind: checks.PodTemplate,
@ -114,6 +116,7 @@ func errors() []checks.Diagnostic {
Owners: template.ObjectMeta.GetOwnerReferences(),
},
{
Check: n.Name(),
Severity: checks.Warning,
Message: "Avoid using the default namespace",
Kind: checks.PersistentVolumeClaim,
@ -121,6 +124,7 @@ func errors() []checks.Diagnostic {
Owners: pvc.ObjectMeta.GetOwnerReferences(),
},
{
Check: n.Name(),
Severity: checks.Warning,
Message: "Avoid using the default namespace",
Kind: checks.ConfigMap,
@ -128,6 +132,7 @@ func errors() []checks.Diagnostic {
Owners: cm.ObjectMeta.GetOwnerReferences(),
},
{
Check: n.Name(),
Severity: checks.Warning,
Message: "Avoid using the default namespace",
Kind: checks.Service,
@ -135,6 +140,7 @@ func errors() []checks.Diagnostic {
Owners: service.ObjectMeta.GetOwnerReferences(),
},
{
Check: n.Name(),
Severity: checks.Warning,
Message: "Avoid using the default namespace",
Kind: checks.Secret,
@ -142,6 +148,7 @@ func errors() []checks.Diagnostic {
Owners: secret.ObjectMeta.GetOwnerReferences(),
},
{
Check: n.Name(),
Severity: checks.Warning,
Message: "Avoid using the default namespace",
Kind: checks.ServiceAccount,

View File

@ -55,6 +55,7 @@ func (p *podStatusCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error)
for _, pod := range objects.Pods.Items {
if corev1.PodFailed == pod.Status.Phase || corev1.PodUnknown == pod.Status.Phase {
d := checks.Diagnostic{
Check: p.Name(),
Severity: checks.Warning,
Message: fmt.Sprintf("Unhealthy pod. State: `%s`. Pod state should be `Running`, `Pending` or `Succeeded`.", pod.Status.Phase),
Kind: checks.Pod,

View File

@ -40,6 +40,7 @@ func TestPodStateCheckRegistration(t *testing.T) {
}
func TestPodStateError(t *testing.T) {
podStatusCheck := podStatusCheck{}
tests := []struct {
name string
objs *kube.Objects
@ -70,6 +71,7 @@ func TestPodStateError(t *testing.T) {
objs: status(corev1.PodFailed),
expected: []checks.Diagnostic{
{
Check: podStatusCheck.Name(),
Severity: checks.Warning,
Message: "Unhealthy pod. State: `Failed`. Pod state should be `Running`, `Pending` or `Succeeded`.",
Kind: checks.Pod,
@ -83,6 +85,7 @@ func TestPodStateError(t *testing.T) {
objs: status(corev1.PodUnknown),
expected: []checks.Diagnostic{
{
Check: podStatusCheck.Name(),
Severity: checks.Warning,
Message: "Unhealthy pod. State: `Unknown`. Pod state should be `Running`, `Pending` or `Succeeded`.",
Kind: checks.Pod,
@ -93,8 +96,6 @@ func TestPodStateError(t *testing.T) {
},
}
podStatusCheck := podStatusCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := podStatusCheck.Run(test.objs)

View File

@ -71,6 +71,7 @@ func (c *unusedCMCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error)
if _, ok := used[kube.Identifier{Name: cm.GetName(), Namespace: cm.GetNamespace()}]; !ok {
cm := cm
d := checks.Diagnostic{
Check: c.Name(),
Severity: checks.Warning,
Message: "Unused config map",
Kind: checks.ConfigMap,

View File

@ -43,6 +43,8 @@ func TestUnusedConfigMapCheckRegistration(t *testing.T) {
}
func TestUnusedConfigMapWarning(t *testing.T) {
unusedCMCheck := unusedCMCheck{}
tests := []struct {
name string
objs *kube.Objects
@ -78,6 +80,7 @@ func TestUnusedConfigMapWarning(t *testing.T) {
objs: initConfigMap(),
expected: []checks.Diagnostic{
{
Check: unusedCMCheck.Name(),
Severity: checks.Warning,
Message: "Unused config map",
Kind: checks.ConfigMap,
@ -88,8 +91,6 @@ func TestUnusedConfigMapWarning(t *testing.T) {
},
}
unusedCMCheck := unusedCMCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := unusedCMCheck.Run(test.objs)

View File

@ -30,29 +30,30 @@ func init() {
type unusedPVCheck struct{}
// Name returns a unique name for this check.
func (pv *unusedPVCheck) Name() string {
func (p *unusedPVCheck) Name() string {
return "unused-pv"
}
// Groups returns a list of group names this check should be part of.
func (pv *unusedPVCheck) Groups() []string {
func (p *unusedPVCheck) Groups() []string {
return []string{"basic"}
}
// Description returns a detailed human-readable description of what this check
// does.
func (pv *unusedPVCheck) Description() string {
func (p *unusedPVCheck) Description() string {
return "Check if there are unused persistent volumes in the cluster"
}
// Run runs this check on a set of Kubernetes objects. It can return warnings
// (low-priority problems) and errors (high-priority problems) as well as an
// error value indicating that the check failed to run.
func (pv *unusedPVCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
func (p *unusedPVCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
var diagnostics []checks.Diagnostic
for _, pv := range objects.PersistentVolumes.Items {
if pv.Spec.ClaimRef == nil {
d := checks.Diagnostic{
Check: p.Name(),
Severity: checks.Warning,
Message: fmt.Sprintf("Unused Persistent Volume '%s'.", pv.GetName()),
Kind: checks.PersistentVolume,

View File

@ -41,6 +41,8 @@ func TestUnusedPVCheckRegistration(t *testing.T) {
}
func TestUnusedPVWarning(t *testing.T) {
unusedPVCheck := unusedPVCheck{}
tests := []struct {
name string
objs *kube.Objects
@ -61,6 +63,7 @@ func TestUnusedPVWarning(t *testing.T) {
objs: unused(),
expected: []checks.Diagnostic{
{
Check: unusedPVCheck.Name(),
Severity: checks.Warning,
Message: "Unused Persistent Volume 'pv_foo'.",
Kind: checks.PersistentVolume,
@ -71,8 +74,6 @@ func TestUnusedPVWarning(t *testing.T) {
},
}
unusedPVCheck := unusedPVCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := unusedPVCheck.Run(test.objs)

View File

@ -62,6 +62,7 @@ func (c *unusedClaimCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, erro
for _, claim := range objects.PersistentVolumeClaims.Items {
if _, ok := used[kube.Identifier{Name: claim.GetName(), Namespace: claim.GetNamespace()}]; !ok {
d := checks.Diagnostic{
Check: c.Name(),
Severity: checks.Warning,
Message: "Unused persistent volume claim",
Kind: checks.PersistentVolumeClaim,

View File

@ -41,6 +41,8 @@ func TestUnusedPVCCheckRegistration(t *testing.T) {
}
func TestUnusedPVCWarning(t *testing.T) {
unusedClaimCheck := unusedClaimCheck{}
tests := []struct {
name string
objs *kube.Objects
@ -65,6 +67,7 @@ func TestUnusedPVCWarning(t *testing.T) {
objs: initPVC(),
expected: []checks.Diagnostic{
{
Check: unusedClaimCheck.Name(),
Severity: checks.Warning,
Message: "Unused persistent volume claim",
Kind: checks.PersistentVolumeClaim,
@ -75,8 +78,6 @@ func TestUnusedPVCWarning(t *testing.T) {
},
}
unusedClaimCheck := unusedClaimCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := unusedClaimCheck.Run(test.objs)

View File

@ -66,6 +66,7 @@ func (s *unusedSecretCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, err
if _, ok := used[kube.Identifier{Name: secret.GetName(), Namespace: secret.GetNamespace()}]; !ok {
secret := secret
d := checks.Diagnostic{
Check: s.Name(),
Severity: checks.Warning,
Message: "Unused secret",
Kind: checks.Secret,

View File

@ -41,6 +41,8 @@ func TestUnusedSecretCheckRegistration(t *testing.T) {
}
func TestUnusedSecretWarning(t *testing.T) {
unusedSecretCheck := unusedSecretCheck{}
tests := []struct {
name string
objs *kube.Objects
@ -76,6 +78,7 @@ func TestUnusedSecretWarning(t *testing.T) {
objs: initSecret(),
expected: []checks.Diagnostic{
{
Check: unusedSecretCheck.Name(),
Severity: checks.Warning,
Message: "Unused secret",
Kind: checks.Secret,
@ -86,8 +89,6 @@ func TestUnusedSecretWarning(t *testing.T) {
},
}
unusedSecretCheck := unusedSecretCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := unusedSecretCheck.Run(test.objs)

View File

@ -23,7 +23,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const checkAnnotation string = "clusterlint.disable.checks"
const checkAnnotation = "clusterlint.digitalocean.com/disabled-checks"
const separator = ","
// Check is a check that can run on Kubernetes objects.
type Check interface {
@ -42,10 +43,14 @@ type Check interface {
Run(*kube.Objects) ([]Diagnostic, error)
}
func IsEnabled(name string, item metav1.ObjectMeta) bool {
// IsEnabled inspects the object annotations to see if a check is disabled
func IsEnabled(name string, item *metav1.ObjectMeta) bool {
annotations := item.GetAnnotations()
if value, ok := annotations[checkAnnotation]; ok && strings.Contains(value, name) {
return false
if value, ok := annotations[checkAnnotation]; ok {
disabledChecks := strings.Split(value, separator)
if contains(disabledChecks, name) {
return false
}
}
return true
}

View File

@ -98,12 +98,3 @@ func getChecksNotInGroups(groups []string) []Check {
}
return ret
}
func contains(list []string, name string) bool {
for _, l := range list {
if l == name {
return true
}
}
return false
}

View File

@ -1,3 +1,19 @@
/*
Copyright 2019 DigitalOcean
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 checks
import (
@ -13,13 +29,13 @@ import (
func TestCheckIsDisabled(t *testing.T) {
const name string = "pod_foo"
pod := initPod(name)
assert.False(t, IsEnabled(name, pod.ObjectMeta))
assert.False(t, IsEnabled(name, &pod.ObjectMeta))
}
func TestCheckIsEnabled(t *testing.T) {
const name string = "pod_foo"
pod := initPod("")
assert.True(t, IsEnabled(name, pod.ObjectMeta))
assert.True(t, IsEnabled(name, &pod.ObjectMeta))
}
func TestNoClusterlintAnnotation(t *testing.T) {
@ -28,13 +44,13 @@ func TestNoClusterlintAnnotation(t *testing.T) {
Name: "pod_foo", Annotations: map[string]string{"porg": "star wars"},
},
}
assert.True(t, IsEnabled("pod_foo", pod.ObjectMeta))
assert.True(t, IsEnabled("pod_foo", &pod.ObjectMeta))
}
func initPod(name string) *corev1.Pod {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod_foo", Annotations: map[string]string{"clusterlint.disable.checks": fmt.Sprintf("%s, bar, baz", name)},
Name: "pod_foo", Annotations: map[string]string{checkAnnotation: fmt.Sprintf("%s, bar, baz", name)},
},
}
return pod

View File

@ -24,6 +24,7 @@ import (
// Diagnostic encapsulates the information each check returns.
type Diagnostic struct {
Check string
Severity Severity
Message string
Kind Kind

View File

@ -29,30 +29,31 @@ func init() {
type podSelectorCheck struct{}
// Name returns a unique name for this check.
func (nc *podSelectorCheck) Name() string {
func (p *podSelectorCheck) Name() string {
return "node-name-pod-selector"
}
// Groups returns a list of group names this check should be part of.
func (nc *podSelectorCheck) Groups() []string {
func (p *podSelectorCheck) Groups() []string {
return []string{"doks"}
}
// Description returns a detailed human-readable description of what this check
// does.
func (nc *podSelectorCheck) Description() string {
func (p *podSelectorCheck) Description() string {
return "Checks if there are pods which use kubernetes.io/hostname label in the node selector."
}
// Run runs this check on a set of Kubernetes objects. It can return warnings
// (low-priority problems) and errors (high-priority problems) as well as an
// error value indicating that the check failed to run.
func (nc *podSelectorCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
func (p *podSelectorCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
var diagnostics []checks.Diagnostic
for _, pod := range objects.Pods.Items {
nodeSelectorMap := pod.Spec.NodeSelector
if _, ok := nodeSelectorMap[corev1.LabelHostname]; ok {
d := checks.Diagnostic{
Check: p.Name(),
Severity: checks.Error,
Message: "Avoid node name label for node selector.",
Kind: checks.Pod,

View File

@ -41,6 +41,8 @@ func TestPodSelectorCheckRegistration(t *testing.T) {
}
func TestNodeNameError(t *testing.T) {
podSelectorCheck := podSelectorCheck{}
tests := []struct {
name string
objs *kube.Objects
@ -54,12 +56,10 @@ func TestNodeNameError(t *testing.T) {
{
name: "node name used in node selector",
objs: invalidPod(),
expected: errors(invalidPod()),
expected: errors(invalidPod(), podSelectorCheck.Name()),
},
}
podSelectorCheck := podSelectorCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := podSelectorCheck.Run(test.objs)
@ -89,10 +89,11 @@ func invalidPod() *kube.Objects {
return objs
}
func errors(objs *kube.Objects) []checks.Diagnostic {
func errors(objs *kube.Objects, name string) []checks.Diagnostic {
pod := objs.Pods.Items[0]
diagnostics := []checks.Diagnostic{
{
Check: name,
Severity: checks.Error,
Message: "Avoid node name label for node selector.",
Kind: checks.Pod,

28
checks/helper.go Normal file
View File

@ -0,0 +1,28 @@
/*
Copyright 2019 DigitalOcean
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 checks
import "strings"
func contains(list []string, name string) bool {
for _, l := range list {
if strings.TrimSpace(l) == name {
return true
}
}
return false
}

View File

@ -53,8 +53,8 @@ func (pc *privilegedContainerCheck) Run(objects *kube.Objects) ([]checks.Diagnos
var diagnostics []checks.Diagnostic
for _, pod := range objects.Pods.Items {
diagnostics = append(diagnostics, checkPrivileged(pod.Spec.Containers, pod)...)
diagnostics = append(diagnostics, checkPrivileged(pod.Spec.InitContainers, pod)...)
diagnostics = append(diagnostics, pc.checkPrivileged(pod.Spec.Containers, pod)...)
diagnostics = append(diagnostics, pc.checkPrivileged(pod.Spec.InitContainers, pod)...)
}
return diagnostics, nil
@ -62,11 +62,12 @@ func (pc *privilegedContainerCheck) Run(objects *kube.Objects) ([]checks.Diagnos
// checkPrivileged checks if the container is running in privileged mode
// Adds a warning if it finds any privileged container
func checkPrivileged(containers []corev1.Container, pod corev1.Pod) []checks.Diagnostic {
func (pc *privilegedContainerCheck) checkPrivileged(containers []corev1.Container, pod corev1.Pod) []checks.Diagnostic {
var diagnostics []checks.Diagnostic
for _, container := range containers {
if container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged {
d := checks.Diagnostic{
Check: pc.Name(),
Severity: checks.Warning,
Message: fmt.Sprintf("Privileged container '%s' found. Please ensure that the image is from a trusted source.", container.Name),
Kind: checks.Pod,

View File

@ -41,6 +41,8 @@ func TestPrivilegedContainersCheckRegistration(t *testing.T) {
}
func TestPrivilegedContainerWarning(t *testing.T) {
privilegedContainerCheck := privilegedContainerCheck{}
tests := []struct {
name string
objs *kube.Objects
@ -54,7 +56,7 @@ func TestPrivilegedContainerWarning(t *testing.T) {
{
name: "pod with container in privileged mode",
objs: container(true),
expected: warnings(container(true)),
expected: warnings(container(true), privilegedContainerCheck.Name()),
},
{
name: "pod with container.SecurityContext = nil",
@ -74,7 +76,7 @@ func TestPrivilegedContainerWarning(t *testing.T) {
{
name: "pod with init container in privileged mode",
objs: initContainer(true),
expected: warnings(initContainer(true)),
expected: warnings(initContainer(true), privilegedContainerCheck.Name()),
},
{
name: "pod with initContainer.SecurityContext = nil",
@ -93,8 +95,6 @@ func TestPrivilegedContainerWarning(t *testing.T) {
},
}
privilegedContainerCheck := privilegedContainerCheck{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
d, err := privilegedContainerCheck.Run(test.objs)
@ -188,10 +188,11 @@ func initContainerPrivilegedNil() *kube.Objects {
return objs
}
func warnings(objs *kube.Objects) []checks.Diagnostic {
func warnings(objs *kube.Objects, name string) []checks.Diagnostic {
pod := objs.Pods.Items[0]
d := []checks.Diagnostic{
{
Check: name,
Severity: checks.Warning,
Message: "Privileged container 'bar' found. Please ensure that the image is from a trusted source.",
Kind: checks.Pod,

View File

@ -176,7 +176,8 @@ func run(objects *kube.Objects, c *cli.Context) error {
func write(diagnostics []checks.Diagnostic, c *cli.Context) error {
output := c.String("output")
level := checks.Severity(c.String("level"))
filtered := filterSeverity(level, diagnostics)
filtered := filterEnabled(diagnostics)
filtered = filterSeverity(level, filtered)
switch output {
case "json":
err := json.NewEncoder(os.Stdout).Encode(filtered)
@ -192,6 +193,16 @@ func write(diagnostics []checks.Diagnostic, c *cli.Context) error {
return nil
}
func filterEnabled(diagnostics []checks.Diagnostic) []checks.Diagnostic {
var ret []checks.Diagnostic
for _, d := range diagnostics {
if checks.IsEnabled(d.Check, d.Object) {
ret = append(ret, d)
}
}
return ret
}
func filterSeverity(level checks.Severity, diagnostics []checks.Diagnostic) []checks.Diagnostic {
if level == "" {
return diagnostics