diff --git a/capten/agent/internal/api/plugin_crossplane_project_apis.go b/capten/agent/internal/api/plugin_crossplane_project_apis.go index 10fe0877..bab17fbc 100644 --- a/capten/agent/internal/api/plugin_crossplane_project_apis.go +++ b/capten/agent/internal/api/plugin_crossplane_project_apis.go @@ -165,7 +165,8 @@ func (a *Agent) configureCrossplaneGitRepo(req *model.CrossplaneProject, provide VaultCredIdentifier: req.GitProjectId, CrossplaneProviders: providers} wd := workers.NewConfig(a.tc, a.log) - wkfId, err := wd.SendAsyncEvent(context.TODO(), &captenmodel.ConfigureParameters{Resource: crossplaneConfigUseCase}, ci) + wkfId, err := wd.SendAsyncEvent(context.TODO(), + &captenmodel.ConfigureParameters{Resource: crossplaneConfigUseCase, Action: model.CrossPlaneProjectSync}, ci) if err != nil { req.Status = string(model.CrossplaneProjectConfigurationFailed) req.WorkflowId = "NA" diff --git a/capten/agent/internal/crossplane/cluster_claims.go b/capten/agent/internal/crossplane/cluster_claims.go index cd0676cf..54123f5d 100644 --- a/capten/agent/internal/crossplane/cluster_claims.go +++ b/capten/agent/internal/crossplane/cluster_claims.go @@ -4,10 +4,13 @@ import ( "context" "encoding/json" "fmt" + "strings" "github.com/google/uuid" "github.com/intelops/go-common/logging" captenstore "github.com/kube-tarian/kad/capten/agent/internal/capten-store" + "github.com/kube-tarian/kad/capten/agent/internal/temporalclient" + "github.com/kube-tarian/kad/capten/agent/internal/workers" "github.com/kube-tarian/kad/capten/agent/internal/pb/captenpluginspb" @@ -19,7 +22,10 @@ import ( ) var ( - readyStatusType = "Ready" + readyStatusType = "ready" + nodePoolStatusType = "nodepool" + controlPlaneStatusType = "controlplane" + clusterNotReadyStatus = "NotReady" clusterReadyStatus = "Ready" readyStatusValue = "True" @@ -37,15 +43,25 @@ var ( type ClusterClaimSyncHandler struct { log logging.Logger + tc *temporalclient.Client dbStore *captenstore.Store } -func NewClusterClaimSyncHandler(log logging.Logger, dbStore *captenstore.Store) *ClusterClaimSyncHandler { - return &ClusterClaimSyncHandler{log: log, dbStore: dbStore} +func NewClusterClaimSyncHandler(log logging.Logger, dbStore *captenstore.Store) (*ClusterClaimSyncHandler, error) { + tc, err := temporalclient.NewClient(log) + if err != nil { + return nil, err + } + + return &ClusterClaimSyncHandler{log: log, dbStore: dbStore, tc: tc}, nil } func registerK8SClusterClaimWatcher(log logging.Logger, dbStore *captenstore.Store, dynamicClient dynamic.Interface) error { - return k8s.RegisterDynamicInformers(NewClusterClaimSyncHandler(log, dbStore), dynamicClient, cgvk) + obj, err := NewClusterClaimSyncHandler(log, dbStore) + if err != nil { + return err + } + return k8s.RegisterDynamicInformers(obj, dynamicClient, cgvk) } func getClusterClaimObj(obj any) (*model.ClusterClaim, error) { @@ -151,55 +167,67 @@ func (h *ClusterClaimSyncHandler) updateManagedClusters(clusterCliams []model.Cl for _, clusterCliam := range clusterCliams { h.log.Infof("processing cluster claim %s", clusterCliam.Metadata.Name) - for _, status := range clusterCliam.Status.Conditions { - if status.Type != readyStatusType { - continue - } + nodePoolStatus, controlPlaneStatus, readyStatus := getClusterClaimStatus(clusterCliam.Status.Conditions) + h.log.Infof("cluster claim %s status: %s-%s-%s", clusterCliam.Metadata.Name, nodePoolStatus, controlPlaneStatus, readyStatus) - managedCluster := &captenpluginspb.ManagedCluster{} - managedCluster.ClusterName = clusterCliam.Metadata.Name + if !(strings.EqualFold(nodePoolStatus, "active") && strings.EqualFold(controlPlaneStatus, "active")) { + h.log.Infof("cluster %s is not created", clusterCliam.Metadata.Name) + return nil + } - clusterObj, ok := clusters[managedCluster.ClusterName] - if !ok { - managedCluster.Id = uuid.New().String() - } else { - h.log.Infof("found existing managed clusterId %s, updating", clusterObj.Id) - managedCluster.Id = clusterObj.Id - managedCluster.ClusterDeployStatus = clusterObj.ClusterDeployStatus - } + managedCluster := &captenpluginspb.ManagedCluster{} + managedCluster.ClusterName = clusterCliam.Metadata.Name - if status.Status == readyStatusValue { - secretName := fmt.Sprintf(clusterSecretName, clusterCliam.Spec.Id) - resp, err := k8sclient.GetSecretData(clusterCliam.Metadata.Namespace, secretName) - if err != nil { - h.log.Errorf("failed to get secret %s/%s, %v", clusterCliam.Metadata.Namespace, secretName, err) - continue - } - - clusterEndpoint := resp.Data[k8sEndpoint] - managedCluster.ClusterEndpoint = clusterEndpoint - cred := map[string]string{} - cred[kubeConfig] = resp.Data[kubeConfig] - cred[k8sClusterCA] = resp.Data[k8sClusterCA] - cred[k8sEndpoint] = clusterEndpoint - - err = credential.PutGenericCredential(context.TODO(), managedClusterEntityName, managedCluster.Id, cred) - if err != nil { - h.log.Errorf("failed to store credential for %s, %v", managedCluster.Id, err) - continue - } - - managedCluster.ClusterDeployStatus = clusterReadyStatus - } else { - managedCluster.ClusterDeployStatus = clusterNotReadyStatus - } + clusterObj, ok := clusters[managedCluster.ClusterName] + if !ok { + managedCluster.Id = uuid.New().String() + } else { + h.log.Infof("found existing managed clusterId %s, updating", clusterObj.Id) + managedCluster.Id = clusterObj.Id + managedCluster.ClusterDeployStatus = clusterObj.ClusterDeployStatus + } + + if strings.EqualFold(readyStatus, readyStatusValue) { + managedCluster.ClusterDeployStatus = clusterReadyStatus + } else { + managedCluster.ClusterDeployStatus = clusterNotReadyStatus + } - err = h.dbStore.UpsertManagedCluster(managedCluster) + secretName := fmt.Sprintf(clusterSecretName, clusterCliam.Spec.Id) + resp, err := k8sclient.GetSecretData(clusterCliam.Metadata.Namespace, secretName) + if err != nil { + h.log.Errorf("failed to get secret %s/%s, %v", clusterCliam.Metadata.Namespace, secretName, err) + continue + } + + clusterEndpoint := resp.Data[k8sEndpoint] + managedCluster.ClusterEndpoint = clusterEndpoint + cred := map[string]string{} + cred[kubeConfig] = resp.Data[kubeConfig] + cred[k8sClusterCA] = resp.Data[k8sClusterCA] + cred[k8sEndpoint] = clusterEndpoint + + err = credential.PutGenericCredential(context.TODO(), managedClusterEntityName, managedCluster.Id, cred) + if err != nil { + h.log.Errorf("failed to store credential for %s, %v", managedCluster.Id, err) + continue + } + + err = h.dbStore.UpsertManagedCluster(managedCluster) + if err != nil { + h.log.Info("failed to update information to db, %v", err) + continue + } + h.log.Infof("updated the cluster claim %s with status %s", managedCluster.ClusterName, managedCluster.ClusterDeployStatus) + + if managedCluster.ClusterDeployStatus == clusterReadyStatus { + // call config-worker. + err = h.triggerClusterUpdates(clusterCliam.Spec.Id, managedCluster.Id) if err != nil { - h.log.Info("failed to update information to db, %v", err) + h.log.Info("failed to trigger cluster update workflow, %v", err) continue } - h.log.Infof("updated the cluster claim %s with status %s", managedCluster.ClusterName, managedCluster.ClusterDeployStatus) + h.log.Infof("triggered cluster update workflow for cluster %s", managedCluster.ClusterName) } } return nil @@ -217,3 +245,29 @@ func (h *ClusterClaimSyncHandler) getManagedClusters() (map[string]*captenplugin } return clusterEndpointMap, nil } + +func (h *ClusterClaimSyncHandler) triggerClusterUpdates(clusterName, managedClusterID string) error { + proj, err := h.dbStore.GetCrossplaneProject() + if err != nil { + return err + } + + ci := model.CrossplaneClusterUpdate{RepoURL: proj.GitProjectUrl, GitProjectId: proj.GitProjectId, Name: clusterName, ManagedClusterId: managedClusterID} + wd := workers.NewConfig(h.tc, h.log) + _, err = wd.SendEvent(context.TODO(), &model.ConfigureParameters{Resource: model.CrossPlaneResource, Action: model.CrossPlaneClusterUpdate}, ci) + return err +} + +func getClusterClaimStatus(conditions []model.ClusterClaimCondition) (nodePoolStatus, controlPlaneStatus, readyStatus string) { + for _, condition := range conditions { + switch strings.ToLower(condition.Type) { + case readyStatusType: + readyStatus = condition.Status + case nodePoolStatusType: + nodePoolStatus = condition.Status + case controlPlaneStatusType: + controlPlaneStatus = condition.Status + } + } + return +} diff --git a/capten/agent/internal/job/crossplane_resources_sync.go b/capten/agent/internal/job/crossplane_resources_sync.go index 4aeb18db..b072b2e3 100644 --- a/capten/agent/internal/job/crossplane_resources_sync.go +++ b/capten/agent/internal/job/crossplane_resources_sync.go @@ -15,11 +15,15 @@ type CrossplaneResourcesSync struct { } func NewCrossplaneResourcesSync(log logging.Logger, frequency string, dbStore *captenstore.Store) (*CrossplaneResourcesSync, error) { + ccObj, err := crossplane.NewClusterClaimSyncHandler(log, dbStore) + if err != nil { + return nil, err + } return &CrossplaneResourcesSync{ log: log, frequency: frequency, dbStore: dbStore, - clusterHandler: crossplane.NewClusterClaimSyncHandler(log, dbStore), + clusterHandler: ccObj, providerHandler: crossplane.NewProvidersSyncHandler(log, dbStore), }, nil } diff --git a/capten/common-pkg/k8s/dynamic_client.go b/capten/common-pkg/k8s/dynamic_client.go index cda7a370..b5585875 100644 --- a/capten/common-pkg/k8s/dynamic_client.go +++ b/capten/common-pkg/k8s/dynamic_client.go @@ -33,6 +33,15 @@ func ConvertYamlToJson(data []byte) ([]byte, error) { return jsonData, nil } +func ConvertJsonToYaml(data []byte) ([]byte, error) { + yamlData, err := yaml.JSONToYAML(data) + if err != nil { + return nil, err + } + + return yamlData, nil +} + func (dc *DynamicClientSet) GetNameNamespace(jsonByte []byte) (string, string, error) { var keyValue map[string]interface{} if err := json.Unmarshal(jsonByte, &keyValue); err != nil { diff --git a/capten/config-worker/internal/app_config/app_git_helper.go b/capten/config-worker/internal/app_config/app_git_helper.go index 378976a6..94f11cf6 100644 --- a/capten/config-worker/internal/app_config/app_git_helper.go +++ b/capten/config-worker/internal/app_config/app_git_helper.go @@ -21,14 +21,18 @@ const ( tmpGitProjectCloneStr = "clone*" gitProjectAccessTokenAttribute = "accessToken" gitUrlSuffix = ".git" + kubeConfig = "kubeconfig" + k8sEndpoint = "endpoint" + k8sClusterCA = "clusterCA" ) type Config struct { - GitDefaultCommiterName string `envconfig:"GIT_COMMIT_NAME" default:"capten-bot"` - GitDefaultCommiterEmail string `envconfig:"GIT_COMMIT_EMAIL" default:"capten-bot@intelops.dev"` - GitVaultEntityName string `envconfig:"GIT_VAULT_ENTITY_NAME" default:"git-project"` - GitCloneDir string `envconfig:"GIT_CLONE_DIR" default:"/gitCloneDir"` - GitBranchName string `envconfig:"GIT_BRANCH_NAME" default:"capten-template-bot"` + GitDefaultCommiterName string `envconfig:"GIT_COMMIT_NAME" default:"capten-bot"` + GitDefaultCommiterEmail string `envconfig:"GIT_COMMIT_EMAIL" default:"capten-bot@intelops.dev"` + GitVaultEntityName string `envconfig:"GIT_VAULT_ENTITY_NAME" default:"git-project"` + GitCloneDir string `envconfig:"GIT_CLONE_DIR" default:"/gitCloneDir"` + GitBranchName string `envconfig:"GIT_BRANCH_NAME" default:"capten-template-bot"` + ManagedClusterEntityName string `envconfig:"MANAGED_CLUSER_VAULT_ENTITY_NAME" default:"managedcluster"` } var logger = logging.NewLogger() @@ -125,6 +129,34 @@ func (ca *AppGitConfigHelper) SyncArgoCDApp(ctx context.Context, ns, resName str return nil } +func (ca *AppGitConfigHelper) CreateCluster(ctx context.Context, id, clusterName string) (string, error) { + credReader, err := credentials.NewCredentialReader(ctx) + if err != nil { + err = errors.WithMessage(err, "error in initializing credential reader") + return "", err + } + + cred, err := credReader.GetCredential(ctx, credentials.GenericCredentialType, + ca.cfg.ManagedClusterEntityName, id) + if err != nil { + err = errors.WithMessagef(err, "error while reading credential %s/%s from the vault", + ca.cfg.GitVaultEntityName, id) + return "", err + } + + client, err := argocd.NewClient(logger) + if err != nil { + return "", err + } + + err = client.CreateOrUpdateCluster(ctx, clusterName, cred[kubeConfig]) + if err != nil { + return "", err + } + + return cred[k8sEndpoint], nil +} + func (ca *AppGitConfigHelper) WaitForArgoCDToSync(ctx context.Context, ns, resName string) error { client, err := argocd.NewClient(logger) if err != nil { diff --git a/capten/config-worker/internal/crossplane/activity.go b/capten/config-worker/internal/crossplane/activity.go index 496e4072..faa4041e 100644 --- a/capten/config-worker/internal/crossplane/activity.go +++ b/capten/config-worker/internal/crossplane/activity.go @@ -3,6 +3,7 @@ package crossplane import ( "context" "encoding/json" + "fmt" "github.com/intelops/go-common/logging" "github.com/kube-tarian/kad/capten/model" @@ -14,33 +15,51 @@ var logger = logging.NewLogger() func (c *CrossPlaneActivities) ConfigurationActivity(ctx context.Context, params model.ConfigureParameters, payload json.RawMessage) (model.ResponsePayload, error) { logger.Infof("Activity: %s, %s", params.Resource, params.Action) - - req := &model.CrossplaneUseCase{} - if err := json.Unmarshal(payload, req); err != nil { + status, err := processConfigurationActivity(ctx, params, payload) + if err != nil { return model.ResponsePayload{ - Status: string(model.WorkFlowStatusFailed), - Message: json.RawMessage("{\"error\": \"failed to read payload\"}"), + Status: status, + Message: json.RawMessage( + fmt.Sprintf("{\"error\": \"%s\"}", err.Error())), }, err } - config, err := NewCrossPlaneApp() + logger.Infof("crossplane plugin action %s configured", params.Action) + return model.ResponsePayload{Status: status}, err +} + +func processConfigurationActivity(ctx context.Context, params model.ConfigureParameters, payload json.RawMessage) (string, error) { + cp, err := NewCrossPlaneApp() if err != nil { - return model.ResponsePayload{ - Status: string(model.WorkFlowStatusFailed), - Message: json.RawMessage("{\"error\": \"failed to initialize crossplane plugin\"}"), - }, err + return string(model.WorkFlowStatusFailed), fmt.Errorf("failed to initialize crossplane plugin") } - status, err := config.Configure(ctx, req) - if err != nil { - logger.Errorf("crossplane plugin configure failed, %v", err) - return model.ResponsePayload{ - Status: status, - Message: json.RawMessage("{\"error\": \"failed to configure crossplane plugin\"}"), - }, err + switch params.Action { + case model.CrossPlaneClusterUpdate: + reqLocal := &model.CrossplaneClusterUpdate{} + if err := json.Unmarshal(payload, reqLocal); err != nil { + logger.Errorf("failed to unmarshall the crossplane req for %s, %v", model.CrossPlaneClusterUpdate, err) + return string(model.WorkFlowStatusFailed), fmt.Errorf("failed to unmarshall the crossplane req for %s", model.CrossPlaneClusterUpdate) + } + status, err := cp.configureClusterUpdate(ctx, reqLocal) + if err != nil { + logger.Errorf("failed to configure crossplane project for %s, %v", model.CrossPlaneClusterUpdate, err) + return status, fmt.Errorf("failed to configure crossplane project for %s", model.CrossPlaneClusterUpdate) + } + return status, nil + case model.CrossPlaneProjectSync: + reqLocal := &model.CrossplaneUseCase{} + if err := json.Unmarshal(payload, reqLocal); err != nil { + logger.Errorf("failed to unmarshall the crossplane req, %v", err) + return string(model.WorkFlowStatusFailed), fmt.Errorf("failed to unmarshall the crossplane req") + } + status, err := cp.configureProjectAndApps(ctx, reqLocal) + if err != nil { + logger.Errorf("failed to configure crossplane project, %v", err) + err = fmt.Errorf("failed to configure crossplane project") + } + return status, nil + default: + return string(model.WorkFlowStatusFailed), fmt.Errorf("invalid crossplane action") } - logger.Infof("crossplane plugin configured") - return model.ResponsePayload{ - Status: status, - }, err } diff --git a/capten/config-worker/internal/crossplane/argocd_app_values.go b/capten/config-worker/internal/crossplane/argocd_app_values.go new file mode 100644 index 00000000..4bb56a86 --- /dev/null +++ b/capten/config-worker/internal/crossplane/argocd_app_values.go @@ -0,0 +1,40 @@ +package crossplane + +type Source struct { + RepoURL string `json:"repoURL,omitempty"` + TargetRevision string `json:"targetRevision,omitempty"` +} + +type Dest struct { + Server string `json:"server,omitempty"` + Namespace string `json:"namespace,omitempty"` +} + +type GlobalValues struct { + ClusterConfigPath string `json:"clusterConfigPath,omitempty"` +} + +type DefaultApps struct { + Name string `json:"name,omitempty"` + ValuesPath string `json:"valuesPath,omitempty"` + RepoURL string `json:"repoURL,omitempty"` + Namespace string `json:"namespace,omitempty"` + Chart string `json:"chart,omitempty"` + TargetRevision string `json:"targetRevision,omitempty"` +} + +type Cluster struct { + Name string `json:"name,omitempty"` + Server string `json:"server,omitempty"` + DefApps []DefaultApps `json:"defaultApps,omitempty"` +} + +type ArgoCDAppValue struct { + Project string `json:"project,omitempty"` + Global GlobalValues `json:"global,omitempty"` + Src Source `json:"source,omitempty"` + Destination Dest `json:"destination,omitempty"` + SyncPolicy interface{} `json:"syncPolicy,omitempty"` + Compositions interface{} `json:"compositions,omitempty"` + Clusters *[]Cluster `json:"clusters,omitempty"` +} diff --git a/capten/config-worker/internal/crossplane/config_cluster_updates.go b/capten/config-worker/internal/crossplane/config_cluster_updates.go new file mode 100644 index 00000000..dc9dcfc1 --- /dev/null +++ b/capten/config-worker/internal/crossplane/config_cluster_updates.go @@ -0,0 +1,153 @@ +package crossplane + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/intelops/go-common/logging" + "github.com/kube-tarian/kad/capten/common-pkg/k8s" + "github.com/kube-tarian/kad/capten/model" + agentmodel "github.com/kube-tarian/kad/capten/model" + "github.com/pkg/errors" +) + +func getAppNameNamespace(ctx context.Context, fileName string) (string, string, error) { + k8sclient, err := k8s.NewK8SClient(logging.NewLogger()) + if err != nil { + return "", "", fmt.Errorf("failed to initalize k8s client: %v", err) + } + + data, err := os.ReadFile(fileName) + if err != nil { + return "", "", err + } + + jsonData, err := k8s.ConvertYamlToJson(data) + if err != nil { + return "", "", err + } + + // For the testing change the reqrepo to template one + ns, resName, err := k8sclient.DynamicClient.GetNameNamespace(jsonData) + if err != nil { + return "", "", fmt.Errorf("failed to create the k8s custom resource: %v", err) + } + + return ns, resName, nil + +} + +func (cp *CrossPlaneApp) configureClusterUpdate(ctx context.Context, req *model.CrossplaneClusterUpdate) (status string, err error) { + logger.Infof("configuring the cluster endpoint for %s", req.RepoURL) + endpoint, err := cp.helper.CreateCluster(ctx, req.ManagedClusterId, req.Name) + if err != nil { + return string(agentmodel.WorkFlowStatusFailed), errors.WithMessage(err, "failed to CreateCluster in argocd app") + } + + logger.Infof("CreateCluster argocd err: ", err) + accessToken, err := cp.helper.GetAccessToken(ctx, req.GitProjectId) + if err != nil { + return string(agentmodel.WorkFlowStatusFailed), errors.WithMessage(err, "failed to get token from vault") + } + + logger.Infof("cloning default templates %s to project %s", cp.pluginConfig.TemplateGitRepo, req.RepoURL) + templateRepo, customerRepo, err := cp.helper.CloneRepos(ctx, cp.pluginConfig.TemplateGitRepo, req.RepoURL, accessToken) + if err != nil { + return string(agentmodel.WorkFlowStatusFailed), errors.WithMessage(err, "failed to clone repos") + } + logger.Infof("cloned default templates to project %s", req.RepoURL) + + defer os.RemoveAll(templateRepo) + defer os.RemoveAll(customerRepo) + + fileName := filepath.Join(customerRepo, cp.pluginConfig.ClusterEndpointUpdates.File) + // replace cluster endpoint + err = updateClusterEndpointDetials(fileName, req.Name, endpoint, cp.cfg.ClusterDefaultAppsFile) + if err != nil { + return string(agentmodel.WorkFlowStatusFailed), errors.WithMessage(err, "failed to replace the file") + } + + err = cp.helper.AddToGit(ctx, model.CrossPlaneClusterUpdate, req.RepoURL, accessToken) + if err != nil { + return string(agentmodel.WorkFlowStatusFailed), errors.WithMessage(err, "failed to add git repo") + } + + logger.Infof("added cloned project %s changed to git", req.RepoURL) + ns, resName, err := getAppNameNamespace(ctx, filepath.Join(customerRepo, cp.pluginConfig.ClusterEndpointUpdates.MainAppGitPath)) + if err != nil { + return string(agentmodel.WorkFlowStatusFailed), errors.WithMessage(err, "failed to get name and namespace from") + } + + err = cp.helper.SyncArgoCDApp(ctx, ns, resName) + if err != nil { + return string(agentmodel.WorkFlowStatusFailed), errors.WithMessage(err, "failed to sync argocd app") + } + logger.Infof("synched provider config main-app %s", resName) + + err = cp.helper.WaitForArgoCDToSync(ctx, ns, resName) + if err != nil { + return string(agentmodel.WorkFlowStatusFailed), errors.WithMessage(err, "failed to fetch argocd app") + } + + return string(agentmodel.WorkFlowStatusCompleted), nil +} + +func updateClusterEndpointDetials(filename, clusterName, clusterEndpoint, defaultAppFile string) error { + data, err := os.ReadFile(filename) + if err != nil { + return err + } + + jsonData, err := k8s.ConvertYamlToJson(data) + if err != nil { + return err + } + + var argoCDAppValue ArgoCDAppValue + + err = json.Unmarshal(jsonData, &argoCDAppValue) + if err != nil { + return err + } + + clusters := *argoCDAppValue.Clusters + for index := range clusters { + cluster := &clusters[index] + if cluster.Name == clusterName { + defaultApps, err := readClusterDefaultApps(defaultAppFile) + if err != nil { + return err + } + + for index := range defaultApps { + localObj := &defaultApps[index] + strings.ReplaceAll(localObj.ValuesPath, clusterNameSub, clusterName) + } + + logger.Infof("udpated the req endpoint details to %s for name %s ", clusterEndpoint, clusterName) + cluster.Server = clusterEndpoint + + break + } + } + + argoCDAppValue.Clusters = &clusters + + jsonBytes, err := json.Marshal(argoCDAppValue) + if err != nil { + return err + } + + yamlBytes, err := k8s.ConvertJsonToYaml(jsonBytes) + if err != nil { + return err + } + + err = os.WriteFile(filename, yamlBytes, os.ModeAppend) + + return err +} diff --git a/capten/config-worker/internal/crossplane/config_crossplane_app.go b/capten/config-worker/internal/crossplane/config_crossplane_app.go index f199ed44..054f0e84 100644 --- a/capten/config-worker/internal/crossplane/config_crossplane_app.go +++ b/capten/config-worker/internal/crossplane/config_crossplane_app.go @@ -17,8 +17,13 @@ import ( "github.com/pkg/errors" ) +var ( + clusterNameSub = "{CLUSTER_NAME}" +) + type Config struct { PluginConfigFile string `envconfig:"CROSSPLANE_PLUGIN_CONFIG_FILE" default:"/crossplane_plugin_config.json"` + ClusterDefaultAppsFile string `envconfig:"CROSSPLANE_CLUSTER_DEFAULT_APPS" default:"/crossplane_cluster_default_apps.json"` CloudProviderEntityName string `envconfig:"CLOUD_PROVIDER_ENTITY_NAME" default:"cloud-provider"` } @@ -60,13 +65,18 @@ func readCrossPlanePluginConfig(pluginFile string) (*crossplanePluginConfig, err return &pluginData, nil } -func (cp *CrossPlaneApp) Configure(ctx context.Context, req *model.CrossplaneUseCase) (status string, err error) { - status, err = cp.configureProjectAndApps(ctx, req) +func readClusterDefaultApps(clusterDefaultApp string) ([]DefaultApps, error) { + data, err := os.ReadFile(filepath.Clean(clusterDefaultApp)) + if err != nil { + return nil, fmt.Errorf("failed to read clusterDefaultApp File: %s, err: %w", clusterDefaultApp, err) + } + + var defaultApps []DefaultApps + err = json.Unmarshal(data, &defaultApps) if err != nil { - logger.Errorf("failed to configure crossplane project, %v", err) - err = fmt.Errorf("failed to configure crossplane project") + return nil, fmt.Errorf("%w", err) } - return + return defaultApps, nil } func (cp *CrossPlaneApp) configureProjectAndApps(ctx context.Context, req *model.CrossplaneUseCase) (status string, err error) { diff --git a/capten/config-worker/internal/crossplane/types.go b/capten/config-worker/internal/crossplane/types.go index 93abf2f4..de2fae36 100644 --- a/capten/config-worker/internal/crossplane/types.go +++ b/capten/config-worker/internal/crossplane/types.go @@ -6,12 +6,18 @@ type appConfig struct { SynchApp bool `json:"synchApp"` } +type clusterConfig struct { + MainAppGitPath string `json:"mainAppGitPath"` + File string `json:"file"` +} + type crossplanePluginConfig struct { TemplateGitRepo string `json:"templateGitRepo"` CrossplaneConfigSyncPath string `json:"crossplaneConfigSyncPath"` ProviderConfigSyncPath string `json:"providerConfigSyncPath"` ProviderPackages map[string]string `json:"providerPackages"` ArgoCDApps []appConfig `json:"argoCDApps"` + ClusterEndpointUpdates clusterConfig `json:"clusterEndpointUpdates"` } const ( diff --git a/capten/model/config_workflow_types.go b/capten/model/config_workflow_types.go index 81c92851..c45a2d12 100644 --- a/capten/model/config_workflow_types.go +++ b/capten/model/config_workflow_types.go @@ -29,3 +29,10 @@ type CrossplaneUseCase struct { OverrideValues map[string]string `json:"OverrideValues,omitempty"` CrossplaneProviders []CrossplaneProvider `json:"ProviderInfo,omitempty"` } + +type CrossplaneClusterUpdate struct { + Name string `json:"name,omitempty"` + GitProjectId string `json:"gitProjectId,omitempty"` + ManagedClusterId string `json:"managedClusterId,omitempty"` + RepoURL string `json:"repoURL,omitempty"` +} diff --git a/capten/model/crossplane_types.go b/capten/model/crossplane_types.go index 9ad4e7e8..abf33c27 100644 --- a/capten/model/crossplane_types.go +++ b/capten/model/crossplane_types.go @@ -9,7 +9,10 @@ import ( ) const ( - providerNamePrefix = "provider" + providerNamePrefix = "provider" + CrossPlaneResource = "crossplane" + CrossPlaneClusterUpdate = "crossplane-cluster-update" + CrossPlaneProjectSync = "crossplane-project-sync" ) type CrossplaneProviderStatus string diff --git a/charts/kad/crossplane_cluster_default_apps.json b/charts/kad/crossplane_cluster_default_apps.json new file mode 100644 index 00000000..3e35243c --- /dev/null +++ b/charts/kad/crossplane_cluster_default_apps.json @@ -0,0 +1,34 @@ + [ + { + "name": "falco", + "appConfigPath": "infra/clusters/{CLUSTER_NAME}/app-configs/default-apps/falco-values.yaml", + "repoURL": "https://kube-tarian.github.io/helmrepo-supporting-tools", + "namespace": "falco", + "chart": "falco", + "targetRevision": "0.0.1" + }, + { + "name": "kyverno", + "appConfigPath": "infra/clusters/{CLUSTER_NAME}/app-configs/default-apps/kyverno-values.yaml", + "repoURL": "https://kube-tarian.github.io/helmrepo-supporting-tools", + "namespace": "kyverno", + "chart": "kyverno", + "targetRevision": "1.0.2" + }, + { + "name": "prometheus", + "appConfigPath": "infra/clusters/{CLUSTER_NAME}/app-configs/default-apps/prometheus-values.yaml", + "repoURL": "https://kube-tarian.github.io/helmrepo-supporting-tools", + "namespace": "prometheus", + "chart": "kube-prometheus-stack", + "targetRevision": "1.0.3" + }, + { + "name": "promtail", + "appConfigPath": "infra/clusters/{CLUSTER_NAME}/app-configs/default-apps/promtail-values.yaml", + "repoURL": "https://kube-tarian.github.io/helmrepo-supporting-tools", + "namespace": "promtail", + "chart": "promtail", + "targetRevision": "1.0.0" + } +] \ No newline at end of file diff --git a/charts/kad/crossplane_plugin_config.json b/charts/kad/crossplane_plugin_config.json index 4916f73e..e7c7276b 100644 --- a/charts/kad/crossplane_plugin_config.json +++ b/charts/kad/crossplane_plugin_config.json @@ -6,6 +6,10 @@ "aws": "xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0", "gcp": "xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.22.0" }, + "clusterEndpointUpdates":{ + "mainAppGitPath": "infra/crossplane/crossplane-main-app.yaml", + "file": "infra/clusters/argocd-apps/values.yaml" + }, "argoCDApps": [ { "mainAppGitPath": "infra/crossplane/crossplane-main-app.yaml", diff --git a/charts/kad/templates/config-worker-deployment.yaml b/charts/kad/templates/config-worker-deployment.yaml index b6c612a3..357b0887 100644 --- a/charts/kad/templates/config-worker-deployment.yaml +++ b/charts/kad/templates/config-worker-deployment.yaml @@ -41,6 +41,8 @@ spec: path: {{ .Values.configWorker.tektonPluginConfigFile }} - key: CROSSPLANE_PLUGIN_CONFIG path: {{ .Values.configWorker.crossplanePluginConfigFile }} + - key: CROSSPLANE_CLUSTER_DEFAULT_APPS + path: {{ .Values.configWorker.crossplaneClusterDefaultApps }} containers: - name: {{ .Chart.Name }}-config-worker securityContext: @@ -79,6 +81,8 @@ spec: value: "{{ .Values.configWorker.pluginConfigDir }}/{{ .Values.configWorker.tektonPluginConfigFile }}" - name: CROSSPLANE_PLUGIN_CONFIG_FILE value: "{{ .Values.configWorker.pluginConfigDir }}/{{ .Values.configWorker.crossplanePluginConfigFile }}" + - name: CROSSPLANE_CLUSTER_DEFAULT_APPS + value: "{{ .Values.configWorker.pluginConfigDir }}/{{ .Values.configWorker.crossplaneClusterDefaultApps }}" - name: CASSANDRA_USERNAME valueFrom: secretKeyRef: diff --git a/charts/kad/values.yaml b/charts/kad/values.yaml index e661cc37..59b5ac87 100644 --- a/charts/kad/values.yaml +++ b/charts/kad/values.yaml @@ -120,6 +120,7 @@ configWorker: pluginConfigDir: "/configs" tektonPluginConfigFile: "tekton_plugin_config.json" crossplanePluginConfigFile: "crossplane_plugin_config.json" + crossplaneClusterDefaultApps: "crossplane_cluster_default_apps.json" temporal: external: true