add karmadactl unjoin command (#67)
This commit is contained in:
parent
1186eeb9a4
commit
edd8763265
|
@ -58,7 +58,7 @@ func (c *Controller) Reconcile(req controllerruntime.Request) (controllerruntime
|
|||
if !work.DeletionTimestamp.IsZero() {
|
||||
applied := c.isResourceApplied(&work.Status)
|
||||
if applied {
|
||||
err := c.deletePropagationWork(work)
|
||||
err := c.tryDeleteWorkload(work)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to delete propagationWork %v, namespace is %v, err is %v", work.Name, work.Namespace, err)
|
||||
return controllerruntime.Result{Requeue: true}, err
|
||||
|
@ -118,23 +118,26 @@ func (c *Controller) isResourceApplied(propagationWorkStatus *propagationstrateg
|
|||
return false
|
||||
}
|
||||
|
||||
func (c *Controller) deletePropagationWork(propagationWork *propagationstrategy.PropagationWork) error {
|
||||
executionSpace, err := names.GetMemberClusterName(propagationWork.Namespace)
|
||||
// tryDeleteWorkload tries to delete resource in the given member cluster.
|
||||
// Abort deleting when the member cluster is unready, otherwise we can't unjoin the member cluster when the member cluster is unready
|
||||
func (c *Controller) tryDeleteWorkload(propagationWork *propagationstrategy.PropagationWork) error {
|
||||
memberClusterName, err := names.GetMemberClusterName(propagationWork.Namespace)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to get member cluster name for propagationWork %s/%s", propagationWork.Namespace, propagationWork.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(RainbowMango): retrieve member cluster from the local cache instead of a real request to API server.
|
||||
memberCluster, err := c.KarmadaClient.MemberclusterV1alpha1().MemberClusters().Get(context.TODO(), executionSpace, v1.GetOptions{})
|
||||
memberCluster, err := c.KarmadaClient.MemberclusterV1alpha1().MemberClusters().Get(context.TODO(), memberClusterName, v1.GetOptions{})
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to get status of the given member cluster %s", executionSpace)
|
||||
klog.Errorf("Failed to get the given member cluster %s", memberClusterName)
|
||||
return err
|
||||
}
|
||||
|
||||
// Do not clean up resource in the given member cluster if the status of the given member cluster is unready
|
||||
if !c.isMemberClusterReady(&memberCluster.Status) {
|
||||
klog.Errorf("The status of the given member cluster %s is unready", memberCluster.Name)
|
||||
return fmt.Errorf("cluster %s not ready, requeuing operation until cluster state is ready", memberCluster.Name)
|
||||
klog.Infof("Do not clean up resource in the given member cluster if the status of the given member cluster %s is unready", memberCluster.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
memberClusterDynamicClient, err := util.NewClusterDynamicClientSet(memberCluster, c.KubeClientSet)
|
||||
|
@ -161,16 +164,16 @@ func (c *Controller) deletePropagationWork(propagationWork *propagationstrategy.
|
|||
}
|
||||
|
||||
func (c *Controller) dispatchPropagationWork(propagationWork *propagationstrategy.PropagationWork) error {
|
||||
executionSpace, err := names.GetMemberClusterName(propagationWork.Namespace)
|
||||
memberClusterName, err := names.GetMemberClusterName(propagationWork.Namespace)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to get member cluster name for propagationWork %s/%s", propagationWork.Namespace, propagationWork.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(RainbowMango): retrieve member cluster from the local cache instead of a real request to API server.
|
||||
memberCluster, err := c.KarmadaClient.MemberclusterV1alpha1().MemberClusters().Get(context.TODO(), executionSpace, v1.GetOptions{})
|
||||
memberCluster, err := c.KarmadaClient.MemberclusterV1alpha1().MemberClusters().Get(context.TODO(), memberClusterName, v1.GetOptions{})
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to get status of the given member cluster %s", executionSpace)
|
||||
klog.Errorf("Failed to the get given member cluster %s", memberClusterName)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package karmadactl
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
|
@ -13,14 +12,17 @@ import (
|
|||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kubeclient "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
memberclusterapi "github.com/huawei-cloudnative/karmada/pkg/apis/membercluster/v1alpha1"
|
||||
"github.com/huawei-cloudnative/karmada/pkg/apis/propagationstrategy/v1alpha1"
|
||||
karmadaclientset "github.com/huawei-cloudnative/karmada/pkg/generated/clientset/versioned"
|
||||
"github.com/huawei-cloudnative/karmada/pkg/karmadactl/options"
|
||||
"github.com/huawei-cloudnative/karmada/pkg/util"
|
||||
"github.com/huawei-cloudnative/karmada/pkg/util/names"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -49,6 +51,8 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
var resourceKind = v1alpha1.SchemeGroupVersion.WithKind("MemberCluster")
|
||||
|
||||
const (
|
||||
// TODO(RainbowMango) token and caData key both used by command and controller.
|
||||
// It's better to put them to a common place, such as API definition.
|
||||
|
@ -72,7 +76,7 @@ func NewCmdJoin(cmdOut io.Writer, karmadaConfig KarmadaConfig) *cobra.Command {
|
|||
return
|
||||
}
|
||||
|
||||
err = Run(cmdOut, karmadaConfig, opts)
|
||||
err = RunJoin(cmdOut, karmadaConfig, opts)
|
||||
if err != nil {
|
||||
klog.Errorf("Error: %v", err)
|
||||
return
|
||||
|
@ -126,9 +130,9 @@ func (j *CommandJoinOption) AddFlags(flags *pflag.FlagSet) {
|
|||
"Path of the member cluster's kubeconfig.")
|
||||
}
|
||||
|
||||
// Run is the implementation of the 'join' command.
|
||||
// RunJoin is the implementation of the 'join' command.
|
||||
// TODO(RainbowMango): consider to remove the 'KarmadaConfig'.
|
||||
func Run(cmdOut io.Writer, karmadaConfig KarmadaConfig, opts CommandJoinOption) error {
|
||||
func RunJoin(cmdOut io.Writer, karmadaConfig KarmadaConfig, opts CommandJoinOption) error {
|
||||
klog.V(1).Infof("joining member cluster. member cluster name: %s", opts.MemberClusterName)
|
||||
klog.V(1).Infof("joining member cluster. cluster namespace: %s", opts.ClusterNamespace)
|
||||
|
||||
|
@ -165,14 +169,14 @@ func Run(cmdOut io.Writer, karmadaConfig KarmadaConfig, opts CommandJoinOption)
|
|||
// create a ServiceAccount in member cluster.
|
||||
serviceAccountObj := &corev1.ServiceAccount{}
|
||||
serviceAccountObj.Namespace = opts.ClusterNamespace
|
||||
serviceAccountObj.Name = fmt.Sprintf("%s-%s", "karmada", opts.MemberClusterName)
|
||||
serviceAccountObj.Name = names.GenerateServiceAccountName(opts.MemberClusterName)
|
||||
if serviceAccountObj, err = ensureServiceAccountExist(memberClusterKubeClient, serviceAccountObj, opts.DryRun); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create a ClusterRole in member cluster.
|
||||
clusterRole := &rbacv1.ClusterRole{}
|
||||
clusterRole.Name = util.GenerateRoleName(serviceAccountObj.Name)
|
||||
clusterRole.Name = names.GenerateRoleName(serviceAccountObj.Name)
|
||||
clusterRole.Rules = clusterPolicyRules
|
||||
if _, err := ensureClusterRoleExist(memberClusterKubeClient, clusterRole, opts.DryRun); err != nil {
|
||||
return err
|
||||
|
@ -208,10 +212,12 @@ func Run(cmdOut io.Writer, karmadaConfig KarmadaConfig, opts CommandJoinOption)
|
|||
}
|
||||
|
||||
// create secret in control plane
|
||||
secretNamespace := opts.ClusterNamespace
|
||||
secretName := opts.MemberClusterName
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: opts.ClusterNamespace,
|
||||
Name: opts.MemberClusterName,
|
||||
Namespace: secretNamespace,
|
||||
Name: secretName,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
caDataKey: memberClusterSecret.Data["ca.crt"], // TODO(RainbowMango): change ca bundle key to 'ca.crt'.
|
||||
|
@ -232,15 +238,29 @@ func Run(cmdOut io.Writer, karmadaConfig KarmadaConfig, opts CommandJoinOption)
|
|||
memberClusterObj.Name = opts.MemberClusterName
|
||||
memberClusterObj.Spec.APIEndpoint = memberClusterConfig.Host
|
||||
memberClusterObj.Spec.SecretRef = &memberclusterapi.LocalSecretReference{
|
||||
Namespace: secret.Namespace,
|
||||
Name: secret.Name,
|
||||
Namespace: secretNamespace,
|
||||
Name: secretName,
|
||||
}
|
||||
err = createMemberClusterObject(controlPlaneKarmadaClient, memberClusterObj, false)
|
||||
memberCluster, err := createMemberClusterObject(controlPlaneKarmadaClient, memberClusterObj, false)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to create member cluster object. cluster name: %s, error: %v", opts.MemberClusterName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
patchSecretBody := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
*metav1.NewControllerRef(memberCluster, resourceKind),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = util.PatchSecret(controlPlaneKubeClient, secretNamespace, secretName, types.MergePatchType, patchSecretBody)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to patch secret %s/%s, error: %v", secret.Namespace, secret.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -352,55 +372,55 @@ func ensureClusterRoleBindingExist(client kubeclient.Interface, clusterRoleBindi
|
|||
return createdObj, nil
|
||||
}
|
||||
|
||||
func createMemberClusterObject(controlPlaneClient *karmadaclientset.Clientset, memberClusterObj *memberclusterapi.MemberCluster, errorOnExisting bool) error {
|
||||
exist, err := IsMemberClusterExist(controlPlaneClient, memberClusterObj.Namespace, memberClusterObj.Name)
|
||||
func createMemberClusterObject(controlPlaneClient *karmadaclientset.Clientset, memberClusterObj *memberclusterapi.MemberCluster, errorOnExisting bool) (*memberclusterapi.MemberCluster, error) {
|
||||
memberCluster, exist, err := GetMemberCluster(controlPlaneClient, memberClusterObj.Namespace, memberClusterObj.Name)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to create member cluster object. member cluster: %s/%s, error: %v", memberClusterObj.Namespace, memberClusterObj.Name, err)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exist {
|
||||
if errorOnExisting {
|
||||
klog.Errorf("failed to create member cluster object. member cluster: %s/%s, error: %v", memberClusterObj.Namespace, memberClusterObj.Name, err)
|
||||
return err
|
||||
return memberCluster, err
|
||||
}
|
||||
|
||||
klog.V(1).Infof("create member cluster succeed as already exist. member cluster: %s/%s", memberClusterObj.Namespace, memberClusterObj.Name)
|
||||
return nil
|
||||
return memberCluster, nil
|
||||
}
|
||||
|
||||
if err := CreateMemberCluster(controlPlaneClient, memberClusterObj); err != nil {
|
||||
if memberCluster, err = CreateMemberCluster(controlPlaneClient, memberClusterObj); err != nil {
|
||||
klog.Warningf("failed to create member cluster. member cluster: %s/%s, error: %v", memberClusterObj.Namespace, memberClusterObj.Name, err)
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil
|
||||
return memberCluster, nil
|
||||
}
|
||||
|
||||
// IsMemberClusterExist tells if a member cluster (namespace/name) already joined to control plane.
|
||||
func IsMemberClusterExist(controlPlaneClient karmadaclientset.Interface, namespace string, name string) (bool, error) {
|
||||
_, err := controlPlaneClient.MemberclusterV1alpha1().MemberClusters().Get(context.TODO(), name, metav1.GetOptions{})
|
||||
// GetMemberCluster tells if a member cluster (namespace/name) already joined to control plane.
|
||||
func GetMemberCluster(client karmadaclientset.Interface, namespace string, name string) (*memberclusterapi.MemberCluster, bool, error) {
|
||||
memberCluster, err := client.MemberclusterV1alpha1().MemberClusters().Get(context.TODO(), name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return false, nil
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
klog.Warningf("failed to retrieve member cluster. member cluster: %s/%s, error: %v", namespace, name, err)
|
||||
return false, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return memberCluster, true, nil
|
||||
}
|
||||
|
||||
// CreateMemberCluster creates a new member cluster object in control plane.
|
||||
func CreateMemberCluster(controlPlaneClient karmadaclientset.Interface, cluster *memberclusterapi.MemberCluster) error {
|
||||
_, err := controlPlaneClient.MemberclusterV1alpha1().MemberClusters().Create(context.TODO(), cluster, metav1.CreateOptions{})
|
||||
func CreateMemberCluster(controlPlaneClient karmadaclientset.Interface, cluster *memberclusterapi.MemberCluster) (*memberclusterapi.MemberCluster, error) {
|
||||
memberCluster, err := controlPlaneClient.MemberclusterV1alpha1().MemberClusters().Create(context.TODO(), cluster, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
klog.Warningf("failed to create member cluster. member cluster: %s/%s, error: %v", cluster.Namespace, cluster.Name, err)
|
||||
return err
|
||||
return memberCluster, err
|
||||
}
|
||||
|
||||
return nil
|
||||
return memberCluster, nil
|
||||
}
|
||||
|
||||
// buildRoleBindingSubjects will generate a subject as per service account.
|
||||
|
|
|
@ -35,6 +35,7 @@ func NewKarmadaCtlCommand(out io.Writer) *cobra.Command {
|
|||
|
||||
karmadaConfig := NewKarmadaConfig(clientcmd.NewDefaultPathOptions())
|
||||
rootCmd.AddCommand(NewCmdJoin(out, karmadaConfig))
|
||||
rootCmd.AddCommand(NewCmdUnjoin(out, karmadaConfig))
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
package karmadactl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kubeclient "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
karmadaclientset "github.com/huawei-cloudnative/karmada/pkg/generated/clientset/versioned"
|
||||
"github.com/huawei-cloudnative/karmada/pkg/karmadactl/options"
|
||||
"github.com/huawei-cloudnative/karmada/pkg/util"
|
||||
"github.com/huawei-cloudnative/karmada/pkg/util/names"
|
||||
)
|
||||
|
||||
var (
|
||||
unjoinLong = `Unjoin removes the registration of a member cluster from control plane.`
|
||||
|
||||
unjoinExample = `
|
||||
karmadactl unjoin CLUSTER_NAME --member-cluster-kubeconfig=<KUBECONFIG>
|
||||
`
|
||||
)
|
||||
|
||||
// NewCmdUnjoin defines the `unjoin` command that removes registration of a member cluster from control plane.
|
||||
func NewCmdUnjoin(cmdOut io.Writer, karmadaConfig KarmadaConfig) *cobra.Command {
|
||||
opts := CommandUnjoinOption{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "unjoin CLUSTER_NAME --member-cluster-kubeconfig=<KUBECONFIG>",
|
||||
Short: "Remove the registration of a member cluster from control plane",
|
||||
Long: unjoinLong,
|
||||
Example: unjoinExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := opts.Complete(args)
|
||||
if err != nil {
|
||||
klog.Errorf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = RunUnjoin(cmdOut, karmadaConfig, opts)
|
||||
if err != nil {
|
||||
klog.Errorf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
opts.AddFlags(flags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// CommandUnjoinOption holds all command options.
|
||||
type CommandUnjoinOption struct {
|
||||
options.GlobalCommandOptions
|
||||
|
||||
// MemberClusterName is the member cluster's name that we are going to join with.
|
||||
MemberClusterName string
|
||||
|
||||
// MemberClusterContext is the member cluster's context that we are going to join with.
|
||||
MemberClusterContext string
|
||||
|
||||
// MemberClusterKubeConfig is the member cluster's kubeconfig path.
|
||||
MemberClusterKubeConfig string
|
||||
|
||||
forceDeletion bool
|
||||
}
|
||||
|
||||
// Complete ensures that options are valid and marshals them if necessary.
|
||||
func (j *CommandUnjoinOption) Complete(args []string) error {
|
||||
// Get member cluster name from the command args.
|
||||
if len(args) == 0 {
|
||||
return errors.New("member cluster name is required")
|
||||
}
|
||||
j.MemberClusterName = args[0]
|
||||
|
||||
// If '--member-cluster-context' not specified, take the cluster name as the context.
|
||||
if len(j.MemberClusterContext) == 0 {
|
||||
j.MemberClusterContext = j.MemberClusterName
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddFlags adds flags to the specified FlagSet.
|
||||
func (j *CommandUnjoinOption) AddFlags(flags *pflag.FlagSet) {
|
||||
j.GlobalCommandOptions.AddFlags(flags)
|
||||
|
||||
flags.StringVar(&j.MemberClusterContext, "member-cluster-context", "",
|
||||
"Context name of member cluster in kubeconfig. Only works when there are multiple contexts in the kubeconfig.")
|
||||
flags.StringVar(&j.MemberClusterKubeConfig, "member-cluster-kubeconfig", "",
|
||||
"Path of the member cluster's kubeconfig.")
|
||||
flags.BoolVar(&j.forceDeletion, "force", false,
|
||||
"Delete membercluster and secret resources even if resources in the member cluster targeted for unjoin are not removed successfully.")
|
||||
}
|
||||
|
||||
// RunUnjoin is the implementation of the 'unjoin' command.
|
||||
// TODO(RainbowMango): consider to remove the 'KarmadaConfig'.
|
||||
func RunUnjoin(cmdOut io.Writer, karmadaConfig KarmadaConfig, opts CommandUnjoinOption) error {
|
||||
klog.V(1).Infof("unjoining member cluster. member cluster name: %s", opts.MemberClusterName)
|
||||
klog.V(1).Infof("unjoining member cluster. cluster namespace: %s", opts.ClusterNamespace)
|
||||
|
||||
// Get control plane kube-apiserver client
|
||||
controlPlaneRestConfig, err := karmadaConfig.GetRestConfig(opts.ClusterContext, opts.KubeConfig)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to get control plane rest config. context: %s, kube-config: %s, error: %v",
|
||||
opts.ClusterContext, opts.KubeConfig, err)
|
||||
return err
|
||||
}
|
||||
|
||||
controlPlaneKarmadaClient := karmadaclientset.NewForConfigOrDie(controlPlaneRestConfig)
|
||||
controlPlaneKubeClient := kubeclient.NewForConfigOrDie(controlPlaneRestConfig)
|
||||
|
||||
// todo: taint member cluster object instead of deleting execution space.
|
||||
// Once the member cluster is tainted, eviction controller will delete all propagationwork in the execution space of the member cluster.
|
||||
executionSpaceName, err := names.GenerateExecutionSpaceName(opts.MemberClusterName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = deleteExecutionSpace(controlPlaneKubeClient, executionSpaceName, opts.DryRun)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to delete execution space %s, error: %v", executionSpaceName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Attempt to delete the cluster role, cluster rolebindings and service account from the unjoining member cluster
|
||||
// if user provides the kubeconfig of member cluster
|
||||
if opts.MemberClusterKubeConfig != "" {
|
||||
// Get member cluster config
|
||||
memberClusterConfig, err := karmadaConfig.GetRestConfig(opts.MemberClusterContext, opts.MemberClusterKubeConfig)
|
||||
if err != nil {
|
||||
klog.V(1).Infof("failed to get unjoining member cluster config. error: %v", err)
|
||||
return err
|
||||
}
|
||||
memberClusterKubeClient := kubeclient.NewForConfigOrDie(memberClusterConfig)
|
||||
|
||||
klog.V(1).Infof("unjoining member cluster config. endpoint: %s", memberClusterConfig.Host)
|
||||
|
||||
// delete RBAC resource from unjoining member cluster
|
||||
err = deleteRBACResources(memberClusterKubeClient, opts.MemberClusterName, opts.forceDeletion, opts.DryRun)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to delete RBAC resource in unjoining member cluster %q: %v", opts.MemberClusterName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// delete service account from unjoining member cluster
|
||||
err = deleteServiceAccount(memberClusterKubeClient, opts.ClusterNamespace, opts.MemberClusterName, opts.forceDeletion, opts.DryRun)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to delete service account in unjoining member cluster %q: %v", opts.MemberClusterName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// delete namespace from unjoining member cluster
|
||||
err = deleteNamespaceFromUnjoinCluster(memberClusterKubeClient, opts.ClusterNamespace, opts.MemberClusterName, opts.forceDeletion, opts.DryRun)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to delete namespace in unjoining member cluster %q: %v", opts.MemberClusterName, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// delete the member cluster object in host cluster that associates the unjoining member cluster
|
||||
err = deleteMemberClusterObject(controlPlaneKarmadaClient, opts.MemberClusterName, opts.DryRun)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to delete member cluster object. cluster name: %s, error: %v", opts.MemberClusterName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteRBACResources deletes the cluster role, cluster rolebindings from the unjoining member cluster.
|
||||
func deleteRBACResources(memberClusterKubeClient kubeclient.Interface, unjoiningClusterName string, forceDeletion, dryRun bool) error {
|
||||
if dryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
serviceAccountName := names.GenerateServiceAccountName(unjoiningClusterName)
|
||||
clusterRoleName := names.GenerateRoleName(serviceAccountName)
|
||||
clusterRoleBindingName := clusterRoleName
|
||||
|
||||
err := util.DeleteClusterRoleBinding(memberClusterKubeClient, clusterRoleBindingName)
|
||||
if err != nil {
|
||||
if !forceDeletion {
|
||||
return err
|
||||
}
|
||||
klog.Errorf("Force deletion. Could not delete cluster role binding %q for service account %q in unjoining member cluster %q: %v.", clusterRoleBindingName, serviceAccountName, unjoiningClusterName, err)
|
||||
}
|
||||
|
||||
err = util.DeleteClusterRole(memberClusterKubeClient, clusterRoleName)
|
||||
if err != nil {
|
||||
if !forceDeletion {
|
||||
return err
|
||||
}
|
||||
klog.Errorf("Force deletion. Could not delete cluster role %q for service account %q in unjoining member cluster %q: %v.", clusterRoleName, serviceAccountName, unjoiningClusterName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteServiceAccount deletes the service account from the unjoining member cluster.
|
||||
func deleteServiceAccount(memberClusterKubeClient kubeclient.Interface, namespace, unjoiningClusterName string, forceDeletion, dryRun bool) error {
|
||||
if dryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
serviceAccountName := names.GenerateServiceAccountName(unjoiningClusterName)
|
||||
err := util.DeleteServiceAccount(memberClusterKubeClient, namespace, serviceAccountName)
|
||||
if err != nil {
|
||||
if !forceDeletion {
|
||||
return err
|
||||
}
|
||||
klog.Errorf("Force deletion. Could not delete service account %q in unjoining member cluster %q: %v.", serviceAccountName, unjoiningClusterName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteNSFromUnjoinCluster deletes the namespace from the unjoining member cluster.
|
||||
func deleteNamespaceFromUnjoinCluster(memberClusterKubeClient kubeclient.Interface, namespace, unjoiningClusterName string, forceDeletion, dryRun bool) error {
|
||||
if dryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := util.DeleteNamespace(memberClusterKubeClient, namespace)
|
||||
if err != nil {
|
||||
if !forceDeletion {
|
||||
return err
|
||||
}
|
||||
klog.Errorf("Force deletion. Could not delete namespace %q in unjoining member cluster %q: %v.", namespace, unjoiningClusterName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteExecutionSpace(hostClient kubeclient.Interface, namespace string, dryRun bool) error {
|
||||
if dryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := util.DeleteNamespace(hostClient, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure the execution space has been deleted
|
||||
err = wait.Poll(1*time.Second, 30*time.Second, func() (done bool, err error) {
|
||||
exist, err := util.IsNamespaceExist(hostClient, namespace)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to get execution space %s. err: %v", namespace, err)
|
||||
return false, err
|
||||
}
|
||||
if !exist {
|
||||
return true, nil
|
||||
}
|
||||
klog.Infof("Waiting for the execution space %s to be deleted", namespace)
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to delete execution space %s, error: %v", namespace, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteMemberClusterObject delete the member cluster object in host cluster that associates the unjoining member cluster
|
||||
func deleteMemberClusterObject(controlPlaneKarmadaClient *karmadaclientset.Clientset, memberClusterName string, dryRun bool) error {
|
||||
if dryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := controlPlaneKarmadaClient.MemberclusterV1alpha1().MemberClusters().Delete(context.TODO(), memberClusterName, metav1.DeleteOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to delete member cluster object. cluster name: %s, error: %v", memberClusterName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure the given member cluster object has been deleted
|
||||
err = wait.Poll(1*time.Second, 30*time.Second, func() (done bool, err error) {
|
||||
_, err = controlPlaneKarmadaClient.MemberclusterV1alpha1().MemberClusters().Get(context.TODO(), memberClusterName, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to get member cluster %s. err: %v", memberClusterName, err)
|
||||
return false, err
|
||||
}
|
||||
klog.Infof("Waiting for the member cluster object %s to be deleted", memberClusterName)
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to delete member cluster object. cluster name: %s, error: %v", memberClusterName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -34,3 +34,13 @@ func GenerateBindingName(namespace, kind, name string) string {
|
|||
func GenerateOwnerLabelValue(namespace, name string) string {
|
||||
return namespace + "." + name
|
||||
}
|
||||
|
||||
// GenerateServiceAccountName generates the name of a ServiceAccount.
|
||||
func GenerateServiceAccountName(clusterName string) string {
|
||||
return fmt.Sprintf("%s-%s", "karmada", clusterName)
|
||||
}
|
||||
|
||||
// GenerateRoleName generates the name of a Role or ClusterRole.
|
||||
func GenerateRoleName(serviceAccountName string) string {
|
||||
return fmt.Sprintf("karmada-controller-manager:%s", serviceAccountName)
|
||||
}
|
||||
|
|
|
@ -36,3 +36,12 @@ func CreateNamespace(client kubeclient.Interface, namespaceObj *corev1.Namespace
|
|||
|
||||
return namespaceObj, nil
|
||||
}
|
||||
|
||||
// DeleteNamespace just try to delete the namespace.
|
||||
func DeleteNamespace(client kubeclient.Interface, namespace string) error {
|
||||
err := client.CoreV1().Namespaces().Delete(context.Background(), namespace, metav1.DeleteOptions{})
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package util
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
@ -10,11 +9,6 @@ import (
|
|||
kubeclient "k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// GenerateRoleName generates the name of a Role or ClusterRole.
|
||||
func GenerateRoleName(serviceAccountName string) string {
|
||||
return fmt.Sprintf("karmada-controller-manager:%s", serviceAccountName)
|
||||
}
|
||||
|
||||
// IsClusterRoleExist tells if specific ClusterRole already exists.
|
||||
func IsClusterRoleExist(client kubeclient.Interface, name string) (bool, error) {
|
||||
_, err := client.RbacV1().ClusterRoles().Get(context.TODO(), name, metav1.GetOptions{})
|
||||
|
@ -43,6 +37,15 @@ func CreateClusterRole(client kubeclient.Interface, clusterRoleObj *rbacv1.Clust
|
|||
return createdObj, nil
|
||||
}
|
||||
|
||||
// DeleteClusterRole just try to delete the ClusterRole.
|
||||
func DeleteClusterRole(client kubeclient.Interface, name string) error {
|
||||
err := client.RbacV1().ClusterRoles().Delete(context.TODO(), name, metav1.DeleteOptions{})
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsClusterRoleBindingExist tells if specific ClusterRole already exists.
|
||||
func IsClusterRoleBindingExist(client kubeclient.Interface, name string) (bool, error) {
|
||||
_, err := client.RbacV1().ClusterRoleBindings().Get(context.TODO(), name, metav1.GetOptions{})
|
||||
|
@ -70,3 +73,12 @@ func CreateClusterRoleBinding(client kubeclient.Interface, clusterRoleBindingObj
|
|||
|
||||
return createdObj, nil
|
||||
}
|
||||
|
||||
// DeleteClusterRoleBinding just try to delete the ClusterRoleBinding.
|
||||
func DeleteClusterRoleBinding(client kubeclient.Interface, name string) error {
|
||||
err := client.RbacV1().ClusterRoleBindings().Delete(context.TODO(), name, metav1.DeleteOptions{})
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@ package util
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
kubeclient "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
@ -41,3 +43,27 @@ func CreateSecret(client kubeclient.Interface, secret *corev1.Secret) (*corev1.S
|
|||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// DeleteSecret just try to delete the secret.
|
||||
func DeleteSecret(client kubeclient.Interface, namespace, name string) error {
|
||||
err := client.CoreV1().Secrets(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PatchSecret just try to patch the secret.
|
||||
func PatchSecret(client kubeclient.Interface, namespace, name string, pt types.PatchType, patchSecretBody *corev1.Secret) error {
|
||||
patchSecretByte, err := json.Marshal(patchSecretBody)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to marshal patch body of secret object %v into JSON: %v", patchSecretByte, err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.CoreV1().Secrets(namespace).Patch(context.TODO(), name, pt, patchSecretByte, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -36,3 +36,12 @@ func IsServiceAccountExist(client kubeclient.Interface, namespace string, name s
|
|||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// DeleteServiceAccount just try to delete the ServiceAccount.
|
||||
func DeleteServiceAccount(client kubeclient.Interface, namespace, name string) error {
|
||||
err := client.CoreV1().ServiceAccounts(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue