Unused config map: check if there are unused configmaps in the cluster.
parent
5bbf305db0
commit
2d097ba31a
15
checks.md
15
checks.md
|
@ -277,5 +277,20 @@ Description: This check reports all the persistent volume claims in the cluster
|
||||||
How to fix:
|
How to fix:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
<<<<<<< HEAD
|
||||||
kubectl delete pvc <unused pvc>
|
kubectl delete pvc <unused pvc>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
###### Unused Config Maps
|
||||||
|
|
||||||
|
Name: `unused-config-map`
|
||||||
|
|
||||||
|
Group: `basic`
|
||||||
|
|
||||||
|
Description: This check reports all the config maps in the cluster that are not referenced by pods in the respective namespaces. The cluster can be cleaned up based on this information and there will be fewer objects to manage.
|
||||||
|
|
||||||
|
How to fix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl delete configmap <unused config map>
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/digitalocean/clusterlint/checks"
|
||||||
|
"github.com/digitalocean/clusterlint/kube"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
checks.Register(&unusedCMCheck{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type unusedCMCheck struct{}
|
||||||
|
|
||||||
|
type identifier struct {
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns a unique name for this check.
|
||||||
|
func (c *unusedCMCheck) Name() string {
|
||||||
|
return "unused-config-map"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Groups returns a list of group names this check should be part of.
|
||||||
|
func (c *unusedCMCheck) Groups() []string {
|
||||||
|
return []string{"basic"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description returns a detailed human-readable description of what this check
|
||||||
|
// does.
|
||||||
|
func (c *unusedCMCheck) Description() string {
|
||||||
|
return "Checks if there are unused config maps 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 (c *unusedCMCheck) Run(objects *kube.Objects) ([]checks.Diagnostic, error) {
|
||||||
|
var diagnostics []checks.Diagnostic
|
||||||
|
|
||||||
|
used, err := checkReferences(objects)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cm := range objects.ConfigMaps.Items {
|
||||||
|
if _, ok := used[identifier{Name: cm.GetName(), Namespace: cm.GetNamespace()}]; !ok {
|
||||||
|
cm := cm
|
||||||
|
d := checks.Diagnostic{
|
||||||
|
Severity: checks.Warning,
|
||||||
|
Message: "Unused config map",
|
||||||
|
Kind: checks.ConfigMap,
|
||||||
|
Object: &cm.ObjectMeta,
|
||||||
|
Owners: cm.ObjectMeta.GetOwnerReferences(),
|
||||||
|
}
|
||||||
|
diagnostics = append(diagnostics, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diagnostics, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//checkReferences checks each pod for config map references in volumes and environment variables
|
||||||
|
func checkReferences(objects *kube.Objects) (map[identifier]bool, error) {
|
||||||
|
used := make(map[identifier]bool)
|
||||||
|
var mu sync.Mutex
|
||||||
|
var g errgroup.Group
|
||||||
|
for _, pod := range objects.Pods.Items {
|
||||||
|
pod := pod
|
||||||
|
namespace := pod.GetNamespace()
|
||||||
|
g.Go(func() error {
|
||||||
|
for _, volume := range pod.Spec.Volumes {
|
||||||
|
cm := volume.VolumeSource.ConfigMap
|
||||||
|
if cm != nil {
|
||||||
|
mu.Lock()
|
||||||
|
used[identifier{Name: cm.LocalObjectReference.Name, Namespace: namespace}] = true
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
identifiers := checkEnvVars(pod.Spec.Containers, namespace)
|
||||||
|
identifiers = append(identifiers, checkEnvVars(pod.Spec.InitContainers, namespace)...)
|
||||||
|
mu.Lock()
|
||||||
|
for _, i := range identifiers {
|
||||||
|
used[i] = true
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return used, g.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkEnvVars checks for config map references in container environment variables
|
||||||
|
func checkEnvVars(containers []corev1.Container, namespace string) []identifier {
|
||||||
|
var refs []identifier
|
||||||
|
for _, container := range containers {
|
||||||
|
for _, env := range container.EnvFrom {
|
||||||
|
if env.ConfigMapRef != nil {
|
||||||
|
refs = append(refs, identifier{Name: env.ConfigMapRef.LocalObjectReference.Name, Namespace: namespace})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return refs
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/digitalocean/clusterlint/checks"
|
||||||
|
"github.com/digitalocean/clusterlint/kube"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const cmNamespace = "k8s"
|
||||||
|
|
||||||
|
func TestUnusedConfigMapCheckMeta(t *testing.T) {
|
||||||
|
unusedCMCheck := unusedCMCheck{}
|
||||||
|
assert.Equal(t, "unused-config-map", unusedCMCheck.Name())
|
||||||
|
assert.Equal(t, []string{"basic"}, unusedCMCheck.Groups())
|
||||||
|
assert.NotEmpty(t, unusedCMCheck.Description())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnusedConfigMapCheckRegistration(t *testing.T) {
|
||||||
|
unusedCMCheck := &unusedCMCheck{}
|
||||||
|
check, err := checks.Get("unused-config-map")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, check, unusedCMCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnusedConfigMapWarning(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
objs *kube.Objects
|
||||||
|
expected []checks.Diagnostic
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no config maps",
|
||||||
|
objs: &kube.Objects{Pods: &corev1.PodList{}, ConfigMaps: &corev1.ConfigMapList{}},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "volume mounted config map",
|
||||||
|
objs: configMapVolume(),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "environment variable references to config map",
|
||||||
|
objs: configMapEnvSource(),
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unused config map",
|
||||||
|
objs: initConfigMap(),
|
||||||
|
expected: []checks.Diagnostic{
|
||||||
|
{
|
||||||
|
Severity: checks.Warning,
|
||||||
|
Message: "Unused config map",
|
||||||
|
Kind: checks.ConfigMap,
|
||||||
|
Object: &metav1.ObjectMeta{Name: "cm_foo", Namespace: cmNamespace},
|
||||||
|
Owners: GetOwners(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
unusedCMCheck := unusedCMCheck{}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
d, err := unusedCMCheck.Run(test.objs)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.ElementsMatch(t, test.expected, d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initConfigMap() *kube.Objects {
|
||||||
|
objs := &kube.Objects{
|
||||||
|
Pods: &corev1.PodList{
|
||||||
|
Items: []corev1.Pod{
|
||||||
|
{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: "Pod", APIVersion: "v1"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: cmNamespace},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ConfigMaps: &corev1.ConfigMapList{
|
||||||
|
Items: []corev1.ConfigMap{
|
||||||
|
{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "cm_foo", Namespace: cmNamespace},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return objs
|
||||||
|
}
|
||||||
|
|
||||||
|
func configMapVolume() *kube.Objects {
|
||||||
|
objs := initConfigMap()
|
||||||
|
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||||
|
Volumes: []corev1.Volume{
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{Name: "cm_foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
return objs
|
||||||
|
}
|
||||||
|
|
||||||
|
func configMapEnvSource() *kube.Objects {
|
||||||
|
objs := initConfigMap()
|
||||||
|
objs.Pods.Items[0].Spec = corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "test-container",
|
||||||
|
Image: "docker.io/nginx",
|
||||||
|
EnvFrom: []corev1.EnvFromSource{
|
||||||
|
{
|
||||||
|
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{Name: "cm_foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
return objs
|
||||||
|
}
|
Loading…
Reference in New Issue