diff --git a/cmd/kubeadm/app/util/apiclient/idempotency.go b/cmd/kubeadm/app/util/apiclient/idempotency.go index bbe9862fa2a..cf4d218239d 100644 --- a/cmd/kubeadm/app/util/apiclient/idempotency.go +++ b/cmd/kubeadm/app/util/apiclient/idempotency.go @@ -28,6 +28,7 @@ import ( rbac "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/wait" @@ -37,28 +38,40 @@ import ( "k8s.io/kubernetes/cmd/kubeadm/app/constants" ) -// ConfigMapMutator is a function that mutates the given ConfigMap and optionally returns an error -type ConfigMapMutator func(*v1.ConfigMap) error +// objectMutator is a function that mutates the given runtime object and optionally returns an error +type objectMutator[T runtime.Object] func(T) error // apiCallRetryInterval holds a local copy of apiCallRetryInterval for testing purposes var apiCallRetryInterval = constants.KubernetesAPICallRetryInterval -// TODO: We should invent a dynamic mechanism for this using the dynamic client instead of hard-coding these functions per-type +type kubernetesInterface[T kubernetesObject] interface { + Create(ctx context.Context, obj T, opts metav1.CreateOptions) (T, error) + Get(ctx context.Context, name string, opts metav1.GetOptions) (T, error) + Update(ctx context.Context, obj T, opts metav1.UpdateOptions) (T, error) +} -// CreateOrUpdateConfigMap creates a ConfigMap if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. -func CreateOrUpdateConfigMap(client clientset.Interface, cm *v1.ConfigMap) error { +type kubernetesObject interface { + runtime.Object + metav1.Object +} + +// CreateOrUpdate creates a runtime object if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +func CreateOrUpdate[T kubernetesObject](ctx context.Context, client kubernetesInterface[T], obj T) error { var lastError error - err := wait.PollUntilContextTimeout(context.Background(), + err := wait.PollUntilContextTimeout(ctx, apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, true, func(_ context.Context) (bool, error) { + // This uses a background context for API calls to avoid confusing callers that don't + // expect context-related errors. ctx := context.Background() - if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(ctx, cm, metav1.CreateOptions{}); err != nil { + if _, err := client.Create(ctx, obj, metav1.CreateOptions{}); err != nil { if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create ConfigMap") + lastError = errors.Wrapf(err, "unable to create %T", obj) return false, nil } - if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(ctx, cm, metav1.UpdateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to update ConfigMap") + if _, err := client.Update(ctx, obj, metav1.UpdateOptions{}); err != nil { + lastError = errors.Wrapf(err, "unable to update %T", obj) return false, nil } } @@ -70,19 +83,30 @@ func CreateOrUpdateConfigMap(client clientset.Interface, cm *v1.ConfigMap) error return lastError } -// CreateOrMutateConfigMap tries to create the ConfigMap provided as cm. If the resource exists already, the latest version will be fetched from -// the cluster and mutator callback will be called on it, then an Update of the mutated ConfigMap will be performed. This function is resilient -// to conflicts, and a retry will be issued if the ConfigMap was modified on the server between the refresh and the update (while the mutation was -// taking place) -func CreateOrMutateConfigMap(client clientset.Interface, cm *v1.ConfigMap, mutator ConfigMapMutator) error { +// CreateOrUpdateConfigMap creates a ConfigMap if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +// +// Deprecated: use CreateOrUpdate() instead. +func CreateOrUpdateConfigMap(client clientset.Interface, cm *v1.ConfigMap) error { + return CreateOrUpdate(context.Background(), client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace), cm) +} + +// CreateOrMutate tries to create the provided object. If the resource exists already, the latest version will be fetched from +// the cluster and mutator callback will be called on it, then an Update of the mutated object will be performed. This function is resilient +// to conflicts, and a retry will be issued if the object was modified on the server between the refresh and the update (while the mutation was +// taking place). +func CreateOrMutate[T kubernetesObject](ctx context.Context, client kubernetesInterface[T], obj T, mutator objectMutator[T]) error { var lastError error - err := wait.PollUntilContextTimeout(context.Background(), + err := wait.PollUntilContextTimeout(ctx, apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, true, func(_ context.Context) (bool, error) { - if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(context.Background(), cm, metav1.CreateOptions{}); err != nil { + // This uses a background context for API calls to avoid confusing callers that don't + // expect context-related errors. + ctx := context.Background() + if _, err := client.Create(ctx, obj, metav1.CreateOptions{}); err != nil { lastError = err if apierrors.IsAlreadyExists(err) { - lastError = mutateConfigMap(client, metav1.ObjectMeta{Namespace: cm.ObjectMeta.Namespace, Name: cm.ObjectMeta.Name}, mutator) + lastError = mutate(ctx, client, metav1.ObjectMeta{Namespace: obj.GetNamespace(), Name: obj.GetName()}, mutator) return lastError == nil, nil } return false, nil @@ -95,273 +119,138 @@ func CreateOrMutateConfigMap(client clientset.Interface, cm *v1.ConfigMap, mutat return lastError } -// mutateConfigMap takes a ConfigMap Object Meta (namespace and name), retrieves the resource from the server and tries to mutate it -// by calling to the mutator callback, then an Update of the mutated ConfigMap will be performed. This function is resilient +// CreateOrMutateConfigMap tries to create the ConfigMap provided as cm. If the resource exists already, the latest version will be fetched from +// the cluster and mutator callback will be called on it, then an Update of the mutated ConfigMap will be performed. This function is resilient // to conflicts, and a retry will be issued if the ConfigMap was modified on the server between the refresh and the update (while the mutation was // taking place). -func mutateConfigMap(client clientset.Interface, meta metav1.ObjectMeta, mutator ConfigMapMutator) error { - ctx := context.Background() - configMap, err := client.CoreV1().ConfigMaps(meta.Namespace).Get(ctx, meta.Name, metav1.GetOptions{}) +// +// Deprecated: use CreateOrMutate() instead. +func CreateOrMutateConfigMap(client clientset.Interface, cm *v1.ConfigMap, mutator objectMutator[*v1.ConfigMap]) error { + return CreateOrMutate(context.Background(), client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace), cm, mutator) +} + +// mutate takes an Object Meta (namespace and name), retrieves the resource from the server and tries to mutate it +// by calling to the mutator callback, then an Update of the mutated object will be performed. This function is resilient +// to conflicts, and a retry will be issued if the object was modified on the server between the refresh and the update (while the mutation was +// taking place). +func mutate[T kubernetesObject](ctx context.Context, client kubernetesInterface[T], meta metav1.ObjectMeta, mutator objectMutator[T]) error { + obj, err := client.Get(ctx, meta.Name, metav1.GetOptions{}) if err != nil { - return errors.Wrap(err, "unable to get ConfigMap") + return errors.Wrapf(err, "unable to get %T", obj) } - if err = mutator(configMap); err != nil { - return errors.Wrap(err, "unable to mutate ConfigMap") + if err = mutator(obj); err != nil { + return errors.Wrapf(err, "unable to mutate %T", obj) } - _, err = client.CoreV1().ConfigMaps(configMap.ObjectMeta.Namespace).Update(ctx, configMap, metav1.UpdateOptions{}) + _, err = client.Update(ctx, obj, metav1.UpdateOptions{}) return err } -// CreateOrRetainConfigMap creates a ConfigMap if the target resource doesn't exist. If the resource exists already, this function will retain the resource instead. +// CreateOrRetain creates a runtime object if the target resource doesn't exist. +// If the resource exists already, this function will retain the resource instead. +func CreateOrRetain[T kubernetesObject](ctx context.Context, client kubernetesInterface[T], obj T) error { + var lastError error + err := wait.PollUntilContextTimeout(ctx, + apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, + true, func(_ context.Context) (bool, error) { + // This uses a background context for API calls to avoid confusing callers that don't + // expect context-related errors. + ctx := context.Background() + if _, err := client.Get(ctx, obj.GetName(), metav1.GetOptions{}); err != nil { + if !apierrors.IsNotFound(err) { + lastError = errors.Wrapf(err, "unable to get %T", obj) + return false, nil + } + if _, err := client.Create(ctx, obj, metav1.CreateOptions{}); err != nil { + lastError = errors.Wrapf(err, "unable to create %T", obj) + return false, nil + } + } + return true, nil + }) + if err == nil { + return nil + } + return lastError +} + +// CreateOrRetainConfigMap creates a ConfigMap if the target resource doesn't exist. +// If the resource exists already, this function will retain the resource instead. +// +// Deprecated: use CreateOrRetain() instead. func CreateOrRetainConfigMap(client clientset.Interface, cm *v1.ConfigMap, configMapName string) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Get(ctx, configMapName, metav1.GetOptions{}); err != nil { - if !apierrors.IsNotFound(err) { - lastError = errors.Wrap(err, "unable to get ConfigMap") - return false, nil - } - if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(ctx, cm, metav1.CreateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to create ConfigMap") - return false, nil - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrRetain(context.Background(), client.CoreV1().ConfigMaps(cm.Namespace), cm) } -// CreateOrUpdateSecret creates a Secret if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +// CreateOrUpdateSecret creates a Secret if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +// +// Deprecated: use CreateOrUpdate() instead. func CreateOrUpdateSecret(client clientset.Interface, secret *v1.Secret) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Create(ctx, secret, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create Secret") - return false, nil - } - if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Update(ctx, secret, metav1.UpdateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to update Secret") - return false, nil - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrUpdate(context.Background(), client.CoreV1().Secrets(secret.Namespace), secret) } -// CreateOrUpdateServiceAccount creates a ServiceAccount if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +// CreateOrUpdateServiceAccount creates a ServiceAccount if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +// +// Deprecated: use CreateOrUpdate() instead. func CreateOrUpdateServiceAccount(client clientset.Interface, sa *v1.ServiceAccount) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.CoreV1().ServiceAccounts(sa.ObjectMeta.Namespace).Create(ctx, sa, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create ServicAccount") - return false, nil - } - if _, err := client.CoreV1().ServiceAccounts(sa.ObjectMeta.Namespace).Update(ctx, sa, metav1.UpdateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to update ServicAccount") - return false, nil - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrUpdate(context.Background(), client.CoreV1().ServiceAccounts(sa.Namespace), sa) } -// CreateOrUpdateDeployment creates a Deployment if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +// CreateOrUpdateDeployment creates a Deployment if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +// +// Deprecated: use CreateOrUpdate() instead. func CreateOrUpdateDeployment(client clientset.Interface, deploy *apps.Deployment) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Create(ctx, deploy, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create Deployment") - return false, nil - } - if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Update(ctx, deploy, metav1.UpdateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to update Deployment") - return false, nil - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrUpdate(context.Background(), client.AppsV1().Deployments(deploy.Namespace), deploy) } -// CreateOrRetainDeployment creates a Deployment if the target resource doesn't exist. If the resource exists already, this function will retain the resource instead. +// CreateOrRetainDeployment creates a Deployment if the target resource doesn't exist. +// If the resource exists already, this function will retain the resource instead. +// +// Deprecated: use CreateOrRetain() instead. func CreateOrRetainDeployment(client clientset.Interface, deploy *apps.Deployment, deployName string) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Get(ctx, deployName, metav1.GetOptions{}); err != nil { - if !apierrors.IsNotFound(err) { - lastError = errors.Wrap(err, "unable to get Deployment") - return false, nil - } - if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Create(ctx, deploy, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create Deployment") - return false, nil - } - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrRetain(context.Background(), client.AppsV1().Deployments(deploy.Namespace), deploy) } -// CreateOrUpdateDaemonSet creates a DaemonSet if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +// CreateOrUpdateDaemonSet creates a DaemonSet if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +// +// Deprecated: use CreateOrUpdate() instead. func CreateOrUpdateDaemonSet(client clientset.Interface, ds *apps.DaemonSet) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.AppsV1().DaemonSets(ds.ObjectMeta.Namespace).Create(ctx, ds, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create DaemonSet") - return false, nil - } - if _, err := client.AppsV1().DaemonSets(ds.ObjectMeta.Namespace).Update(ctx, ds, metav1.UpdateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to update DaemonSet") - return false, nil - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrUpdate(context.Background(), client.AppsV1().DaemonSets(ds.Namespace), ds) } -// CreateOrUpdateRole creates a Role if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +// CreateOrUpdateRole creates a Role if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +// +// Deprecated: use CreateOrUpdate() instead. func CreateOrUpdateRole(client clientset.Interface, role *rbac.Role) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.RbacV1().Roles(role.ObjectMeta.Namespace).Create(ctx, role, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create Role") - return false, nil - } - if _, err := client.RbacV1().Roles(role.ObjectMeta.Namespace).Update(ctx, role, metav1.UpdateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to update Role") - return false, nil - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrUpdate(context.Background(), client.RbacV1().Roles(role.Namespace), role) } -// CreateOrUpdateRoleBinding creates a RoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +// CreateOrUpdateRoleBinding creates a RoleBinding if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +// +// Deprecated: use CreateOrUpdate() instead. func CreateOrUpdateRoleBinding(client clientset.Interface, roleBinding *rbac.RoleBinding) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.RbacV1().RoleBindings(roleBinding.ObjectMeta.Namespace).Create(ctx, roleBinding, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create RoleBinding") - return false, nil - } - if _, err := client.RbacV1().RoleBindings(roleBinding.ObjectMeta.Namespace).Update(ctx, roleBinding, metav1.UpdateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to update RoleBinding") - return false, nil - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrUpdate(context.Background(), client.RbacV1().RoleBindings(roleBinding.Namespace), roleBinding) } -// CreateOrUpdateClusterRole creates a ClusterRole if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +// CreateOrUpdateClusterRole creates a ClusterRole if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +// +// Deprecated: use CreateOrUpdate() instead. func CreateOrUpdateClusterRole(client clientset.Interface, clusterRole *rbac.ClusterRole) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create ClusterRole") - return false, nil - } - if _, err := client.RbacV1().ClusterRoles().Update(ctx, clusterRole, metav1.UpdateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to update ClusterRole") - return false, nil - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrUpdate(context.Background(), client.RbacV1().ClusterRoles(), clusterRole) } -// CreateOrUpdateClusterRoleBinding creates a ClusterRoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +// CreateOrUpdateClusterRoleBinding creates a ClusterRoleBinding if the target resource doesn't exist. +// If the resource exists already, this function will update the resource instead. +// +// Deprecated: use CreateOrUpdate() instead. func CreateOrUpdateClusterRoleBinding(client clientset.Interface, clusterRoleBinding *rbac.ClusterRoleBinding) error { - var lastError error - err := wait.PollUntilContextTimeout(context.Background(), - apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, - true, func(_ context.Context) (bool, error) { - ctx := context.Background() - if _, err := client.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{}); err != nil { - if !apierrors.IsAlreadyExists(err) { - lastError = errors.Wrap(err, "unable to create ClusterRoleBinding") - return false, nil - } - if _, err := client.RbacV1().ClusterRoleBindings().Update(ctx, clusterRoleBinding, metav1.UpdateOptions{}); err != nil { - lastError = errors.Wrap(err, "unable to update ClusterRoleBinding") - return false, nil - } - } - return true, nil - }) - if err == nil { - return nil - } - return lastError + return CreateOrUpdate(context.Background(), client.RbacV1().ClusterRoleBindings(), clusterRoleBinding) } // PatchNodeOnce executes patchFn on the node object found by the node name. diff --git a/cmd/kubeadm/app/util/apiclient/idempotency_test.go b/cmd/kubeadm/app/util/apiclient/idempotency_test.go index 9acea1153a7..c2e7c0d94a3 100644 --- a/cmd/kubeadm/app/util/apiclient/idempotency_test.go +++ b/cmd/kubeadm/app/util/apiclient/idempotency_test.go @@ -18,6 +18,7 @@ package apiclient import ( "context" + "fmt" "os" "testing" "time" @@ -32,6 +33,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" clientsetfake "k8s.io/client-go/kubernetes/fake" clientgotesting "k8s.io/client-go/testing" @@ -56,49 +58,49 @@ func TestMain(m *testing.M) { os.Exit(exitVal) } -func TestCreateOrUpdateConfigMap(t *testing.T) { +func testCreateOrUpdate[T kubernetesObject](t *testing.T, resource, resources string, empty T, clientBuilder func(kubernetes.Interface, T) kubernetesInterface[T]) { tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) + nameFormat string + setupClient func(*clientsetfake.Clientset, string) expectedError bool }{ { - name: "create configmap success", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { + nameFormat: "create %s success", + setupClient: func(client *clientsetfake.Clientset, resources string) { + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { return true, nil, nil }) }, expectedError: false, }, { - name: "create configmap returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { + nameFormat: "create %s returns error", + setupClient: func(client *clientsetfake.Clientset, resources string) { + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("unknown error") }) }, expectedError: true, }, { - name: "configmap exists, update it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { + nameFormat: "%s exists, update it", + setupClient: func(client *clientsetfake.Clientset, resources string) { + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") }) - client.PrependReactor("update", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { + client.PrependReactor("update", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { return true, nil, nil }) }, expectedError: false, }, { - name: "configmap exists, update error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { + nameFormat: "%s exists, update error", + setupClient: func(client *clientsetfake.Clientset, resources string) { + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") }) - client.PrependReactor("update", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { + client.PrependReactor("update", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("") }) }, @@ -107,10 +109,103 @@ func TestCreateOrUpdateConfigMap(t *testing.T) { } for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { + t.Run(fmt.Sprintf(tc.nameFormat, resource), func(t *testing.T) { + client := clientsetfake.NewSimpleClientset() + tc.setupClient(client, resources) + err := CreateOrUpdate(context.Background(), clientBuilder(client, empty), empty) + if (err != nil) != tc.expectedError { + t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) + } + }) + } +} + +func TestCreateOrUpdateConfigMap(t *testing.T) { + testCreateOrUpdate(t, "configmap", "configmaps", &v1.ConfigMap{}, + func(client kubernetes.Interface, obj *v1.ConfigMap) kubernetesInterface[*v1.ConfigMap] { + return client.CoreV1().ConfigMaps(obj.ObjectMeta.Namespace) + }) +} + +func testCreateOrMutate[T kubernetesObject](t *testing.T, resource, resources string, empty T, clientBuilder func(kubernetes.Interface, T) kubernetesInterface[T]) { + tests := []struct { + nameFormat string + setupClient func(*clientsetfake.Clientset) + mutator objectMutator[T] + expectedError bool + }{ + { + nameFormat: "create %s", + setupClient: func(client *clientsetfake.Clientset) { + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("get", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + client.PrependReactor("update", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, nil + }) + }, + expectedError: false, + }, + { + nameFormat: "create %s error", + setupClient: func(client *clientsetfake.Clientset) { + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.New("") + }) + }, + expectedError: true, + }, + { + nameFormat: "%s exists, mutate returns error", + setupClient: func(client *clientsetfake.Clientset) { + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") + }) + client.PrependReactor("get", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, empty, nil + }) + }, + mutator: func(T) error { return errors.New("") }, + expectedError: true, + }, + { + nameFormat: "%s exists, get returns error", + setupClient: func(client *clientsetfake.Clientset) { + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") + }) + client.PrependReactor("get", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.New("") + }) + }, + expectedError: true, + }, + { + nameFormat: "%s exists, mutate returns error", + setupClient: func(client *clientsetfake.Clientset) { + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") + }) + client.PrependReactor("get", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, empty, nil + }) + client.PrependReactor("update", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.New("") + }) + }, + mutator: func(T) error { return nil }, + expectedError: true, + }, + } + + for _, tc := range tests { + t.Run(fmt.Sprintf(tc.nameFormat, resource), func(t *testing.T) { client := clientsetfake.NewSimpleClientset() tc.setupClient(client) - err := CreateOrUpdateConfigMap(client, &v1.ConfigMap{}) + err := CreateOrMutate[T](context.Background(), clientBuilder(client, empty), empty, tc.mutator) if (err != nil) != tc.expectedError { t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) } @@ -119,84 +214,67 @@ func TestCreateOrUpdateConfigMap(t *testing.T) { } func TestCreateOrMutateConfigMap(t *testing.T) { + testCreateOrMutate(t, "configmap", "configmaps", &v1.ConfigMap{}, + func(client kubernetes.Interface, obj *v1.ConfigMap) kubernetesInterface[*v1.ConfigMap] { + return client.CoreV1().ConfigMaps(obj.ObjectMeta.Namespace) + }) +} + +func testCreateOrRetain[T kubernetesObject](t *testing.T, resource, resources string, empty T, clientBuilder func(kubernetes.Interface, T) kubernetesInterface[T]) { tests := []struct { - name string + nameFormat string setupClient func(*clientsetfake.Clientset) - mutator func(*v1.ConfigMap) error expectedError bool }{ { - name: "create configmap", + nameFormat: "%s exists", setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil + client.PrependReactor("get", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, empty, nil }) - client.PrependReactor("get", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil + }, + expectedError: false, + }, + { + nameFormat: "%s get returns an error", + setupClient: func(client *clientsetfake.Clientset) { + client.PrependReactor("get", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.New("") }) - client.PrependReactor("update", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { + }, + expectedError: true, + }, + { + nameFormat: "%s is not found, create it", + setupClient: func(client *clientsetfake.Clientset) { + client.PrependReactor("get", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewNotFound(schema.GroupResource{}, "name") + }) + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { return true, nil, nil }) }, expectedError: false, }, { - name: "create configmap error", + nameFormat: "%s is not found, create returns an error", setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { + client.PrependReactor("get", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewNotFound(schema.GroupResource{}, "name") + }) + client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) { return true, nil, errors.New("") }) }, expectedError: true, }, - { - name: "configmap exists, mutate returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("get", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, &v1.ConfigMap{}, nil - }) - }, - mutator: func(*v1.ConfigMap) error { return errors.New("") }, - expectedError: true, - }, - { - name: "configmap exists, get returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("get", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - { - name: "configmap exists, mutate returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("get", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, &v1.ConfigMap{}, nil - }) - client.PrependReactor("update", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - mutator: func(*v1.ConfigMap) error { return nil }, - expectedError: true, - }, } for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { + t.Run(fmt.Sprintf(tc.nameFormat, resource), func(t *testing.T) { client := clientsetfake.NewSimpleClientset() tc.setupClient(client) - err := CreateOrMutateConfigMap(client, &v1.ConfigMap{}, tc.mutator) + err := CreateOrRetain[T](context.Background(), clientBuilder(client, empty), empty) if (err != nil) != tc.expectedError { t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) } @@ -205,623 +283,73 @@ func TestCreateOrMutateConfigMap(t *testing.T) { } func TestCreateOrRetainConfigMap(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "configmap exists", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("get", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, &v1.ConfigMap{}, nil - }) - }, - expectedError: false, - }, - { - name: "configmap get returns an error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("get", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - { - name: "configmap is not found, create it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("get", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewNotFound(schema.GroupResource{}, "name") - }) - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "configmap is not found, create returns an error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("get", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewNotFound(schema.GroupResource{}, "name") - }) - client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrRetainConfigMap(client, &v1.ConfigMap{}, "some-cm") - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrRetain(t, "configmap", "configmaps", &v1.ConfigMap{}, + func(client kubernetes.Interface, obj *v1.ConfigMap) kubernetesInterface[*v1.ConfigMap] { + return client.CoreV1().ConfigMaps(obj.ObjectMeta.Namespace) }) - } } func TestCreateOrUpdateSecret(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "create secret success", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "secrets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "create secret returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "secrets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("unknown error") - }) - }, - expectedError: true, - }, - { - name: "secret exists, update it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "secrets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "secrets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "secret exists, update error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "secrets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "secrets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrUpdateSecret(client, &v1.Secret{}) - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrUpdate(t, "secret", "secrets", &v1.Secret{}, + func(client kubernetes.Interface, obj *v1.Secret) kubernetesInterface[*v1.Secret] { + return client.CoreV1().Secrets(obj.ObjectMeta.Namespace) }) - } } func TestCreateOrUpdateServiceAccount(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "create serviceaccount success", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "serviceaccounts", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "create serviceaccount returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "serviceaccounts", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("unknown error") - }) - }, - expectedError: true, - }, - { - name: "serviceaccount exists, update it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "serviceaccounts", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "serviceaccounts", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "serviceaccount exists, update error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "serviceaccounts", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "serviceaccounts", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrUpdateServiceAccount(client, &v1.ServiceAccount{}) - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrUpdate(t, "serviceaccount", "serviceaccounts", &v1.ServiceAccount{}, + func(client kubernetes.Interface, obj *v1.ServiceAccount) kubernetesInterface[*v1.ServiceAccount] { + return client.CoreV1().ServiceAccounts(obj.ObjectMeta.Namespace) }) - } } func TestCreateOrUpdateDeployment(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "create deployment success", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "create deployment returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("unknown error") - }) - }, - expectedError: true, - }, - { - name: "deployment exists, update it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "deployment exists, update error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrUpdateDeployment(client, &apps.Deployment{}) - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrUpdate(t, "deployment", "deployments", &apps.Deployment{}, + func(client kubernetes.Interface, obj *apps.Deployment) kubernetesInterface[*apps.Deployment] { + return client.AppsV1().Deployments(obj.ObjectMeta.Namespace) }) - } } func TestCreateOrRetainDeployment(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "deployment exists", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("get", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, &apps.Deployment{}, nil - }) - }, - expectedError: false, - }, - { - name: "deployment get returns an error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("get", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - { - name: "deployment is not found, create it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("get", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewNotFound(schema.GroupResource{}, "name") - }) - client.PrependReactor("create", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "deployment is not found, create returns an error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("get", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewNotFound(schema.GroupResource{}, "name") - }) - client.PrependReactor("create", "deployments", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrRetainDeployment(client, &apps.Deployment{}, "some-deployment") - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrRetain(t, "deployment", "deployments", &apps.Deployment{}, + func(client kubernetes.Interface, obj *apps.Deployment) kubernetesInterface[*apps.Deployment] { + return client.AppsV1().Deployments(obj.ObjectMeta.Namespace) }) - } } func TestCreateOrUpdateDaemonSet(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "create daemonset success", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "daemonsets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "create daemonset returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "daemonsets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("unknown error") - }) - }, - expectedError: true, - }, - { - name: "daemonset exists, update it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "daemonsets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "daemonsets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "daemonset exists, update error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "daemonsets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "daemonsets", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrUpdateDaemonSet(client, &apps.DaemonSet{}) - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrUpdate(t, "daemonset", "daemonsets", &apps.DaemonSet{}, + func(client kubernetes.Interface, obj *apps.DaemonSet) kubernetesInterface[*apps.DaemonSet] { + return client.AppsV1().DaemonSets(obj.ObjectMeta.Namespace) }) - } } func TestCreateOrUpdateRole(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "create role success", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "roles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "create role returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "roles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("unknown error") - }) - }, - expectedError: true, - }, - { - name: "role exists, update it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "roles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "roles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "role exists, update error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "roles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "roles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrUpdateRole(client, &rbac.Role{}) - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrUpdate(t, "role", "roles", &rbac.Role{}, + func(client kubernetes.Interface, obj *rbac.Role) kubernetesInterface[*rbac.Role] { + return client.RbacV1().Roles(obj.ObjectMeta.Namespace) }) - } } func TestCreateOrUpdateRoleBindings(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "create rolebinding success", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "rolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "create rolebinding returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "rolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("unknown error") - }) - }, - expectedError: true, - }, - { - name: "rolebinding exists, update it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "rolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "rolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "rolebinding exists, update error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "rolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "rolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{}) - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrUpdate(t, "rolebinding", "rolebindings", &rbac.RoleBinding{}, + func(client kubernetes.Interface, obj *rbac.RoleBinding) kubernetesInterface[*rbac.RoleBinding] { + return client.RbacV1().RoleBindings(obj.ObjectMeta.Namespace) }) - } } func TestCreateOrUpdateClusterRole(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "create clusterrole success", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "clusterroles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "create clusterrole returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "clusterroles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("unknown error") - }) - }, - expectedError: true, - }, - { - name: "clusterrole exists, update it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "clusterroles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "clusterroles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "clusterrole exists, update error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "clusterroles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "clusterroles", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrUpdateClusterRole(client, &rbac.ClusterRole{}) - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrUpdate(t, "clusterrole", "clusterroles", &rbac.ClusterRole{}, + func(client kubernetes.Interface, obj *rbac.ClusterRole) kubernetesInterface[*rbac.ClusterRole] { + return client.RbacV1().ClusterRoles() }) - } } func TestCreateOrUpdateClusterRoleBindings(t *testing.T) { - tests := []struct { - name string - setupClient func(*clientsetfake.Clientset) - expectedError bool - }{ - { - name: "create clusterrolebinding success", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "clusterrolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "create clusterrolebinding returns error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "clusterrolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("unknown error") - }) - }, - expectedError: true, - }, - { - name: "clusterrolebinding exists, update it", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "clusterrolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "clusterrolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, nil - }) - }, - expectedError: false, - }, - { - name: "clusterrolebinding exists, update error", - setupClient: func(client *clientsetfake.Clientset) { - client.PrependReactor("create", "clusterrolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") - }) - client.PrependReactor("update", "clusterrolebindings", func(clientgotesting.Action) (bool, runtime.Object, error) { - return true, nil, errors.New("") - }) - }, - expectedError: true, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - client := clientsetfake.NewSimpleClientset() - tc.setupClient(client) - err := CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{}) - if (err != nil) != tc.expectedError { - t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) - } + testCreateOrUpdate(t, "clusterrolebinding", "clusterrolebindings", &rbac.ClusterRoleBinding{}, + func(client kubernetes.Interface, obj *rbac.ClusterRoleBinding) kubernetesInterface[*rbac.ClusterRoleBinding] { + return client.RbacV1().ClusterRoleBindings() }) - } } func TestPatchNodeOnce(t *testing.T) {