diff --git a/capten/common-pkg/plugins/argocd/clusters.go b/capten/common-pkg/plugins/argocd/clusters.go index 7f16d256..bd904a9e 100644 --- a/capten/common-pkg/plugins/argocd/clusters.go +++ b/capten/common-pkg/plugins/argocd/clusters.go @@ -2,45 +2,130 @@ package argocd import ( "context" + "encoding/base64" + "fmt" + "strings" "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/util/io" + "gopkg.in/yaml.v2" + k8sapi "k8s.io/client-go/tools/clientcmd/api" ) -func (a *ArgoCDClient) CreateCluster(ctx context.Context, clusterReq *Cluster) (*v1alpha1.Cluster, error) { - conn, appClient, err := a.client.NewClusterClient() +const ( + CredEntityName = "k8s" + CredIdentifier = "kubeconfig" +) + +func parseKubeconfigString(kubeconfigString string) (*k8sapi.Config, error) { + config := &k8sapi.Config{} + err := yaml.Unmarshal([]byte(kubeconfigString), config) if err != nil { return nil, err } + return config, nil +} + +func (a *ArgoCDClient) CreateOrUpdateCluster(ctx context.Context, clusterName, kubeconfigData string) error { + kubeConfig, err := parseKubeconfigString(kubeconfigData) + if err != nil { + return err + } + + clusterData, ok := kubeConfig.Clusters[clusterName] + if !ok { + return fmt.Errorf("cluster %s not found in kubeconfig", clusterName) + } + caData, err := base64.StdEncoding.DecodeString(string(clusterData.CertificateAuthorityData)) + if err != nil { + return err + } + + var clusterCAuthInfo *k8sapi.AuthInfo + for _, authInfo := range kubeConfig.AuthInfos { + clusterCAuthInfo = authInfo + break + } + + if clusterCAuthInfo == nil { + return fmt.Errorf("auth info not found for cluster") + } + + var clientCertData []byte + if len(clusterCAuthInfo.ClientCertificateData) != 0 { + clientCertData, err = base64.StdEncoding.DecodeString(string(clusterCAuthInfo.ClientCertificateData)) + if err != nil { + return err + } + } + + a.logger.Infof("Cluster create or update request for cluster %s", clusterName) + var clientKeyData []byte + if len(clusterCAuthInfo.ClientKeyData) != 0 { + clientKeyData, err = base64.StdEncoding.DecodeString(string(clusterCAuthInfo.ClientKeyData)) + if err != nil { + return err + } + } + + conn, appClient, err := a.client.NewClusterClient() + if err != nil { + return fmt.Errorf("failed to create argocd cluster client, %v", err) + } defer io.Close(conn) - resp, err := appClient.Create(ctx, &cluster.ClusterCreateRequest{ + var update bool + _, err = appClient.Create(ctx, &cluster.ClusterCreateRequest{ Cluster: &v1alpha1.Cluster{ - Server: clusterReq.Server, - Name: clusterReq.Name, + Server: clusterData.Server, + Name: clusterName, Config: v1alpha1.ClusterConfig{ - Username: clusterReq.Config.Username, - Password: clusterReq.Config.Password, + BearerToken: clusterCAuthInfo.Token, + Username: clusterCAuthInfo.Username, + Password: clusterCAuthInfo.Password, TLSClientConfig: v1alpha1.TLSClientConfig{ - Insecure: clusterReq.Config.Insecure, - ServerName: clusterReq.Config.ServerName, - CertData: clusterReq.Config.CertData, - KeyData: clusterReq.Config.KeyData, - CAData: clusterReq.Config.CAData, + ServerName: clusterData.Server, + CAData: caData, + CertData: clientCertData, + KeyData: clientKeyData, }, }, - ConnectionState: v1alpha1.ConnectionState{ - Status: clusterReq.ConnectionState.Status, - Message: clusterReq.ConnectionState.Message, - }, - Namespaces: clusterReq.Namespaces, }, }) if err != nil { - return nil, err + if strings.Contains(err.Error(), "already exists") { + update = true + } + return fmt.Errorf("failed to create cluster %s, %v", clusterName, err) } - return resp, nil + + if update { + _, err := appClient.Update(ctx, &cluster.ClusterUpdateRequest{ + Cluster: &v1alpha1.Cluster{ + Server: clusterData.Server, + Name: clusterName, + Config: v1alpha1.ClusterConfig{ + BearerToken: clusterCAuthInfo.Token, + Username: clusterCAuthInfo.Username, + Password: clusterCAuthInfo.Password, + TLSClientConfig: v1alpha1.TLSClientConfig{ + ServerName: clusterData.Server, + CAData: caData, + CertData: clientCertData, + KeyData: clientKeyData, + }, + }, + }, + }) + if err != nil { + return fmt.Errorf("failed to update cluster %s, %v", clusterName, err) + } + a.logger.Infof("Cluster %s created", clusterName) + } else { + a.logger.Infof("Cluster %s updated", clusterName) + } + return nil } func (a *ArgoCDClient) DeleteCluster(ctx context.Context, clusterURL string) (*cluster.ClusterResponse, error) { @@ -69,7 +154,7 @@ func (a *ArgoCDClient) GetCluster(ctx context.Context, clusterURL string) (*v1al } defer io.Close(conn) - repository, err := appClient.Get(ctx, &cluster.ClusterQuery{ + cluster, err := appClient.Get(ctx, &cluster.ClusterQuery{ Id: &cluster.ClusterID{ Value: clusterURL, }, @@ -78,7 +163,7 @@ func (a *ArgoCDClient) GetCluster(ctx context.Context, clusterURL string) (*v1al return nil, err } - return repository, nil + return cluster, nil } func (a *ArgoCDClient) ListClusters(ctx context.Context) (*v1alpha1.ClusterList, error) { diff --git a/capten/common-pkg/plugins/argocd/config.go b/capten/common-pkg/plugins/argocd/config.go index f915ef24..72a4f972 100644 --- a/capten/common-pkg/plugins/argocd/config.go +++ b/capten/common-pkg/plugins/argocd/config.go @@ -1,6 +1,8 @@ package argocd -import "time" +import ( + "time" +) type Configuration struct { ServiceURL string `envconfig:"ARGOCD_SERVICE_URL" default:"argo-cd-argocd-server.argo-cd.svc.cluster.local"` @@ -26,38 +28,3 @@ type Repository struct { InsecureIgnoreHostKey bool `json:"InsecureIgnoreHostKey"` ConnectionState ConnectionState `json:"ConnectionState"` } - -type TLSClientConfig struct { - // Insecure specifies that the server should be accessed without verifying the TLS certificate. For testing only. - Insecure bool `json:"insecure" ` - // ServerName is passed to the server for SNI and is used in the client to check server - // certificates against. If ServerName is empty, the hostname used to contact the - // server is used. - ServerName string `json:"serverName,omitempty" ` - // CertData holds PEM-encoded bytes (typically read from a client certificate file). - // CertData takes precedence over CertFile - CertData []byte `json:"certData,omitempty" ` - // KeyData holds PEM-encoded bytes (typically read from a client certificate key file). - // KeyData takes precedence over KeyFile - KeyData []byte `json:"keyData,omitempty" ` - // CAData holds PEM-encoded bytes (typically read from a root certificates bundle). - // CAData takes precedence over CAFile - CAData []byte `json:"caData,omitempty" ` -} - -type ClusterConfig struct { - // Server requires Basic authentication - Username string `json:"username,omitempty" ` - Password string `json:"password,omitempty"` - - // TLSClientConfig contains settings to enable transport layer security - TLSClientConfig `json:"tlsClientConfig"` -} - -type Cluster struct { - Server string `json:"server"` - Name string `json:"name"` - Config ClusterConfig `json:"config"` - ConnectionState ConnectionState `json:"ConnectionState"` - Namespaces []string `json:"namespaces,omitempty"` -}