From 8317d135c52ab00e0e27d4507868830f7330ff8e Mon Sep 17 00:00:00 2001 From: Phanindra Padala Date: Thu, 13 Jun 2024 16:00:14 -0700 Subject: [PATCH] add configmap support Signed-off-by: Phanindra Padala --- docs/syntax.md | 4 +- kubedog.go | 3 +- pkg/kube/kube.go | 4 ++ pkg/kube/structured/structured.go | 11 ++++ pkg/kube/structured/structured_helper.go | 14 +++++ pkg/kube/structured/structured_test.go | 70 ++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 3 deletions(-) diff --git a/docs/syntax.md b/docs/syntax.md index 2c262842..78268562 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -42,7 +42,7 @@ Below you will find the step syntax next to the name of the method it utilizes. - ` [I] (create|submit|update) [the] secret in namespace from [environment variable] ` kdt.KubeClientSet.SecretOperationFromEnvironmentVariable - ` [I] delete [the] secret in namespace ` kdt.KubeClientSet.SecretDelete - ` node[s] with selector should be (found|ready)` kdt.KubeClientSet.NodesWithSelectorShouldBe -- ` [the] (deployment|hpa|horizontalpodautoscaler|service|pdb|poddisruptionbudget|sa|serviceaccount) (is|is not) in namespace ` kdt.KubeClientSet.ResourceInNamespace +- ` [the] (deployment|hpa|horizontalpodautoscaler|service|pdb|poddisruptionbudget|sa|serviceaccount|configmap) (is|is not) in namespace ` kdt.KubeClientSet.ResourceInNamespace - ` [I] scale [the] deployment in namespace to ` kdt.KubeClientSet.ScaleDeployment - ` [I] validate Prometheus Statefulset in namespace has volumeClaimTemplates name ` kdt.KubeClientSet.ValidatePrometheusVolumeClaimTemplatesName - ` [I] get [the] nodes list` kdt.KubeClientSet.ListNodes @@ -52,7 +52,7 @@ Below you will find the step syntax next to the name of the method it utilizes. - ` [the] (clusterrole|clusterrolebinding) with name should be found` kdt.KubeClientSet.ClusterRbacIsFound - ` [the] ingress in [the] namespace [is] [available] on port and path ` kdt.KubeClientSet.IngressAvailable - ` [I] send tps to ingress in [the] namespace [available] on port and path for (minutes|seconds) expecting up to error[s]` kdt.KubeClientSet.SendTrafficToIngress - +- ` configmap exists in namespace ` kdt.KubeClientSet.ConfigMapExists) ## AWS steps - ` [there are] [valid] AWS Credentials` kdt.AwsClientSet.DiscoverClients - ` an Auto Scaling Group named ` kdt.AwsClientSet.AnASGNamed diff --git a/kubedog.go b/kubedog.go index 75c66112..f9a4edc9 100644 --- a/kubedog.go +++ b/kubedog.go @@ -72,12 +72,13 @@ func (kdt *Test) SetScenario(scenario *godog.ScenarioContext) { kdt.scenario.Step(`^(?:I )?(create|submit|update) (?:the )?secret (\S+) in namespace (\S+) from (?:environment variable )?(\S+)$`, kdt.KubeClientSet.SecretOperationFromEnvironmentVariable) kdt.scenario.Step(`^(?:I )?delete (?:the )?secret (\S+) in namespace (\S+)$`, kdt.KubeClientSet.SecretDelete) kdt.scenario.Step(`^(\d+) node(?:s)? with selector (\S+) should be (found|ready)$`, kdt.KubeClientSet.NodesWithSelectorShouldBe) - kdt.scenario.Step(`^(?:the )?(deployment|hpa|horizontalpodautoscaler|service|pdb|poddisruptionbudget|sa|serviceaccount) ([^"]*) (is|is not) in namespace ([^"]*)$`, kdt.KubeClientSet.ResourceInNamespace) + kdt.scenario.Step(`^(?:the )?(deployment|hpa|horizontalpodautoscaler|service|pdb|poddisruptionbudget|sa|serviceaccount|configmap) ([^"]*) (is|is not) in namespace ([^"]*)$`, kdt.KubeClientSet.ResourceInNamespace) kdt.scenario.Step(`^(?:I )?scale (?:the )?deployment ([^"]*) in namespace ([^"]*) to (\d+)$`, kdt.KubeClientSet.ScaleDeployment) kdt.scenario.Step(`^(?:I )?validate Prometheus Statefulset ([^"]*) in namespace ([^"]*) has volumeClaimTemplates name ([^"]*)$`, kdt.KubeClientSet.ValidatePrometheusVolumeClaimTemplatesName) kdt.scenario.Step(`^(?:I )?get (?:the )?nodes list$`, kdt.KubeClientSet.ListNodes) kdt.scenario.Step(`^(?:the )?daemonset ([^"]*) is running in namespace ([^"]*)$`, kdt.KubeClientSet.DaemonSetIsRunning) kdt.scenario.Step(`^(?:the )?deployment ([^"]*) is running in namespace ([^"]*)$`, kdt.KubeClientSet.DeploymentIsRunning) + kdt.scenario.Step(`^configmap ([^"]*) exists in namespace ([^"]*)$`, kdt.KubeClientSet.ConfigMapExists) kdt.scenario.Step(`^(?:the )?persistentvolume ([^"]*) exists with status (Available|Bound|Released|Failed|Pending)$`, kdt.KubeClientSet.PersistentVolExists) kdt.scenario.Step(`^(?:the )?(clusterrole|clusterrolebinding) with name ([^"]*) should be found$`, kdt.KubeClientSet.ClusterRbacIsFound) kdt.scenario.Step(`^(?:the )?ingress (\S+) in (?:the )?namespace (\S+) (?:is )?(?:available )?on port (\d+) and path ([^"]*)$`, kdt.KubeClientSet.IngressAvailable) diff --git a/pkg/kube/kube.go b/pkg/kube/kube.go index 335cf30d..66bb7192 100644 --- a/pkg/kube/kube.go +++ b/pkg/kube/kube.go @@ -312,6 +312,10 @@ func (kc *ClientSet) DeploymentIsRunning(name, namespace string) error { return structured.DeploymentIsRunning(kc.KubeInterface, name, namespace) } +func (kc *ClientSet) ConfigMapExists(name, namespace string) error { + return structured.ConfigMapExists(kc.KubeInterface, name, namespace) +} + func (kc *ClientSet) PersistentVolExists(name, expectedPhase string) error { return structured.PersistentVolExists(kc.KubeInterface, name, expectedPhase) } diff --git a/pkg/kube/structured/structured.go b/pkg/kube/structured/structured.go index 36829e9e..1023f553 100644 --- a/pkg/kube/structured/structured.go +++ b/pkg/kube/structured/structured.go @@ -210,6 +210,15 @@ func DeploymentIsRunning(kubeClientset kubernetes.Interface, name, namespace str return nil } +func ConfigMapExists(kubeClientset kubernetes.Interface, name, namespace string) error { + configMap, err := GetConfigMap(kubeClientset, name, namespace) + if err != nil || configMap.Name != name { + return fmt.Errorf("configMap %s/%s does not exist", namespace, name) + } + + return nil +} + func PersistentVolExists(kubeClientset kubernetes.Interface, name, expectedPhase string) error { vol, err := GetPersistentVolume(kubeClientset, name) if err != nil { @@ -407,6 +416,8 @@ func ResourceInNamespace(kubeClientset kubernetes.Interface, resourceType, name, _, err = kubeClientset.PolicyV1().PodDisruptionBudgets(namespace).Get(context.Background(), name, metav1.GetOptions{}) case "sa", "serviceaccount": _, err = kubeClientset.CoreV1().ServiceAccounts(namespace).Get(context.Background(), name, metav1.GetOptions{}) + case "configmap": + _, err = kubeClientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), name, metav1.GetOptions{}) default: return errors.Errorf("Invalid resource type") } diff --git a/pkg/kube/structured/structured_helper.go b/pkg/kube/structured/structured_helper.go index b4cfc60a..359116c3 100644 --- a/pkg/kube/structured/structured_helper.go +++ b/pkg/kube/structured/structured_helper.go @@ -73,6 +73,20 @@ func GetDeployment(kubeClientset kubernetes.Interface, name, namespace string) ( return deploy.(*appsv1.Deployment), nil } +func GetConfigMap(kubeClientset kubernetes.Interface, name, namespace string) (*corev1.ConfigMap, error) { + if err := common.ValidateClientset(kubeClientset); err != nil { + return nil, err + } + + configMap, err := util.RetryOnError(&util.DefaultRetry, util.IsRetriable, func() (interface{}, error) { + return kubeClientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), name, metav1.GetOptions{}) + }) + if err != nil { + return nil, errors.Wrap(err, "failed to get configMap") + } + return configMap.(*corev1.ConfigMap), nil +} + func GetPersistentVolume(kubeClientset kubernetes.Interface, name string) (*corev1.PersistentVolume, error) { if err := common.ValidateClientset(kubeClientset); err != nil { return nil, err diff --git a/pkg/kube/structured/structured_test.go b/pkg/kube/structured/structured_test.go index 3c232bb1..d8595168 100644 --- a/pkg/kube/structured/structured_test.go +++ b/pkg/kube/structured/structured_test.go @@ -36,6 +36,7 @@ import ( ) const ( + configMapType = "configmap" deploymentType = "deployment" serviceType = "service" hpaType = "horizontalpodautoscaler" @@ -108,6 +109,7 @@ func TestResourceInNamespace(t *testing.T) { hpaName := "horizontalpodautoscaler1" pdbName := "poddisruptionbudget1" saName := "serviceaccount1" + configMapName := "configmap1" namespace := "namespace1" tests := []struct { @@ -125,6 +127,25 @@ func TestResourceInNamespace(t *testing.T) { namespace: namespace, }, }, + { + name: "Positive Test: configmap", + args: args{ + kubeClientset: fake.NewSimpleClientset(getResourceWithNamespace(t, configMapType, configMapName, namespace)), + resourceType: configMapType, + name: configMapName, + namespace: namespace, + }, + }, + { + name: "Negative Test: Invalid resource type", + args: args{ + kubeClientset: fake.NewSimpleClientset(getResourceWithNamespace(t, configMapType, configMapName, namespace)), + resourceType: "configmaps", + name: configMapName, + namespace: namespace, + }, + wantErr: true, + }, { name: "Positive Test: service", args: args{ @@ -380,6 +401,47 @@ func TestDeploymentIsRunning(t *testing.T) { } } +func TestConfigMapExists(t *testing.T) { + type args struct { + kubeClientset kubernetes.Interface + name string + namespace string + } + configMapName := "configMap1" + namespace := "namespace1" + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Positive Test", + args: args{ + kubeClientset: fake.NewSimpleClientset(getResourceWithNamespace(t, configMapType, configMapName, namespace)), + name: configMapName, + namespace: namespace, + }, + wantErr: false, + }, + { + name: "Negative Test", + args: args{ + kubeClientset: fake.NewSimpleClientset(getResourceWithNamespace(t, configMapType, "configMapName", namespace)), + name: configMapName, + namespace: namespace, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ConfigMapExists(tt.args.kubeClientset, tt.args.name, tt.args.namespace); (err != nil) != tt.wantErr { + t.Errorf("ConfigMapExists() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + func TestPersistentVolExists(t *testing.T) { type args struct { kubeClientset kubernetes.Interface @@ -684,6 +746,14 @@ func getResourceWithAll(t *testing.T, resourceType, name, namespace, label strin Labels: labels, }, } + case configMapType: + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: labels, + }, + } case serviceType: return &corev1.Service{ ObjectMeta: metav1.ObjectMeta{