github.com/oam-dev/kubevela@v1.9.11/pkg/multicluster/cluster_management.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package multicluster 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "strings" 24 "time" 25 26 "github.com/briandowns/spinner" 27 "github.com/kubevela/pkg/util/k8s" 28 clusterv1alpha1 "github.com/oam-dev/cluster-gateway/pkg/apis/cluster/v1alpha1" 29 clustercommon "github.com/oam-dev/cluster-gateway/pkg/common" 30 "github.com/oam-dev/cluster-register/pkg/hub" 31 "github.com/oam-dev/cluster-register/pkg/spoke" 32 "github.com/pkg/errors" 33 corev1 "k8s.io/api/core/v1" 34 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 35 apierrors "k8s.io/apimachinery/pkg/api/errors" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 apitypes "k8s.io/apimachinery/pkg/types" 38 "k8s.io/client-go/rest" 39 "k8s.io/client-go/tools/clientcmd" 40 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 41 ocmclusterv1 "open-cluster-management.io/api/cluster/v1" 42 "sigs.k8s.io/controller-runtime/pkg/client" 43 44 "github.com/oam-dev/kubevela/pkg/utils" 45 cmdutil "github.com/oam-dev/kubevela/pkg/utils/util" 46 ) 47 48 // ContextKey defines the key in context 49 type ContextKey string 50 51 // KubeConfigContext marks the kubeConfig object in context 52 const KubeConfigContext ContextKey = "kubeConfig" 53 54 // KubeClusterConfig info for cluster management 55 type KubeClusterConfig struct { 56 FilePath string 57 ClusterName string 58 CreateNamespace string 59 *clientcmdapi.Config 60 *clientcmdapi.Cluster 61 *clientcmdapi.AuthInfo 62 63 // ClusterAlreadyExistCallback callback for handling cluster already exist, 64 // if no error returned, the logic will pass through 65 ClusterAlreadyExistCallback func(string) bool 66 67 // Logs records intermediate logs (which do not return error) during running 68 Logs bytes.Buffer 69 } 70 71 // SetClusterName set cluster name if not empty 72 func (clusterConfig *KubeClusterConfig) SetClusterName(clusterName string) *KubeClusterConfig { 73 if clusterName != "" { 74 clusterConfig.ClusterName = clusterName 75 } 76 return clusterConfig 77 } 78 79 // SetCreateNamespace set create namespace, if empty, no namespace will be created 80 func (clusterConfig *KubeClusterConfig) SetCreateNamespace(createNamespace string) *KubeClusterConfig { 81 clusterConfig.CreateNamespace = createNamespace 82 return clusterConfig 83 } 84 85 // Validate check if config is valid for join 86 func (clusterConfig *KubeClusterConfig) Validate() error { 87 switch clusterConfig.ClusterName { 88 case "": 89 return errors.Errorf("ClusterName cannot be empty") 90 case ClusterLocalName: 91 return errors.Errorf("ClusterName cannot be `%s`, it is reserved as the local cluster", ClusterLocalName) 92 } 93 return nil 94 } 95 96 // PostRegistration try to create namespace after cluster registered. If failed, cluster will be unregistered. 97 func (clusterConfig *KubeClusterConfig) PostRegistration(ctx context.Context, cli client.Client) error { 98 if clusterConfig.CreateNamespace == "" { 99 return nil 100 } 101 // retry 3 times. 102 for i := 0; i < 3; i++ { 103 if err := ensureNamespaceExists(ctx, cli, clusterConfig.ClusterName, clusterConfig.CreateNamespace); err != nil { 104 // Cluster gateway discovers the cluster maybe be deferred, so we should retry. 105 if strings.Contains(err.Error(), "no such cluster") { 106 if i < 2 { 107 time.Sleep(time.Second * 1) 108 continue 109 } 110 } 111 _ = DetachCluster(ctx, cli, clusterConfig.ClusterName, DetachClusterManagedClusterKubeConfigPathOption(clusterConfig.FilePath)) 112 return fmt.Errorf("failed to ensure %s namespace installed in cluster %s: %w", clusterConfig.CreateNamespace, clusterConfig.ClusterName, err) 113 } 114 break 115 } 116 return nil 117 } 118 119 func (clusterConfig *KubeClusterConfig) createOrUpdateClusterSecret(ctx context.Context, cli client.Client, withEndpoint bool) error { 120 var credentialType clusterv1alpha1.CredentialType 121 data := map[string][]byte{} 122 if withEndpoint { 123 data["endpoint"] = []byte(clusterConfig.Cluster.Server) 124 if !clusterConfig.Cluster.InsecureSkipTLSVerify { 125 data["ca.crt"] = clusterConfig.Cluster.CertificateAuthorityData 126 } 127 } 128 if len(clusterConfig.AuthInfo.Token) > 0 { 129 credentialType = clusterv1alpha1.CredentialTypeServiceAccountToken 130 data["token"] = []byte(clusterConfig.AuthInfo.Token) 131 } else { 132 credentialType = clusterv1alpha1.CredentialTypeX509Certificate 133 data["tls.crt"] = clusterConfig.AuthInfo.ClientCertificateData 134 data["tls.key"] = clusterConfig.AuthInfo.ClientKeyData 135 } 136 if clusterConfig.Cluster.ProxyURL != "" { 137 data["proxy-url"] = []byte(clusterConfig.Cluster.ProxyURL) 138 } 139 secret := &corev1.Secret{} 140 if err := cli.Get(ctx, apitypes.NamespacedName{Name: clusterConfig.ClusterName, Namespace: ClusterGatewaySecretNamespace}, secret); client.IgnoreNotFound(err) != nil { 141 return err 142 } 143 secret.Name = clusterConfig.ClusterName 144 secret.Namespace = ClusterGatewaySecretNamespace 145 secret.Type = corev1.SecretTypeOpaque 146 _ = k8s.AddLabel(secret, clustercommon.LabelKeyClusterCredentialType, string(credentialType)) 147 secret.Data = data 148 if secret.ResourceVersion == "" { 149 return cli.Create(ctx, secret) 150 } 151 return cli.Update(ctx, secret) 152 } 153 154 // RegisterByVelaSecret create cluster secrets for KubeVela to use 155 func (clusterConfig *KubeClusterConfig) RegisterByVelaSecret(ctx context.Context, cli client.Client) error { 156 cluster, err := NewClusterClient(cli).Get(ctx, clusterConfig.ClusterName) 157 if client.IgnoreNotFound(err) != nil { 158 return err 159 } 160 if cluster != nil { 161 if clusterConfig.ClusterAlreadyExistCallback == nil { 162 return fmt.Errorf("cluster %s already exists", cluster.Name) 163 } 164 if !clusterConfig.ClusterAlreadyExistCallback(clusterConfig.ClusterName) { 165 return nil 166 } 167 if cluster.Spec.CredentialType == clusterv1alpha1.CredentialTypeInternal || cluster.Spec.CredentialType == clusterv1alpha1.CredentialTypeOCMManagedCluster { 168 return fmt.Errorf("cannot override %s typed cluster", cluster.Spec.CredentialType) 169 } 170 } 171 172 if err := clusterConfig.createOrUpdateClusterSecret(ctx, cli, true); err != nil { 173 return errors.Wrapf(err, "failed to add cluster to kubernetes") 174 } 175 return clusterConfig.PostRegistration(ctx, cli) 176 } 177 178 // CreateBootstrapConfigMapIfNotExists alternative to 179 // https://github.com/kubernetes/kubernetes/blob/v1.24.1/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go#L43 180 func CreateBootstrapConfigMapIfNotExists(ctx context.Context, cli client.Client) error { 181 cm := &corev1.ConfigMap{} 182 key := apitypes.NamespacedName{Namespace: metav1.NamespacePublic, Name: "cluster-info"} 183 if err := cli.Get(ctx, key, cm); err != nil { 184 if apierrors.IsNotFound(err) { 185 cm.ObjectMeta = metav1.ObjectMeta{Namespace: key.Namespace, Name: key.Name} 186 adminConfig, err := clientcmd.NewDefaultPathOptions().GetStartingConfig() 187 if err != nil { 188 return err 189 } 190 adminCluster := adminConfig.Contexts[adminConfig.CurrentContext].Cluster 191 bs, err := clientcmd.Write(clientcmdapi.Config{ 192 Clusters: map[string]*clientcmdapi.Cluster{"": adminConfig.Clusters[adminCluster]}, 193 }) 194 if err != nil { 195 return err 196 } 197 cm.Data = map[string]string{"kubeconfig": string(bs)} 198 return cli.Create(ctx, cm) 199 } 200 return err 201 } 202 return nil 203 } 204 205 // RegisterClusterManagedByOCM create ocm managed cluster for use 206 // TODO(somefive): OCM ManagedCluster only support cli join now 207 func (clusterConfig *KubeClusterConfig) RegisterClusterManagedByOCM(ctx context.Context, cli client.Client, args *JoinClusterArgs) error { 208 newTrackingSpinner := args.trackingSpinnerFactory 209 hubCluster, err := hub.NewHubCluster(args.hubConfig) 210 if err != nil { 211 return errors.Wrap(err, "fail to create client connect to hub cluster") 212 } 213 214 hubTracker := newTrackingSpinner("Checking the environment of hub cluster..") 215 hubTracker.FinalMSG = "Hub cluster all set, continue registration.\n" 216 hubTracker.Start() 217 crdName := apitypes.NamespacedName{Name: "managedclusters." + ocmclusterv1.GroupName} 218 if err := hubCluster.Client.Get(context.Background(), crdName, &apiextensionsv1.CustomResourceDefinition{}); err != nil { 219 return err 220 } 221 222 clusters, err := ListVirtualClusters(context.Background(), hubCluster.Client) 223 if err != nil { 224 return err 225 } 226 227 for _, cluster := range clusters { 228 if cluster.Name == clusterConfig.ClusterName && cluster.Accepted { 229 return errors.Errorf("you have register a cluster named %s", clusterConfig.ClusterName) 230 } 231 } 232 hubTracker.Stop() 233 234 spokeRestConf, err := clientcmd.BuildConfigFromKubeconfigGetter("", func() (*clientcmdapi.Config, error) { 235 return clusterConfig.Config, nil 236 }) 237 if err != nil { 238 return errors.Wrap(err, "fail to convert spoke-cluster kubeconfig") 239 } 240 241 if err = CreateBootstrapConfigMapIfNotExists(ctx, cli); err != nil { 242 return fmt.Errorf("failed to ensure cluster-info ConfigMap in kube-public namespace exists: %w", err) 243 } 244 spokeTracker := newTrackingSpinner("Building registration config for the managed cluster") 245 spokeTracker.FinalMSG = "Successfully prepared registration config.\n" 246 spokeTracker.Start() 247 overridingRegistrationEndpoint := "" 248 if !*args.inClusterBootstrap { 249 args.ioStreams.Infof("Using the api endpoint from hub kubeconfig %q as registration entry.\n", args.hubConfig.Host) 250 overridingRegistrationEndpoint = args.hubConfig.Host 251 } 252 253 hubKubeToken, err := hubCluster.GenerateHubClusterKubeConfig(ctx, overridingRegistrationEndpoint) 254 if err != nil { 255 return errors.Wrap(err, "fail to generate the token for spoke-cluster") 256 } 257 258 spokeCluster, err := spoke.NewSpokeCluster(clusterConfig.ClusterName, spokeRestConf, hubKubeToken) 259 if err != nil { 260 return errors.Wrap(err, "fail to connect spoke cluster") 261 } 262 263 err = spokeCluster.InitSpokeClusterEnv(ctx) 264 if err != nil { 265 return errors.Wrap(err, "fail to prepare the env for spoke-cluster") 266 } 267 spokeTracker.Stop() 268 269 registrationOperatorTracker := newTrackingSpinner("Waiting for registration operators running: (`kubectl -n open-cluster-management get pod -l app=klusterlet`)") 270 registrationOperatorTracker.FinalMSG = "Registration operator successfully deployed.\n" 271 registrationOperatorTracker.Start() 272 if err := spokeCluster.WaitForRegistrationOperatorReady(ctx); err != nil { 273 return errors.Wrap(err, "fail to setup registration operator for spoke-cluster") 274 } 275 registrationOperatorTracker.Stop() 276 277 registrationAgentTracker := newTrackingSpinner("Waiting for registration agent running: (`kubectl -n open-cluster-management-agent get pod -l app=klusterlet-registration-agent`)") 278 registrationAgentTracker.FinalMSG = "Registration agent successfully deployed.\n" 279 registrationAgentTracker.Start() 280 if err := spokeCluster.WaitForRegistrationAgentReady(ctx); err != nil { 281 return errors.Wrap(err, "fail to setup registration agent for spoke-cluster") 282 } 283 registrationAgentTracker.Stop() 284 285 csrCreationTracker := newTrackingSpinner("Waiting for CSRs created (`kubectl get csr -l open-cluster-management.io/cluster-name=" + spokeCluster.Name + "`)") 286 csrCreationTracker.FinalMSG = "Successfully found corresponding CSR from the agent.\n" 287 csrCreationTracker.Start() 288 if err := hubCluster.WaitForCSRCreated(ctx, spokeCluster.Name); err != nil { 289 return errors.Wrap(err, "failed found CSR created by registration agent") 290 } 291 csrCreationTracker.Stop() 292 293 args.ioStreams.Infof("Approving the CSR for cluster %q.\n", spokeCluster.Name) 294 if err := hubCluster.ApproveCSR(ctx, spokeCluster.Name); err != nil { 295 return errors.Wrap(err, "failed found CSR created by registration agent") 296 } 297 298 ready, err := hubCluster.WaitForSpokeClusterReady(ctx, clusterConfig.ClusterName) 299 if err != nil || !ready { 300 return errors.Errorf("fail to waiting for register request") 301 } 302 303 if err = hubCluster.RegisterSpokeCluster(ctx, spokeCluster.Name); err != nil { 304 return errors.Wrap(err, "fail to approve spoke cluster") 305 } 306 return nil 307 } 308 309 // LoadKubeClusterConfigFromFile create KubeClusterConfig from kubeconfig file 310 func LoadKubeClusterConfigFromFile(filepath string) (*KubeClusterConfig, error) { 311 clusterConfig := &KubeClusterConfig{FilePath: filepath} 312 var err error 313 clusterConfig.Config, err = clientcmd.LoadFromFile(filepath) 314 if err != nil { 315 return nil, errors.Wrapf(err, "failed to get kubeconfig") 316 } 317 if len(clusterConfig.Config.CurrentContext) == 0 { 318 return nil, fmt.Errorf("current-context is not set") 319 } 320 var ok bool 321 ctx, ok := clusterConfig.Config.Contexts[clusterConfig.Config.CurrentContext] 322 if !ok { 323 return nil, fmt.Errorf("current-context %s not found", clusterConfig.Config.CurrentContext) 324 } 325 clusterConfig.Cluster, ok = clusterConfig.Config.Clusters[ctx.Cluster] 326 if !ok { 327 return nil, fmt.Errorf("cluster %s not found", ctx.Cluster) 328 } 329 clusterConfig.AuthInfo, ok = clusterConfig.Config.AuthInfos[ctx.AuthInfo] 330 if !ok { 331 return nil, fmt.Errorf("authInfo %s not found", ctx.AuthInfo) 332 } 333 clusterConfig.ClusterName = ctx.Cluster 334 if endpoint, err := utils.ParseAPIServerEndpoint(clusterConfig.Cluster.Server); err == nil { 335 clusterConfig.Cluster.Server = endpoint 336 } else { 337 _, _ = fmt.Fprintf(&clusterConfig.Logs, "failed to parse server endpoint: %v", err) 338 } 339 return clusterConfig, nil 340 } 341 342 const ( 343 // ClusterGateWayEngine cluster-gateway cluster management solution 344 ClusterGateWayEngine = "cluster-gateway" 345 // OCMEngine ocm cluster management solution 346 OCMEngine = "ocm" 347 ) 348 349 // JoinClusterArgs args for join cluster 350 type JoinClusterArgs struct { 351 engine string 352 createNamespace string 353 ioStreams cmdutil.IOStreams 354 hubConfig *rest.Config 355 inClusterBootstrap *bool 356 trackingSpinnerFactory func(string) *spinner.Spinner 357 clusterAlreadyExistCallback func(string) bool 358 } 359 360 func newJoinClusterArgs(options ...JoinClusterOption) *JoinClusterArgs { 361 args := &JoinClusterArgs{ 362 engine: ClusterGateWayEngine, 363 } 364 for _, op := range options { 365 op.ApplyToArgs(args) 366 } 367 return args 368 } 369 370 // JoinClusterOption option for join cluster 371 type JoinClusterOption interface { 372 ApplyToArgs(args *JoinClusterArgs) 373 } 374 375 // JoinClusterCreateNamespaceOption create namespace when join cluster, if empty, no creation 376 type JoinClusterCreateNamespaceOption string 377 378 // ApplyToArgs apply to args 379 func (op JoinClusterCreateNamespaceOption) ApplyToArgs(args *JoinClusterArgs) { 380 args.createNamespace = string(op) 381 } 382 383 // JoinClusterEngineOption configure engine for join cluster, either cluster-gateway or ocm 384 type JoinClusterEngineOption string 385 386 // ApplyToArgs apply to args 387 func (op JoinClusterEngineOption) ApplyToArgs(args *JoinClusterArgs) { 388 args.engine = string(op) 389 } 390 391 // JoinClusterAlreadyExistCallback configure the callback when cluster already exist 392 type JoinClusterAlreadyExistCallback func(string) bool 393 394 // ApplyToArgs apply to args 395 func (op JoinClusterAlreadyExistCallback) ApplyToArgs(args *JoinClusterArgs) { 396 args.clusterAlreadyExistCallback = op 397 } 398 399 // JoinClusterOCMOptions options used when joining clusters by ocm, only support cli for now 400 type JoinClusterOCMOptions struct { 401 IoStreams cmdutil.IOStreams 402 HubConfig *rest.Config 403 InClusterBootstrap *bool 404 TrackingSpinnerFactory func(string) *spinner.Spinner 405 } 406 407 // ApplyToArgs apply to args 408 func (op JoinClusterOCMOptions) ApplyToArgs(args *JoinClusterArgs) { 409 args.ioStreams = op.IoStreams 410 args.hubConfig = op.HubConfig 411 args.inClusterBootstrap = op.InClusterBootstrap 412 args.trackingSpinnerFactory = op.TrackingSpinnerFactory 413 } 414 415 // JoinClusterByKubeConfig add child cluster by kubeconfig path, return cluster info and error 416 func JoinClusterByKubeConfig(ctx context.Context, cli client.Client, kubeconfigPath string, clusterName string, options ...JoinClusterOption) (*KubeClusterConfig, error) { 417 args := newJoinClusterArgs(options...) 418 clusterConfig, err := LoadKubeClusterConfigFromFile(kubeconfigPath) 419 if err != nil { 420 return nil, err 421 } 422 if err := clusterConfig.SetClusterName(clusterName).SetCreateNamespace(args.createNamespace).Validate(); err != nil { 423 return nil, err 424 } 425 clusterConfig.ClusterAlreadyExistCallback = args.clusterAlreadyExistCallback 426 switch args.engine { 427 case ClusterGateWayEngine: 428 if err = clusterConfig.RegisterByVelaSecret(ctx, cli); err != nil { 429 return nil, err 430 } 431 case OCMEngine: 432 if args.inClusterBootstrap == nil { 433 return nil, errors.Wrapf(err, "failed to determine the registration endpoint for the hub cluster "+ 434 "when parsing --in-cluster-bootstrap flag") 435 } 436 if err = clusterConfig.RegisterClusterManagedByOCM(ctx, cli, args); err != nil { 437 return clusterConfig, err 438 } 439 } 440 if cfg, ok := ctx.Value(KubeConfigContext).(*rest.Config); ok { 441 if err = SetClusterVersionInfo(ctx, cfg, clusterConfig.ClusterName); err != nil { 442 return nil, err 443 } 444 } 445 return clusterConfig, nil 446 } 447 448 // DetachClusterArgs args for detaching cluster 449 type DetachClusterArgs struct { 450 managedClusterKubeConfigPath string 451 } 452 453 func newDetachClusterArgs(options ...DetachClusterOption) *DetachClusterArgs { 454 args := &DetachClusterArgs{} 455 for _, op := range options { 456 op.ApplyToArgs(args) 457 } 458 return args 459 } 460 461 // DetachClusterOption option for detach cluster 462 type DetachClusterOption interface { 463 ApplyToArgs(args *DetachClusterArgs) 464 } 465 466 // DetachClusterManagedClusterKubeConfigPathOption configure the managed cluster kubeconfig path while detach ocm cluster 467 type DetachClusterManagedClusterKubeConfigPathOption string 468 469 // ApplyToArgs apply to args 470 func (op DetachClusterManagedClusterKubeConfigPathOption) ApplyToArgs(args *DetachClusterArgs) { 471 args.managedClusterKubeConfigPath = string(op) 472 } 473 474 // DetachCluster detach cluster by name, if cluster is using by application, it will return error 475 func DetachCluster(ctx context.Context, cli client.Client, clusterName string, options ...DetachClusterOption) error { 476 args := newDetachClusterArgs(options...) 477 if clusterName == ClusterLocalName { 478 return ErrReservedLocalClusterName 479 } 480 vc, err := NewClusterClient(cli).Get(ctx, clusterName) 481 if err != nil { 482 return err 483 } 484 485 switch vc.Spec.CredentialType { 486 case clusterv1alpha1.CredentialTypeX509Certificate, clusterv1alpha1.CredentialTypeServiceAccountToken: 487 clusterSecret, err := getMutableClusterSecret(ctx, cli, clusterName) 488 if err != nil { 489 return errors.Wrapf(err, "cluster %s is not mutable now", clusterName) 490 } 491 if err := cli.Delete(ctx, clusterSecret); err != nil { 492 return errors.Wrapf(err, "failed to detach cluster %s", clusterName) 493 } 494 case clusterv1alpha1.CredentialTypeOCMManagedCluster: 495 if args.managedClusterKubeConfigPath == "" { 496 return errors.New("kubeconfig-path must be set to detach ocm managed cluster") 497 } 498 config, err := clientcmd.LoadFromFile(args.managedClusterKubeConfigPath) 499 if err != nil { 500 return err 501 } 502 restConfig, err := clientcmd.BuildConfigFromKubeconfigGetter("", func() (*clientcmdapi.Config, error) { 503 return config, nil 504 }) 505 if err != nil { 506 return err 507 } 508 if err = spoke.CleanSpokeClusterEnv(restConfig); err != nil { 509 return err 510 } 511 managedCluster := ocmclusterv1.ManagedCluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName}} 512 if err = cli.Delete(ctx, &managedCluster); err != nil { 513 if !apierrors.IsNotFound(err) { 514 return err 515 } 516 } 517 case clusterv1alpha1.CredentialTypeInternal: 518 return fmt.Errorf("cannot detach internal cluster `local`") 519 } 520 return nil 521 } 522 523 // RenameCluster rename cluster 524 func RenameCluster(ctx context.Context, k8sClient client.Client, oldClusterName string, newClusterName string) error { 525 if newClusterName == ClusterLocalName { 526 return ErrReservedLocalClusterName 527 } 528 clusterSecret, err := getMutableClusterSecret(ctx, k8sClient, oldClusterName) 529 if err != nil { 530 return errors.Wrapf(err, "cluster %s is not mutable now", oldClusterName) 531 } 532 if err := ensureClusterNotExists(ctx, k8sClient, newClusterName); err != nil { 533 return errors.Wrapf(err, "cannot set cluster name to %s", newClusterName) 534 } 535 if err := k8sClient.Delete(ctx, clusterSecret); err != nil { 536 return errors.Wrapf(err, "failed to rename cluster from %s to %s", oldClusterName, newClusterName) 537 } 538 clusterSecret.ObjectMeta = metav1.ObjectMeta{ 539 Name: newClusterName, 540 Namespace: ClusterGatewaySecretNamespace, 541 Labels: clusterSecret.Labels, 542 Annotations: clusterSecret.Annotations, 543 } 544 if err := k8sClient.Create(ctx, clusterSecret); err != nil { 545 return errors.Wrapf(err, "failed to rename cluster from %s to %s", oldClusterName, newClusterName) 546 } 547 return nil 548 } 549 550 // AliasCluster alias cluster 551 func AliasCluster(ctx context.Context, cli client.Client, clusterName string, aliasName string) error { 552 if clusterName == ClusterLocalName { 553 return ErrReservedLocalClusterName 554 } 555 vc, err := GetVirtualCluster(ctx, cli, clusterName) 556 if err != nil { 557 return err 558 } 559 setClusterAlias(vc.Object, aliasName) 560 return cli.Update(ctx, vc.Object) 561 } 562 563 // ensureClusterNotExists will check the cluster is not existed in control plane 564 func ensureClusterNotExists(ctx context.Context, c client.Client, clusterName string) error { 565 _, err := NewClusterClient(c).Get(ctx, clusterName) 566 if err != nil { 567 return client.IgnoreNotFound(err) 568 } 569 return ErrClusterExists 570 } 571 572 // ensureNamespaceExists ensures vela namespace to be installed in child cluster 573 func ensureNamespaceExists(ctx context.Context, c client.Client, clusterName string, createNamespace string) error { 574 remoteCtx := ContextWithClusterName(ctx, clusterName) 575 if err := c.Get(remoteCtx, apitypes.NamespacedName{Name: createNamespace}, &corev1.Namespace{}); err != nil { 576 if !apierrors.IsNotFound(err) { 577 return errors.Wrapf(err, "failed to check if namespace %s exists", createNamespace) 578 } 579 if err = c.Create(remoteCtx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: createNamespace}}); err != nil { 580 return errors.Wrapf(err, "failed to create namespace %s", createNamespace) 581 } 582 } 583 return nil 584 } 585 586 // getMutableClusterSecret retrieves the cluster secret and check if any application is using the cluster 587 // TODO(somefive): should rework the logic of checking application cluster usage 588 func getMutableClusterSecret(ctx context.Context, c client.Client, clusterName string) (*corev1.Secret, error) { 589 clusterSecret := &corev1.Secret{} 590 if err := c.Get(ctx, apitypes.NamespacedName{Namespace: ClusterGatewaySecretNamespace, Name: clusterName}, clusterSecret); err != nil { 591 return nil, errors.Wrapf(err, "failed to find target cluster secret %s", clusterName) 592 } 593 labels := clusterSecret.GetLabels() 594 if labels == nil || labels[clustercommon.LabelKeyClusterCredentialType] == "" { 595 return nil, fmt.Errorf("invalid cluster secret %s: cluster credential type label %s is not set", clusterName, clustercommon.LabelKeyClusterCredentialType) 596 } 597 return clusterSecret, nil 598 }