github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/admin/cluster.go (about) 1 package admin 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "os" 8 "sort" 9 "strings" 10 "text/tabwriter" 11 "time" 12 13 "github.com/argoproj/gitops-engine/pkg/utils/kube" 14 "github.com/redis/go-redis/v9" 15 log "github.com/sirupsen/logrus" 16 "github.com/spf13/cobra" 17 corev1 "k8s.io/api/core/v1" 18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 "k8s.io/client-go/kubernetes" 20 "k8s.io/client-go/kubernetes/fake" 21 "k8s.io/client-go/rest" 22 "k8s.io/client-go/tools/clientcmd" 23 "k8s.io/utils/ptr" 24 25 cmdutil "github.com/argoproj/argo-cd/v3/cmd/util" 26 "github.com/argoproj/argo-cd/v3/common" 27 "github.com/argoproj/argo-cd/v3/controller/sharding" 28 argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient" 29 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 30 "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned" 31 "github.com/argoproj/argo-cd/v3/util/argo" 32 cacheutil "github.com/argoproj/argo-cd/v3/util/cache" 33 appstatecache "github.com/argoproj/argo-cd/v3/util/cache/appstate" 34 "github.com/argoproj/argo-cd/v3/util/cli" 35 "github.com/argoproj/argo-cd/v3/util/clusterauth" 36 "github.com/argoproj/argo-cd/v3/util/db" 37 "github.com/argoproj/argo-cd/v3/util/errors" 38 "github.com/argoproj/argo-cd/v3/util/glob" 39 kubeutil "github.com/argoproj/argo-cd/v3/util/kube" 40 "github.com/argoproj/argo-cd/v3/util/settings" 41 "github.com/argoproj/argo-cd/v3/util/text/label" 42 ) 43 44 func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientcmd.PathOptions) *cobra.Command { 45 command := &cobra.Command{ 46 Use: "cluster", 47 Short: "Manage clusters configuration", 48 Example: ` 49 #Generate declarative config for a cluster 50 argocd admin cluster generate-spec my-cluster -o yaml 51 52 #Generate a kubeconfig for a cluster named "my-cluster" and display it in the console 53 argocd admin cluster kubeconfig my-cluster 54 55 #Print information namespaces which Argo CD manages in each cluster 56 argocd admin cluster namespaces my-cluster `, 57 Run: func(c *cobra.Command, args []string) { 58 c.HelpFunc()(c, args) 59 }, 60 } 61 62 command.AddCommand(NewClusterConfig()) 63 command.AddCommand(NewGenClusterConfigCommand(pathOpts)) 64 command.AddCommand(NewClusterStatsCommand(clientOpts)) 65 command.AddCommand(NewClusterShardsCommand(clientOpts)) 66 namespacesCommand := NewClusterNamespacesCommand() 67 namespacesCommand.AddCommand(NewClusterEnableNamespacedMode()) 68 namespacesCommand.AddCommand(NewClusterDisableNamespacedMode()) 69 command.AddCommand(namespacesCommand) 70 71 return command 72 } 73 74 type ClusterWithInfo struct { 75 v1alpha1.Cluster 76 // Shard holds controller shard number that handles the cluster 77 Shard int 78 // Namespaces holds list of namespaces managed by Argo CD in the cluster 79 Namespaces []string 80 } 81 82 func loadClusters(ctx context.Context, kubeClient kubernetes.Interface, appClient versioned.Interface, replicas int, shardingAlgorithm string, namespace string, portForwardRedis bool, cacheSrc func() (*appstatecache.Cache, error), shard int, redisName string, redisHaProxyName string, redisCompressionStr string) ([]ClusterWithInfo, error) { 83 settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace) 84 85 argoDB := db.NewDB(namespace, settingsMgr, kubeClient) 86 clustersList, err := argoDB.ListClusters(ctx) 87 if err != nil { 88 return nil, err 89 } 90 appItems, err := appClient.ArgoprojV1alpha1().Applications(namespace).List(ctx, metav1.ListOptions{}) 91 if err != nil { 92 return nil, err 93 } 94 clusterShardingCache := sharding.NewClusterSharding(argoDB, shard, replicas, shardingAlgorithm) 95 clusterShardingCache.Init(clustersList, appItems) 96 clusterShards := clusterShardingCache.GetDistribution() 97 98 var cache *appstatecache.Cache 99 if portForwardRedis { 100 overrides := clientcmd.ConfigOverrides{} 101 redisHaProxyPodLabelSelector := common.LabelKeyAppName + "=" + redisHaProxyName 102 redisPodLabelSelector := common.LabelKeyAppName + "=" + redisName 103 port, err := kubeutil.PortForward(6379, namespace, &overrides, 104 redisHaProxyPodLabelSelector, redisPodLabelSelector) 105 if err != nil { 106 return nil, err 107 } 108 109 redisOptions := &redis.Options{Addr: fmt.Sprintf("localhost:%d", port)} 110 if err = common.SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClient, namespace, redisOptions); err != nil { 111 log.Warnf("Failed to fetch & set redis password for namespace %s: %v", namespace, err) 112 } 113 client := redis.NewClient(redisOptions) 114 compressionType, err := cacheutil.CompressionTypeFromString(redisCompressionStr) 115 if err != nil { 116 return nil, err 117 } 118 cache = appstatecache.NewCache(cacheutil.NewCache(cacheutil.NewRedisCache(client, time.Hour, compressionType)), time.Hour) 119 } else { 120 cache, err = cacheSrc() 121 if err != nil { 122 return nil, err 123 } 124 } 125 126 apps := appItems.Items 127 clusters := make([]ClusterWithInfo, len(clustersList.Items)) 128 129 batchSize := 10 130 batchesCount := int(math.Ceil(float64(len(clusters)) / float64(batchSize))) 131 for batchNum := 0; batchNum < batchesCount; batchNum++ { 132 batchStart := batchSize * batchNum 133 batchEnd := batchSize * (batchNum + 1) 134 if batchEnd > len(clustersList.Items) { 135 batchEnd = len(clustersList.Items) 136 } 137 batch := clustersList.Items[batchStart:batchEnd] 138 _ = kube.RunAllAsync(len(batch), func(i int) error { 139 clusterShard := 0 140 cluster := batch[i] 141 if replicas > 0 { 142 clusterShard = clusterShards[cluster.Server] 143 cluster.Shard = ptr.To(int64(clusterShard)) 144 log.Infof("Cluster with uid: %s will be processed by shard %d", cluster.ID, clusterShard) 145 } 146 if shard != -1 && clusterShard != shard { 147 return nil 148 } 149 nsSet := map[string]bool{} 150 for _, app := range apps { 151 destCluster, err := argo.GetDestinationCluster(ctx, app.Spec.Destination, argoDB) 152 if err != nil { 153 return fmt.Errorf("error validating application destination: %w", err) 154 } 155 if destCluster.Server == cluster.Server { 156 nsSet[app.Spec.Destination.Namespace] = true 157 } 158 } 159 var namespaces []string 160 for ns := range nsSet { 161 namespaces = append(namespaces, ns) 162 } 163 _ = cache.GetClusterInfo(cluster.Server, &cluster.Info) 164 clusters[batchStart+i] = ClusterWithInfo{cluster, clusterShard, namespaces} 165 return nil 166 }) 167 } 168 return clusters, nil 169 } 170 171 func getControllerReplicas(ctx context.Context, kubeClient *kubernetes.Clientset, namespace string, appControllerName string) (int, error) { 172 appControllerPodLabelSelector := common.LabelKeyAppName + "=" + appControllerName 173 controllerPods, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ 174 LabelSelector: appControllerPodLabelSelector, 175 }) 176 if err != nil { 177 return 0, err 178 } 179 return len(controllerPods.Items), nil 180 } 181 182 func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 183 var ( 184 shard int 185 replicas int 186 shardingAlgorithm string 187 clientConfig clientcmd.ClientConfig 188 cacheSrc func() (*appstatecache.Cache, error) 189 portForwardRedis bool 190 ) 191 command := cobra.Command{ 192 Use: "shards", 193 Short: "Print information about each controller shard and the estimated portion of Kubernetes resources it is responsible for.", 194 Run: func(cmd *cobra.Command, _ []string) { 195 ctx := cmd.Context() 196 197 log.SetLevel(log.WarnLevel) 198 199 clientCfg, err := clientConfig.ClientConfig() 200 errors.CheckError(err) 201 namespace, _, err := clientConfig.Namespace() 202 errors.CheckError(err) 203 kubeClient := kubernetes.NewForConfigOrDie(clientCfg) 204 appClient := versioned.NewForConfigOrDie(clientCfg) 205 206 if replicas == 0 { 207 replicas, err = getControllerReplicas(ctx, kubeClient, namespace, clientOpts.AppControllerName) 208 errors.CheckError(err) 209 } 210 if replicas == 0 { 211 return 212 } 213 clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, shardingAlgorithm, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, clientOpts.RedisCompression) 214 errors.CheckError(err) 215 if len(clusters) == 0 { 216 return 217 } 218 219 printStatsSummary(clusters) 220 }, 221 } 222 clientConfig = cli.AddKubectlFlagsToCmd(&command) 223 command.Flags().IntVar(&shard, "shard", -1, "Cluster shard filter") 224 command.Flags().IntVar(&replicas, "replicas", 0, "Application controller replicas count. Inferred from number of running controller pods if not specified") 225 command.Flags().StringVar(&shardingAlgorithm, "sharding-method", common.DefaultShardingAlgorithm, "Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin, consistent-hashing] ") 226 command.Flags().BoolVar(&portForwardRedis, "port-forward-redis", true, "Automatically port-forward ha proxy redis from current namespace?") 227 228 cacheSrc = appstatecache.AddCacheFlagsToCmd(&command) 229 230 // parse all added flags so far to get the redis-compression flag that was added by AddCacheFlagsToCmd() above 231 // we can ignore unchecked error here as the command will be parsed again and checked when command.Execute() is run later 232 //nolint:errcheck 233 command.ParseFlags(os.Args[1:]) 234 return &command 235 } 236 237 func printStatsSummary(clusters []ClusterWithInfo) { 238 totalResourcesCount := int64(0) 239 resourcesCountByShard := map[int]int64{} 240 for _, c := range clusters { 241 totalResourcesCount += c.Info.CacheInfo.ResourcesCount 242 resourcesCountByShard[c.Shard] += c.Info.CacheInfo.ResourcesCount 243 } 244 245 avgResourcesByShard := totalResourcesCount / int64(len(resourcesCountByShard)) 246 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 247 _, _ = fmt.Fprintf(w, "SHARD\tRESOURCES COUNT\n") 248 for shard := 0; shard < len(resourcesCountByShard); shard++ { 249 cnt := resourcesCountByShard[shard] 250 percent := (float64(cnt) / float64(avgResourcesByShard)) * 100.0 251 _, _ = fmt.Fprintf(w, "%d\t%s\n", shard, fmt.Sprintf("%d (%.0f%%)", cnt, percent)) 252 } 253 _ = w.Flush() 254 } 255 256 func runClusterNamespacesCommand(ctx context.Context, clientConfig clientcmd.ClientConfig, action func(appClient *versioned.Clientset, argoDB db.ArgoDB, clusters map[string][]string) error) error { 257 clientCfg, err := clientConfig.ClientConfig() 258 if err != nil { 259 return fmt.Errorf("error while creating client config: %w", err) 260 } 261 namespace, _, err := clientConfig.Namespace() 262 if err != nil { 263 return fmt.Errorf("error while getting namespace from client config: %w", err) 264 } 265 266 kubeClient := kubernetes.NewForConfigOrDie(clientCfg) 267 appClient := versioned.NewForConfigOrDie(clientCfg) 268 269 settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace) 270 argoDB := db.NewDB(namespace, settingsMgr, kubeClient) 271 clustersList, err := argoDB.ListClusters(ctx) 272 if err != nil { 273 return fmt.Errorf("error listing clusters: %w", err) 274 } 275 appItems, err := appClient.ArgoprojV1alpha1().Applications(namespace).List(ctx, metav1.ListOptions{}) 276 if err != nil { 277 return fmt.Errorf("error listing application: %w", err) 278 } 279 apps := appItems.Items 280 clusters := map[string][]string{} 281 for _, cluster := range clustersList.Items { 282 nsSet := map[string]bool{} 283 for _, app := range apps { 284 destCluster, err := argo.GetDestinationCluster(ctx, app.Spec.Destination, argoDB) 285 if err != nil { 286 return fmt.Errorf("error validating application destination: %w", err) 287 } 288 289 if destCluster.Server != cluster.Server { 290 continue 291 } 292 // Use namespaces of actually deployed resources, since some application use dummy target namespace 293 // If resources list is empty then use target namespace 294 if len(app.Status.Resources) != 0 { 295 for _, res := range app.Status.Resources { 296 if res.Namespace != "" { 297 nsSet[res.Namespace] = true 298 } 299 } 300 } else { 301 nsSet[app.Spec.Destination.Namespace] = true 302 } 303 } 304 var namespaces []string 305 for ns := range nsSet { 306 namespaces = append(namespaces, ns) 307 } 308 clusters[cluster.Server] = namespaces 309 } 310 return action(appClient, argoDB, clusters) 311 } 312 313 func NewClusterNamespacesCommand() *cobra.Command { 314 var clientConfig clientcmd.ClientConfig 315 command := cobra.Command{ 316 Use: "namespaces", 317 Short: "Print information namespaces which Argo CD manages in each cluster.", 318 Run: func(cmd *cobra.Command, _ []string) { 319 ctx := cmd.Context() 320 321 log.SetLevel(log.WarnLevel) 322 323 err := runClusterNamespacesCommand(ctx, clientConfig, func(_ *versioned.Clientset, _ db.ArgoDB, clusters map[string][]string) error { 324 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 325 _, _ = fmt.Fprintf(w, "CLUSTER\tNAMESPACES\n") 326 327 for cluster, namespaces := range clusters { 328 // print shortest namespace names first 329 sort.Slice(namespaces, func(i, j int) bool { 330 return len(namespaces[j]) > len(namespaces[i]) 331 }) 332 namespacesStr := "" 333 if len(namespaces) > 4 { 334 namespacesStr = fmt.Sprintf("%s (total %d)", strings.Join(namespaces[:4], ","), len(namespaces)) 335 } else { 336 namespacesStr = strings.Join(namespaces, ",") 337 } 338 339 _, _ = fmt.Fprintf(w, "%s\t%s\n", cluster, namespacesStr) 340 } 341 _ = w.Flush() 342 return nil 343 }) 344 errors.CheckError(err) 345 }, 346 } 347 clientConfig = cli.AddKubectlFlagsToCmd(&command) 348 return &command 349 } 350 351 func NewClusterEnableNamespacedMode() *cobra.Command { 352 var ( 353 clientConfig clientcmd.ClientConfig 354 dryRun bool 355 clusterResources bool 356 namespacesCount int 357 ) 358 command := cobra.Command{ 359 Use: "enable-namespaced-mode PATTERN", 360 Short: "Enable namespaced mode for clusters which name matches to the specified pattern.", 361 Run: func(cmd *cobra.Command, args []string) { 362 ctx := cmd.Context() 363 364 log.SetLevel(log.WarnLevel) 365 366 if len(args) == 0 { 367 cmd.HelpFunc()(cmd, args) 368 os.Exit(1) 369 } 370 pattern := args[0] 371 372 errors.CheckError(runClusterNamespacesCommand(ctx, clientConfig, func(_ *versioned.Clientset, argoDB db.ArgoDB, clusters map[string][]string) error { 373 for server, namespaces := range clusters { 374 if len(namespaces) == 0 || len(namespaces) > namespacesCount || !glob.Match(pattern, server) { 375 continue 376 } 377 378 cluster, err := argoDB.GetCluster(ctx, server) 379 if err != nil { 380 return fmt.Errorf("error getting cluster from server: %w", err) 381 } 382 cluster.Namespaces = namespaces 383 cluster.ClusterResources = clusterResources 384 fmt.Printf("Setting cluster %s namespaces to %v...", server, namespaces) 385 if !dryRun { 386 if _, err = argoDB.UpdateCluster(ctx, cluster); err != nil { 387 return fmt.Errorf("error updating cluster: %w", err) 388 } 389 fmt.Println("done") 390 } else { 391 fmt.Println("done (dry run)") 392 } 393 } 394 return nil 395 })) 396 }, 397 } 398 clientConfig = cli.AddKubectlFlagsToCmd(&command) 399 command.Flags().BoolVar(&dryRun, "dry-run", true, "Print what will be performed") 400 command.Flags().BoolVar(&clusterResources, "cluster-resources", false, "Indicates if cluster level resources should be managed.") 401 command.Flags().IntVar(&namespacesCount, "max-namespace-count", 0, "Max number of namespaces that cluster should managed managed namespaces is less or equal to specified count") 402 403 return &command 404 } 405 406 func NewClusterDisableNamespacedMode() *cobra.Command { 407 var ( 408 clientConfig clientcmd.ClientConfig 409 dryRun bool 410 ) 411 command := cobra.Command{ 412 Use: "disable-namespaced-mode PATTERN", 413 Short: "Disable namespaced mode for clusters which name matches to the specified pattern.", 414 Run: func(cmd *cobra.Command, args []string) { 415 ctx := cmd.Context() 416 417 log.SetLevel(log.WarnLevel) 418 419 if len(args) == 0 { 420 cmd.HelpFunc()(cmd, args) 421 os.Exit(1) 422 } 423 424 pattern := args[0] 425 426 errors.CheckError(runClusterNamespacesCommand(ctx, clientConfig, func(_ *versioned.Clientset, argoDB db.ArgoDB, clusters map[string][]string) error { 427 for server := range clusters { 428 if !glob.Match(pattern, server) { 429 continue 430 } 431 432 cluster, err := argoDB.GetCluster(ctx, server) 433 if err != nil { 434 return fmt.Errorf("error getting cluster from server: %w", err) 435 } 436 437 if len(cluster.Namespaces) == 0 { 438 continue 439 } 440 441 cluster.Namespaces = nil 442 fmt.Printf("Disabling namespaced mode for cluster %s...", server) 443 if !dryRun { 444 if _, err = argoDB.UpdateCluster(ctx, cluster); err != nil { 445 return fmt.Errorf("error updating cluster: %w", err) 446 } 447 fmt.Println("done") 448 } else { 449 fmt.Println("done (dry run)") 450 } 451 } 452 return nil 453 })) 454 }, 455 } 456 clientConfig = cli.AddKubectlFlagsToCmd(&command) 457 command.Flags().BoolVar(&dryRun, "dry-run", true, "Print what will be performed") 458 return &command 459 } 460 461 func NewClusterStatsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { 462 var ( 463 shard int 464 replicas int 465 shardingAlgorithm string 466 clientConfig clientcmd.ClientConfig 467 cacheSrc func() (*appstatecache.Cache, error) 468 portForwardRedis bool 469 ) 470 command := cobra.Command{ 471 Use: "stats", 472 Short: "Prints information cluster statistics and inferred shard number", 473 Example: ` 474 #Display stats and shards for clusters 475 argocd admin cluster stats 476 477 #Display Cluster Statistics for a Specific Shard 478 argocd admin cluster stats --shard=1 479 480 #In a multi-cluster environment to print stats for a specific cluster say(target-cluster) 481 argocd admin cluster stats target-cluster`, 482 Run: func(cmd *cobra.Command, _ []string) { 483 ctx := cmd.Context() 484 485 log.SetLevel(log.WarnLevel) 486 487 clientCfg, err := clientConfig.ClientConfig() 488 errors.CheckError(err) 489 namespace, _, err := clientConfig.Namespace() 490 errors.CheckError(err) 491 492 kubeClient := kubernetes.NewForConfigOrDie(clientCfg) 493 appClient := versioned.NewForConfigOrDie(clientCfg) 494 if replicas == 0 { 495 replicas, err = getControllerReplicas(ctx, kubeClient, namespace, clientOpts.AppControllerName) 496 errors.CheckError(err) 497 } 498 clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, shardingAlgorithm, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, clientOpts.RedisCompression) 499 errors.CheckError(err) 500 501 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 502 _, _ = fmt.Fprintf(w, "SERVER\tSHARD\tCONNECTION\tNAMESPACES COUNT\tAPPS COUNT\tRESOURCES COUNT\n") 503 for _, cluster := range clusters { 504 _, _ = fmt.Fprintf(w, "%s\t%d\t%s\t%d\t%d\t%d\n", cluster.Server, cluster.Shard, cluster.Info.ConnectionState.Status, len(cluster.Namespaces), cluster.Info.ApplicationsCount, cluster.Info.CacheInfo.ResourcesCount) 505 } 506 _ = w.Flush() 507 }, 508 } 509 clientConfig = cli.AddKubectlFlagsToCmd(&command) 510 command.Flags().IntVar(&shard, "shard", -1, "Cluster shard filter") 511 command.Flags().IntVar(&replicas, "replicas", 0, "Application controller replicas count. Inferred from number of running controller pods if not specified") 512 command.Flags().StringVar(&shardingAlgorithm, "sharding-method", common.DefaultShardingAlgorithm, "Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin, consistent-hashing] ") 513 command.Flags().BoolVar(&portForwardRedis, "port-forward-redis", true, "Automatically port-forward ha proxy redis from current namespace?") 514 cacheSrc = appstatecache.AddCacheFlagsToCmd(&command) 515 516 // parse all added flags so far to get the redis-compression flag that was added by AddCacheFlagsToCmd() above 517 // we can ignore unchecked error here as the command will be parsed again and checked when command.Execute() is run later 518 //nolint:errcheck 519 command.ParseFlags(os.Args[1:]) 520 return &command 521 } 522 523 // NewClusterConfig returns a new instance of `argocd admin kubeconfig` command 524 func NewClusterConfig() *cobra.Command { 525 var clientConfig clientcmd.ClientConfig 526 command := &cobra.Command{ 527 Use: "kubeconfig CLUSTER_URL OUTPUT_PATH", 528 Short: "Generates kubeconfig for the specified cluster", 529 DisableAutoGenTag: true, 530 Example: ` 531 #Generate a kubeconfig for a cluster named "my-cluster" on console 532 argocd admin cluster kubeconfig my-cluster 533 534 #Listing available kubeconfigs for clusters managed by argocd 535 argocd admin cluster kubeconfig 536 537 #Removing a specific kubeconfig file 538 argocd admin cluster kubeconfig my-cluster --delete 539 540 #Generate a Kubeconfig for a Cluster with TLS Verification Disabled 541 argocd admin cluster kubeconfig https://cluster-api-url:6443 /path/to/output/kubeconfig.yaml --insecure-skip-tls-verify`, 542 Run: func(c *cobra.Command, args []string) { 543 ctx := c.Context() 544 545 if len(args) != 2 { 546 c.HelpFunc()(c, args) 547 os.Exit(1) 548 } 549 serverURL := args[0] 550 output := args[1] 551 conf, err := clientConfig.ClientConfig() 552 errors.CheckError(err) 553 namespace, _, err := clientConfig.Namespace() 554 errors.CheckError(err) 555 kubeclientset, err := kubernetes.NewForConfig(conf) 556 errors.CheckError(err) 557 558 cluster, err := db.NewDB(namespace, settings.NewSettingsManager(ctx, kubeclientset, namespace), kubeclientset).GetCluster(ctx, serverURL) 559 errors.CheckError(err) 560 rawConfig, err := cluster.RawRestConfig() 561 errors.CheckError(err) 562 err = kube.WriteKubeConfig(rawConfig, namespace, output) 563 errors.CheckError(err) 564 }, 565 } 566 clientConfig = cli.AddKubectlFlagsToCmd(command) 567 return command 568 } 569 570 func NewGenClusterConfigCommand(pathOpts *clientcmd.PathOptions) *cobra.Command { 571 var ( 572 clusterOpts cmdutil.ClusterOptions 573 bearerToken string 574 generateToken bool 575 outputFormat string 576 labels []string 577 annotations []string 578 ) 579 command := &cobra.Command{ 580 Use: "generate-spec CONTEXT", 581 Short: "Generate declarative config for a cluster", 582 Run: func(c *cobra.Command, args []string) { 583 ctx := c.Context() 584 585 log.SetLevel(log.WarnLevel) 586 var configAccess clientcmd.ConfigAccess = pathOpts 587 if len(args) == 0 { 588 log.Error("Choose a context name from:") 589 cmdutil.PrintKubeContexts(configAccess) 590 os.Exit(1) 591 } 592 cfgAccess, err := configAccess.GetStartingConfig() 593 errors.CheckError(err) 594 contextName := args[0] 595 clstContext := cfgAccess.Contexts[contextName] 596 if clstContext == nil { 597 log.Fatalf("Context %s does not exist in kubeconfig", contextName) 598 return 599 } 600 601 if clusterOpts.InCluster && clusterOpts.ClusterEndpoint != "" { 602 log.Fatal("Can only use one of --in-cluster or --cluster-endpoint") 603 return 604 } 605 606 overrides := clientcmd.ConfigOverrides{ 607 Context: *clstContext, 608 } 609 clientConfig := clientcmd.NewDefaultClientConfig(*cfgAccess, &overrides) 610 conf, err := clientConfig.ClientConfig() 611 errors.CheckError(err) 612 // Seed a minimal in-memory Argo CD environment so settings retrieval succeeds 613 argoCDCM := &corev1.ConfigMap{ 614 TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"}, 615 ObjectMeta: metav1.ObjectMeta{ 616 Name: common.ArgoCDConfigMapName, 617 Namespace: ArgoCDNamespace, 618 Labels: map[string]string{ 619 "app.kubernetes.io/part-of": "argocd", 620 }, 621 }, 622 } 623 argoCDSecret := &corev1.Secret{ 624 TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"}, 625 ObjectMeta: metav1.ObjectMeta{ 626 Name: common.ArgoCDSecretName, 627 Namespace: ArgoCDNamespace, 628 Labels: map[string]string{ 629 "app.kubernetes.io/part-of": "argocd", 630 }, 631 }, 632 Data: map[string][]byte{ 633 "server.secretkey": []byte("test"), 634 }, 635 } 636 kubeClientset := fake.NewClientset(argoCDCM, argoCDSecret) 637 638 var awsAuthConf *v1alpha1.AWSAuthConfig 639 var execProviderConf *v1alpha1.ExecProviderConfig 640 switch { 641 case clusterOpts.AwsClusterName != "": 642 awsAuthConf = &v1alpha1.AWSAuthConfig{ 643 ClusterName: clusterOpts.AwsClusterName, 644 RoleARN: clusterOpts.AwsRoleArn, 645 Profile: clusterOpts.AwsProfile, 646 } 647 case clusterOpts.ExecProviderCommand != "": 648 execProviderConf = &v1alpha1.ExecProviderConfig{ 649 Command: clusterOpts.ExecProviderCommand, 650 Args: clusterOpts.ExecProviderArgs, 651 Env: clusterOpts.ExecProviderEnv, 652 APIVersion: clusterOpts.ExecProviderAPIVersion, 653 InstallHint: clusterOpts.ExecProviderInstallHint, 654 } 655 case generateToken: 656 bearerToken, err = GenerateToken(clusterOpts, conf) 657 errors.CheckError(err) 658 case bearerToken == "": 659 bearerToken = "bearer-token" 660 } 661 if clusterOpts.Name != "" { 662 contextName = clusterOpts.Name 663 } 664 665 labelsMap, err := label.Parse(labels) 666 errors.CheckError(err) 667 annotationsMap, err := label.Parse(annotations) 668 errors.CheckError(err) 669 670 clst := cmdutil.NewCluster(contextName, clusterOpts.Namespaces, clusterOpts.ClusterResources, conf, bearerToken, awsAuthConf, execProviderConf, labelsMap, annotationsMap) 671 if clusterOpts.InClusterEndpoint() { 672 clst.Server = v1alpha1.KubernetesInternalAPIServerAddr 673 } 674 if clusterOpts.ClusterEndpoint == string(cmdutil.KubePublicEndpoint) { 675 // Ignore `kube-public` cluster endpoints, since this command is intended to run without invoking any network connections. 676 log.Warn("kube-public cluster endpoints are not supported. Falling back to the endpoint listed in the kubconfig context.") 677 } 678 if clusterOpts.Shard >= 0 { 679 clst.Shard = &clusterOpts.Shard 680 } 681 682 settingsMgr := settings.NewSettingsManager(ctx, kubeClientset, ArgoCDNamespace) 683 argoDB := db.NewDB(ArgoCDNamespace, settingsMgr, kubeClientset) 684 685 _, err = argoDB.CreateCluster(ctx, clst) 686 errors.CheckError(err) 687 688 secName, err := db.URIToSecretName("cluster", clst.Server) 689 errors.CheckError(err) 690 691 secret, err := kubeClientset.CoreV1().Secrets(ArgoCDNamespace).Get(ctx, secName, metav1.GetOptions{}) 692 errors.CheckError(err) 693 694 errors.CheckError(PrintResources(outputFormat, os.Stdout, secret)) 695 }, 696 } 697 command.PersistentFlags().StringVar(&pathOpts.LoadingRules.ExplicitPath, pathOpts.ExplicitFileFlag, pathOpts.LoadingRules.ExplicitPath, "use a particular kubeconfig file") 698 command.Flags().StringVar(&bearerToken, "bearer-token", "", "Authentication token that should be used to access K8S API server") 699 command.Flags().BoolVar(&generateToken, "generate-bearer-token", false, "Generate authentication token that should be used to access K8S API server") 700 command.Flags().StringVar(&clusterOpts.ServiceAccount, "service-account", "argocd-manager", fmt.Sprintf("System namespace service account to use for kubernetes resource management. If not set then default %q SA will be used", clusterauth.ArgoCDManagerServiceAccount)) 701 command.Flags().StringVar(&clusterOpts.SystemNamespace, "system-namespace", common.DefaultSystemNamespace, "Use different system namespace") 702 command.Flags().StringVarP(&outputFormat, "output", "o", "yaml", "Output format. One of: json|yaml") 703 command.Flags().StringArrayVar(&labels, "label", nil, "Set metadata labels (e.g. --label key=value)") 704 command.Flags().StringArrayVar(&annotations, "annotation", nil, "Set metadata annotations (e.g. --annotation key=value)") 705 cmdutil.AddClusterFlags(command, &clusterOpts) 706 return command 707 } 708 709 func GenerateToken(clusterOpts cmdutil.ClusterOptions, conf *rest.Config) (string, error) { 710 clientset, err := kubernetes.NewForConfig(conf) 711 errors.CheckError(err) 712 713 bearerToken, err := clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount, common.BearerTokenTimeout) 714 if err != nil { 715 return "", err 716 } 717 return bearerToken, nil 718 }