github.com/argoproj/argo-cd/v3@v3.2.1/cmd/util/cluster.go (about)

     1  package util
     2  
     3  import (
     4  	"context"
     5  	stderrors "errors"
     6  	"fmt"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  	"text/tabwriter"
    11  
    12  	"github.com/spf13/cobra"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/client-go/kubernetes"
    15  	"k8s.io/client-go/rest"
    16  	"k8s.io/client-go/tools/clientcmd"
    17  	clientcmdapiv1 "k8s.io/client-go/tools/clientcmd/api/v1"
    18  	"sigs.k8s.io/yaml"
    19  
    20  	argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    21  	"github.com/argoproj/argo-cd/v3/util/errors"
    22  )
    23  
    24  type ClusterEndpoint string
    25  
    26  const (
    27  	KubeConfigEndpoint   ClusterEndpoint = "kubeconfig"
    28  	KubePublicEndpoint   ClusterEndpoint = "kube-public"
    29  	KubeInternalEndpoint ClusterEndpoint = "internal"
    30  )
    31  
    32  func PrintKubeContexts(ca clientcmd.ConfigAccess) {
    33  	config, err := ca.GetStartingConfig()
    34  	errors.CheckError(err)
    35  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
    36  	defer func() { _ = w.Flush() }()
    37  	columnNames := []string{"CURRENT", "NAME", "CLUSTER", "SERVER"}
    38  	_, err = fmt.Fprintf(w, "%s\n", strings.Join(columnNames, "\t"))
    39  	errors.CheckError(err)
    40  
    41  	// sort names so output is deterministic
    42  	contextNames := make([]string, 0)
    43  	for name := range config.Contexts {
    44  		contextNames = append(contextNames, name)
    45  	}
    46  	sort.Strings(contextNames)
    47  
    48  	if config.Clusters == nil {
    49  		return
    50  	}
    51  
    52  	for _, name := range contextNames {
    53  		// ignore malformed kube config entries
    54  		context := config.Contexts[name]
    55  		if context == nil {
    56  			continue
    57  		}
    58  		cluster := config.Clusters[context.Cluster]
    59  		if cluster == nil {
    60  			continue
    61  		}
    62  		prefix := " "
    63  		if config.CurrentContext == name {
    64  			prefix = "*"
    65  		}
    66  		_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", prefix, name, context.Cluster, cluster.Server)
    67  		errors.CheckError(err)
    68  	}
    69  }
    70  
    71  func NewCluster(name string, namespaces []string, clusterResources bool, conf *rest.Config, managerBearerToken string, awsAuthConf *argoappv1.AWSAuthConfig, execProviderConf *argoappv1.ExecProviderConfig, labels, annotations map[string]string) *argoappv1.Cluster {
    72  	tlsClientConfig := argoappv1.TLSClientConfig{
    73  		Insecure:   conf.Insecure,
    74  		ServerName: conf.ServerName,
    75  		CAData:     conf.CAData,
    76  		CertData:   conf.CertData,
    77  		KeyData:    conf.KeyData,
    78  	}
    79  	if len(conf.CAData) == 0 && conf.CAFile != "" {
    80  		data, err := os.ReadFile(conf.CAFile)
    81  		errors.CheckError(err)
    82  		tlsClientConfig.CAData = data
    83  	}
    84  	if len(conf.CertData) == 0 && conf.CertFile != "" {
    85  		data, err := os.ReadFile(conf.CertFile)
    86  		errors.CheckError(err)
    87  		tlsClientConfig.CertData = data
    88  	}
    89  	if len(conf.KeyData) == 0 && conf.KeyFile != "" {
    90  		data, err := os.ReadFile(conf.KeyFile)
    91  		errors.CheckError(err)
    92  		tlsClientConfig.KeyData = data
    93  	}
    94  
    95  	clst := argoappv1.Cluster{
    96  		Server:           conf.Host,
    97  		Name:             name,
    98  		Namespaces:       namespaces,
    99  		ClusterResources: clusterResources,
   100  		Config: argoappv1.ClusterConfig{
   101  			TLSClientConfig:    tlsClientConfig,
   102  			AWSAuthConfig:      awsAuthConf,
   103  			ExecProviderConfig: execProviderConf,
   104  			DisableCompression: conf.DisableCompression,
   105  		},
   106  		Labels:      labels,
   107  		Annotations: annotations,
   108  	}
   109  	// it's a tradeoff to get proxy url from rest config
   110  	// more detail: https://github.com/kubernetes/kubernetes/pull/81443
   111  	if conf.Proxy != nil {
   112  		if url, err := conf.Proxy(nil); err == nil {
   113  			clst.Config.ProxyUrl = url.String()
   114  		}
   115  	}
   116  	// Bearer token will preferentially be used for auth if present,
   117  	// Even in presence of key/cert credentials
   118  	// So set bearer token only if the key/cert data is absent
   119  	if len(tlsClientConfig.CertData) == 0 || len(tlsClientConfig.KeyData) == 0 {
   120  		clst.Config.BearerToken = managerBearerToken
   121  	}
   122  
   123  	return &clst
   124  }
   125  
   126  // GetKubePublicEndpoint returns the kubernetes apiserver endpoint and certificate authority data as published
   127  // in the kube-public.
   128  func GetKubePublicEndpoint(client kubernetes.Interface) (string, []byte, error) {
   129  	clusterInfo, err := client.CoreV1().ConfigMaps("kube-public").Get(context.TODO(), "cluster-info", metav1.GetOptions{})
   130  	if err != nil {
   131  		return "", nil, err
   132  	}
   133  	kubeconfig, ok := clusterInfo.Data["kubeconfig"]
   134  	if !ok {
   135  		return "", nil, stderrors.New("cluster-info does not contain a public kubeconfig")
   136  	}
   137  	// Parse Kubeconfig and get server address
   138  	config := &clientcmdapiv1.Config{}
   139  	err = yaml.Unmarshal([]byte(kubeconfig), config)
   140  	if err != nil {
   141  		return "", nil, fmt.Errorf("failed to parse cluster-info kubeconfig: %w", err)
   142  	}
   143  	if len(config.Clusters) == 0 {
   144  		return "", nil, stderrors.New("cluster-info kubeconfig does not have any clusters")
   145  	}
   146  
   147  	endpoint := config.Clusters[0].Cluster.Server
   148  	certificateAuthorityData := config.Clusters[0].Cluster.CertificateAuthorityData
   149  	return endpoint, certificateAuthorityData, nil
   150  }
   151  
   152  type ClusterOptions struct {
   153  	InCluster               bool
   154  	Upsert                  bool
   155  	ServiceAccount          string
   156  	AwsRoleArn              string
   157  	AwsProfile              string
   158  	AwsClusterName          string
   159  	SystemNamespace         string
   160  	Namespaces              []string
   161  	ClusterResources        bool
   162  	Name                    string
   163  	Project                 string
   164  	Shard                   int64
   165  	ExecProviderCommand     string
   166  	ExecProviderArgs        []string
   167  	ExecProviderEnv         map[string]string
   168  	ExecProviderAPIVersion  string
   169  	ExecProviderInstallHint string
   170  	ClusterEndpoint         string
   171  	DisableCompression      bool
   172  	ProxyUrl                string //nolint:revive //FIXME(var-naming)
   173  }
   174  
   175  // InClusterEndpoint returns true if ArgoCD should reference the in-cluster
   176  // endpoint when registering the target cluster.
   177  func (o ClusterOptions) InClusterEndpoint() bool {
   178  	return o.InCluster || o.ClusterEndpoint == string(KubeInternalEndpoint)
   179  }
   180  
   181  func AddClusterFlags(command *cobra.Command, opts *ClusterOptions) {
   182  	command.Flags().BoolVar(&opts.InCluster, "in-cluster", false, "Indicates Argo CD resides inside this cluster and should connect using the internal k8s hostname (kubernetes.default.svc)")
   183  	command.Flags().StringVar(&opts.AwsClusterName, "aws-cluster-name", "", "AWS Cluster name if set then aws cli eks token command will be used to access cluster")
   184  	command.Flags().StringVar(&opts.AwsRoleArn, "aws-role-arn", "", "Optional AWS role arn. If set then AWS IAM Authenticator assumes a role to perform cluster operations instead of the default AWS credential provider chain.")
   185  	command.Flags().StringVar(&opts.AwsProfile, "aws-profile", "", "Optional AWS profile. If set then AWS IAM Authenticator uses this profile to perform cluster operations instead of the default AWS credential provider chain.")
   186  	command.Flags().StringArrayVar(&opts.Namespaces, "namespace", nil, "List of namespaces which are allowed to manage")
   187  	command.Flags().BoolVar(&opts.ClusterResources, "cluster-resources", false, "Indicates if cluster level resources should be managed. The setting is used only if list of managed namespaces is not empty.")
   188  	command.Flags().StringVar(&opts.Name, "name", "", "Overwrite the cluster name")
   189  	command.Flags().StringVar(&opts.Project, "project", "", "project of the cluster")
   190  	command.Flags().Int64Var(&opts.Shard, "shard", -1, "Cluster shard number; inferred from hostname if not set")
   191  	command.Flags().StringVar(&opts.ExecProviderCommand, "exec-command", "", "Command to run to provide client credentials to the cluster. You may need to build a custom ArgoCD image to ensure the command is available at runtime.")
   192  	command.Flags().StringArrayVar(&opts.ExecProviderArgs, "exec-command-args", nil, "Arguments to supply to the --exec-command executable")
   193  	command.Flags().StringToStringVar(&opts.ExecProviderEnv, "exec-command-env", nil, "Environment vars to set when running the --exec-command executable")
   194  	command.Flags().StringVar(&opts.ExecProviderAPIVersion, "exec-command-api-version", "", "Preferred input version of the ExecInfo for the --exec-command executable")
   195  	command.Flags().StringVar(&opts.ExecProviderInstallHint, "exec-command-install-hint", "", "Text shown to the user when the --exec-command executable doesn't seem to be present")
   196  	command.Flags().StringVar(&opts.ClusterEndpoint, "cluster-endpoint", "", "Cluster endpoint to use. Can be one of the following: 'kubeconfig', 'kube-public', or 'internal'.")
   197  	command.Flags().BoolVar(&opts.DisableCompression, "disable-compression", false, "Bypasses automatic GZip compression requests to the server")
   198  }