Filter diagnostics based on enabled checks while writing diagnostics to stdout
parent
5f62173e0b
commit
98cd5d2b7a
|
@ -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,
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
// Diagnostic encapsulates the information each check returns.
|
||||
type Diagnostic struct {
|
||||
Check string
|
||||
Severity Severity
|
||||
Message string
|
||||
Kind Kind
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue