mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
716 lines
26 KiB
Go
716 lines
26 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"text/tabwriter"
|
|
"time"
|
|
|
|
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube"
|
|
"github.com/redis/go-redis/v9"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
"k8s.io/utils/ptr"
|
|
|
|
cmdutil "github.com/argoproj/argo-cd/v3/cmd/util"
|
|
"github.com/argoproj/argo-cd/v3/common"
|
|
"github.com/argoproj/argo-cd/v3/controller/sharding"
|
|
argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient"
|
|
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
|
"github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned"
|
|
"github.com/argoproj/argo-cd/v3/util/argo"
|
|
cacheutil "github.com/argoproj/argo-cd/v3/util/cache"
|
|
appstatecache "github.com/argoproj/argo-cd/v3/util/cache/appstate"
|
|
"github.com/argoproj/argo-cd/v3/util/cli"
|
|
"github.com/argoproj/argo-cd/v3/util/clusterauth"
|
|
"github.com/argoproj/argo-cd/v3/util/db"
|
|
"github.com/argoproj/argo-cd/v3/util/errors"
|
|
"github.com/argoproj/argo-cd/v3/util/glob"
|
|
kubeutil "github.com/argoproj/argo-cd/v3/util/kube"
|
|
"github.com/argoproj/argo-cd/v3/util/settings"
|
|
"github.com/argoproj/argo-cd/v3/util/text/label"
|
|
)
|
|
|
|
func NewClusterCommand(clientOpts *argocdclient.ClientOptions, pathOpts *clientcmd.PathOptions) *cobra.Command {
|
|
command := &cobra.Command{
|
|
Use: "cluster",
|
|
Short: "Manage clusters configuration",
|
|
Example: `
|
|
#Generate declarative config for a cluster
|
|
argocd admin cluster generate-spec my-cluster -o yaml
|
|
|
|
#Generate a kubeconfig for a cluster named "my-cluster" and display it in the console
|
|
argocd admin cluster kubeconfig my-cluster
|
|
|
|
#Print information namespaces which Argo CD manages in each cluster
|
|
argocd admin cluster namespaces my-cluster `,
|
|
Run: func(c *cobra.Command, args []string) {
|
|
c.HelpFunc()(c, args)
|
|
},
|
|
}
|
|
|
|
command.AddCommand(NewClusterConfig())
|
|
command.AddCommand(NewGenClusterConfigCommand(pathOpts))
|
|
command.AddCommand(NewClusterStatsCommand(clientOpts))
|
|
command.AddCommand(NewClusterShardsCommand(clientOpts))
|
|
namespacesCommand := NewClusterNamespacesCommand()
|
|
namespacesCommand.AddCommand(NewClusterEnableNamespacedMode())
|
|
namespacesCommand.AddCommand(NewClusterDisableNamespacedMode())
|
|
command.AddCommand(namespacesCommand)
|
|
|
|
return command
|
|
}
|
|
|
|
type ClusterWithInfo struct {
|
|
v1alpha1.Cluster
|
|
// Shard holds controller shard number that handles the cluster
|
|
Shard int
|
|
// Namespaces holds list of namespaces managed by Argo CD in the cluster
|
|
Namespaces []string
|
|
}
|
|
|
|
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) {
|
|
settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace)
|
|
|
|
argoDB := db.NewDB(namespace, settingsMgr, kubeClient)
|
|
clustersList, err := argoDB.ListClusters(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
appItems, err := appClient.ArgoprojV1alpha1().Applications(namespace).List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clusterShardingCache := sharding.NewClusterSharding(argoDB, shard, replicas, shardingAlgorithm)
|
|
clusterShardingCache.Init(clustersList, appItems)
|
|
clusterShards := clusterShardingCache.GetDistribution()
|
|
|
|
var cache *appstatecache.Cache
|
|
if portForwardRedis {
|
|
overrides := clientcmd.ConfigOverrides{}
|
|
redisHaProxyPodLabelSelector := common.LabelKeyAppName + "=" + redisHaProxyName
|
|
redisPodLabelSelector := common.LabelKeyAppName + "=" + redisName
|
|
port, err := kubeutil.PortForward(6379, namespace, &overrides,
|
|
redisHaProxyPodLabelSelector, redisPodLabelSelector)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
redisOptions := &redis.Options{Addr: fmt.Sprintf("localhost:%d", port)}
|
|
if err = common.SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClient, namespace, redisOptions); err != nil {
|
|
log.Warnf("Failed to fetch & set redis password for namespace %s: %v", namespace, err)
|
|
}
|
|
client := redis.NewClient(redisOptions)
|
|
compressionType, err := cacheutil.CompressionTypeFromString(redisCompressionStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cache = appstatecache.NewCache(cacheutil.NewCache(cacheutil.NewRedisCache(client, time.Hour, compressionType)), time.Hour)
|
|
} else {
|
|
cache, err = cacheSrc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
apps := appItems.Items
|
|
clusters := make([]ClusterWithInfo, len(clustersList.Items))
|
|
|
|
batchSize := 10
|
|
batchesCount := int(math.Ceil(float64(len(clusters)) / float64(batchSize)))
|
|
for batchNum := range batchesCount {
|
|
batchStart := batchSize * batchNum
|
|
batchEnd := min((batchSize * (batchNum + 1)), len(clustersList.Items))
|
|
batch := clustersList.Items[batchStart:batchEnd]
|
|
_ = kube.RunAllAsync(len(batch), func(i int) error {
|
|
clusterShard := 0
|
|
cluster := batch[i]
|
|
if replicas > 0 {
|
|
clusterShard = clusterShards[cluster.Server]
|
|
cluster.Shard = ptr.To(int64(clusterShard))
|
|
log.Infof("Cluster with uid: %s will be processed by shard %d", cluster.ID, clusterShard)
|
|
}
|
|
if shard != -1 && clusterShard != shard {
|
|
return nil
|
|
}
|
|
nsSet := map[string]bool{}
|
|
for _, app := range apps {
|
|
destCluster, err := argo.GetDestinationCluster(ctx, app.Spec.Destination, argoDB)
|
|
if err != nil {
|
|
return fmt.Errorf("error validating application destination: %w", err)
|
|
}
|
|
if destCluster.Server == cluster.Server {
|
|
nsSet[app.Spec.Destination.Namespace] = true
|
|
}
|
|
}
|
|
var namespaces []string
|
|
for ns := range nsSet {
|
|
namespaces = append(namespaces, ns)
|
|
}
|
|
_ = cache.GetClusterInfo(cluster.Server, &cluster.Info)
|
|
clusters[batchStart+i] = ClusterWithInfo{cluster, clusterShard, namespaces}
|
|
return nil
|
|
})
|
|
}
|
|
return clusters, nil
|
|
}
|
|
|
|
func getControllerReplicas(ctx context.Context, kubeClient *kubernetes.Clientset, namespace string, appControllerName string) (int, error) {
|
|
appControllerPodLabelSelector := common.LabelKeyAppName + "=" + appControllerName
|
|
controllerPods, err := kubeClient.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
|
|
LabelSelector: appControllerPodLabelSelector,
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return len(controllerPods.Items), nil
|
|
}
|
|
|
|
func NewClusterShardsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
var (
|
|
shard int
|
|
replicas int
|
|
shardingAlgorithm string
|
|
clientConfig clientcmd.ClientConfig
|
|
cacheSrc func() (*appstatecache.Cache, error)
|
|
portForwardRedis bool
|
|
)
|
|
command := cobra.Command{
|
|
Use: "shards",
|
|
Short: "Print information about each controller shard and the estimated portion of Kubernetes resources it is responsible for.",
|
|
Run: func(cmd *cobra.Command, _ []string) {
|
|
ctx := cmd.Context()
|
|
|
|
log.SetLevel(log.WarnLevel)
|
|
|
|
clientCfg, err := clientConfig.ClientConfig()
|
|
errors.CheckError(err)
|
|
namespace, _, err := clientConfig.Namespace()
|
|
errors.CheckError(err)
|
|
kubeClient := kubernetes.NewForConfigOrDie(clientCfg)
|
|
appClient := versioned.NewForConfigOrDie(clientCfg)
|
|
|
|
if replicas == 0 {
|
|
replicas, err = getControllerReplicas(ctx, kubeClient, namespace, clientOpts.AppControllerName)
|
|
errors.CheckError(err)
|
|
}
|
|
if replicas == 0 {
|
|
return
|
|
}
|
|
clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, shardingAlgorithm, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, clientOpts.RedisCompression)
|
|
errors.CheckError(err)
|
|
if len(clusters) == 0 {
|
|
return
|
|
}
|
|
|
|
printStatsSummary(clusters)
|
|
},
|
|
}
|
|
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
|
command.Flags().IntVar(&shard, "shard", -1, "Cluster shard filter")
|
|
command.Flags().IntVar(&replicas, "replicas", 0, "Application controller replicas count. Inferred from number of running controller pods if not specified")
|
|
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", common.DefaultShardingAlgorithm, "Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin, consistent-hashing] ")
|
|
command.Flags().BoolVar(&portForwardRedis, "port-forward-redis", true, "Automatically port-forward ha proxy redis from current namespace?")
|
|
|
|
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command)
|
|
|
|
// parse all added flags so far to get the redis-compression flag that was added by AddCacheFlagsToCmd() above
|
|
// we can ignore unchecked error here as the command will be parsed again and checked when command.Execute() is run later
|
|
//nolint:errcheck
|
|
command.ParseFlags(os.Args[1:])
|
|
return &command
|
|
}
|
|
|
|
func printStatsSummary(clusters []ClusterWithInfo) {
|
|
totalResourcesCount := int64(0)
|
|
resourcesCountByShard := map[int]int64{}
|
|
for _, c := range clusters {
|
|
totalResourcesCount += c.Info.CacheInfo.ResourcesCount
|
|
resourcesCountByShard[c.Shard] += c.Info.CacheInfo.ResourcesCount
|
|
}
|
|
|
|
avgResourcesByShard := totalResourcesCount / int64(len(resourcesCountByShard))
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
_, _ = fmt.Fprintf(w, "SHARD\tRESOURCES COUNT\n")
|
|
for shard := 0; shard < len(resourcesCountByShard); shard++ {
|
|
cnt := resourcesCountByShard[shard]
|
|
percent := (float64(cnt) / float64(avgResourcesByShard)) * 100.0
|
|
_, _ = fmt.Fprintf(w, "%d\t%s\n", shard, fmt.Sprintf("%d (%.0f%%)", cnt, percent))
|
|
}
|
|
_ = w.Flush()
|
|
}
|
|
|
|
func runClusterNamespacesCommand(ctx context.Context, clientConfig clientcmd.ClientConfig, action func(appClient *versioned.Clientset, argoDB db.ArgoDB, clusters map[string][]string) error) error {
|
|
clientCfg, err := clientConfig.ClientConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("error while creating client config: %w", err)
|
|
}
|
|
namespace, _, err := clientConfig.Namespace()
|
|
if err != nil {
|
|
return fmt.Errorf("error while getting namespace from client config: %w", err)
|
|
}
|
|
|
|
kubeClient := kubernetes.NewForConfigOrDie(clientCfg)
|
|
appClient := versioned.NewForConfigOrDie(clientCfg)
|
|
|
|
settingsMgr := settings.NewSettingsManager(ctx, kubeClient, namespace)
|
|
argoDB := db.NewDB(namespace, settingsMgr, kubeClient)
|
|
clustersList, err := argoDB.ListClusters(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("error listing clusters: %w", err)
|
|
}
|
|
appItems, err := appClient.ArgoprojV1alpha1().Applications(namespace).List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
return fmt.Errorf("error listing application: %w", err)
|
|
}
|
|
apps := appItems.Items
|
|
clusters := map[string][]string{}
|
|
for _, cluster := range clustersList.Items {
|
|
nsSet := map[string]bool{}
|
|
for _, app := range apps {
|
|
destCluster, err := argo.GetDestinationCluster(ctx, app.Spec.Destination, argoDB)
|
|
if err != nil {
|
|
return fmt.Errorf("error validating application destination: %w", err)
|
|
}
|
|
|
|
if destCluster.Server != cluster.Server {
|
|
continue
|
|
}
|
|
// Use namespaces of actually deployed resources, since some application use dummy target namespace
|
|
// If resources list is empty then use target namespace
|
|
if len(app.Status.Resources) != 0 {
|
|
for _, res := range app.Status.Resources {
|
|
if res.Namespace != "" {
|
|
nsSet[res.Namespace] = true
|
|
}
|
|
}
|
|
} else {
|
|
nsSet[app.Spec.Destination.Namespace] = true
|
|
}
|
|
}
|
|
var namespaces []string
|
|
for ns := range nsSet {
|
|
namespaces = append(namespaces, ns)
|
|
}
|
|
clusters[cluster.Server] = namespaces
|
|
}
|
|
return action(appClient, argoDB, clusters)
|
|
}
|
|
|
|
func NewClusterNamespacesCommand() *cobra.Command {
|
|
var clientConfig clientcmd.ClientConfig
|
|
command := cobra.Command{
|
|
Use: "namespaces",
|
|
Short: "Print information namespaces which Argo CD manages in each cluster.",
|
|
Run: func(cmd *cobra.Command, _ []string) {
|
|
ctx := cmd.Context()
|
|
|
|
log.SetLevel(log.WarnLevel)
|
|
|
|
err := runClusterNamespacesCommand(ctx, clientConfig, func(_ *versioned.Clientset, _ db.ArgoDB, clusters map[string][]string) error {
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
_, _ = fmt.Fprintf(w, "CLUSTER\tNAMESPACES\n")
|
|
|
|
for cluster, namespaces := range clusters {
|
|
// print shortest namespace names first
|
|
sort.Slice(namespaces, func(i, j int) bool {
|
|
return len(namespaces[j]) > len(namespaces[i])
|
|
})
|
|
namespacesStr := ""
|
|
if len(namespaces) > 4 {
|
|
namespacesStr = fmt.Sprintf("%s (total %d)", strings.Join(namespaces[:4], ","), len(namespaces))
|
|
} else {
|
|
namespacesStr = strings.Join(namespaces, ",")
|
|
}
|
|
|
|
_, _ = fmt.Fprintf(w, "%s\t%s\n", cluster, namespacesStr)
|
|
}
|
|
_ = w.Flush()
|
|
return nil
|
|
})
|
|
errors.CheckError(err)
|
|
},
|
|
}
|
|
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
|
return &command
|
|
}
|
|
|
|
func NewClusterEnableNamespacedMode() *cobra.Command {
|
|
var (
|
|
clientConfig clientcmd.ClientConfig
|
|
dryRun bool
|
|
clusterResources bool
|
|
namespacesCount int
|
|
)
|
|
command := cobra.Command{
|
|
Use: "enable-namespaced-mode PATTERN",
|
|
Short: "Enable namespaced mode for clusters which name matches to the specified pattern.",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
ctx := cmd.Context()
|
|
|
|
log.SetLevel(log.WarnLevel)
|
|
|
|
if len(args) == 0 {
|
|
cmd.HelpFunc()(cmd, args)
|
|
os.Exit(1)
|
|
}
|
|
pattern := args[0]
|
|
|
|
errors.CheckError(runClusterNamespacesCommand(ctx, clientConfig, func(_ *versioned.Clientset, argoDB db.ArgoDB, clusters map[string][]string) error {
|
|
for server, namespaces := range clusters {
|
|
if len(namespaces) == 0 || len(namespaces) > namespacesCount || !glob.Match(pattern, server) {
|
|
continue
|
|
}
|
|
|
|
cluster, err := argoDB.GetCluster(ctx, server)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting cluster from server: %w", err)
|
|
}
|
|
cluster.Namespaces = namespaces
|
|
cluster.ClusterResources = clusterResources
|
|
fmt.Printf("Setting cluster %s namespaces to %v...", server, namespaces)
|
|
if !dryRun {
|
|
if _, err = argoDB.UpdateCluster(ctx, cluster); err != nil {
|
|
return fmt.Errorf("error updating cluster: %w", err)
|
|
}
|
|
fmt.Println("done")
|
|
} else {
|
|
fmt.Println("done (dry run)")
|
|
}
|
|
}
|
|
return nil
|
|
}))
|
|
},
|
|
}
|
|
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
|
command.Flags().BoolVar(&dryRun, "dry-run", true, "Print what will be performed")
|
|
command.Flags().BoolVar(&clusterResources, "cluster-resources", false, "Indicates if cluster level resources should be managed.")
|
|
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")
|
|
|
|
return &command
|
|
}
|
|
|
|
func NewClusterDisableNamespacedMode() *cobra.Command {
|
|
var (
|
|
clientConfig clientcmd.ClientConfig
|
|
dryRun bool
|
|
)
|
|
command := cobra.Command{
|
|
Use: "disable-namespaced-mode PATTERN",
|
|
Short: "Disable namespaced mode for clusters which name matches to the specified pattern.",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
ctx := cmd.Context()
|
|
|
|
log.SetLevel(log.WarnLevel)
|
|
|
|
if len(args) == 0 {
|
|
cmd.HelpFunc()(cmd, args)
|
|
os.Exit(1)
|
|
}
|
|
|
|
pattern := args[0]
|
|
|
|
errors.CheckError(runClusterNamespacesCommand(ctx, clientConfig, func(_ *versioned.Clientset, argoDB db.ArgoDB, clusters map[string][]string) error {
|
|
for server := range clusters {
|
|
if !glob.Match(pattern, server) {
|
|
continue
|
|
}
|
|
|
|
cluster, err := argoDB.GetCluster(ctx, server)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting cluster from server: %w", err)
|
|
}
|
|
|
|
if len(cluster.Namespaces) == 0 {
|
|
continue
|
|
}
|
|
|
|
cluster.Namespaces = nil
|
|
fmt.Printf("Disabling namespaced mode for cluster %s...", server)
|
|
if !dryRun {
|
|
if _, err = argoDB.UpdateCluster(ctx, cluster); err != nil {
|
|
return fmt.Errorf("error updating cluster: %w", err)
|
|
}
|
|
fmt.Println("done")
|
|
} else {
|
|
fmt.Println("done (dry run)")
|
|
}
|
|
}
|
|
return nil
|
|
}))
|
|
},
|
|
}
|
|
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
|
command.Flags().BoolVar(&dryRun, "dry-run", true, "Print what will be performed")
|
|
return &command
|
|
}
|
|
|
|
func NewClusterStatsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
var (
|
|
shard int
|
|
replicas int
|
|
shardingAlgorithm string
|
|
clientConfig clientcmd.ClientConfig
|
|
cacheSrc func() (*appstatecache.Cache, error)
|
|
portForwardRedis bool
|
|
)
|
|
command := cobra.Command{
|
|
Use: "stats",
|
|
Short: "Prints information cluster statistics and inferred shard number",
|
|
Example: `
|
|
#Display stats and shards for clusters
|
|
argocd admin cluster stats
|
|
|
|
#Display Cluster Statistics for a Specific Shard
|
|
argocd admin cluster stats --shard=1
|
|
|
|
#In a multi-cluster environment to print stats for a specific cluster say(target-cluster)
|
|
argocd admin cluster stats target-cluster`,
|
|
Run: func(cmd *cobra.Command, _ []string) {
|
|
ctx := cmd.Context()
|
|
|
|
log.SetLevel(log.WarnLevel)
|
|
|
|
clientCfg, err := clientConfig.ClientConfig()
|
|
errors.CheckError(err)
|
|
namespace, _, err := clientConfig.Namespace()
|
|
errors.CheckError(err)
|
|
|
|
kubeClient := kubernetes.NewForConfigOrDie(clientCfg)
|
|
appClient := versioned.NewForConfigOrDie(clientCfg)
|
|
if replicas == 0 {
|
|
replicas, err = getControllerReplicas(ctx, kubeClient, namespace, clientOpts.AppControllerName)
|
|
errors.CheckError(err)
|
|
}
|
|
clusters, err := loadClusters(ctx, kubeClient, appClient, replicas, shardingAlgorithm, namespace, portForwardRedis, cacheSrc, shard, clientOpts.RedisName, clientOpts.RedisHaProxyName, clientOpts.RedisCompression)
|
|
errors.CheckError(err)
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
_, _ = fmt.Fprintf(w, "SERVER\tSHARD\tCONNECTION\tNAMESPACES COUNT\tAPPS COUNT\tRESOURCES COUNT\n")
|
|
for _, cluster := range clusters {
|
|
_, _ = 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)
|
|
}
|
|
_ = w.Flush()
|
|
},
|
|
}
|
|
clientConfig = cli.AddKubectlFlagsToCmd(&command)
|
|
command.Flags().IntVar(&shard, "shard", -1, "Cluster shard filter")
|
|
command.Flags().IntVar(&replicas, "replicas", 0, "Application controller replicas count. Inferred from number of running controller pods if not specified")
|
|
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", common.DefaultShardingAlgorithm, "Sharding method. Defaults: legacy. Supported sharding methods are : [legacy, round-robin, consistent-hashing] ")
|
|
command.Flags().BoolVar(&portForwardRedis, "port-forward-redis", true, "Automatically port-forward ha proxy redis from current namespace?")
|
|
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command)
|
|
|
|
// parse all added flags so far to get the redis-compression flag that was added by AddCacheFlagsToCmd() above
|
|
// we can ignore unchecked error here as the command will be parsed again and checked when command.Execute() is run later
|
|
//nolint:errcheck
|
|
command.ParseFlags(os.Args[1:])
|
|
return &command
|
|
}
|
|
|
|
// NewClusterConfig returns a new instance of `argocd admin kubeconfig` command
|
|
func NewClusterConfig() *cobra.Command {
|
|
var clientConfig clientcmd.ClientConfig
|
|
command := &cobra.Command{
|
|
Use: "kubeconfig CLUSTER_URL OUTPUT_PATH",
|
|
Short: "Generates kubeconfig for the specified cluster",
|
|
DisableAutoGenTag: true,
|
|
Example: `
|
|
#Generate a kubeconfig for a cluster named "my-cluster" on console
|
|
argocd admin cluster kubeconfig my-cluster
|
|
|
|
#Listing available kubeconfigs for clusters managed by argocd
|
|
argocd admin cluster kubeconfig
|
|
|
|
#Removing a specific kubeconfig file
|
|
argocd admin cluster kubeconfig my-cluster --delete
|
|
|
|
#Generate a Kubeconfig for a Cluster with TLS Verification Disabled
|
|
argocd admin cluster kubeconfig https://cluster-api-url:6443 /path/to/output/kubeconfig.yaml --insecure-skip-tls-verify`,
|
|
Run: func(c *cobra.Command, args []string) {
|
|
ctx := c.Context()
|
|
|
|
if len(args) != 2 {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
}
|
|
serverURL := args[0]
|
|
output := args[1]
|
|
conf, err := clientConfig.ClientConfig()
|
|
errors.CheckError(err)
|
|
namespace, _, err := clientConfig.Namespace()
|
|
errors.CheckError(err)
|
|
kubeclientset, err := kubernetes.NewForConfig(conf)
|
|
errors.CheckError(err)
|
|
|
|
cluster, err := db.NewDB(namespace, settings.NewSettingsManager(ctx, kubeclientset, namespace), kubeclientset).GetCluster(ctx, serverURL)
|
|
errors.CheckError(err)
|
|
rawConfig, err := cluster.RawRestConfig()
|
|
errors.CheckError(err)
|
|
err = kube.WriteKubeConfig(rawConfig, namespace, output)
|
|
errors.CheckError(err)
|
|
},
|
|
}
|
|
clientConfig = cli.AddKubectlFlagsToCmd(command)
|
|
return command
|
|
}
|
|
|
|
func NewGenClusterConfigCommand(pathOpts *clientcmd.PathOptions) *cobra.Command {
|
|
var (
|
|
clusterOpts cmdutil.ClusterOptions
|
|
bearerToken string
|
|
generateToken bool
|
|
outputFormat string
|
|
labels []string
|
|
annotations []string
|
|
)
|
|
command := &cobra.Command{
|
|
Use: "generate-spec CONTEXT",
|
|
Short: "Generate declarative config for a cluster",
|
|
Run: func(c *cobra.Command, args []string) {
|
|
ctx := c.Context()
|
|
|
|
log.SetLevel(log.WarnLevel)
|
|
var configAccess clientcmd.ConfigAccess = pathOpts
|
|
if len(args) == 0 {
|
|
log.Error("Choose a context name from:")
|
|
cmdutil.PrintKubeContexts(configAccess)
|
|
os.Exit(1)
|
|
}
|
|
cfgAccess, err := configAccess.GetStartingConfig()
|
|
errors.CheckError(err)
|
|
contextName := args[0]
|
|
clstContext := cfgAccess.Contexts[contextName]
|
|
if clstContext == nil {
|
|
log.Fatalf("Context %s does not exist in kubeconfig", contextName)
|
|
return
|
|
}
|
|
|
|
if clusterOpts.InCluster && clusterOpts.ClusterEndpoint != "" {
|
|
log.Fatal("Can only use one of --in-cluster or --cluster-endpoint")
|
|
return
|
|
}
|
|
|
|
overrides := clientcmd.ConfigOverrides{
|
|
Context: *clstContext,
|
|
}
|
|
clientConfig := clientcmd.NewDefaultClientConfig(*cfgAccess, &overrides)
|
|
conf, err := clientConfig.ClientConfig()
|
|
errors.CheckError(err)
|
|
// Seed a minimal in-memory Argo CD environment so settings retrieval succeeds
|
|
argoCDCM := &corev1.ConfigMap{
|
|
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: ArgoCDNamespace,
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
}
|
|
argoCDSecret := &corev1.Secret{
|
|
TypeMeta: metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: ArgoCDNamespace,
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server.secretkey": []byte("test"),
|
|
},
|
|
}
|
|
kubeClientset := fake.NewClientset(argoCDCM, argoCDSecret)
|
|
|
|
var awsAuthConf *v1alpha1.AWSAuthConfig
|
|
var execProviderConf *v1alpha1.ExecProviderConfig
|
|
switch {
|
|
case clusterOpts.AwsClusterName != "":
|
|
awsAuthConf = &v1alpha1.AWSAuthConfig{
|
|
ClusterName: clusterOpts.AwsClusterName,
|
|
RoleARN: clusterOpts.AwsRoleArn,
|
|
Profile: clusterOpts.AwsProfile,
|
|
}
|
|
case clusterOpts.ExecProviderCommand != "":
|
|
execProviderConf = &v1alpha1.ExecProviderConfig{
|
|
Command: clusterOpts.ExecProviderCommand,
|
|
Args: clusterOpts.ExecProviderArgs,
|
|
Env: clusterOpts.ExecProviderEnv,
|
|
APIVersion: clusterOpts.ExecProviderAPIVersion,
|
|
InstallHint: clusterOpts.ExecProviderInstallHint,
|
|
}
|
|
case generateToken:
|
|
bearerToken, err = GenerateToken(clusterOpts, conf)
|
|
errors.CheckError(err)
|
|
case bearerToken == "":
|
|
bearerToken = "bearer-token"
|
|
}
|
|
if clusterOpts.Name != "" {
|
|
contextName = clusterOpts.Name
|
|
}
|
|
|
|
labelsMap, err := label.Parse(labels)
|
|
errors.CheckError(err)
|
|
annotationsMap, err := label.Parse(annotations)
|
|
errors.CheckError(err)
|
|
|
|
clst := cmdutil.NewCluster(contextName, clusterOpts.Namespaces, clusterOpts.ClusterResources, conf, bearerToken, awsAuthConf, execProviderConf, labelsMap, annotationsMap)
|
|
if clusterOpts.InClusterEndpoint() {
|
|
clst.Server = v1alpha1.KubernetesInternalAPIServerAddr
|
|
}
|
|
if clusterOpts.ClusterEndpoint == string(cmdutil.KubePublicEndpoint) {
|
|
// Ignore `kube-public` cluster endpoints, since this command is intended to run without invoking any network connections.
|
|
log.Warn("kube-public cluster endpoints are not supported. Falling back to the endpoint listed in the kubconfig context.")
|
|
}
|
|
if clusterOpts.Shard >= 0 {
|
|
clst.Shard = &clusterOpts.Shard
|
|
}
|
|
|
|
settingsMgr := settings.NewSettingsManager(ctx, kubeClientset, ArgoCDNamespace)
|
|
argoDB := db.NewDB(ArgoCDNamespace, settingsMgr, kubeClientset)
|
|
|
|
_, err = argoDB.CreateCluster(ctx, clst)
|
|
errors.CheckError(err)
|
|
|
|
secName, err := db.URIToSecretName("cluster", clst.Server)
|
|
errors.CheckError(err)
|
|
|
|
secret, err := kubeClientset.CoreV1().Secrets(ArgoCDNamespace).Get(ctx, secName, metav1.GetOptions{})
|
|
errors.CheckError(err)
|
|
|
|
errors.CheckError(PrintResources(outputFormat, os.Stdout, secret))
|
|
},
|
|
}
|
|
command.PersistentFlags().StringVar(&pathOpts.LoadingRules.ExplicitPath, pathOpts.ExplicitFileFlag, pathOpts.LoadingRules.ExplicitPath, "use a particular kubeconfig file")
|
|
command.Flags().StringVar(&bearerToken, "bearer-token", "", "Authentication token that should be used to access K8S API server")
|
|
command.Flags().BoolVar(&generateToken, "generate-bearer-token", false, "Generate authentication token that should be used to access K8S API server")
|
|
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))
|
|
command.Flags().StringVar(&clusterOpts.SystemNamespace, "system-namespace", common.DefaultSystemNamespace, "Use different system namespace")
|
|
command.Flags().StringVarP(&outputFormat, "output", "o", "yaml", "Output format. One of: json|yaml")
|
|
command.Flags().StringArrayVar(&labels, "label", nil, "Set metadata labels (e.g. --label key=value)")
|
|
command.Flags().StringArrayVar(&annotations, "annotation", nil, "Set metadata annotations (e.g. --annotation key=value)")
|
|
cmdutil.AddClusterFlags(command, &clusterOpts)
|
|
return command
|
|
}
|
|
|
|
func GenerateToken(clusterOpts cmdutil.ClusterOptions, conf *rest.Config) (string, error) {
|
|
clientset, err := kubernetes.NewForConfig(conf)
|
|
errors.CheckError(err)
|
|
|
|
bearerToken, err := clusterauth.GetServiceAccountBearerToken(clientset, clusterOpts.SystemNamespace, clusterOpts.ServiceAccount, common.BearerTokenTimeout)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return bearerToken, nil
|
|
}
|