diff --git a/cmd/configmaps.go b/cmd/configmaps.go index 4663e9f..c59c983 100644 --- a/cmd/configmaps.go +++ b/cmd/configmaps.go @@ -38,7 +38,7 @@ var ( configMapService := configmap.NewConfigMapsService( coreClient.ConfigMaps(config.Namespace), kubernetes.New(), - configmap.Configuration{Batch: config.Log.Batch}) + configmap.ServiceConfiguration{Batch: config.Log.Batch}) return executeConfigMapCleanupCommand(configMapService) }, } @@ -68,15 +68,10 @@ func executeConfigMapCleanupCommand(service configmap.Service) error { c := config.Resource namespace := config.Namespace if len(config.Resource.Labels) == 0 { - configMaps, labels, err := service.ListNamesAndLabels() + err := service.PrintNamesAndLabels(namespace) if err != nil { return err } - log.WithFields(log.Fields{ - "\n - namespace": namespace, - "\n - 🔐 configMaps": configMaps, - "\n - 🎫 labels": labels, - }).Info("Please use labels to select ConfigMaps. The following ConfigMaps and Labels are available:") return nil } diff --git a/cmd/secrets.go b/cmd/secrets.go index cf15ba2..369a456 100644 --- a/cmd/secrets.go +++ b/cmd/secrets.go @@ -37,7 +37,7 @@ var ( secretService := secret.NewSecretsService( coreClient.Secrets(config.Namespace), kubernetes.New(), - secret.Configuration{Batch: config.Log.Batch}) + secret.ServiceConfiguration{Batch: config.Log.Batch}) return executeSecretCleanupCommand(secretService) }, } @@ -66,15 +66,10 @@ func executeSecretCleanupCommand(service secret.Service) error { c := config.Resource namespace := config.Namespace if len(config.Resource.Labels) == 0 { - secrets, labels, err := service.ListNamesAndLabels() + err := service.PrintNamesAndLabels(namespace) if err != nil { return err } - log.WithFields(log.Fields{ - "\n - namespace": namespace, - "\n - 🔐 secrets": secrets, - "\n - 🎫 labels": labels, - }).Info("Please use labels to select Secrets. The following Secrets and Labels are available:") return nil } diff --git a/pkg/configmap/configmap.go b/pkg/configmap/configmap.go index f00198e..d74edae 100644 --- a/pkg/configmap/configmap.go +++ b/pkg/configmap/configmap.go @@ -17,7 +17,7 @@ import ( type ( Service interface { - ListNamesAndLabels() (configMapNames, labels []string, err error) + PrintNamesAndLabels(namespace string) error List(listOptions metav1.ListOptions) (configMaps []v1.ConfigMap, err error) GetUnused(namespace string, configMaps []v1.ConfigMap) (unusedConfigMaps []v1.ConfigMap, funcErr error) Delete(configMaps []v1.ConfigMap) @@ -26,18 +26,17 @@ type ( Print(configMaps []v1.ConfigMap) } ConfigMapsService struct { - configuration Configuration + configuration ServiceConfiguration client core.ConfigMapInterface helper kubernetes.Kubernetes } + ServiceConfiguration struct { + Batch bool + } ) -type Configuration struct { - Batch bool -} - // NewConfigMapsService creates a new Service instance -func NewConfigMapsService(client core.ConfigMapInterface, helper kubernetes.Kubernetes, configuration Configuration) ConfigMapsService { +func NewConfigMapsService(client core.ConfigMapInterface, helper kubernetes.Kubernetes, configuration ServiceConfiguration) ConfigMapsService { return ConfigMapsService{ client: client, helper: helper, @@ -45,15 +44,22 @@ func NewConfigMapsService(client core.ConfigMapInterface, helper kubernetes.Kube } } -// ListNamesAndLabels return names and labels of Config Maps -func (cms ConfigMapsService) ListNamesAndLabels() (resourceNames, labels []string, err error) { +// PrintNamesAndLabels return names and labels of Config Maps +func (cms ConfigMapsService) PrintNamesAndLabels(namespace string) error { configMaps, err := cms.List(metav1.ListOptions{}) + if err != nil { + return err + } var objectMetas []metav1.ObjectMeta for _, cm := range configMaps { objectMetas = append(objectMetas, cm.ObjectMeta) } - configMapNames, labels := util.GetNamesAndLabels(objectMetas) - return configMapNames, labels, nil + log.Infof("Following Config Maps are available in namespace %s", namespace) + namesAndLabels := util.GetNamesAndLabels(objectMetas) + for name, labels := range namesAndLabels { + log.Infof("Name: %s, labels: %s", name, labels) + } + return nil } // List returns a list of ConfigMaps from a namespace @@ -102,20 +108,19 @@ func (cms ConfigMapsService) GetUnused(namespace string, configMaps []v1.ConfigM // Delete removes Config Maps func (cms ConfigMapsService) Delete(configMaps []v1.ConfigMap) { for _, resource := range configMaps { - namespace := resource.GetNamespace() - name := resource.GetName() - kind := "ConfigMaps" + namespace := resource.Namespace + name := resource.Name if cms.configuration.Batch { - fmt.Println(resource.GetName()) + fmt.Println(name) } else { - log.Infof("Deleting %s %s/%s", kind, namespace, name) + log.Infof("Deleting configmap %s/%s", namespace, name) } err := cms.client.Delete(name, &metav1.DeleteOptions{}) if err != nil { - log.WithError(err).Errorf("Failed to delete config map %s/%s", namespace, name) + log.WithError(err).Errorf("Failed to delete configmap %s/%s", namespace, name) } } } @@ -134,7 +139,6 @@ func (cms ConfigMapsService) FilterByTime(configMaps []v1.ConfigMap, olderThan t log.WithFields(log.Fields{ "configMap": resource.Name, }).Debug("Filtering resource") - } else { log.WithField("name", resource.GetName()).Debug("Filtered resource") } @@ -159,7 +163,7 @@ func (cms ConfigMapsService) FilterByMaxCount(configMaps []v1.ConfigMap, keep in } else if timestampSecond.IsZero() { return false } - return configMaps[j].GetCreationTimestamp().Time.Before(configMaps[i].GetCreationTimestamp().Time) + return timestampFirst.Time.Before(timestampSecond.Time) }) if len(configMaps) <= keep { diff --git a/pkg/configmap/configmap_test.go b/pkg/configmap/configmap_test.go index 2a22247..799ca9d 100644 --- a/pkg/configmap/configmap_test.go +++ b/pkg/configmap/configmap_test.go @@ -1,15 +1,21 @@ package configmap import ( + "errors" "github.com/stretchr/testify/assert" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/kubernetes/typed/core/v1" + test "k8s.io/client-go/testing" "testing" + "time" ) type HelperKubernetes struct{} +type HelperKubernetesErr struct{} func (k *HelperKubernetes) ResourceContains(namespace, value string, resource schema.GroupVersionResource) (bool, error) { if "nameA" == value { @@ -19,77 +25,176 @@ func (k *HelperKubernetes) ResourceContains(namespace, value string, resource sc } } -var now = metav1.Now() +func (k *HelperKubernetesErr) ResourceContains(namespace, value string, resource schema.GroupVersionResource) (bool, error) { + return false, errors.New("error") +} + var testNamespace = "testNamespace" -func Test_ListNamesAndLabels(t *testing.T) { +func Test_PrintNamesAndLabels(t *testing.T) { + + tests := []struct { + name string + configMaps []v1.ConfigMap + err error + }{ + { + name: "GivenListOfConfigMaps_WhenListError_ThenReturnError", + configMaps: []v1.ConfigMap{}, + err: errors.New("error config map"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + clientset := fake.NewSimpleClientset() + clientset.PrependReactor("list", "configmaps", func(action test.Action) (handled bool, ret runtime.Object, err error) { + return true, &v1.ConfigMapList{}, tt.err + }) + fakeConfigMapsInterface := clientset.CoreV1().ConfigMaps(testNamespace) + service := NewConfigMapsService(fakeConfigMapsInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + err := service.PrintNamesAndLabels(testNamespace) + assert.EqualError(t, err, tt.err.Error()) + }) + } +} + +func Test_List(t *testing.T) { + tests := []struct { - name string - configMaps []v1.ConfigMap - configMapNames []string - labels []string - err error + name string + configMaps []v1.ConfigMap + err error }{ { - name: "GivenListOfConfigMaps_WhenLabelsAndNamesDefined_ThenReturnNamesAndLabels", - configMaps: generateBaseTestConfigMaps(), - configMapNames: []string{"nameA", "nameB"}, - labels: []string{"keyA=valueA", "keyB=valueB", "keyC=valueC"}, + name: "GivenListOfConfigMaps_WhenAllPresent_ThenReturnAllOfThem", + configMaps: generateBaseTestConfigMaps(), }, { - name: "GivenListOfConfigMaps_WhenOnlyNamesDefined_ThenReturnNamesWithNoLabels", - configMaps: []v1.ConfigMap{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "nameA", - Namespace: testNamespace, - CreationTimestamp: now, - }, - }, + name: "GivenEmptyListOfConfigMaps_ThenReturnNothing", + configMaps: []v1.ConfigMap{}, + }, + { + name: "GivenListOfConfigMap_WhenListError_ThenReturnError", + configMaps: []v1.ConfigMap{}, + err: errors.New("error configmap"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var fakeConfigMapInterface core.ConfigMapInterface + if len(tt.configMaps) != 0 { + fakeConfigMapInterface = fake.NewSimpleClientset(&tt.configMaps[0], &tt.configMaps[1]).CoreV1().ConfigMaps(testNamespace) + } else { + clientset := fake.NewSimpleClientset() + clientset.PrependReactor("list", "configmaps", func(action test.Action) (handled bool, ret runtime.Object, err error) { + return true, &v1.ConfigMapList{}, tt.err + }) + fakeConfigMapInterface = clientset.CoreV1().ConfigMaps(testNamespace) + } + + service := NewConfigMapsService(fakeConfigMapInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + + list, err := service.List(metav1.ListOptions{}) + if tt.err == nil { + assert.NoError(t, err) + assert.ElementsMatch(t, tt.configMaps, list) + } else { + assert.EqualError(t, err, tt.err.Error()) + } + }) + } +} + +func Test_FilterByTime(t *testing.T) { + + tests := []struct { + name string + configMaps []v1.ConfigMap + filteredConfigMaps []v1.ConfigMap + cutOffDate time.Time + err error + }{ + { + name: "GivenListOfConfigMaps_WhenFilteredByTime_ThenReturnASubsetOfConfigMaps", + configMaps: generateBaseTestConfigMaps(), + filteredConfigMaps: []v1.ConfigMap{ { ObjectMeta: metav1.ObjectMeta{ - Name: "nameB", - Namespace: testNamespace, - CreationTimestamp: now, + Name: "nameB", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2010, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyB": "valueB", "keyC": "valueC"}, }, }, }, - configMapNames: []string{"nameA", "nameB"}, - labels: []string{}, + cutOffDate: time.Date(2015, 1, 1, 1, 0, 0, 0, time.UTC), + }, + { + name: "GivenListOfConfigMaps_WhenFilteredBefore2010_ThenReturnEmptyList", + configMaps: generateBaseTestConfigMaps(), + filteredConfigMaps: []v1.ConfigMap{}, + cutOffDate: time.Date(2005, 1, 1, 1, 0, 0, 0, time.UTC), }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fakeConfigMapInterface := fake.NewSimpleClientset(&tt.configMaps[0], &tt.configMaps[1]).CoreV1().ConfigMaps(testNamespace) - service := NewConfigMapsService(fakeConfigMapInterface, &HelperKubernetes{}, Configuration{Batch: false}) - resources, labels, err := service.ListNamesAndLabels() - assert.NoError(t, err) - assert.ElementsMatch(t, tt.configMapNames, resources) - assert.ElementsMatch(t, tt.labels, labels) + service := NewConfigMapsService(fakeConfigMapInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + filteredConfigMaps := service.FilterByTime(tt.configMaps, tt.cutOffDate) + assert.ElementsMatch(t, filteredConfigMaps, tt.filteredConfigMaps) }) } } -func Test_List(t *testing.T) { +func Test_FilterByMaxCount(t *testing.T) { + tests := []struct { - name string - configMaps []v1.ConfigMap - err error + name string + configMaps []v1.ConfigMap + filteredConfigMaps []v1.ConfigMap + keep int + err error }{ { - name: "GivenListOfConfigMaps_WhenAllPresent_ThenReturnAllOfThem", + name: "GivenListOfConfigMaps_FilterByMaxCountOne_ThenReturnOneConfigMap", configMaps: generateBaseTestConfigMaps(), + filteredConfigMaps: []v1.ConfigMap{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "nameB", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2010, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyB": "valueB", "keyC": "valueC"}, + }, + }, + }, + keep: 1, + }, + { + name: "GivenListOfConfigMaps_FilterByMaxCountZero_ThenReturnTwoConfigMaps", + configMaps: generateBaseTestConfigMaps(), + filteredConfigMaps: generateBaseTestConfigMaps(), + keep: 0, + }, + { + name: "GivenListOfConfigMaps_FilterByMaxCountTwo_ThenReturnEmptyList", + configMaps: generateBaseTestConfigMaps(), + filteredConfigMaps: []v1.ConfigMap{}, + keep: 2, }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fakeConfigMapInterface := fake.NewSimpleClientset(&tt.configMaps[0], &tt.configMaps[1]).CoreV1().ConfigMaps(testNamespace) - service := NewConfigMapsService(fakeConfigMapInterface, &HelperKubernetes{}, Configuration{Batch: false}) - list, err := service.List(metav1.ListOptions{}) - assert.NoError(t, err) - assert.ElementsMatch(t, tt.configMaps, list) + service := NewConfigMapsService(fakeConfigMapInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + filteredConfigMaps := service.FilterByMaxCount(tt.configMaps, tt.keep) + assert.ElementsMatch(t, filteredConfigMaps, tt.filteredConfigMaps) }) } } @@ -104,16 +209,35 @@ func Test_Delete(t *testing.T) { name: "GivenASetOfConfigMaps_WhenAllPresent_ThenDeleteAllOfThem", configMaps: generateBaseTestConfigMaps(), }, + { + name: "GivenASetOfConfigMaps_WhenError_ThenReturnError", + configMaps: generateBaseTestConfigMaps(), + err: errors.New("ConfigMap error"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - fakeConfigMapInterface := fake.NewSimpleClientset(&tt.configMaps[0], &tt.configMaps[1]).CoreV1().ConfigMaps(testNamespace) - service := NewConfigMapsService(fakeConfigMapInterface, &HelperKubernetes{}, Configuration{Batch: false}) + var fakeConfigMapInterface core.ConfigMapInterface + if tt.err == nil { + fakeConfigMapInterface = fake.NewSimpleClientset(&tt.configMaps[0], &tt.configMaps[1]).CoreV1().ConfigMaps(testNamespace) + } else { + clientset := fake.NewSimpleClientset(&tt.configMaps[0], &tt.configMaps[1]) + clientset.PrependReactor("delete", "configmaps", func(action test.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, tt.err + }) + fakeConfigMapInterface = clientset.CoreV1().ConfigMaps(testNamespace) + } + service := NewConfigMapsService(fakeConfigMapInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) service.Delete(tt.configMaps) list, err := fakeConfigMapInterface.List(metav1.ListOptions{}) + assert.NoError(t, err) - assert.EqualValues(t, 0, len(list.Items)) + if tt.err == nil { + assert.EqualValues(t, 0, len(list.Items)) + } else { + assert.EqualValues(t, 2, len(list.Items)) + } }) } } @@ -126,27 +250,42 @@ func Test_GetUnused(t *testing.T) { err error }{ { - name: "GivenASetOfConfigMaps_WhenOneSecretIsUsed_ThenFilterItOut", + name: "GivenASetOfConfigMaps_WhenOneConfigMapIsUsed_ThenFilterItOut", allConfigMaps: generateBaseTestConfigMaps(), unusedConfigMaps: []v1.ConfigMap{ { ObjectMeta: metav1.ObjectMeta{ - Name: "nameA", - Namespace: testNamespace, - CreationTimestamp: now, - Labels: map[string]string{"keyA": "valueA"}, + Name: "nameA", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyA": "valueA"}, }, }, }, }, + { + name: "GivenASetOfConfigMaps_WhenError_ThenReturnError", + allConfigMaps: generateBaseTestConfigMaps(), + unusedConfigMaps: generateBaseTestConfigMaps(), + err: errors.New("error"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - service := NewConfigMapsService(nil, &HelperKubernetes{}, Configuration{Batch: false}) - unused, err := service.GetUnused(testNamespace, tt.allConfigMaps) - assert.NoError(t, err) - assert.ElementsMatch(t, tt.unusedConfigMaps, unused) + if tt.err == nil { + service := NewConfigMapsService(nil, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + unused, err := service.GetUnused(testNamespace, tt.allConfigMaps) + assert.NoError(t, err) + assert.ElementsMatch(t, tt.unusedConfigMaps, unused) + } else { + service := NewConfigMapsService(nil, &HelperKubernetesErr{}, ServiceConfiguration{Batch: false}) + unused, err := service.GetUnused(testNamespace, tt.allConfigMaps) + assert.Error(t, err) + assert.ElementsMatch(t, tt.unusedConfigMaps, unused) + } }) } } @@ -155,18 +294,22 @@ func generateBaseTestConfigMaps() []v1.ConfigMap { return []v1.ConfigMap{ { ObjectMeta: metav1.ObjectMeta{ - Name: "nameA", - Namespace: testNamespace, - CreationTimestamp: now, - Labels: map[string]string{"keyA": "valueA"}, + Name: "nameA", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyA": "valueA"}, }, }, { ObjectMeta: metav1.ObjectMeta{ - Name: "nameB", - Namespace: testNamespace, - CreationTimestamp: now, - Labels: map[string]string{"keyB": "valueB", "keyC": "valueC"}, + Name: "nameB", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2010, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyB": "valueB", "keyC": "valueC"}, }, }, } diff --git a/pkg/secret/secret.go b/pkg/secret/secret.go index a3405b9..94c271e 100644 --- a/pkg/secret/secret.go +++ b/pkg/secret/secret.go @@ -17,7 +17,7 @@ import ( type ( Service interface { - ListNamesAndLabels() (resourceNames, labels []string, err error) + PrintNamesAndLabels(namespace string) error List(listOptions metav1.ListOptions) (resources []v1.Secret, err error) GetUnused(namespace string, secrets []v1.Secret) (unusedSecrets []v1.Secret, funcErr error) Delete(secrets []v1.Secret) @@ -27,18 +27,17 @@ type ( } SecretsService struct { - configuration Configuration + configuration ServiceConfiguration client core.SecretInterface helper kubernetes.Kubernetes } + ServiceConfiguration struct { + Batch bool + } ) -type Configuration struct { - Batch bool -} - // NewSecretsService creates a new Service instance -func NewSecretsService(client core.SecretInterface, helper kubernetes.Kubernetes, configuration Configuration) Service { +func NewSecretsService(client core.SecretInterface, helper kubernetes.Kubernetes, configuration ServiceConfiguration) Service { return SecretsService{ client: client, helper: helper, @@ -46,15 +45,22 @@ func NewSecretsService(client core.SecretInterface, helper kubernetes.Kubernetes } } -// ListNamesAndLabels return the names and labels of all secrets -func (ss SecretsService) ListNamesAndLabels() (resourceNames, labels []string, err error) { +// PrintNamesAndLabels return the names and labels of all secrets +func (ss SecretsService) PrintNamesAndLabels(namespace string) error { secrets, err := ss.List(metav1.ListOptions{}) + if err != nil { + return err + } var objectMetas []metav1.ObjectMeta for _, s := range secrets { objectMetas = append(objectMetas, s.ObjectMeta) } - secretNames, labels := util.GetNamesAndLabels(objectMetas) - return secretNames, labels, nil + log.Infof("Following Secrets are available in namespace %s", namespace) + namesAndLabels := util.GetNamesAndLabels(objectMetas) + for name, labels := range namesAndLabels { + log.Infof("Name: %s, labels: %s", name, labels) + } + return nil } // List returns a list of secrets from a namespace @@ -102,20 +108,19 @@ func (ss SecretsService) GetUnused(namespace string, resources []v1.Secret) (unu // Delete removes secrets func (ss SecretsService) Delete(secrets []v1.Secret) { for _, resource := range secrets { - namespace := resource.GetNamespace() - kind := "Secret" - name := resource.GetName() + namespace := resource.Namespace + name := resource.Name if ss.configuration.Batch { - fmt.Println(resource.GetName()) + fmt.Println(name) } else { - log.Infof("Deleting %s %s/%s", kind, namespace, name) + log.Infof("Deleting secret %s/%s", namespace, name) } err := ss.client.Delete(name, &metav1.DeleteOptions{}) if err != nil { - log.WithError(err).Errorf("Failed to delete %s %s/%s", kind, namespace, name) + log.WithError(err).Errorf("Failed to delete secret %s/%s", namespace, name) } } } diff --git a/pkg/secret/secret_test.go b/pkg/secret/secret_test.go index defc86e..289dde2 100644 --- a/pkg/secret/secret_test.go +++ b/pkg/secret/secret_test.go @@ -1,15 +1,21 @@ package secret import ( + "errors" "github.com/stretchr/testify/assert" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes/fake" + core "k8s.io/client-go/kubernetes/typed/core/v1" + test "k8s.io/client-go/testing" "testing" + "time" ) type HelperKubernetes struct{} +type HelperKubernetesErr struct{} func (k *HelperKubernetes) ResourceContains(namespace, value string, resource schema.GroupVersionResource) (bool, error) { if "nameA" == value { @@ -19,54 +25,36 @@ func (k *HelperKubernetes) ResourceContains(namespace, value string, resource sc } } -var now = metav1.Now() +func (k *HelperKubernetesErr) ResourceContains(namespace, value string, resource schema.GroupVersionResource) (bool, error) { + return false, errors.New("error") +} + var testNamespace = "testNamespace" -func Test_ListNamesAndLabels(t *testing.T) { +func Test_PrintNamesAndLabels(t *testing.T) { + tests := []struct { - name string - secrets []v1.Secret - secretNames []string - labels []string - err error + name string + secrets []v1.Secret + err error }{ { - name: "GivenListOfSecrets_WhenLabelsAndNamesDefined_ThenReturnNamesAndLabels", - secrets: generateBaseTestSecrets(), - secretNames: []string{"nameA", "nameB"}, - labels: []string{"keyA=valueA", "keyB=valueB", "keyC=valueC"}, - }, - { - name: "GivenListOfSecrets_WhenOnlyNamesDefined_ThenReturnNamesWithNoLabels", - secrets: []v1.Secret{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "nameA", - Namespace: testNamespace, - CreationTimestamp: now, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "nameB", - Namespace: testNamespace, - CreationTimestamp: now, - }, - }, - }, - secretNames: []string{"nameA", "nameB"}, - labels: []string{}, + name: "GivenListOfSecrets_WhenListError_ThenReturnError", + secrets: []v1.Secret{}, + err: errors.New("error secret"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - fakeSecretInterface := fake.NewSimpleClientset(&tt.secrets[0], &tt.secrets[1]).CoreV1().Secrets(testNamespace) - service := NewSecretsService(fakeSecretInterface, &HelperKubernetes{}, Configuration{Batch: false}) - resources, labels, err := service.ListNamesAndLabels() - assert.NoError(t, err) - assert.ElementsMatch(t, tt.secretNames, resources) - assert.ElementsMatch(t, tt.labels, labels) + clientset := fake.NewSimpleClientset() + clientset.PrependReactor("list", "secrets", func(action test.Action) (handled bool, ret runtime.Object, err error) { + return true, &v1.SecretList{}, tt.err + }) + fakeSecretInterface := clientset.CoreV1().Secrets(testNamespace) + service := NewSecretsService(fakeSecretInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + err := service.PrintNamesAndLabels(testNamespace) + assert.EqualError(t, err, tt.err.Error()) }) } } @@ -82,39 +70,178 @@ func Test_List(t *testing.T) { name: "GivenListOfSecrets_WhenAllPresent_ThenReturnAllOfThem", secrets: generateBaseTestSecrets(), }, + { + name: "GivenEmptyListOfSecrets_ThenReturnNothing", + secrets: []v1.Secret{}, + }, + { + name: "GivenListOfSecrets_WhenListError_ThenReturnError", + secrets: []v1.Secret{}, + err: errors.New("error secret"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - fakeSecretInterface := fake.NewSimpleClientset(&tt.secrets[0], &tt.secrets[1]).CoreV1().Secrets(testNamespace) - service := NewSecretsService(fakeSecretInterface, &HelperKubernetes{}, Configuration{Batch: false}) + var fakeSecretInterface core.SecretInterface + if len(tt.secrets) != 0 { + fakeSecretInterface = fake.NewSimpleClientset(&tt.secrets[0], &tt.secrets[1]).CoreV1().Secrets(testNamespace) + } else { + clientset := fake.NewSimpleClientset() + clientset.PrependReactor("list", "secrets", func(action test.Action) (handled bool, ret runtime.Object, err error) { + return true, &v1.SecretList{}, tt.err + }) + fakeSecretInterface = clientset.CoreV1().Secrets(testNamespace) + } + + service := NewSecretsService(fakeSecretInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + list, err := service.List(metav1.ListOptions{}) - assert.NoError(t, err) - assert.ElementsMatch(t, tt.secrets, list) + if tt.err == nil { + assert.NoError(t, err) + assert.ElementsMatch(t, tt.secrets, list) + } else { + assert.EqualError(t, err, tt.err.Error()) + } }) } } -func Test_Delete(t *testing.T) { +func Test_FilterByTime(t *testing.T) { + tests := []struct { - name string - secrets []v1.Secret - err error + name string + secrets []v1.Secret + filteredSecrets []v1.Secret + cutOffDate time.Time + err error }{ { - name: "GivenASetOfSecrets_WhenAllPresent_ThenDeleteAllOfThem", + name: "GivenListOfSecrets_WhenFilteredByTime_ThenReturnASubsetOfSecrets", secrets: generateBaseTestSecrets(), + filteredSecrets: []v1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "nameB", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2010, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyB": "valueB", "keyC": "valueC"}, + }, + }, + }, + cutOffDate: time.Date(2015, 1, 1, 1, 0, 0, 0, time.UTC), + }, + { + name: "GivenListOfSecrets_WhenFilteredBefore2010_ThenReturnEmptyList", + secrets: generateBaseTestSecrets(), + filteredSecrets: []v1.Secret{}, + cutOffDate: time.Date(2005, 1, 1, 1, 0, 0, 0, time.UTC), }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeSecretInterface := fake.NewSimpleClientset(&tt.secrets[0], &tt.secrets[1]).CoreV1().Secrets(testNamespace) + service := NewSecretsService(fakeSecretInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + filteredSecrets := service.FilterByTime(tt.secrets, tt.cutOffDate) + assert.ElementsMatch(t, filteredSecrets, tt.filteredSecrets) + }) + } +} +func Test_FilterByMaxCount(t *testing.T) { + + tests := []struct { + name string + secrets []v1.Secret + filteredSecrets []v1.Secret + keep int + err error + }{ + { + name: "GivenListOfSecrets_FilterByMaxCountOne_ThenReturnOneSecret", + secrets: generateBaseTestSecrets(), + filteredSecrets: []v1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "nameB", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2010, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyB": "valueB", "keyC": "valueC"}, + }, + }, + }, + keep: 1, + }, + { + name: "GivenListOfSecrets_FilterByMaxCountZero_ThenReturnTwoSecrets", + secrets: generateBaseTestSecrets(), + filteredSecrets: generateBaseTestSecrets(), + keep: 0, + }, + { + name: "GivenListOfSecrets_FilterByMaxCountTwo_ThenReturnEmptyList", + secrets: generateBaseTestSecrets(), + filteredSecrets: []v1.Secret{}, + keep: 2, + }, + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fakeSecretInterface := fake.NewSimpleClientset(&tt.secrets[0], &tt.secrets[1]).CoreV1().Secrets(testNamespace) - service := NewSecretsService(fakeSecretInterface, &HelperKubernetes{}, Configuration{Batch: false}) + service := NewSecretsService(fakeSecretInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + filteredSecrets := service.FilterByMaxCount(tt.secrets, tt.keep) + assert.ElementsMatch(t, filteredSecrets, tt.filteredSecrets) + }) + } +} + +func Test_Delete(t *testing.T) { + tests := []struct { + name string + secrets []v1.Secret + remained int + err error + }{ + { + name: "GivenASetOfSecrets_WhenAllPresent_ThenDeleteAllOfThem", + secrets: generateBaseTestSecrets(), + remained: 0, + }, + { + name: "GivenASetOfSecrets_WhenError_ThenReturnError", + secrets: generateBaseTestSecrets(), + remained: 2, + err: errors.New("secret error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var fakeSecretInterface core.SecretInterface + if tt.err == nil { + fakeSecretInterface = fake.NewSimpleClientset(&tt.secrets[0], &tt.secrets[1]).CoreV1().Secrets(testNamespace) + } else { + clientset := fake.NewSimpleClientset(&tt.secrets[0], &tt.secrets[1]) + clientset.PrependReactor("delete", "secrets", func(action test.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, tt.err + }) + fakeSecretInterface = clientset.CoreV1().Secrets(testNamespace) + } + service := NewSecretsService(fakeSecretInterface, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) service.Delete(tt.secrets) list, err := fakeSecretInterface.List(metav1.ListOptions{}) + assert.NoError(t, err) - assert.EqualValues(t, 0, len(list.Items)) + if tt.err == nil { + assert.EqualValues(t, tt.remained, len(list.Items)) + } else { + assert.EqualValues(t, tt.remained, len(list.Items)) + } + }) } } @@ -132,22 +259,37 @@ func Test_GetUnused(t *testing.T) { unusedSecrets: []v1.Secret{ { ObjectMeta: metav1.ObjectMeta{ - Name: "nameA", - Namespace: testNamespace, - CreationTimestamp: now, - Labels: map[string]string{"keyA": "valueA"}, + Name: "nameA", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyA": "valueA"}, }, }, }, }, + { + name: "GivenASetOfSecrets_WhenError_ThenReturnError", + allSecrets: generateBaseTestSecrets(), + unusedSecrets: generateBaseTestSecrets(), + err: errors.New("error"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - service := NewSecretsService(nil, &HelperKubernetes{}, Configuration{Batch: false}) - unused, err := service.GetUnused(testNamespace, tt.allSecrets) - assert.NoError(t, err) - assert.ElementsMatch(t, tt.unusedSecrets, unused) + if tt.err == nil { + service := NewSecretsService(nil, &HelperKubernetes{}, ServiceConfiguration{Batch: false}) + unused, err := service.GetUnused(testNamespace, tt.allSecrets) + assert.NoError(t, err) + assert.ElementsMatch(t, tt.unusedSecrets, unused) + } else { + service := NewSecretsService(nil, &HelperKubernetesErr{}, ServiceConfiguration{Batch: false}) + unused, err := service.GetUnused(testNamespace, tt.allSecrets) + assert.Error(t, err) + assert.ElementsMatch(t, tt.unusedSecrets, unused) + } }) } } @@ -156,18 +298,22 @@ func generateBaseTestSecrets() []v1.Secret { return []v1.Secret{ { ObjectMeta: metav1.ObjectMeta{ - Name: "nameA", - Namespace: testNamespace, - CreationTimestamp: now, - Labels: map[string]string{"keyA": "valueA"}, + Name: "nameA", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyA": "valueA"}, }, }, { ObjectMeta: metav1.ObjectMeta{ - Name: "nameB", - Namespace: testNamespace, - CreationTimestamp: now, - Labels: map[string]string{"keyB": "valueB", "keyC": "valueC"}, + Name: "nameB", + Namespace: testNamespace, + CreationTimestamp: metav1.Time{ + Time: time.Date(2010, 1, 1, 1, 0, 0, 0, time.UTC), + }, + Labels: map[string]string{"keyB": "valueB", "keyC": "valueC"}, }, }, } diff --git a/pkg/util/common.go b/pkg/util/common.go index 96090fe..0c8d074 100644 --- a/pkg/util/common.go +++ b/pkg/util/common.go @@ -5,16 +5,18 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func GetNamesAndLabels(resources []metav1.ObjectMeta) (resourceNames, labels []string) { +func GetNamesAndLabels(resources []metav1.ObjectMeta) map[string][]string { + resourceNamesWithLabels := make(map[string][]string) for _, resource := range resources { - resourceNames = append(resourceNames, resource.GetName()) - for key, element := range resource.GetLabels() { + var labels []string + for key, element := range resource.Labels { label := key + "=" + element if !funk.ContainsString(labels, label) { labels = append(labels, label) } } + resourceNamesWithLabels[resource.Name] = labels } - return resourceNames, labels + return resourceNamesWithLabels } diff --git a/pkg/util/common_test.go b/pkg/util/common_test.go new file mode 100644 index 0000000..4877b71 --- /dev/null +++ b/pkg/util/common_test.go @@ -0,0 +1,64 @@ +package util + +import ( + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "testing" +) + +func Test_PrintNamesAndLabels(t *testing.T) { + + tests := []struct { + name string + resources []metav1.ObjectMeta + resourceNamesWithLabels map[string][]string + }{ + { + name: "GivenListOfObjectMetas_WhenNamesAndLabelsDefined_ThenReturnAMapOfNamesAndLabels", + resources: generateObjectMetas(), + resourceNamesWithLabels: map[string][]string{ + "NameA": { + "LabelKeyA=LabelValueA", + "LabelKeyB=LabelValueB", + "LabelKeyC=LabelValueC", + }, + "NameB": nil, + "NameC": {"LabelKeyD=LabelValueD"}, + }, + }, + { + name: "GivenEmptyListOfObjectMetas_ThenReturnAnEmptyMapOfNamesAndLabels", + resources: []metav1.ObjectMeta{}, + resourceNamesWithLabels: map[string][]string{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + namesAndLabels := GetNamesAndLabels(tt.resources) + assert.Equal(t, namesAndLabels, tt.resourceNamesWithLabels) + }) + } +} + +func generateObjectMetas() []metav1.ObjectMeta { + return []metav1.ObjectMeta{ + { + Name: "NameA", + Labels: map[string]string{ + "LabelKeyA": "LabelValueA", + "LabelKeyB": "LabelValueB", + "LabelKeyC": "LabelValueC", + }, + }, + { + Name: "NameB", + Labels: map[string]string{}, + }, + { + Name: "NameC", + Labels: map[string]string{ + "LabelKeyD": "LabelValueD", + }, + }, + } +}