kubeadm: generalise CreateOrUpdate etc.

This uses generics to generalise the various CreateOrUpdate,
CreateOrRetain etc. functions. Where appropriate, the context is added
as an initial argument to the new functions.

ConfigMapMutator isn't used anywhere else, so it's dropped in favour
of the private objectMutator added in this commit.

Signed-off-by: Stephen Kitt <skitt@redhat.com>
This commit is contained in:
Stephen Kitt 2023-10-08 22:16:45 -04:00
parent 4a0b0365ef
commit db4c509e71
No known key found for this signature in database
GPG Key ID: 1CC5FA453662A71D
2 changed files with 318 additions and 901 deletions

View File

@ -28,6 +28,7 @@ import (
rbac "k8s.io/api/rbac/v1" rbac "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
@ -37,28 +38,40 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/constants"
) )
// ConfigMapMutator is a function that mutates the given ConfigMap and optionally returns an error // objectMutator is a function that mutates the given runtime object and optionally returns an error
type ConfigMapMutator func(*v1.ConfigMap) error type objectMutator[T runtime.Object] func(T) error
// apiCallRetryInterval holds a local copy of apiCallRetryInterval for testing purposes // apiCallRetryInterval holds a local copy of apiCallRetryInterval for testing purposes
var apiCallRetryInterval = constants.KubernetesAPICallRetryInterval 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. type kubernetesObject interface {
func CreateOrUpdateConfigMap(client clientset.Interface, cm *v1.ConfigMap) error { 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 var lastError error
err := wait.PollUntilContextTimeout(context.Background(), err := wait.PollUntilContextTimeout(ctx,
apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration,
true, func(_ context.Context) (bool, error) { 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() 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) { if !apierrors.IsAlreadyExists(err) {
lastError = errors.Wrap(err, "unable to create ConfigMap") lastError = errors.Wrapf(err, "unable to create %T", obj)
return false, nil return false, nil
} }
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(ctx, cm, metav1.UpdateOptions{}); err != nil { if _, err := client.Update(ctx, obj, metav1.UpdateOptions{}); err != nil {
lastError = errors.Wrap(err, "unable to update ConfigMap") lastError = errors.Wrapf(err, "unable to update %T", obj)
return false, nil return false, nil
} }
} }
@ -70,19 +83,30 @@ func CreateOrUpdateConfigMap(client clientset.Interface, cm *v1.ConfigMap) error
return lastError return lastError
} }
// CreateOrMutateConfigMap tries to create the ConfigMap provided as cm. If the resource exists already, the latest version will be fetched from // CreateOrUpdateConfigMap creates a ConfigMap if the target resource doesn't exist.
// the cluster and mutator callback will be called on it, then an Update of the mutated ConfigMap will be performed. This function is resilient // If the resource exists already, this function will update the resource instead.
// 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) // Deprecated: use CreateOrUpdate() instead.
func CreateOrMutateConfigMap(client clientset.Interface, cm *v1.ConfigMap, mutator ConfigMapMutator) error { 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 var lastError error
err := wait.PollUntilContextTimeout(context.Background(), err := wait.PollUntilContextTimeout(ctx,
apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration, apiCallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration,
true, func(_ context.Context) (bool, error) { 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 lastError = err
if apierrors.IsAlreadyExists(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 lastError == nil, nil
} }
return false, nil return false, nil
@ -95,273 +119,138 @@ func CreateOrMutateConfigMap(client clientset.Interface, cm *v1.ConfigMap, mutat
return lastError return lastError
} }
// mutateConfigMap takes a ConfigMap Object Meta (namespace and name), retrieves the resource from the server and tries to mutate it // CreateOrMutateConfigMap tries to create the ConfigMap provided as cm. If the resource exists already, the latest version will be fetched from
// by calling to the mutator callback, then an Update of the mutated ConfigMap will be performed. This function is resilient // 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 // 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). // taking place).
func mutateConfigMap(client clientset.Interface, meta metav1.ObjectMeta, mutator ConfigMapMutator) error { //
ctx := context.Background() // Deprecated: use CreateOrMutate() instead.
configMap, err := client.CoreV1().ConfigMaps(meta.Namespace).Get(ctx, meta.Name, metav1.GetOptions{}) 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 { 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 { if err = mutator(obj); err != nil {
return errors.Wrap(err, "unable to mutate ConfigMap") 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 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 { func CreateOrRetainConfigMap(client clientset.Interface, cm *v1.ConfigMap, configMapName string) error {
var lastError error return CreateOrRetain(context.Background(), client.CoreV1().ConfigMaps(cm.Namespace), cm)
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
} }
// 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 { func CreateOrUpdateSecret(client clientset.Interface, secret *v1.Secret) error {
var lastError error return CreateOrUpdate(context.Background(), client.CoreV1().Secrets(secret.Namespace), secret)
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
} }
// 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 { func CreateOrUpdateServiceAccount(client clientset.Interface, sa *v1.ServiceAccount) error {
var lastError error return CreateOrUpdate(context.Background(), client.CoreV1().ServiceAccounts(sa.Namespace), sa)
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
} }
// 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 { func CreateOrUpdateDeployment(client clientset.Interface, deploy *apps.Deployment) error {
var lastError error return CreateOrUpdate(context.Background(), client.AppsV1().Deployments(deploy.Namespace), deploy)
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
} }
// 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 { func CreateOrRetainDeployment(client clientset.Interface, deploy *apps.Deployment, deployName string) error {
var lastError error return CreateOrRetain(context.Background(), client.AppsV1().Deployments(deploy.Namespace), deploy)
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
} }
// 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 { func CreateOrUpdateDaemonSet(client clientset.Interface, ds *apps.DaemonSet) error {
var lastError error return CreateOrUpdate(context.Background(), client.AppsV1().DaemonSets(ds.Namespace), ds)
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
} }
// 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 { func CreateOrUpdateRole(client clientset.Interface, role *rbac.Role) error {
var lastError error return CreateOrUpdate(context.Background(), client.RbacV1().Roles(role.Namespace), role)
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
} }
// 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 { func CreateOrUpdateRoleBinding(client clientset.Interface, roleBinding *rbac.RoleBinding) error {
var lastError error return CreateOrUpdate(context.Background(), client.RbacV1().RoleBindings(roleBinding.Namespace), roleBinding)
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
} }
// 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 { func CreateOrUpdateClusterRole(client clientset.Interface, clusterRole *rbac.ClusterRole) error {
var lastError error return CreateOrUpdate(context.Background(), client.RbacV1().ClusterRoles(), clusterRole)
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
} }
// 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 { func CreateOrUpdateClusterRoleBinding(client clientset.Interface, clusterRoleBinding *rbac.ClusterRoleBinding) error {
var lastError error return CreateOrUpdate(context.Background(), client.RbacV1().ClusterRoleBindings(), clusterRoleBinding)
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
} }
// PatchNodeOnce executes patchFn on the node object found by the node name. // PatchNodeOnce executes patchFn on the node object found by the node name.

View File

@ -18,6 +18,7 @@ package apiclient
import ( import (
"context" "context"
"fmt"
"os" "os"
"testing" "testing"
"time" "time"
@ -32,6 +33,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
clientsetfake "k8s.io/client-go/kubernetes/fake" clientsetfake "k8s.io/client-go/kubernetes/fake"
clientgotesting "k8s.io/client-go/testing" clientgotesting "k8s.io/client-go/testing"
@ -56,49 +58,49 @@ func TestMain(m *testing.M) {
os.Exit(exitVal) 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 { tests := []struct {
name string nameFormat string
setupClient func(*clientsetfake.Clientset) setupClient func(*clientsetfake.Clientset, string)
expectedError bool expectedError bool
}{ }{
{ {
name: "create configmap success", nameFormat: "create %s success",
setupClient: func(client *clientsetfake.Clientset) { setupClient: func(client *clientsetfake.Clientset, resources string) {
client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) {
return true, nil, nil return true, nil, nil
}) })
}, },
expectedError: false, expectedError: false,
}, },
{ {
name: "create configmap returns error", nameFormat: "create %s returns error",
setupClient: func(client *clientsetfake.Clientset) { setupClient: func(client *clientsetfake.Clientset, resources string) {
client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("unknown error") return true, nil, errors.New("unknown error")
}) })
}, },
expectedError: true, expectedError: true,
}, },
{ {
name: "configmap exists, update it", nameFormat: "%s exists, update it",
setupClient: func(client *clientsetfake.Clientset) { setupClient: func(client *clientsetfake.Clientset, resources string) {
client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") 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 return true, nil, nil
}) })
}, },
expectedError: false, expectedError: false,
}, },
{ {
name: "configmap exists, update error", nameFormat: "%s exists, update error",
setupClient: func(client *clientsetfake.Clientset) { setupClient: func(client *clientsetfake.Clientset, resources string) {
client.PrependReactor("create", "configmaps", func(clientgotesting.Action) (bool, runtime.Object, error) { client.PrependReactor("create", resources, func(clientgotesting.Action) (bool, runtime.Object, error) {
return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{}, "name") 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("") return true, nil, errors.New("")
}) })
}, },
@ -107,10 +109,103 @@ func TestCreateOrUpdateConfigMap(t *testing.T) {
} }
for _, tc := range tests { 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() client := clientsetfake.NewSimpleClientset()
tc.setupClient(client) tc.setupClient(client)
err := CreateOrUpdateConfigMap(client, &v1.ConfigMap{}) err := CreateOrMutate[T](context.Background(), clientBuilder(client, empty), empty, tc.mutator)
if (err != nil) != tc.expectedError { if (err != nil) != tc.expectedError {
t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) 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) { 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 { tests := []struct {
name string nameFormat string
setupClient func(*clientsetfake.Clientset) setupClient func(*clientsetfake.Clientset)
mutator func(*v1.ConfigMap) error
expectedError bool expectedError bool
}{ }{
{ {
name: "create configmap", nameFormat: "%s exists",
setupClient: func(client *clientsetfake.Clientset) { 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, nil 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 return true, nil, nil
}) })
}, },
expectedError: false, expectedError: false,
}, },
{ {
name: "create configmap error", nameFormat: "%s is not found, create returns an error",
setupClient: func(client *clientsetfake.Clientset) { 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("") return true, nil, errors.New("")
}) })
}, },
expectedError: true, 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 { 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() client := clientsetfake.NewSimpleClientset()
tc.setupClient(client) tc.setupClient(client)
err := CreateOrMutateConfigMap(client, &v1.ConfigMap{}, tc.mutator) err := CreateOrRetain[T](context.Background(), clientBuilder(client, empty), empty)
if (err != nil) != tc.expectedError { if (err != nil) != tc.expectedError {
t.Fatalf("expected error: %v, got %v, error: %v", tc.expectedError, err != nil, err) 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) { func TestCreateOrRetainConfigMap(t *testing.T) {
tests := []struct { testCreateOrRetain(t, "configmap", "configmaps", &v1.ConfigMap{},
name string func(client kubernetes.Interface, obj *v1.ConfigMap) kubernetesInterface[*v1.ConfigMap] {
setupClient func(*clientsetfake.Clientset) return client.CoreV1().ConfigMaps(obj.ObjectMeta.Namespace)
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)
}
})
}
} }
func TestCreateOrUpdateSecret(t *testing.T) { func TestCreateOrUpdateSecret(t *testing.T) {
tests := []struct { testCreateOrUpdate(t, "secret", "secrets", &v1.Secret{},
name string func(client kubernetes.Interface, obj *v1.Secret) kubernetesInterface[*v1.Secret] {
setupClient func(*clientsetfake.Clientset) return client.CoreV1().Secrets(obj.ObjectMeta.Namespace)
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)
}
})
}
} }
func TestCreateOrUpdateServiceAccount(t *testing.T) { func TestCreateOrUpdateServiceAccount(t *testing.T) {
tests := []struct { testCreateOrUpdate(t, "serviceaccount", "serviceaccounts", &v1.ServiceAccount{},
name string func(client kubernetes.Interface, obj *v1.ServiceAccount) kubernetesInterface[*v1.ServiceAccount] {
setupClient func(*clientsetfake.Clientset) return client.CoreV1().ServiceAccounts(obj.ObjectMeta.Namespace)
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)
}
})
}
} }
func TestCreateOrUpdateDeployment(t *testing.T) { func TestCreateOrUpdateDeployment(t *testing.T) {
tests := []struct { testCreateOrUpdate(t, "deployment", "deployments", &apps.Deployment{},
name string func(client kubernetes.Interface, obj *apps.Deployment) kubernetesInterface[*apps.Deployment] {
setupClient func(*clientsetfake.Clientset) return client.AppsV1().Deployments(obj.ObjectMeta.Namespace)
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)
}
})
}
} }
func TestCreateOrRetainDeployment(t *testing.T) { func TestCreateOrRetainDeployment(t *testing.T) {
tests := []struct { testCreateOrRetain(t, "deployment", "deployments", &apps.Deployment{},
name string func(client kubernetes.Interface, obj *apps.Deployment) kubernetesInterface[*apps.Deployment] {
setupClient func(*clientsetfake.Clientset) return client.AppsV1().Deployments(obj.ObjectMeta.Namespace)
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)
}
})
}
} }
func TestCreateOrUpdateDaemonSet(t *testing.T) { func TestCreateOrUpdateDaemonSet(t *testing.T) {
tests := []struct { testCreateOrUpdate(t, "daemonset", "daemonsets", &apps.DaemonSet{},
name string func(client kubernetes.Interface, obj *apps.DaemonSet) kubernetesInterface[*apps.DaemonSet] {
setupClient func(*clientsetfake.Clientset) return client.AppsV1().DaemonSets(obj.ObjectMeta.Namespace)
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)
}
})
}
} }
func TestCreateOrUpdateRole(t *testing.T) { func TestCreateOrUpdateRole(t *testing.T) {
tests := []struct { testCreateOrUpdate(t, "role", "roles", &rbac.Role{},
name string func(client kubernetes.Interface, obj *rbac.Role) kubernetesInterface[*rbac.Role] {
setupClient func(*clientsetfake.Clientset) return client.RbacV1().Roles(obj.ObjectMeta.Namespace)
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)
}
})
}
} }
func TestCreateOrUpdateRoleBindings(t *testing.T) { func TestCreateOrUpdateRoleBindings(t *testing.T) {
tests := []struct { testCreateOrUpdate(t, "rolebinding", "rolebindings", &rbac.RoleBinding{},
name string func(client kubernetes.Interface, obj *rbac.RoleBinding) kubernetesInterface[*rbac.RoleBinding] {
setupClient func(*clientsetfake.Clientset) return client.RbacV1().RoleBindings(obj.ObjectMeta.Namespace)
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)
}
})
}
} }
func TestCreateOrUpdateClusterRole(t *testing.T) { func TestCreateOrUpdateClusterRole(t *testing.T) {
tests := []struct { testCreateOrUpdate(t, "clusterrole", "clusterroles", &rbac.ClusterRole{},
name string func(client kubernetes.Interface, obj *rbac.ClusterRole) kubernetesInterface[*rbac.ClusterRole] {
setupClient func(*clientsetfake.Clientset) return client.RbacV1().ClusterRoles()
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)
}
})
}
} }
func TestCreateOrUpdateClusterRoleBindings(t *testing.T) { func TestCreateOrUpdateClusterRoleBindings(t *testing.T) {
tests := []struct { testCreateOrUpdate(t, "clusterrolebinding", "clusterrolebindings", &rbac.ClusterRoleBinding{},
name string func(client kubernetes.Interface, obj *rbac.ClusterRoleBinding) kubernetesInterface[*rbac.ClusterRoleBinding] {
setupClient func(*clientsetfake.Clientset) return client.RbacV1().ClusterRoleBindings()
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)
}
})
}
} }
func TestPatchNodeOnce(t *testing.T) { func TestPatchNodeOnce(t *testing.T) {