github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/cluster.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "net/http" 6 "os" 7 "regexp" 8 "strings" 9 "text/tabwriter" 10 11 "google.golang.org/grpc/codes" 12 "google.golang.org/grpc/status" 13 14 "github.com/mattn/go-isatty" 15 log "github.com/sirupsen/logrus" 16 "github.com/spf13/cobra" 17 "k8s.io/client-go/kubernetes" 18 "k8s.io/client-go/rest" 19 "k8s.io/client-go/tools/clientcmd" 20 21 "github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless" 22 cmdutil "github.com/argoproj/argo-cd/v3/cmd/util" 23 "github.com/argoproj/argo-cd/v3/common" 24 argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient" 25 clusterpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster" 26 argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 27 "github.com/argoproj/argo-cd/v3/util/cli" 28 "github.com/argoproj/argo-cd/v3/util/clusterauth" 29 "github.com/argoproj/argo-cd/v3/util/errors" 30 utilio "github.com/argoproj/argo-cd/v3/util/io" 31 "github.com/argoproj/argo-cd/v3/util/text/label" 32 ) 33 34 const ( 35 // type of the cluster ID is 'name' 36 clusterIdTypeName = "name" 37 // cluster field is 'name' 38 clusterFieldName = "name" 39 // cluster field is 'namespaces' 40 clusterFieldNamespaces = "namespaces" 41 // cluster field is 'labels' 42 clusterFieldLabel = "labels" 43 // cluster field is 'annotations' 44 clusterFieldAnnotation = "annotations" 45 // indicates managing all namespaces 46 allNamespaces = "*" 47 ) 48 49 // NewClusterCommand returns a new instance of an `argocd cluster` command 50 func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientcmd.PathOptions) *cobra.Command { 51 command := &cobra.Command{ 52 Use: "cluster", 53 Short: "Manage cluster credentials", 54 Run: func(c *cobra.Command, args []string) { 55 c.HelpFunc()(c, args) 56 os.Exit(1) 57 }, 58 Example: ` # List all known clusters in JSON format: 59 argocd cluster list -o json 60 61 # Add a target cluster configuration to ArgoCD. The context must exist in your kubectl config: 62 argocd cluster add example-cluster 63 64 # Get specific details about a cluster in plain text (wide) format: 65 argocd cluster get example-cluster -o wide 66 67 # Remove a target cluster context from ArgoCD 68 argocd cluster rm example-cluster 69 70 # Set a target cluster context from ArgoCD 71 argocd cluster set CLUSTER_NAME --name new-cluster-name --namespace '*' 72 argocd cluster set CLUSTER_NAME --name new-cluster-name --namespace namespace-one --namespace namespace-two`, 73 } 74 75 command.AddCommand(NewClusterAddCommand(clientOpts, pathOpts)) 76 command.AddCommand(NewClusterGetCommand(clientOpts)) 77 command.AddCommand(NewClusterListCommand(clientOpts)) 78 command.AddCommand(NewClusterRemoveCommand(clientOpts, pathOpts)) 79 command.AddCommand(NewClusterRotateAuthCommand(clientOpts)) 80 command.AddCommand(NewClusterSetCommand(clientOpts)) 81 return command 82 } 83 84 // NewClusterAddCommand returns a new instance of an `argocd cluster add` command 85 func NewClusterAddCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientcmd.PathOptions) *cobra.Command { 86 var ( 87 clusterOpts cmdutil.ClusterOptions 88 skipConfirmation bool 89 labels []string 90 annotations []string 91 ) 92 command := &cobra.Command{ 93 Use: "add CONTEXT", 94 Short: cliName + " cluster add CONTEXT", 95 Run: func(c *cobra.Command, args []string) { 96 ctx := c.Context() 97 98 var configAccess clientcmd.ConfigAccess = pathOpts 99 if len(args) == 0 { 100 log.Error("Choose a context name from:") 101 cmdutil.PrintKubeContexts(configAccess) 102 os.Exit(1) 103 } 104 105 if clusterOpts.InCluster && clusterOpts.ClusterEndpoint != "" { 106 log.Fatal("Can only use one of --in-cluster or --cluster-endpoint") 107 return 108 } 109 110 contextName := args[0] 111 conf, err := getRestConfig(pathOpts, contextName) 112 errors.CheckError(err) 113 if clusterOpts.ProxyUrl != "" { 114 u, err := argoappv1.ParseProxyUrl(clusterOpts.ProxyUrl) 115 errors.CheckError(err) 116 conf.Proxy = http.ProxyURL(u) 117 } 118 clientset, err := kubernetes.NewForConfig(conf) 119 errors.CheckError(err) 120 managerBearerToken := "" 121 var awsAuthConf *argoappv1.AWSAuthConfig 122 var execProviderConf *argoappv1.ExecProviderConfig 123 switch { 124 case clusterOpts.AwsClusterName != "": 125 awsAuthConf = &argoappv1.AWSAuthConfig{ 126 ClusterName: clusterOpts.AwsClusterName, 127 RoleARN: clusterOpts.AwsRoleArn, 128 Profile: clusterOpts.AwsProfile, 129 } 130 case clusterOpts.ExecProviderCommand != "": 131 execProviderConf = &argoappv1.ExecProviderConfig{ 132 Command: clusterOpts.ExecProviderCommand, 133 Args: clusterOpts.ExecProviderArgs, 134 Env: clusterOpts.ExecProviderEnv, 135 APIVersion: clusterOpts.ExecProviderAPIVersion, 136 InstallHint: clusterOpts.ExecProviderInstallHint, 137 } 138 default: 139 // Install RBAC resources for managing the cluster 140 if clusterOpts.ServiceAccount != "" { 141 managerBearerToken, err = clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount, common.BearerTokenTimeout) 142 } else { 143 isTerminal := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) 144 if isTerminal && !skipConfirmation { 145 accessLevel := "cluster" 146 if len(clusterOpts.Namespaces) > 0 { 147 accessLevel = "namespace" 148 } 149 message := fmt.Sprintf("WARNING: This will create a service account `argocd-manager` on the cluster referenced by context `%s` with full %s level privileges. Do you want to continue [y/N]? ", contextName, accessLevel) 150 if !cli.AskToProceed(message) { 151 os.Exit(1) 152 } 153 } 154 managerBearerToken, err = clusterauth.InstallClusterManagerRBAC(clientset, clusterOpts.SystemNamespace, clusterOpts.Namespaces, common.BearerTokenTimeout) 155 } 156 errors.CheckError(err) 157 } 158 159 labelsMap, err := label.Parse(labels) 160 errors.CheckError(err) 161 annotationsMap, err := label.Parse(annotations) 162 errors.CheckError(err) 163 164 conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie() 165 defer utilio.Close(conn) 166 if clusterOpts.Name != "" { 167 contextName = clusterOpts.Name 168 } 169 clst := cmdutil.NewCluster(contextName, clusterOpts.Namespaces, clusterOpts.ClusterResources, conf, managerBearerToken, awsAuthConf, execProviderConf, labelsMap, annotationsMap) 170 if clusterOpts.InClusterEndpoint() { 171 clst.Server = argoappv1.KubernetesInternalAPIServerAddr 172 } else if clusterOpts.ClusterEndpoint == string(cmdutil.KubePublicEndpoint) { 173 endpoint, caData, err := cmdutil.GetKubePublicEndpoint(clientset) 174 if err != nil || endpoint == "" { 175 log.Warnf("Failed to find the cluster endpoint from kube-public data: %v", err) 176 log.Infof("Falling back to the endpoint '%s' as listed in the kubeconfig context", clst.Server) 177 endpoint = clst.Server 178 } 179 clst.Server = endpoint 180 clst.Config.CAData = caData 181 } 182 183 if clusterOpts.Shard >= 0 { 184 clst.Shard = &clusterOpts.Shard 185 } 186 if clusterOpts.Project != "" { 187 clst.Project = clusterOpts.Project 188 } 189 clstCreateReq := clusterpkg.ClusterCreateRequest{ 190 Cluster: clst, 191 Upsert: clusterOpts.Upsert, 192 } 193 _, err = clusterIf.Create(ctx, &clstCreateReq) 194 errors.CheckError(err) 195 fmt.Printf("Cluster '%s' added\n", clst.Server) 196 }, 197 } 198 command.PersistentFlags().StringVar(&pathOpts.LoadingRules.ExplicitPath, pathOpts.ExplicitFileFlag, pathOpts.LoadingRules.ExplicitPath, "use a particular kubeconfig file") 199 command.Flags().BoolVar(&clusterOpts.Upsert, "upsert", false, "Override an existing cluster with the same name even if the spec differs") 200 command.Flags().StringVar(&clusterOpts.ServiceAccount, "service-account", "", fmt.Sprintf("System namespace service account to use for kubernetes resource management. If not set then default %q SA will be created", clusterauth.ArgoCDManagerServiceAccount)) 201 command.Flags().StringVar(&clusterOpts.SystemNamespace, "system-namespace", common.DefaultSystemNamespace, "Use different system namespace") 202 command.Flags().BoolVarP(&skipConfirmation, "yes", "y", false, "Skip explicit confirmation") 203 command.Flags().StringArrayVar(&labels, "label", nil, "Set metadata labels (e.g. --label key=value)") 204 command.Flags().StringArrayVar(&annotations, "annotation", nil, "Set metadata annotations (e.g. --annotation key=value)") 205 command.Flags().StringVar(&clusterOpts.ProxyUrl, "proxy-url", "", "use proxy to connect cluster") 206 cmdutil.AddClusterFlags(command, &clusterOpts) 207 return command 208 } 209 210 func getRestConfig(pathOpts *clientcmd.PathOptions, ctxName string) (*rest.Config, error) { 211 config, err := pathOpts.GetStartingConfig() 212 if err != nil { 213 return nil, err 214 } 215 216 clstContext := config.Contexts[ctxName] 217 if clstContext == nil { 218 return nil, fmt.Errorf("context %s does not exist in kubeconfig", ctxName) 219 } 220 221 overrides := clientcmd.ConfigOverrides{ 222 Context: *clstContext, 223 } 224 225 clientConfig := clientcmd.NewDefaultClientConfig(*config, &overrides) 226 conf, err := clientConfig.ClientConfig() 227 if err != nil { 228 return nil, err 229 } 230 231 return conf, nil 232 } 233 234 // NewClusterSetCommand returns a new instance of an `argocd cluster set` command 235 func NewClusterSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 236 var ( 237 clusterOptions cmdutil.ClusterOptions 238 clusterName string 239 labels []string 240 annotations []string 241 ) 242 command := &cobra.Command{ 243 Use: "set NAME", 244 Short: "Set cluster information", 245 Example: ` # Set cluster information 246 argocd cluster set CLUSTER_NAME --name new-cluster-name --namespace '*' 247 argocd cluster set CLUSTER_NAME --name new-cluster-name --namespace namespace-one --namespace namespace-two`, 248 Run: func(c *cobra.Command, args []string) { 249 ctx := c.Context() 250 if len(args) != 1 { 251 c.HelpFunc()(c, args) 252 os.Exit(1) 253 } 254 // name of the cluster whose fields have to be updated. 255 clusterName = args[0] 256 conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie() 257 defer utilio.Close(conn) 258 // checks the fields that needs to be updated 259 updatedFields := checkFieldsToUpdate(clusterOptions, labels, annotations) 260 namespaces := clusterOptions.Namespaces 261 // check if all namespaces have to be considered 262 if len(namespaces) == 1 && strings.EqualFold(namespaces[0], allNamespaces) { 263 namespaces[0] = "" 264 } 265 // parse the labels you're receiving from the label flag 266 labelsMap, err := label.Parse(labels) 267 errors.CheckError(err) 268 // parse the annotations you're receiving from the annotation flag 269 annotationsMap, err := label.Parse(annotations) 270 errors.CheckError(err) 271 if updatedFields != nil { 272 clusterUpdateRequest := clusterpkg.ClusterUpdateRequest{ 273 Cluster: &argoappv1.Cluster{ 274 Name: clusterOptions.Name, 275 Namespaces: namespaces, 276 Labels: labelsMap, 277 Annotations: annotationsMap, 278 }, 279 UpdatedFields: updatedFields, 280 Id: &clusterpkg.ClusterID{ 281 Type: clusterIdTypeName, 282 Value: clusterName, 283 }, 284 } 285 _, err := clusterIf.Update(ctx, &clusterUpdateRequest) 286 if err != nil { 287 if status.Code(err) == codes.PermissionDenied { 288 log.Error("Ensure that the cluster is present and you have the necessary permissions to update the cluster") 289 } 290 errors.CheckError(err) 291 } 292 fmt.Printf("Cluster '%s' updated.\n", clusterName) 293 } else { 294 fmt.Print("Specify the cluster field to be updated.\n") 295 } 296 }, 297 } 298 command.Flags().StringVar(&clusterOptions.Name, "name", "", "Overwrite the cluster name") 299 command.Flags().StringArrayVar(&clusterOptions.Namespaces, "namespace", nil, "List of namespaces which are allowed to manage. Specify '*' to manage all namespaces") 300 command.Flags().StringArrayVar(&labels, "label", nil, "Set metadata labels (e.g. --label key=value)") 301 command.Flags().StringArrayVar(&annotations, "annotation", nil, "Set metadata annotations (e.g. --annotation key=value)") 302 return command 303 } 304 305 // checkFieldsToUpdate returns the fields that needs to be updated 306 func checkFieldsToUpdate(clusterOptions cmdutil.ClusterOptions, labels []string, annotations []string) []string { 307 var updatedFields []string 308 if clusterOptions.Name != "" { 309 updatedFields = append(updatedFields, clusterFieldName) 310 } 311 if clusterOptions.Namespaces != nil { 312 updatedFields = append(updatedFields, clusterFieldNamespaces) 313 } 314 if labels != nil { 315 updatedFields = append(updatedFields, clusterFieldLabel) 316 } 317 if annotations != nil { 318 updatedFields = append(updatedFields, clusterFieldAnnotation) 319 } 320 return updatedFields 321 } 322 323 // NewClusterGetCommand returns a new instance of an `argocd cluster get` command 324 func NewClusterGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 325 var output string 326 command := &cobra.Command{ 327 Use: "get SERVER/NAME", 328 Short: "Get cluster information", 329 Example: `argocd cluster get https://12.34.567.89 330 argocd cluster get in-cluster`, 331 Run: func(c *cobra.Command, args []string) { 332 ctx := c.Context() 333 334 if len(args) == 0 { 335 c.HelpFunc()(c, args) 336 os.Exit(1) 337 } 338 conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie() 339 defer utilio.Close(conn) 340 clusters := make([]argoappv1.Cluster, 0) 341 for _, clusterSelector := range args { 342 clst, err := clusterIf.Get(ctx, getQueryBySelector(clusterSelector)) 343 errors.CheckError(err) 344 clusters = append(clusters, *clst) 345 } 346 switch output { 347 case "yaml", "json": 348 err := PrintResourceList(clusters, output, true) 349 errors.CheckError(err) 350 case "wide", "": 351 printClusterDetails(clusters) 352 case "server": 353 printClusterServers(clusters) 354 default: 355 errors.CheckError(fmt.Errorf("unknown output format: %s", output)) 356 } 357 }, 358 } 359 // we have yaml as default to not break backwards-compatibility 360 command.Flags().StringVarP(&output, "output", "o", "yaml", "Output format. One of: json|yaml|wide|server") 361 return command 362 } 363 364 func strWithDefault(value string, def string) string { 365 if value == "" { 366 return def 367 } 368 return value 369 } 370 371 func formatNamespaces(cluster argoappv1.Cluster) string { 372 if len(cluster.Namespaces) == 0 { 373 return "all namespaces" 374 } 375 return strings.Join(cluster.Namespaces, ", ") 376 } 377 378 func printClusterDetails(clusters []argoappv1.Cluster) { 379 for _, cluster := range clusters { 380 fmt.Printf("Cluster information\n\n") 381 fmt.Printf(" Server URL: %s\n", cluster.Server) 382 fmt.Printf(" Server Name: %s\n", strWithDefault(cluster.Name, "-")) 383 //nolint:staticcheck 384 fmt.Printf(" Server Version: %s\n", cluster.ServerVersion) 385 fmt.Printf(" Namespaces: %s\n", formatNamespaces(cluster)) 386 fmt.Printf("\nTLS configuration\n\n") 387 fmt.Printf(" Client cert: %v\n", len(cluster.Config.CertData) != 0) 388 fmt.Printf(" Cert validation: %v\n", !cluster.Config.Insecure) 389 fmt.Printf("\nAuthentication\n\n") 390 fmt.Printf(" Basic authentication: %v\n", cluster.Config.Username != "") 391 fmt.Printf(" oAuth authentication: %v\n", cluster.Config.BearerToken != "") 392 fmt.Printf(" AWS authentication: %v\n", cluster.Config.AWSAuthConfig != nil) 393 fmt.Printf("\nDisable compression: %v\n", cluster.Config.DisableCompression) 394 fmt.Printf("\nUse proxy: %v\n", cluster.Config.ProxyUrl != "") 395 fmt.Println() 396 } 397 } 398 399 // NewClusterRemoveCommand returns a new instance of an `argocd cluster rm` command 400 func NewClusterRemoveCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientcmd.PathOptions) *cobra.Command { 401 var noPrompt bool 402 command := &cobra.Command{ 403 Use: "rm SERVER/NAME", 404 Short: "Remove cluster credentials", 405 Example: `argocd cluster rm https://12.34.567.89 406 argocd cluster rm cluster-name`, 407 Run: func(c *cobra.Command, args []string) { 408 ctx := c.Context() 409 410 if len(args) == 0 { 411 c.HelpFunc()(c, args) 412 os.Exit(1) 413 } 414 conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie() 415 defer utilio.Close(conn) 416 numOfClusters := len(args) 417 var isConfirmAll bool 418 419 for _, clusterSelector := range args { 420 clusterQuery := getQueryBySelector(clusterSelector) 421 var lowercaseAnswer string 422 if !noPrompt { 423 if numOfClusters == 1 { 424 lowercaseAnswer = cli.AskToProceedS("Are you sure you want to remove '" + clusterSelector + "'? Any Apps deploying to this cluster will go to health status Unknown.[y/n] ") 425 } else { 426 if !isConfirmAll { 427 lowercaseAnswer = cli.AskToProceedS("Are you sure you want to remove '" + clusterSelector + "'? Any Apps deploying to this cluster will go to health status Unknown.[y/n/A] where 'A' is to remove all specified clusters without prompting. Any Apps deploying to these clusters will go to health status Unknown. ") 428 if lowercaseAnswer == "a" { 429 lowercaseAnswer = "y" 430 isConfirmAll = true 431 } 432 } else { 433 lowercaseAnswer = "y" 434 } 435 } 436 } else { 437 lowercaseAnswer = "y" 438 } 439 440 if lowercaseAnswer == "y" { 441 // get the cluster name to use as context to delete RBAC on cluster 442 clst, err := clusterIf.Get(ctx, clusterQuery) 443 errors.CheckError(err) 444 445 // remove cluster 446 _, err = clusterIf.Delete(ctx, clusterQuery) 447 errors.CheckError(err) 448 fmt.Printf("Cluster '%s' removed\n", clusterSelector) 449 450 // remove RBAC from cluster 451 conf, err := getRestConfig(pathOpts, clst.Name) 452 errors.CheckError(err) 453 454 clientset, err := kubernetes.NewForConfig(conf) 455 errors.CheckError(err) 456 457 err = clusterauth.UninstallClusterManagerRBAC(clientset) 458 errors.CheckError(err) 459 } else { 460 fmt.Println("The command to remove '" + clusterSelector + "' was cancelled.") 461 } 462 } 463 }, 464 } 465 command.Flags().BoolVarP(&noPrompt, "yes", "y", false, "Turn off prompting to confirm remove of cluster resources") 466 return command 467 } 468 469 // Print table of cluster information 470 func printClusterTable(clusters []argoappv1.Cluster) { 471 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 472 _, _ = fmt.Fprintf(w, "SERVER\tNAME\tVERSION\tSTATUS\tMESSAGE\tPROJECT\n") 473 for _, c := range clusters { 474 server := c.Server 475 if len(c.Namespaces) > 0 { 476 server = fmt.Sprintf("%s (%d namespaces)", c.Server, len(c.Namespaces)) 477 } 478 //nolint:staticcheck 479 _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", server, c.Name, c.ServerVersion, c.ConnectionState.Status, c.ConnectionState.Message, c.Project) 480 } 481 _ = w.Flush() 482 } 483 484 // Returns cluster query for getting cluster depending on the cluster selector 485 func getQueryBySelector(clusterSelector string) *clusterpkg.ClusterQuery { 486 var query clusterpkg.ClusterQuery 487 isServer, err := regexp.MatchString(`^https?://`, clusterSelector) 488 if isServer || err != nil { 489 query.Server = clusterSelector 490 } else { 491 query.Name = clusterSelector 492 } 493 return &query 494 } 495 496 // Print list of cluster servers 497 func printClusterServers(clusters []argoappv1.Cluster) { 498 for _, c := range clusters { 499 fmt.Println(c.Server) 500 } 501 } 502 503 // NewClusterListCommand returns a new instance of an `argocd cluster rm` command 504 func NewClusterListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 505 var output string 506 command := &cobra.Command{ 507 Use: "list", 508 Short: "List configured clusters", 509 Run: func(c *cobra.Command, _ []string) { 510 ctx := c.Context() 511 512 conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie() 513 defer utilio.Close(conn) 514 clusters, err := clusterIf.List(ctx, &clusterpkg.ClusterQuery{}) 515 errors.CheckError(err) 516 switch output { 517 case "yaml", "json": 518 err := PrintResourceList(clusters.Items, output, false) 519 errors.CheckError(err) 520 case "server": 521 printClusterServers(clusters.Items) 522 case "wide", "": 523 printClusterTable(clusters.Items) 524 default: 525 errors.CheckError(fmt.Errorf("unknown output format: %s", output)) 526 } 527 }, 528 Example: ` 529 # List Clusters in Default "Wide" Format 530 argocd cluster list 531 532 # List Cluster via specifying the server 533 argocd cluster list --server <ARGOCD_SERVER_ADDRESS> 534 535 # List Clusters in JSON Format 536 argocd cluster list -o json --server <ARGOCD_SERVER_ADDRESS> 537 538 # List Clusters in YAML Format 539 argocd cluster list -o yaml --server <ARGOCD_SERVER_ADDRESS> 540 541 # List Clusters that have been added to your Argo CD 542 argocd cluster list -o server <ARGOCD_SERVER_ADDRESS> 543 544 `, 545 } 546 command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|server") 547 return command 548 } 549 550 // NewClusterRotateAuthCommand returns a new instance of an `argocd cluster rotate-auth` command 551 func NewClusterRotateAuthCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 552 command := &cobra.Command{ 553 Use: "rotate-auth SERVER/NAME", 554 Short: cliName + " cluster rotate-auth SERVER/NAME", 555 Example: `argocd cluster rotate-auth https://12.34.567.89 556 argocd cluster rotate-auth cluster-name`, 557 Run: func(c *cobra.Command, args []string) { 558 ctx := c.Context() 559 560 if len(args) != 1 { 561 c.HelpFunc()(c, args) 562 os.Exit(1) 563 } 564 conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie() 565 defer utilio.Close(conn) 566 567 cluster := args[0] 568 clusterQuery := getQueryBySelector(cluster) 569 _, err := clusterIf.RotateAuth(ctx, clusterQuery) 570 errors.CheckError(err) 571 572 fmt.Printf("Cluster '%s' rotated auth\n", cluster) 573 }, 574 } 575 return command 576 }