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  }