Merge pull request #8 from digitalocean/varsha/namespace-check
Namespace check: Check if there are any k8s objects in the default namespacevarsha/versions
commit
8d1893c06d
|
@ -3,5 +3,6 @@
|
||||||
package all
|
package all
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/digitalocean/clusterlint/checks/basic"
|
||||||
_ "github.com/digitalocean/clusterlint/checks/noop"
|
_ "github.com/digitalocean/clusterlint/checks/noop"
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/digitalocean/clusterlint/checks"
|
||||||
|
"github.com/digitalocean/clusterlint/kube"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const namespace = "default"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
checks.Register(&defaultNamespaceCheck{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultNamespaceCheck struct{}
|
||||||
|
|
||||||
|
type alert struct {
|
||||||
|
warnings []error
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWarnings returns alert.warnings
|
||||||
|
func (alert *alert) GetWarnings() []error {
|
||||||
|
return alert.warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWarnings sets alert.warnings
|
||||||
|
func (alert *alert) SetWarnings(w []error) {
|
||||||
|
alert.warnings = w
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendWarning appends a warning to the warnings slice
|
||||||
|
func (alert *alert) AppendWarning(err error) {
|
||||||
|
alert.warnings = append(alert.warnings, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// warn adds warnings for k8s objects that should not be in the default namespace
|
||||||
|
func (alert *alert) warn(k8stype string, item metav1.ObjectMeta) {
|
||||||
|
alert.mu.Lock()
|
||||||
|
alert.AppendWarning(fmt.Errorf("%s '%s' is in the default namespace.", k8stype, item.GetName()))
|
||||||
|
alert.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns a unique name for this check.
|
||||||
|
func (nc *defaultNamespaceCheck) Name() string {
|
||||||
|
return "default-namespace"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Groups returns a list of group names this check should be part of.
|
||||||
|
func (nc *defaultNamespaceCheck) Groups() []string {
|
||||||
|
return []string{"basic"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description returns a detailed human-readable description of what this check
|
||||||
|
// does.
|
||||||
|
func (nc *defaultNamespaceCheck) Description() string {
|
||||||
|
return "Checks if there are any user created k8s objects in the default namespace."
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPods checks if there are pods in the default namespace
|
||||||
|
func checkPods(items *corev1.PodList, alert *alert) {
|
||||||
|
for _, item := range items.Items {
|
||||||
|
if namespace == item.GetNamespace() {
|
||||||
|
alert.warn("Pod", item.ObjectMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPodTemplates checks if there are pod templates in the default namespace
|
||||||
|
func checkPodTemplates(items *corev1.PodTemplateList, alert *alert) {
|
||||||
|
for _, item := range items.Items {
|
||||||
|
if namespace == item.GetNamespace() {
|
||||||
|
alert.warn("Pod template", item.ObjectMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPVCs checks if there are pvcs in the default namespace
|
||||||
|
func checkPVCs(items *corev1.PersistentVolumeClaimList, alert *alert) {
|
||||||
|
for _, item := range items.Items {
|
||||||
|
if namespace == item.GetNamespace() {
|
||||||
|
alert.warn("Persistent Volume Claim", item.ObjectMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkConfigMaps checks if there are config maps in the default namespace
|
||||||
|
func checkConfigMaps(items *corev1.ConfigMapList, alert *alert) {
|
||||||
|
for _, item := range items.Items {
|
||||||
|
if namespace == item.GetNamespace() {
|
||||||
|
alert.warn("Config Map", item.ObjectMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkQuotas checks if there are quotas in the default namespace
|
||||||
|
func checkQuotas(items *corev1.ResourceQuotaList, alert *alert) {
|
||||||
|
for _, item := range items.Items {
|
||||||
|
if namespace == item.GetNamespace() {
|
||||||
|
alert.warn("Resource Quota", item.ObjectMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkLimits checks if there are limits in the default namespace
|
||||||
|
func checkLimits(items *corev1.LimitRangeList, alert *alert) {
|
||||||
|
for _, item := range items.Items {
|
||||||
|
if namespace == item.GetNamespace() {
|
||||||
|
alert.warn("Limit Range", item.ObjectMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkServices checks if there are user created services in the default namespace
|
||||||
|
func checkServices(items *corev1.ServiceList, alert *alert) {
|
||||||
|
for _, item := range items.Items {
|
||||||
|
if namespace == item.GetNamespace() && item.GetName() != "kubernetes" {
|
||||||
|
alert.warn("Service", item.ObjectMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSecrets checks if there are user created secrets in the default namespace
|
||||||
|
func checkSecrets(items *corev1.SecretList, alert *alert) {
|
||||||
|
for _, item := range items.Items {
|
||||||
|
if namespace == item.GetNamespace() && !strings.Contains(item.GetName(), "default-token-") {
|
||||||
|
alert.warn("Secret", item.ObjectMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSA checks if there are user created SAs in the default namespace
|
||||||
|
func checkSA(items *corev1.ServiceAccountList, alert *alert) {
|
||||||
|
for _, item := range items.Items {
|
||||||
|
if namespace == item.GetNamespace() && item.GetName() != "default" {
|
||||||
|
alert.warn("Service Account", item.ObjectMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 *defaultNamespaceCheck) Run(objects *kube.Objects) (warnings []error, errors []error, err error) {
|
||||||
|
alert := &alert{}
|
||||||
|
var g errgroup.Group
|
||||||
|
g.Go(func() error {
|
||||||
|
checkPods(objects.Pods, alert)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
checkPodTemplates(objects.PodTemplates, alert)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
checkPVCs(objects.PersistentVolumeClaims, alert)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
checkConfigMaps(objects.ConfigMaps, alert)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
checkQuotas(objects.ResourceQuotas, alert)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
checkLimits(objects.LimitRanges, alert)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
checkServices(objects.Services, alert)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
checkSecrets(objects.Secrets, alert)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
checkSA(objects.ServiceAccounts, alert)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return alert.warnings, nil, g.Wait()
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/digitalocean/clusterlint/kube"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNamespaceWarning(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
name string
|
||||||
|
arg *kube.Objects
|
||||||
|
expected []error
|
||||||
|
}{
|
||||||
|
{"no objects in cluster", empty(), nil},
|
||||||
|
{"user created objects in default namespace", userCreatedObjects(), errors()},
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := defaultNamespaceCheck{}
|
||||||
|
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
t.Run(scenario.name, func(t *testing.T) {
|
||||||
|
w, _, _ := namespace.Run(scenario.arg)
|
||||||
|
assert.ElementsMatch(t, scenario.expected, w)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func empty() *kube.Objects {
|
||||||
|
objs := &kube.Objects{
|
||||||
|
Pods: &corev1.PodList{},
|
||||||
|
PodTemplates: &corev1.PodTemplateList{},
|
||||||
|
PersistentVolumeClaims: &corev1.PersistentVolumeClaimList{},
|
||||||
|
ConfigMaps: &corev1.ConfigMapList{},
|
||||||
|
Services: &corev1.ServiceList{},
|
||||||
|
Secrets: &corev1.SecretList{},
|
||||||
|
ServiceAccounts: &corev1.ServiceAccountList{},
|
||||||
|
ResourceQuotas: &corev1.ResourceQuotaList{},
|
||||||
|
LimitRanges: &corev1.LimitRangeList{},
|
||||||
|
}
|
||||||
|
return objs
|
||||||
|
}
|
||||||
|
|
||||||
|
func userCreatedObjects() *kube.Objects {
|
||||||
|
objs := empty()
|
||||||
|
objs.Pods = &corev1.PodList{Items: []corev1.Pod{{ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: "default"}}}}
|
||||||
|
objs.PodTemplates = &corev1.PodTemplateList{Items: []corev1.PodTemplate{{ObjectMeta: metav1.ObjectMeta{Name: "template_foo", Namespace: "default"}}}}
|
||||||
|
objs.PersistentVolumeClaims = &corev1.PersistentVolumeClaimList{Items: []corev1.PersistentVolumeClaim{{ObjectMeta: metav1.ObjectMeta{Name: "pvc_foo", Namespace: "default"}}}}
|
||||||
|
objs.ConfigMaps = &corev1.ConfigMapList{Items: []corev1.ConfigMap{{ObjectMeta: metav1.ObjectMeta{Name: "cm_foo", Namespace: "default"}}}}
|
||||||
|
objs.ResourceQuotas = &corev1.ResourceQuotaList{Items: []corev1.ResourceQuota{{ObjectMeta: metav1.ObjectMeta{Name: "quota_foo", Namespace: "default"}}}}
|
||||||
|
objs.LimitRanges = &corev1.LimitRangeList{Items: []corev1.LimitRange{{ObjectMeta: metav1.ObjectMeta{Name: "limit_foo", Namespace: "default"}}}}
|
||||||
|
return objs
|
||||||
|
}
|
||||||
|
|
||||||
|
func errors() []error {
|
||||||
|
w := []error{
|
||||||
|
fmt.Errorf("Pod 'pod_foo' is in the default namespace."),
|
||||||
|
fmt.Errorf("Pod template 'template_foo' is in the default namespace."),
|
||||||
|
fmt.Errorf("Persistent Volume Claim 'pvc_foo' is in the default namespace."),
|
||||||
|
fmt.Errorf("Config Map 'cm_foo' is in the default namespace."),
|
||||||
|
fmt.Errorf("Resource Quota 'quota_foo' is in the default namespace."),
|
||||||
|
fmt.Errorf("Limit Range 'limit_foo' is in the default namespace."),
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
Loading…
Reference in New Issue