Merge pull request #2724 from lonelyCZ/pr-karmadactl-util-idempotency
Fix and unify `CreateOrUpdateXXX` functions for karmadactl
This commit is contained in:
commit
f32741e13f
|
@ -15,6 +15,7 @@ import (
|
||||||
addonutils "github.com/karmada-io/karmada/pkg/karmadactl/addons/utils"
|
addonutils "github.com/karmada-io/karmada/pkg/karmadactl/addons/utils"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes"
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes"
|
||||||
initutils "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
initutils "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
||||||
|
cmdutil "github.com/karmada-io/karmada/pkg/karmadactl/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var karmadaDeschedulerLabels = map[string]string{"app": addoninit.DeschedulerResourceName}
|
var karmadaDeschedulerLabels = map[string]string{"app": addoninit.DeschedulerResourceName}
|
||||||
|
@ -59,7 +60,7 @@ var enableDescheduler = func(opts *addoninit.CommandAddonsEnableOption) error {
|
||||||
return fmt.Errorf("decode descheduler deployment error: %v", err)
|
return fmt.Errorf("decode descheduler deployment error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addonutils.CreateOrUpdateDeployment(opts.KubeClientSet, karmadaDeschedulerDeployment); err != nil {
|
if err := cmdutil.CreateOrUpdateDeployment(opts.KubeClientSet, karmadaDeschedulerDeployment); err != nil {
|
||||||
return fmt.Errorf("create karmada descheduler deployment error: %v", err)
|
return fmt.Errorf("create karmada descheduler deployment error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
addonutils "github.com/karmada-io/karmada/pkg/karmadactl/addons/utils"
|
addonutils "github.com/karmada-io/karmada/pkg/karmadactl/addons/utils"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes"
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes"
|
||||||
initutils "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
initutils "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
||||||
|
cmdutil "github.com/karmada-io/karmada/pkg/karmadactl/util"
|
||||||
"github.com/karmada-io/karmada/pkg/util/names"
|
"github.com/karmada-io/karmada/pkg/util/names"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ var enableEstimator = func(opts *addoninit.CommandAddonsEnableOption) error {
|
||||||
|
|
||||||
secretName := fmt.Sprintf("%s-kubeconfig", opts.Cluster)
|
secretName := fmt.Sprintf("%s-kubeconfig", opts.Cluster)
|
||||||
secret := secretFromSpec(secretName, opts.Namespace, corev1.SecretTypeOpaque, map[string]string{secretName: string(configBytes)})
|
secret := secretFromSpec(secretName, opts.Namespace, corev1.SecretTypeOpaque, map[string]string{secretName: string(configBytes)})
|
||||||
if err := addonutils.CreateOrUpdateSecret(opts.KubeClientSet, secret); err != nil {
|
if err := cmdutil.CreateOrUpdateSecret(opts.KubeClientSet, secret); err != nil {
|
||||||
return fmt.Errorf("create or update scheduler estimator secret error: %v", err)
|
return fmt.Errorf("create or update scheduler estimator secret error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +90,7 @@ var enableEstimator = func(opts *addoninit.CommandAddonsEnableOption) error {
|
||||||
if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), karmadaEstimatorServiceBytes, karmadaEstimatorService); err != nil {
|
if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), karmadaEstimatorServiceBytes, karmadaEstimatorService); err != nil {
|
||||||
return fmt.Errorf("decode karmada scheduler estimator service error: %v", err)
|
return fmt.Errorf("decode karmada scheduler estimator service error: %v", err)
|
||||||
}
|
}
|
||||||
if err := addonutils.CreateService(opts.KubeClientSet, karmadaEstimatorService); err != nil {
|
if err := cmdutil.CreateService(opts.KubeClientSet, karmadaEstimatorService); err != nil {
|
||||||
return fmt.Errorf("create or update scheduler estimator service error: %v", err)
|
return fmt.Errorf("create or update scheduler estimator service error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +109,7 @@ var enableEstimator = func(opts *addoninit.CommandAddonsEnableOption) error {
|
||||||
if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), karmadaEstimatorDeploymentBytes, karmadaEstimatorDeployment); err != nil {
|
if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), karmadaEstimatorDeploymentBytes, karmadaEstimatorDeployment); err != nil {
|
||||||
return fmt.Errorf("decode karmada scheduler estimator deployment error: %v", err)
|
return fmt.Errorf("decode karmada scheduler estimator deployment error: %v", err)
|
||||||
}
|
}
|
||||||
if err := addonutils.CreateOrUpdateDeployment(opts.KubeClientSet, karmadaEstimatorDeployment); err != nil {
|
if err := cmdutil.CreateOrUpdateDeployment(opts.KubeClientSet, karmadaEstimatorDeployment); err != nil {
|
||||||
return fmt.Errorf("create or update scheduler estimator deployment error: %v", err)
|
return fmt.Errorf("create or update scheduler estimator deployment error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
initkarmada "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/karmada"
|
initkarmada "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/karmada"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes"
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes"
|
||||||
initutils "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
initutils "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
||||||
|
cmdutil "github.com/karmada-io/karmada/pkg/karmadactl/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -135,7 +136,7 @@ func installComponentsOnHostCluster(opts *addoninit.CommandAddonsEnableOption) e
|
||||||
return fmt.Errorf("decode karmada search service error: %v", err)
|
return fmt.Errorf("decode karmada search service error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addonutils.CreateService(opts.KubeClientSet, karmadaSearchService); err != nil {
|
if err := cmdutil.CreateService(opts.KubeClientSet, karmadaSearchService); err != nil {
|
||||||
return fmt.Errorf("create karmada search service error: %v", err)
|
return fmt.Errorf("create karmada search service error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +162,7 @@ func installComponentsOnHostCluster(opts *addoninit.CommandAddonsEnableOption) e
|
||||||
if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), karmadaSearchDeploymentBytes, karmadaSearchDeployment); err != nil {
|
if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), karmadaSearchDeploymentBytes, karmadaSearchDeployment); err != nil {
|
||||||
return fmt.Errorf("decode karmada search deployment error: %v", err)
|
return fmt.Errorf("decode karmada search deployment error: %v", err)
|
||||||
}
|
}
|
||||||
if err := addonutils.CreateOrUpdateDeployment(opts.KubeClientSet, karmadaSearchDeployment); err != nil {
|
if err := cmdutil.CreateOrUpdateDeployment(opts.KubeClientSet, karmadaSearchDeployment); err != nil {
|
||||||
return fmt.Errorf("create karmada search deployment error: %v", err)
|
return fmt.Errorf("create karmada search deployment error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +187,7 @@ func installComponentsOnKarmadaControlPlane(opts *addoninit.CommandAddonsEnableO
|
||||||
if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), aaServiceBytes, aaService); err != nil {
|
if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), aaServiceBytes, aaService); err != nil {
|
||||||
return fmt.Errorf("decode karmada search AA service error: %v", err)
|
return fmt.Errorf("decode karmada search AA service error: %v", err)
|
||||||
}
|
}
|
||||||
if err := addonutils.CreateService(opts.KarmadaKubeClientSet, aaService); err != nil {
|
if err := cmdutil.CreateService(opts.KarmadaKubeClientSet, aaService); err != nil {
|
||||||
return fmt.Errorf("create karmada search AA service error: %v", err)
|
return fmt.Errorf("create karmada search AA service error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +205,7 @@ func installComponentsOnKarmadaControlPlane(opts *addoninit.CommandAddonsEnableO
|
||||||
return fmt.Errorf("decode karmada search AA apiservice error: %v", err)
|
return fmt.Errorf("decode karmada search AA apiservice error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = addonutils.CreateOrUpdateAPIService(opts.KarmadaAggregatorClientSet, aaAPIService); err != nil {
|
if err = cmdutil.CreateOrUpdateAPIService(opts.KarmadaAggregatorClientSet, aaAPIService); err != nil {
|
||||||
return fmt.Errorf("craete karmada search AA apiservice error: %v", err)
|
return fmt.Errorf("craete karmada search AA apiservice error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
|
||||||
aggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateService creates a Service if the target resource doesn't exist. If the resource exists already, return directly
|
|
||||||
func CreateService(KubeClientSet *kubernetes.Clientset, service *corev1.Service) error {
|
|
||||||
if _, err := KubeClientSet.CoreV1().Services(service.ObjectMeta.Namespace).Create(context.TODO(), service, metav1.CreateOptions{}); err != nil {
|
|
||||||
if !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("unable to create service: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.Warningf("Service %s is existed, creation process will skip", service.ObjectMeta.Name)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOrUpdateSecret creates a Secret if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
||||||
func CreateOrUpdateSecret(KubeClientSet *kubernetes.Clientset, secret *corev1.Secret) error {
|
|
||||||
if _, err := KubeClientSet.CoreV1().Secrets(secret.ObjectMeta.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil {
|
|
||||||
if !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("unable to create service: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := KubeClientSet.CoreV1().Secrets(secret.ObjectMeta.Namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil {
|
|
||||||
return fmt.Errorf("unable to update deployment: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOrUpdateDeployment creates a Deployment if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
||||||
func CreateOrUpdateDeployment(client kubernetes.Interface, deploy *appsv1.Deployment) error {
|
|
||||||
if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Create(context.TODO(), deploy, metav1.CreateOptions{}); err != nil {
|
|
||||||
if !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("unable to create deployment: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Update(context.TODO(), deploy, metav1.UpdateOptions{}); err != nil {
|
|
||||||
return fmt.Errorf("unable to update deployment: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOrUpdateAPIService creates a ApiService if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
||||||
func CreateOrUpdateAPIService(apiRegistrationClient *aggregator.Clientset, apiservice *apiregistrationv1.APIService) error {
|
|
||||||
if _, err := apiRegistrationClient.ApiregistrationV1().APIServices().Create(context.TODO(), apiservice, metav1.CreateOptions{}); err != nil {
|
|
||||||
if !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("unable to create apiService: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
existAPIService, err := apiRegistrationClient.ApiregistrationV1().APIServices().Get(context.TODO(), apiservice.ObjectMeta.Name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
apiservice.ObjectMeta.ResourceVersion = existAPIService.ObjectMeta.ResourceVersion
|
|
||||||
|
|
||||||
if _, err := apiRegistrationClient.ApiregistrationV1().APIServices().Update(context.TODO(), apiservice, metav1.UpdateOptions{}); err != nil {
|
|
||||||
return fmt.Errorf("unable to update apiService: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
|
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
cmdutil "github.com/karmada-io/karmada/pkg/karmadactl/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -47,7 +47,7 @@ func CreateBootstrapConfigMapIfNotExists(clientSet *kubernetes.Clientset, file s
|
||||||
|
|
||||||
// Create or update the ConfigMap in the kube-public namespace
|
// Create or update the ConfigMap in the kube-public namespace
|
||||||
klog.V(1).Infoln("[bootstrap-token] creating/updating ConfigMap in kube-public namespace")
|
klog.V(1).Infoln("[bootstrap-token] creating/updating ConfigMap in kube-public namespace")
|
||||||
return utils.CreateOrUpdateConfigMap(clientSet, &corev1.ConfigMap{
|
return cmdutil.CreateOrUpdateConfigMap(clientSet, &corev1.ConfigMap{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: bootstrapapi.ConfigMapClusterInfo,
|
Name: bootstrapapi.ConfigMapClusterInfo,
|
||||||
Namespace: metav1.NamespacePublic,
|
Namespace: metav1.NamespacePublic,
|
||||||
|
@ -61,7 +61,7 @@ func CreateBootstrapConfigMapIfNotExists(clientSet *kubernetes.Clientset, file s
|
||||||
// CreateClusterInfoRBACRules creates the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace to unauthenticated users
|
// CreateClusterInfoRBACRules creates the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace to unauthenticated users
|
||||||
func CreateClusterInfoRBACRules(clientSet *kubernetes.Clientset) error {
|
func CreateClusterInfoRBACRules(clientSet *kubernetes.Clientset) error {
|
||||||
klog.V(1).Infoln("creating the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace")
|
klog.V(1).Infoln("creating the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace")
|
||||||
err := utils.CreateOrUpdateRole(clientSet, &rbacv1.Role{
|
err := cmdutil.CreateOrUpdateRole(clientSet, &rbacv1.Role{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: BootstrapSignerClusterRoleName,
|
Name: BootstrapSignerClusterRoleName,
|
||||||
Namespace: metav1.NamespacePublic,
|
Namespace: metav1.NamespacePublic,
|
||||||
|
@ -79,7 +79,7 @@ func CreateClusterInfoRBACRules(clientSet *kubernetes.Clientset) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.CreateOrUpdateRoleBinding(clientSet, &rbacv1.RoleBinding{
|
return cmdutil.CreateOrUpdateRoleBinding(clientSet, &rbacv1.RoleBinding{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: BootstrapSignerClusterRoleName,
|
Name: BootstrapSignerClusterRoleName,
|
||||||
Namespace: metav1.NamespacePublic,
|
Namespace: metav1.NamespacePublic,
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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(clientSet *kubernetes.Clientset, cm *corev1.ConfigMap) error {
|
|
||||||
if _, err := clientSet.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(context.TODO(), cm, metav1.CreateOptions{}); err != nil {
|
|
||||||
if !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("unable to create ConfigMap: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := clientSet.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(context.TODO(), cm, metav1.UpdateOptions{}); err != nil {
|
|
||||||
return fmt.Errorf("unable to update ConfigMap: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
klog.Infof("ConfigMap %s/%s has been created or updated.", cm.ObjectMeta.Namespace, cm.ObjectMeta.Name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -87,35 +87,3 @@ func CreateIfNotExistClusterRoleBinding(clientSet kubernetes.Interface, binding
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOrUpdateRole creates a Role if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
||||||
func CreateOrUpdateRole(clientSet kubernetes.Interface, role *rbacv1.Role) error {
|
|
||||||
if _, err := clientSet.RbacV1().Roles(role.ObjectMeta.Namespace).Create(context.TODO(), role, metav1.CreateOptions{}); err != nil {
|
|
||||||
if !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("unable to create RBAC role: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := clientSet.RbacV1().Roles(role.ObjectMeta.Namespace).Update(context.TODO(), role, metav1.UpdateOptions{}); err != nil {
|
|
||||||
return fmt.Errorf("unable to update RBAC role: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
klog.Infof("Role %s%s has been created or updated.", role.ObjectMeta.Namespace, role.ObjectMeta.Name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOrUpdateRoleBinding creates a RoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
||||||
func CreateOrUpdateRoleBinding(clientSet kubernetes.Interface, roleBinding *rbacv1.RoleBinding) error {
|
|
||||||
if _, err := clientSet.RbacV1().RoleBindings(roleBinding.ObjectMeta.Namespace).Create(context.TODO(), roleBinding, metav1.CreateOptions{}); err != nil {
|
|
||||||
if !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("unable to create RBAC rolebinding: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := clientSet.RbacV1().RoleBindings(roleBinding.ObjectMeta.Namespace).Update(context.TODO(), roleBinding, metav1.UpdateOptions{}); err != nil {
|
|
||||||
return fmt.Errorf("unable to update RBAC rolebinding: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
klog.Infof("RoleBinding %s/%s has been created or updated.", roleBinding.ObjectMeta.Namespace, roleBinding.ObjectMeta.Name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ import (
|
||||||
check "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes"
|
check "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/options"
|
"github.com/karmada-io/karmada/pkg/karmadactl/options"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/util"
|
cmdutil "github.com/karmada-io/karmada/pkg/karmadactl/util"
|
||||||
tokenutil "github.com/karmada-io/karmada/pkg/karmadactl/util/bootstraptoken"
|
tokenutil "github.com/karmada-io/karmada/pkg/karmadactl/util/bootstraptoken"
|
||||||
karmadautil "github.com/karmada-io/karmada/pkg/util"
|
karmadautil "github.com/karmada-io/karmada/pkg/util"
|
||||||
"github.com/karmada-io/karmada/pkg/util/lifted/pubkeypin"
|
"github.com/karmada-io/karmada/pkg/util/lifted/pubkeypin"
|
||||||
|
@ -148,7 +148,7 @@ func NewCmdRegister(parentCommand string) *cobra.Command {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
util.TagCommandGroup: util.GroupClusterRegistration,
|
cmdutil.TagCommandGroup: cmdutil.GroupClusterRegistration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
@ -570,7 +570,7 @@ func (o *CommandRegisterOption) createSecretAndRBACInMemberCluster(karmadaAgentC
|
||||||
}
|
}
|
||||||
|
|
||||||
// cerate karmada-kubeconfig secret to be used by karmada-agent component.
|
// cerate karmada-kubeconfig secret to be used by karmada-agent component.
|
||||||
if err := karmadautil.CreateOrUpdateSecret(o.memberClusterClient, kubeConfigSecret); err != nil {
|
if err := cmdutil.CreateOrUpdateSecret(o.memberClusterClient, kubeConfigSecret); err != nil {
|
||||||
return fmt.Errorf("create secret %s failed: %v", kubeConfigSecret.Name, err)
|
return fmt.Errorf("create secret %s failed: %v", kubeConfigSecret.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets"
|
bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
karmadautil "github.com/karmada-io/karmada/pkg/util"
|
cmdutil "github.com/karmada-io/karmada/pkg/karmadactl/util"
|
||||||
"github.com/karmada-io/karmada/pkg/util/lifted/pubkeypin"
|
"github.com/karmada-io/karmada/pkg/util/lifted/pubkeypin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ func UpdateOrCreateToken(client kubeclient.Interface, failIfExists bool, token *
|
||||||
updatedOrNewSecret := ConvertBootstrapTokenToSecret(token)
|
updatedOrNewSecret := ConvertBootstrapTokenToSecret(token)
|
||||||
// Try to create or update the token with an exponential backoff
|
// Try to create or update the token with an exponential backoff
|
||||||
err = TryRunCommand(func() error {
|
err = TryRunCommand(func() error {
|
||||||
if err := karmadautil.CreateOrUpdateSecret(client, updatedOrNewSecret); err != nil {
|
if err := cmdutil.CreateOrUpdateSecret(client, updatedOrNewSecret); err != nil {
|
||||||
return fmt.Errorf("failed to create or update bootstrap token with name %s, err: %w", secretName, err)
|
return fmt.Errorf("failed to create or update bootstrap token with name %s, err: %w", secretName, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
kubeclient "k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||||
|
aggregator "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateService creates a Service if the target resource doesn't exist. If the resource exists already, return directly
|
||||||
|
func CreateService(client kubeclient.Interface, service *corev1.Service) error {
|
||||||
|
if _, err := client.CoreV1().Services(service.ObjectMeta.Namespace).Create(context.TODO(), service, metav1.CreateOptions{}); err != nil {
|
||||||
|
if !apierrors.IsAlreadyExists(err) {
|
||||||
|
return fmt.Errorf("unable to create service: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.Warningf("Service %s is existed, creation process will skip", service.ObjectMeta.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrUpdateSecret creates a Secret if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
||||||
|
func CreateOrUpdateSecret(client kubeclient.Interface, secret *corev1.Secret) error {
|
||||||
|
if _, err := client.CoreV1().Secrets(secret.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil {
|
||||||
|
if !apierrors.IsAlreadyExists(err) {
|
||||||
|
return fmt.Errorf("unable to create service: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existSecret, err := client.CoreV1().Secrets(secret.Namespace).Get(context.TODO(), secret.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
secret.ResourceVersion = existSecret.ResourceVersion
|
||||||
|
|
||||||
|
if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("unable to update deployment: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrUpdateDeployment creates a Deployment if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
||||||
|
func CreateOrUpdateDeployment(client kubeclient.Interface, deploy *appsv1.Deployment) error {
|
||||||
|
if _, err := client.AppsV1().Deployments(deploy.Namespace).Create(context.TODO(), deploy, metav1.CreateOptions{}); err != nil {
|
||||||
|
if !apierrors.IsAlreadyExists(err) {
|
||||||
|
return fmt.Errorf("unable to create deployment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existDeployment, err := client.AppsV1().Deployments(deploy.Namespace).Get(context.TODO(), deploy.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy.ResourceVersion = existDeployment.ResourceVersion
|
||||||
|
|
||||||
|
if _, err := client.AppsV1().Deployments(deploy.ObjectMeta.Namespace).Update(context.TODO(), deploy, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("unable to update deployment: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrUpdateAPIService creates a ApiService if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
||||||
|
func CreateOrUpdateAPIService(apiRegistrationClient *aggregator.Clientset, apiservice *apiregistrationv1.APIService) error {
|
||||||
|
if _, err := apiRegistrationClient.ApiregistrationV1().APIServices().Create(context.TODO(), apiservice, metav1.CreateOptions{}); err != nil {
|
||||||
|
if !apierrors.IsAlreadyExists(err) {
|
||||||
|
return fmt.Errorf("unable to create apiService: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existAPIService, err := apiRegistrationClient.ApiregistrationV1().APIServices().Get(context.TODO(), apiservice.ObjectMeta.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiservice.ObjectMeta.ResourceVersion = existAPIService.ObjectMeta.ResourceVersion
|
||||||
|
|
||||||
|
if _, err := apiRegistrationClient.ApiregistrationV1().APIServices().Update(context.TODO(), apiservice, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("unable to update apiService: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrUpdateRole creates a Role if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
||||||
|
func CreateOrUpdateRole(client kubeclient.Interface, role *rbacv1.Role) error {
|
||||||
|
if _, err := client.RbacV1().Roles(role.ObjectMeta.Namespace).Create(context.TODO(), role, metav1.CreateOptions{}); err != nil {
|
||||||
|
if !apierrors.IsAlreadyExists(err) {
|
||||||
|
return fmt.Errorf("unable to create RBAC role: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existRole, err := client.AppsV1().Deployments(role.Namespace).Get(context.TODO(), role.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
role.ResourceVersion = existRole.ResourceVersion
|
||||||
|
|
||||||
|
if _, err := client.RbacV1().Roles(role.ObjectMeta.Namespace).Update(context.TODO(), role, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("unable to update RBAC role: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
klog.V(2).Infof("Role %s%s has been created or updated.", role.ObjectMeta.Namespace, role.ObjectMeta.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrUpdateRoleBinding creates a RoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
||||||
|
func CreateOrUpdateRoleBinding(client kubeclient.Interface, roleBinding *rbacv1.RoleBinding) error {
|
||||||
|
if _, err := client.RbacV1().RoleBindings(roleBinding.ObjectMeta.Namespace).Create(context.TODO(), roleBinding, metav1.CreateOptions{}); err != nil {
|
||||||
|
if !apierrors.IsAlreadyExists(err) {
|
||||||
|
return fmt.Errorf("unable to create RBAC rolebinding: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existRoleBinding, err := client.AppsV1().Deployments(roleBinding.Namespace).Get(context.TODO(), roleBinding.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
roleBinding.ResourceVersion = existRoleBinding.ResourceVersion
|
||||||
|
|
||||||
|
if _, err := client.RbacV1().RoleBindings(roleBinding.ObjectMeta.Namespace).Update(context.TODO(), roleBinding, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("unable to update RBAC rolebinding: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
klog.V(2).Infof("RoleBinding %s/%s has been created or updated.", roleBinding.ObjectMeta.Namespace, roleBinding.ObjectMeta.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 *kubeclient.Clientset, cm *corev1.ConfigMap) error {
|
||||||
|
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(context.TODO(), cm, metav1.CreateOptions{}); err != nil {
|
||||||
|
if !apierrors.IsAlreadyExists(err) {
|
||||||
|
return fmt.Errorf("unable to create ConfigMap: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existCm, err := client.AppsV1().Deployments(cm.Namespace).Get(context.TODO(), cm.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cm.ResourceVersion = existCm.ResourceVersion
|
||||||
|
|
||||||
|
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(context.TODO(), cm, metav1.UpdateOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("unable to update ConfigMap: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
klog.V(2).Infof("ConfigMap %s/%s has been created or updated.", cm.ObjectMeta.Namespace, cm.ObjectMeta.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
coretesting "k8s.io/client-go/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateOrUpdateSecret(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
client kubernetes.Interface
|
||||||
|
secret *corev1.Secret
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "create success",
|
||||||
|
args: args{
|
||||||
|
client: fake.NewSimpleClientset(),
|
||||||
|
secret: makeSecret("test"),
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update success",
|
||||||
|
args: args{
|
||||||
|
client: fake.NewSimpleClientset(makeSecret("test")),
|
||||||
|
secret: makeSecret("test"),
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create error",
|
||||||
|
args: args{
|
||||||
|
client: func() kubernetes.Interface {
|
||||||
|
c := fake.NewSimpleClientset()
|
||||||
|
c.PrependReactor("create", "*", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
|
return true, nil, errors.New("create secret error")
|
||||||
|
})
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
secret: makeSecret("test"),
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update error",
|
||||||
|
args: args{
|
||||||
|
client: func() kubernetes.Interface {
|
||||||
|
c := fake.NewSimpleClientset(makeSecret("test"))
|
||||||
|
c.PrependReactor("update", "*", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
|
return true, nil, errors.New("update secret error")
|
||||||
|
})
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
secret: makeSecret("test"),
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := CreateOrUpdateSecret(tt.args.client, tt.args.secret); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("CreateOrUpdateSecret() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSecret(name string) *corev1.Secret {
|
||||||
|
return &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: metav1.NamespaceDefault,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ package util
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
@ -45,17 +44,3 @@ func PatchSecret(client kubeclient.Interface, namespace, name string, pt types.P
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOrUpdateSecret creates a Secret if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
|
|
||||||
func CreateOrUpdateSecret(client kubeclient.Interface, secret *corev1.Secret) error {
|
|
||||||
if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil {
|
|
||||||
if !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("unable to create secret, err: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil {
|
|
||||||
return fmt.Errorf("unable to update secret, err: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,83 +1,16 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
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/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
coretesting "k8s.io/client-go/testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateOrUpdateSecret(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
client kubernetes.Interface
|
|
||||||
secret *corev1.Secret
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "create success",
|
|
||||||
args: args{
|
|
||||||
client: fake.NewSimpleClientset(),
|
|
||||||
secret: makeSecret("test"),
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "update success",
|
|
||||||
args: args{
|
|
||||||
client: fake.NewSimpleClientset(makeSecret("test")),
|
|
||||||
secret: makeSecret("test"),
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create error",
|
|
||||||
args: args{
|
|
||||||
client: func() kubernetes.Interface {
|
|
||||||
c := fake.NewSimpleClientset()
|
|
||||||
c.PrependReactor("create", "*", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
|
|
||||||
return true, nil, errors.New("create secret error")
|
|
||||||
})
|
|
||||||
return c
|
|
||||||
}(),
|
|
||||||
secret: makeSecret("test"),
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "update error",
|
|
||||||
args: args{
|
|
||||||
client: func() kubernetes.Interface {
|
|
||||||
c := fake.NewSimpleClientset(makeSecret("test"))
|
|
||||||
c.PrependReactor("update", "*", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) {
|
|
||||||
return true, nil, errors.New("update secret error")
|
|
||||||
})
|
|
||||||
return c
|
|
||||||
}(),
|
|
||||||
secret: makeSecret("test"),
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := CreateOrUpdateSecret(tt.args.client, tt.args.secret); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("CreateOrUpdateSecret() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateSecret(t *testing.T) {
|
func TestCreateSecret(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
client kubernetes.Interface
|
client kubernetes.Interface
|
||||||
|
|
Loading…
Reference in New Issue