From 03f4862eb7b13167e51cecdb1c8150f8ebe7a2c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristhian=20Fern=C3=A1ndez?= Date: Wed, 14 Feb 2024 14:41:21 -0500 Subject: [PATCH] feat: catalog multicluster (#296) * feat: catalog multicluster * feat: adding metaphor service to dev, stage adn prod * feat: moving func to pkg --- go.mod | 6 +- go.sum | 4 +- internal/db/services.go | 10 +- internal/environments/defaultEnvironments.go | 31 ++ internal/router/api/v1/services.go | 20 +- internal/services/services.go | 334 +++++++------------ pkg/types/gitopsCatalog.go | 22 +- pkg/utils/tokens.go | 144 ++++++++ 8 files changed, 334 insertions(+), 237 deletions(-) create mode 100644 pkg/utils/tokens.go diff --git a/go.mod b/go.mod index e37f6b69..1738a567 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/aws/aws-sdk-go-v2 v1.17.8 github.com/aws/aws-sdk-go-v2/config v1.18.19 github.com/aws/aws-sdk-go-v2/credentials v1.13.18 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.91.0 github.com/aws/aws-sdk-go-v2/service/eks v1.27.10 github.com/aws/aws-sdk-go-v2/service/route53 v1.27.5 github.com/caarlos0/env/v10 v10.0.0 @@ -30,13 +29,12 @@ require ( github.com/hashicorp/vault/api v1.9.0 github.com/joho/godotenv v1.5.1 github.com/kubefirst/metrics-client v0.3.0 - github.com/kubefirst/runtime v0.3.35 + github.com/kubefirst/runtime v0.4.1 github.com/minio/minio-go/v7 v7.0.49 github.com/nxadm/tail v1.4.8 github.com/otiai10/copy v1.7.0 github.com/rs/zerolog v1.29.1 github.com/segmentio/analytics-go v3.1.0+incompatible - github.com/sirupsen/logrus v1.9.0 github.com/swaggo/files v1.0.0 github.com/swaggo/gin-swagger v1.5.3 github.com/swaggo/swag v1.16.1 @@ -53,6 +51,7 @@ require ( ) require ( + github.com/aws/aws-sdk-go-v2/service/ec2 v1.91.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect ) @@ -109,7 +108,6 @@ require ( github.com/cloudflare/circl v1.1.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/denisbrodbeck/machineid v1.0.1 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/digitalocean/godo v1.98.0 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect diff --git a/go.sum b/go.sum index e5a75180..e0bc2394 100644 --- a/go.sum +++ b/go.sum @@ -742,8 +742,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kubefirst/metrics-client v0.3.0 h1:zCug82pEzeWhHhpeYQvdhytRNDxrLxX18dPQ5PSxY3s= github.com/kubefirst/metrics-client v0.3.0/go.mod h1:GR7wsMcyYhd+EU67PeuMCBYFE6OJ7P/j5OI5BLOoRMc= -github.com/kubefirst/runtime v0.3.35 h1:wn430Irf0E1vc3X0WX3lYBpyhQ5TN6xxMcargILA9uI= -github.com/kubefirst/runtime v0.3.35/go.mod h1:0CnYy+8JTG+/0f3QlkTQJqTT654Su6JXk30OufFVY98= +github.com/kubefirst/runtime v0.4.1 h1:0aqBZa3bPwHb5pFFA1eaZ5VObNjuVxDO4d8m9VFs4Jo= +github.com/kubefirst/runtime v0.4.1/go.mod h1:0CnYy+8JTG+/0f3QlkTQJqTT654Su6JXk30OufFVY98= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= diff --git a/internal/db/services.go b/internal/db/services.go index 317e8461..9e17e0ec 100644 --- a/internal/db/services.go +++ b/internal/db/services.go @@ -17,8 +17,8 @@ import ( ) // CreateClusterServiceList adds an entry for a cluster to the service list -func (mdbcl *MongoDBClient) CreateClusterServiceList(cl *pkgtypes.Cluster) error { - filter := bson.D{{Key: "cluster_name", Value: cl.ClusterName}} +func (mdbcl *MongoDBClient) CreateClusterServiceList(clusterName string) error { + filter := bson.D{{Key: "cluster_name", Value: clusterName}} var result pkgtypes.Cluster err := mdbcl.ServicesCollection.FindOne(mdbcl.Context, filter).Decode(&result) if err != nil { @@ -26,15 +26,15 @@ func (mdbcl *MongoDBClient) CreateClusterServiceList(cl *pkgtypes.Cluster) error if err == mongo.ErrNoDocuments { // Create if entry does not exist _, err := mdbcl.ServicesCollection.InsertOne(mdbcl.Context, types.ClusterServiceList{ - ClusterName: cl.ClusterName, + ClusterName: clusterName, Services: []types.Service{}, }) if err != nil { - return fmt.Errorf("error inserting cluster service list for cluster %s: %s", cl.ClusterName, err) + return fmt.Errorf("error inserting cluster service list for cluster %s: %s", clusterName, err) } } } else { - log.Info().Msgf("cluster service list record for %s already exists - skipping", cl.ClusterName) + log.Info().Msgf("cluster service list record for %s already exists - skipping", clusterName) } return nil diff --git a/internal/environments/defaultEnvironments.go b/internal/environments/defaultEnvironments.go index a66ef4a4..651330e2 100644 --- a/internal/environments/defaultEnvironments.go +++ b/internal/environments/defaultEnvironments.go @@ -19,7 +19,9 @@ import ( "github.com/kubefirst/kubefirst-api/internal/constants" "github.com/kubefirst/kubefirst-api/internal/db" "github.com/kubefirst/kubefirst-api/internal/env" + internalTypes "github.com/kubefirst/kubefirst-api/internal/types" "github.com/kubefirst/kubefirst-api/pkg/types" + log "github.com/rs/zerolog/log" "go.mongodb.org/mongo-driver/bson/primitive" ) @@ -84,6 +86,35 @@ func CreateDefaultEnvironments(mgmtCluster types.Cluster) error { Clusters: defaultClusters, } + var fullDomainName string + if mgmtCluster.SubdomainName != "" { + fullDomainName = fmt.Sprintf("%s.%s", mgmtCluster.SubdomainName, mgmtCluster.DomainName) + } else { + fullDomainName = mgmtCluster.DomainName + } + + for _, clusterName := range defaultClusterNames { + // Add to list + err := db.Client.CreateClusterServiceList(clusterName) + if err != nil { + return err + } + + // Update list + err = db.Client.InsertClusterServiceListEntry(clusterName, &internalTypes.Service{ + Name: "Metaphor", + Default: true, + Description: "A multi-environment demonstration space for frontend application best practices that's easy to apply to other projects.", + Image: "https://assets.kubefirst.com/console/metaphor.svg", + Links: []string{fmt.Sprintf("https://metaphor-%s.%s", clusterName, fullDomainName)}, + Status: "", + }) + + if err != nil { + return err + } + } + // call api-ee to create clusters return callApiEE(defaultEnvironmentSet) } diff --git a/internal/router/api/v1/services.go b/internal/router/api/v1/services.go index 8ab7e5a2..6827fa93 100644 --- a/internal/router/api/v1/services.go +++ b/internal/router/api/v1/services.go @@ -179,6 +179,10 @@ func PostAddServiceToCluster(c *gin.Context) { }) return } + + c.JSON(http.StatusOK, types.JSONSuccessResponse{ + Message: fmt.Sprintf("service %s has been created", serviceName), + }) } // DeleteServiceFromCluster godoc @@ -241,11 +245,25 @@ func DeleteServiceFromCluster(c *gin.Context) { return } - err = services.DeleteService(&cl, serviceName) + // Bind to variable as application/json, handle error + var serviceDefinition pkgtypes.GitopsCatalogAppDeleteRequest + err = c.Bind(&serviceDefinition) if err != nil { c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ Message: err.Error(), }) return } + + err = services.DeleteService(&cl, serviceName, serviceDefinition) + if err != nil { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: err.Error(), + }) + return + } + + c.JSON(http.StatusOK, types.JSONSuccessResponse{ + Message: fmt.Sprintf("service %s has been deleted", serviceName), + }) } diff --git a/internal/services/services.go b/internal/services/services.go index 34aa6f34..2862a45b 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -13,7 +13,6 @@ import ( "net/http" "os" "path/filepath" - "strconv" "strings" "time" @@ -30,13 +29,13 @@ import ( "github.com/kubefirst/kubefirst-api/internal/types" "github.com/kubefirst/kubefirst-api/pkg/providerConfigs" pkgtypes "github.com/kubefirst/kubefirst-api/pkg/types" + utils "github.com/kubefirst/kubefirst-api/pkg/utils" "github.com/kubefirst/runtime/pkg/argocd" "github.com/kubefirst/runtime/pkg/gitClient" "github.com/kubefirst/runtime/pkg/k8s" "github.com/kubefirst/runtime/pkg/vault" cp "github.com/otiai10/copy" log "github.com/rs/zerolog/log" - "github.com/thanhpk/randstr" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -77,31 +76,14 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi gitopsRepo, _ := git.PlainOpen(tmpGitopsDir) - var registryPath string - if cl.CloudProvider == "civo" && cl.GitProvider == "github" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "civo" && cl.GitProvider == "gitlab" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "aws" && cl.GitProvider == "github" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "aws" && cl.GitProvider == "gitlab" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "google" && cl.GitProvider == "github" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "google" && cl.GitProvider == "gitlab" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "digitalocean" && cl.GitProvider == "github" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "digitalocean" && cl.GitProvider == "gitlab" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "vultr" && cl.GitProvider == "github" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "vultr" && cl.GitProvider == "gitlab" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else { - registryPath = fmt.Sprintf("registry/%s", cl.ClusterName) + clusterName := cl.ClusterName + + if req.WorkloadClusterName != "" { + clusterName = req.WorkloadClusterName } + registryPath := getRegistryPath(clusterName, cl.CloudProvider, req.IsTemplate) + clusterRegistryPath := fmt.Sprintf("%s/%s", tmpGitopsDir, registryPath) catalogServiceFolder := fmt.Sprintf("%s/%s", tmpGitopsCatalogDir, serviceName) @@ -131,7 +113,7 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi // If there are secret values, create a vault secret if len(req.SecretKeys) > 0 { - log.Info().Msgf("cluster %s - application %s has secrets, creating vault values", cl.ClusterName, appDef.Name) + log.Info().Msgf("cluster %s - application %s has secrets, creating vault values", clusterName, appDef.Name) s := make(map[string]interface{}, 0) @@ -142,24 +124,24 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi // Get token existingKubernetesSecret, err := k8s.ReadSecretV2(kcfg.Clientset, vault.VaultNamespace, vault.VaultSecretName) if err != nil || existingKubernetesSecret == nil { - return fmt.Errorf("cluster %s - error getting vault token: %s", cl.ClusterName, err) + return fmt.Errorf("cluster %s - error getting vault token: %s", clusterName, err) } vaultClient, err := vaultapi.NewClient(&vaultapi.Config{ Address: vaultUrl, }) if err != nil { - return fmt.Errorf("cluster %s - error initializing vault client: %s", cl.ClusterName, err) + return fmt.Errorf("cluster %s - error initializing vault client: %s", clusterName, err) } vaultClient.SetToken(existingKubernetesSecret["root-token"]) resp, err := vaultClient.KVv2("secret").Put(context.Background(), appDef.Name, s) if err != nil { - return fmt.Errorf("cluster %s - error putting vault secret: %s", cl.ClusterName, err) + return fmt.Errorf("cluster %s - error putting vault secret: %s", clusterName, err) } - log.Info().Msgf("cluster %s - created vault secret data for application %s %s", cl.ClusterName, appDef.Name, resp.VersionMetadata.CreatedTime) + log.Info().Msgf("cluster %s - created vault secret data for application %s %s", clusterName, appDef.Name, resp.VersionMetadata.CreatedTime) } // Create service files in gitops dir @@ -173,22 +155,24 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi }, ) if err != nil { - log.Warn().Msgf("cluster %s - error pulling gitops repo: %s", cl.ClusterName, err) + log.Warn().Msgf("cluster %s - error pulling gitops repo: %s", clusterName, err) } - //Create Tokens - gitopsKubefirstTokens := CreateTokensFromDatabaseRecord(cl, registryPath) + if !req.IsTemplate { + //Create Tokens + gitopsKubefirstTokens := utils.CreateTokensFromDatabaseRecord(cl, registryPath) - //Detokenize App Template - err = providerConfigs.DetokenizeGitGitops(catalogServiceFolder, gitopsKubefirstTokens, cl.GitProtocol, cl.CloudflareAuth.OriginCaIssuerKey != "") - if err != nil { - return fmt.Errorf("cluster %s - error opening file: %s", cl.ClusterName, err) - } + //Detokenize App Template + err = providerConfigs.DetokenizeGitGitops(catalogServiceFolder, gitopsKubefirstTokens, cl.GitProtocol, cl.CloudflareAuth.OriginCaIssuerKey != "") + if err != nil { + return fmt.Errorf("cluster %s - error opening file: %s", clusterName, err) + } - //Detokenize Config Keys - err = DetokenizeConfigKeys(catalogServiceFolder, req.ConfigKeys) - if err != nil { - return fmt.Errorf("cluster %s - error opening file: %s", cl.ClusterName, err) + //Detokenize Config Keys + err = DetokenizeConfigKeys(catalogServiceFolder, req.ConfigKeys) + if err != nil { + return fmt.Errorf("cluster %s - error opening file: %s", clusterName, err) + } } err = cp.Copy(catalogServiceFolder, clusterRegistryPath, cp.Options{}) @@ -200,7 +184,7 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi // Commit to gitops repository err = gitClient.Commit(gitopsRepo, fmt.Sprintf("committing files for service %s", serviceName)) if err != nil { - return fmt.Errorf("cluster %s - error committing service file: %s", cl.ClusterName, err) + return fmt.Errorf("cluster %s - error committing service file: %s", clusterName, err) } err = gitopsRepo.Push(&git.PushOptions{ RemoteName: "origin", @@ -211,11 +195,21 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi Force: true, }) if err != nil { - return fmt.Errorf("cluster %s - error pushing commit for service file: %s", cl.ClusterName, err) + return fmt.Errorf("cluster %s - error pushing commit for service file: %s", clusterName, err) } - // Add to list - err = db.Client.InsertClusterServiceListEntry(cl.ClusterName, &types.Service{ + existingService, err := db.Client.GetServices(clusterName) + + if existingService.ClusterName == "" { + // Add to list + err = db.Client.CreateClusterServiceList(clusterName) + if err != nil { + return err + } + } + + // Update list + err = db.Client.InsertClusterServiceListEntry(clusterName, &types.Service{ Name: serviceName, Default: false, Description: appDef.Description, @@ -259,30 +253,30 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi for i := 0; i < 50; i++ { _, err := argocdClient.ArgoprojV1alpha1().Applications("argocd").Get(context.Background(), serviceName, v1.GetOptions{}) if err != nil { - log.Info().Msgf("cluster %s - waiting for app %s to be created", cl.ClusterName, serviceName) + log.Info().Msgf("cluster %s - waiting for app %s to be created", clusterName, serviceName) time.Sleep(time.Second * 10) } else { break } if i == 50 { - return fmt.Errorf("cluster %s - error waiting for app %s to be created: %s", cl.ClusterName, serviceName, err) + return fmt.Errorf("cluster %s - error waiting for app %s to be created: %s", clusterName, serviceName, err) } } // Wait for app to be synchronized and healthy for i := 0; i < 50; i++ { if i == 50 { - return fmt.Errorf("cluster %s - error waiting for app %s to synchronize: %s", cl.ClusterName, serviceName, err) + return fmt.Errorf("cluster %s - error waiting for app %s to synchronize: %s", clusterName, serviceName, err) } app, err := argocdClient.ArgoprojV1alpha1().Applications("argocd").Get(context.Background(), serviceName, v1.GetOptions{}) if err != nil { - return fmt.Errorf("cluster %s - error getting argocd application %s: %s", cl.ClusterName, serviceName, err) + return fmt.Errorf("cluster %s - error getting argocd application %s: %s", clusterName, serviceName, err) } if app.Status.Sync.Status == v1alpha1.SyncStatusCodeSynced && app.Status.Health.Status == health.HealthStatusHealthy { - log.Info().Msgf("cluster %s - app %s synchronized", cl.ClusterName, serviceName) + log.Info().Msgf("cluster %s - app %s synchronized", clusterName, serviceName) break } - log.Info().Msgf("cluster %s - waiting for app %s to sync", cl.ClusterName, serviceName) + log.Info().Msgf("cluster %s - waiting for app %s to sync", clusterName, serviceName) time.Sleep(time.Second * 10) } @@ -290,40 +284,43 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi } // DeleteService -func DeleteService(cl *pkgtypes.Cluster, serviceName string) error { +func DeleteService(cl *pkgtypes.Cluster, serviceName string, def pkgtypes.GitopsCatalogAppDeleteRequest) error { var gitopsRepo *git.Repository + clusterName := cl.ClusterName + + if def.WorkloadClusterName != "" { + clusterName = def.WorkloadClusterName + } + + // Remove from list + svc, err := db.Client.GetService(clusterName, serviceName) + if err != nil { + return fmt.Errorf("cluster %s - error finding service: %s", clusterName, err) + } + homeDir, err := os.UserHomeDir() + tmpGitopsDir := fmt.Sprintf("%s/.k1/%s/%s/gitops", homeDir, cl.ClusterName, serviceName) + + // Remove gitops dir + err = os.RemoveAll(tmpGitopsDir) if err != nil { - log.Fatal().Msgf("error getting home path: %s", err) - } - gitopsDir := fmt.Sprintf("%s/.k1/%s/gitops", homeDir, cl.ClusterName) - gitopsRepo, _ = git.PlainOpen(gitopsDir) - - var registryPath string - if cl.CloudProvider == "civo" && cl.GitProvider == "github" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "civo" && cl.GitProvider == "gitlab" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "aws" && cl.GitProvider == "github" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "aws" && cl.GitProvider == "gitlab" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "google" && cl.GitProvider == "github" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "google" && cl.GitProvider == "gitlab" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "vultr" && cl.GitProvider == "github" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else if cl.CloudProvider == "vultr" && cl.GitProvider == "gitlab" { - registryPath = fmt.Sprintf("registry/clusters/%s", cl.ClusterName) - } else { - registryPath = fmt.Sprintf("registry/%s", cl.ClusterName) + log.Fatal().Msgf("error removing gitops dir %s: %s", tmpGitopsDir, err) + return err } - serviceFile := fmt.Sprintf("%s/%s/%s/%s.yaml", gitopsDir, registryPath, cl.ClusterName, serviceName) + err = gitShim.PrepareGitEnvironment(cl, tmpGitopsDir) + if err != nil { + log.Fatal().Msgf("an error ocurred preparing git environment %s %s", tmpGitopsDir, err) + } + + gitopsRepo, _ = git.PlainOpen(tmpGitopsDir) + + registryPath := getRegistryPath(clusterName, cl.CloudProvider, def.IsTemplate) + + serviceFile := fmt.Sprintf("%s/%s/%s.yaml", tmpGitopsDir, registryPath, serviceName) + componentsServiceFolder := fmt.Sprintf("%s/%s/components/%s", tmpGitopsDir, registryPath, serviceName) - // Delete service files from gitops dir err = gitShim.PullWithAuth( gitopsRepo, cl.GitProvider, @@ -333,41 +330,51 @@ func DeleteService(cl *pkgtypes.Cluster, serviceName string) error { Password: cl.GitAuth.Token, }, ) + if err != nil { - log.Warn().Msgf("cluster %s - error pulling gitops repo: %s", cl.ClusterName, err) + log.Warn().Msgf("cluster %s - error pulling gitops repo: %s", clusterName, err) } + + // removing registry service file _, err = os.Stat(serviceFile) if err != nil { return fmt.Errorf("file %s does not exist in repository", serviceFile) } else { err := os.Remove(serviceFile) if err != nil { - return fmt.Errorf("cluster %s - error deleting file: %s", cl.ClusterName, err) + return fmt.Errorf("cluster %s - error deleting file: %s", clusterName, err) + } + } + + // removing componentes service folder + _, err = os.Stat(componentsServiceFolder) + if err != nil { + return fmt.Errorf("folder %s does not exist in repository", componentsServiceFolder) + } else { + err := os.RemoveAll(componentsServiceFolder) + if err != nil { + return fmt.Errorf("cluster %s - error deleting components folder: %s", clusterName, err) } } // Commit to gitops repository err = gitClient.Commit(gitopsRepo, fmt.Sprintf("deleting files for service %s", serviceName)) if err != nil { - return fmt.Errorf("cluster %s - error deleting service file: %s", cl.ClusterName, err) + return fmt.Errorf("cluster %s - error deleting service file: %s", clusterName, err) } err = gitopsRepo.Push(&git.PushOptions{ - RemoteName: cl.GitProvider, + RemoteName: "origin", Auth: &githttps.BasicAuth{ Username: cl.GitAuth.User, Password: cl.GitAuth.Token, }, }) - if err != nil { - return fmt.Errorf("cluster %s - error pushing commit for service file: %s", cl.ClusterName, err) - } - // Remove from list - svc, err := db.Client.GetService(cl.ClusterName, serviceName) if err != nil { - return fmt.Errorf("cluster %s - error finding service: %s", cl.ClusterName, err) + return fmt.Errorf("cluster %s - error pushing commit for service file: %s", clusterName, err) } - err = db.Client.DeleteClusterServiceListEntry(cl.ClusterName, &svc) + + err = db.Client.DeleteClusterServiceListEntry(clusterName, &svc) if err != nil { return err } @@ -377,7 +384,7 @@ func DeleteService(cl *pkgtypes.Cluster, serviceName string) error { // AddDefaultServices func AddDefaultServices(cl *pkgtypes.Cluster) error { - err := db.Client.CreateClusterServiceList(cl) + err := db.Client.CreateClusterServiceList(cl.ClusterName) if err != nil { return err } @@ -431,7 +438,10 @@ func AddDefaultServices(cl *pkgtypes.Cluster) error { Links: []string{fmt.Sprintf("https://atlantis.%s", fullDomainName)}, Status: "", }, - { + } + + if cl.CloudProvider == "k3d" { + defaults = append(defaults, types.Service{ Name: "Metaphor", Default: true, Description: "A multi-environment demonstration space for frontend application best practices that's easy to apply to other projects.", @@ -440,7 +450,7 @@ func AddDefaultServices(cl *pkgtypes.Cluster) error { fmt.Sprintf("https://metaphor-staging.%s", fullDomainName), fmt.Sprintf("https://metaphor-production.%s", fullDomainName)}, Status: "", - }, + }) } for _, svc := range defaults { @@ -453,131 +463,6 @@ func AddDefaultServices(cl *pkgtypes.Cluster) error { return nil } -func CreateTokensFromDatabaseRecord(cl *pkgtypes.Cluster, registryPath string) *providerConfigs.GitopsDirectoryValues { - env, _ := env.GetEnv(constants.SilenceGetEnv) - - fullDomainName := "" - - if cl.SubdomainName != "" { - fullDomainName = fmt.Sprintf("%s.%s", cl.SubdomainName, cl.DomainName) - } else { - fullDomainName = cl.DomainName - } - - destinationGitopsRepoURL := fmt.Sprintf("https://%s/%s/gitops.git", cl.GitHost, cl.GitAuth.Owner) - - if cl.GitProtocol == "ssh" { - destinationGitopsRepoURL = fmt.Sprintf("git@%s:%s/gitops.git", cl.GitHost, cl.GitAuth.Owner) - } - - var externalDNSProviderTokenEnvName, externalDNSProviderSecretKey string - if cl.DnsProvider == "cloudflare" { - externalDNSProviderTokenEnvName = "CF_API_TOKEN" - externalDNSProviderSecretKey = "cf-api-token" - } else { - switch cl.CloudProvider { - // provider auth secret gets mapped to these values - case "aws": - externalDNSProviderTokenEnvName = "not-used-uses-service-account" - case "google": - // Normally this would be GOOGLE_APPLICATION_CREDENTIALS but we are using a service account instead and - // if you set externalDNSProviderTokenEnvName to GOOGLE_APPLICATION_CREDENTIALS then externaldns will overlook the service account - // if you want to use the provided keyfile instead of a service account then set the var accordingly - externalDNSProviderTokenEnvName = fmt.Sprintf("%s_auth", strings.ToUpper(cl.CloudProvider)) - case "civo": - externalDNSProviderTokenEnvName = fmt.Sprintf("%s_TOKEN", strings.ToUpper(cl.CloudProvider)) - case "vultr": - externalDNSProviderTokenEnvName = fmt.Sprintf("%s_API_KEY", strings.ToUpper(cl.CloudProvider)) - case "digitalocean": - externalDNSProviderTokenEnvName = "DO_TOKEN" - } - externalDNSProviderSecretKey = fmt.Sprintf("%s-auth", cl.CloudProvider) - } - - containerRegistryHost := "ghcr.io" - - if cl.GitProvider == "gitlab" { - containerRegistryHost = "registry.gitlab.com" - } - - // Default gitopsTemplateTokens - gitopsTemplateTokens := &providerConfigs.GitopsDirectoryValues{ - AlertsEmail: cl.AlertsEmail, - AtlantisAllowList: fmt.Sprintf("%s/%s/*", cl.GitHost, cl.GitAuth.Owner), - CloudProvider: cl.CloudProvider, - CloudRegion: cl.CloudRegion, - ClusterName: cl.ClusterName, - ClusterType: cl.ClusterType, - DomainName: cl.DomainName, - SubdomainName: cl.SubdomainName, - KubefirstTeam: cl.KubefirstTeam, - NodeType: cl.NodeType, - NodeCount: cl.NodeCount, - KubefirstVersion: env.KubefirstVersion, - ArgoCDIngressURL: fmt.Sprintf("https://argocd.%s", fullDomainName), - ArgoCDIngressNoHTTPSURL: fmt.Sprintf("argocd.%s", fullDomainName), - ArgoWorkflowsIngressURL: fmt.Sprintf("https://argo.%s", fullDomainName), - ArgoWorkflowsIngressNoHTTPSURL: fmt.Sprintf("argo.%s", fullDomainName), - AtlantisIngressURL: fmt.Sprintf("https://atlantis.%s", fullDomainName), - AtlantisIngressNoHTTPSURL: fmt.Sprintf("atlantis.%s", fullDomainName), - ChartMuseumIngressURL: fmt.Sprintf("https://chartmuseum.%s", fullDomainName), - VaultIngressURL: fmt.Sprintf("https://vault.%s", fullDomainName), - VaultIngressNoHTTPSURL: fmt.Sprintf("vault.%s", fullDomainName), - VouchIngressURL: fmt.Sprintf("https://vouch.%s", fullDomainName), - RegistryPath: registryPath, - - GitDescription: fmt.Sprintf("%s hosted git", cl.GitProvider), - GitNamespace: "N/A", - GitProvider: cl.GitProvider, - GitRunner: fmt.Sprintf("%s Runner", cl.GitProvider), - GitRunnerDescription: fmt.Sprintf("Self Hosted %s Runner", cl.GitProvider), - GitRunnerNS: fmt.Sprintf("%s-runner", cl.GitProvider), - GitURL: cl.GitopsTemplateURL, - GitopsRepoURL: destinationGitopsRepoURL, - - GitHubHost: fmt.Sprintf("https://github.com/%s/gitops.git", cl.GitAuth.Owner), - GitHubOwner: cl.GitAuth.Owner, - GitHubUser: cl.GitAuth.User, - - GitlabHost: cl.GitHost, - GitlabOwner: cl.GitAuth.Owner, - GitlabOwnerGroupID: cl.GitlabOwnerGroupID, - GitlabUser: cl.GitAuth.User, - - GitopsRepoAtlantisWebhookURL: cl.AtlantisWebhookURL, - GitopsRepoNoHTTPSURL: fmt.Sprintf("%s/%s/gitops.git", cl.GitHost, cl.GitAuth.Owner), - WorkloadClusterTerraformModuleURL: fmt.Sprintf("git::https://%s/%s/gitops.git//terraform/%s/modules/workload-cluster?ref=main", cl.GitHost, cl.GitAuth.Owner, cl.CloudProvider), - WorkloadClusterBootstrapTerraformModuleURL: fmt.Sprintf("git::https://%s/%s/gitops.git//terraform/%s/modules/bootstrap?ref=main", cl.GitHost, cl.GitAuth.Owner, cl.CloudProvider), - ClusterId: cl.ClusterID, - - // external-dns optionality to provide cloudflare support regardless of cloud provider - ExternalDNSProviderName: cl.DnsProvider, - ExternalDNSProviderTokenEnvName: externalDNSProviderTokenEnvName, - ExternalDNSProviderSecretName: fmt.Sprintf("%s-auth", cl.CloudProvider), - ExternalDNSProviderSecretKey: externalDNSProviderSecretKey, - - ContainerRegistryURL: fmt.Sprintf("%s/%s", containerRegistryHost, cl.GitAuth.Owner), // Not Supported for AWS ECR - } - - //Handle provider specific tokens - switch cl.CloudProvider { - case "vultr": - gitopsTemplateTokens.StateStoreBucketHostname = cl.StateStoreDetails.Hostname - case "google": - gitopsTemplateTokens.GoogleAuth = cl.GoogleAuth.KeyFile - gitopsTemplateTokens.GoogleProject = cl.GoogleAuth.ProjectId - gitopsTemplateTokens.GoogleUniqueness = strings.ToLower(randstr.String(5)) - gitopsTemplateTokens.ForceDestroy = strconv.FormatBool(true) //TODO make this optional - gitopsTemplateTokens.KubefirstArtifactsBucket = cl.StateStoreDetails.Name - gitopsTemplateTokens.VaultDataBucketName = fmt.Sprintf("%s-vault-data-%s", cl.GoogleAuth.ProjectId, cl.ClusterName) - case "aws": - gitopsTemplateTokens.KubefirstArtifactsBucket = cl.StateStoreDetails.Name - gitopsTemplateTokens.AtlantisWebhookURL = cl.AtlantisWebhookURL - } - - return gitopsTemplateTokens -} - func DetokenizeConfigKeys(serviceFilePath string, configKeys []pkgtypes.GitopsCatalogAppKeys) error { return filepath.Walk(serviceFilePath, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -602,3 +487,16 @@ func DetokenizeConfigKeys(serviceFilePath string, configKeys []pkgtypes.GitopsCa return nil }) } + +func getRegistryPath(clusterName string, cloudProvider string, isTemplate bool) string { + if isTemplate && cloudProvider != "k3d" { + return fmt.Sprintf("templates/%s", clusterName) + } + + if cloudProvider == "k3d" { + return fmt.Sprintf("registry/%s", clusterName) + } else { + return fmt.Sprintf("registry/clusters/%s", clusterName) + } + +} diff --git a/pkg/types/gitopsCatalog.go b/pkg/types/gitopsCatalog.go index d9882fc7..8c320dc1 100644 --- a/pkg/types/gitopsCatalog.go +++ b/pkg/types/gitopsCatalog.go @@ -14,13 +14,14 @@ type GitopsCatalogApps struct { // GitopsCatalogApp describes a Kubefirst gitops catalog application type GitopsCatalogApp struct { - Name string `bson:"name" json:"name" yaml:"name"` - DisplayName string `bson:"display_name" json:"display_name" yaml:"displayName"` - SecretKeys []GitopsCatalogAppKeys `bson:"secret_keys" json:"secret_keys" yaml:"secretKeys"` + Category string `bson:"category" json:"category" yaml:"category"` ConfigKeys []GitopsCatalogAppKeys `bson:"config_keys" json:"config_keys" yaml:"configKeys"` - ImageURL string `bson:"image_url" json:"image_url" yaml:"imageUrl"` Description string `bson:"description" json:"description" yaml:"description"` - Category string `bson:"category" json:"category" yaml:"category"` + DisplayName string `bson:"display_name" json:"display_name" yaml:"displayName"` + ImageURL string `bson:"image_url" json:"image_url" yaml:"imageUrl"` + IsTemplate bool `bson:"is_template" json:"is_template" yaml:"is_template"` + Name string `bson:"name" json:"name" yaml:"name"` + SecretKeys []GitopsCatalogAppKeys `bson:"secret_keys" json:"secret_keys" yaml:"secretKeys"` } // GitopsCatalogAppSecretKey describes a required secret value when creating a @@ -35,6 +36,13 @@ type GitopsCatalogAppKeys struct { // GitopsCatalogAppCreateRequest describes a request to create a service for a cluster // based on a gitops catalog app type GitopsCatalogAppCreateRequest struct { - SecretKeys []GitopsCatalogAppKeys `bson:"secret_keys,omitempty" json:"secret_keys,omitempty"` - ConfigKeys []GitopsCatalogAppKeys `bson:"config_keys,omitempty" json:"config_keys,omitempty"` + WorkloadClusterName string `bson:"workload_cluster_name" json:"workload_cluster_name"` + IsTemplate bool `bson:"is_template" json:"is_template"` + SecretKeys []GitopsCatalogAppKeys `bson:"secret_keys,omitempty" json:"secret_keys,omitempty"` + ConfigKeys []GitopsCatalogAppKeys `bson:"config_keys,omitempty" json:"config_keys,omitempty"` +} + +type GitopsCatalogAppDeleteRequest struct { + WorkloadClusterName string `bson:"workload_cluster_name" json:"workload_cluster_name"` + IsTemplate bool `bson:"is_template" json:"is_template"` } diff --git a/pkg/utils/tokens.go b/pkg/utils/tokens.go new file mode 100644 index 00000000..1f1b0a22 --- /dev/null +++ b/pkg/utils/tokens.go @@ -0,0 +1,144 @@ +/* +Copyright (C) 2021-2023, Kubefirst + +This program is licensed under MIT. +See the LICENSE file for more details. +*/ +package pkg + +import ( + "fmt" + "strconv" + "strings" + + "github.com/kubefirst/kubefirst-api/internal/constants" + "github.com/kubefirst/kubefirst-api/internal/env" + "github.com/kubefirst/kubefirst-api/pkg/providerConfigs" + pkgtypes "github.com/kubefirst/kubefirst-api/pkg/types" + "github.com/thanhpk/randstr" +) + +func CreateTokensFromDatabaseRecord(cl *pkgtypes.Cluster, registryPath string) *providerConfigs.GitopsDirectoryValues { + env, _ := env.GetEnv(constants.SilenceGetEnv) + + fullDomainName := "" + + if cl.SubdomainName != "" { + fullDomainName = fmt.Sprintf("%s.%s", cl.SubdomainName, cl.DomainName) + } else { + fullDomainName = cl.DomainName + } + + destinationGitopsRepoURL := fmt.Sprintf("https://%s/%s/gitops.git", cl.GitHost, cl.GitAuth.Owner) + + if cl.GitProtocol == "ssh" { + destinationGitopsRepoURL = fmt.Sprintf("git@%s:%s/gitops.git", cl.GitHost, cl.GitAuth.Owner) + } + + var externalDNSProviderTokenEnvName, externalDNSProviderSecretKey string + if cl.DnsProvider == "cloudflare" { + externalDNSProviderTokenEnvName = "CF_API_TOKEN" + externalDNSProviderSecretKey = "cf-api-token" + } else { + switch cl.CloudProvider { + // provider auth secret gets mapped to these values + case "aws": + externalDNSProviderTokenEnvName = "not-used-uses-service-account" + case "google": + // Normally this would be GOOGLE_APPLICATION_CREDENTIALS but we are using a service account instead and + // if you set externalDNSProviderTokenEnvName to GOOGLE_APPLICATION_CREDENTIALS then externaldns will overlook the service account + // if you want to use the provided keyfile instead of a service account then set the var accordingly + externalDNSProviderTokenEnvName = fmt.Sprintf("%s_auth", strings.ToUpper(cl.CloudProvider)) + case "civo": + externalDNSProviderTokenEnvName = fmt.Sprintf("%s_TOKEN", strings.ToUpper(cl.CloudProvider)) + case "vultr": + externalDNSProviderTokenEnvName = fmt.Sprintf("%s_API_KEY", strings.ToUpper(cl.CloudProvider)) + case "digitalocean": + externalDNSProviderTokenEnvName = "DO_TOKEN" + } + externalDNSProviderSecretKey = fmt.Sprintf("%s-auth", cl.CloudProvider) + } + + containerRegistryHost := "ghcr.io" + + if cl.GitProvider == "gitlab" { + containerRegistryHost = "registry.gitlab.com" + } + + // Default gitopsTemplateTokens + gitopsTemplateTokens := &providerConfigs.GitopsDirectoryValues{ + AlertsEmail: cl.AlertsEmail, + AtlantisAllowList: fmt.Sprintf("%s/%s/*", cl.GitHost, cl.GitAuth.Owner), + CloudProvider: cl.CloudProvider, + CloudRegion: cl.CloudRegion, + ClusterName: cl.ClusterName, + ClusterType: cl.ClusterType, + DomainName: cl.DomainName, + SubdomainName: cl.SubdomainName, + KubefirstTeam: cl.KubefirstTeam, + NodeType: cl.NodeType, + NodeCount: cl.NodeCount, + KubefirstVersion: env.KubefirstVersion, + ArgoCDIngressURL: fmt.Sprintf("https://argocd.%s", fullDomainName), + ArgoCDIngressNoHTTPSURL: fmt.Sprintf("argocd.%s", fullDomainName), + ArgoWorkflowsIngressURL: fmt.Sprintf("https://argo.%s", fullDomainName), + ArgoWorkflowsIngressNoHTTPSURL: fmt.Sprintf("argo.%s", fullDomainName), + AtlantisIngressURL: fmt.Sprintf("https://atlantis.%s", fullDomainName), + AtlantisIngressNoHTTPSURL: fmt.Sprintf("atlantis.%s", fullDomainName), + ChartMuseumIngressURL: fmt.Sprintf("https://chartmuseum.%s", fullDomainName), + VaultIngressURL: fmt.Sprintf("https://vault.%s", fullDomainName), + VaultIngressNoHTTPSURL: fmt.Sprintf("vault.%s", fullDomainName), + VouchIngressURL: fmt.Sprintf("https://vouch.%s", fullDomainName), + RegistryPath: registryPath, + + GitDescription: fmt.Sprintf("%s hosted git", cl.GitProvider), + GitNamespace: "N/A", + GitProvider: cl.GitProvider, + GitRunner: fmt.Sprintf("%s Runner", cl.GitProvider), + GitRunnerDescription: fmt.Sprintf("Self Hosted %s Runner", cl.GitProvider), + GitRunnerNS: fmt.Sprintf("%s-runner", cl.GitProvider), + GitURL: cl.GitopsTemplateURL, + GitopsRepoURL: destinationGitopsRepoURL, + + GitHubHost: fmt.Sprintf("https://github.com/%s/gitops.git", cl.GitAuth.Owner), + GitHubOwner: cl.GitAuth.Owner, + GitHubUser: cl.GitAuth.User, + + GitlabHost: cl.GitHost, + GitlabOwner: cl.GitAuth.Owner, + GitlabOwnerGroupID: cl.GitlabOwnerGroupID, + GitlabUser: cl.GitAuth.User, + + GitopsRepoAtlantisWebhookURL: cl.AtlantisWebhookURL, + GitopsRepoNoHTTPSURL: fmt.Sprintf("%s/%s/gitops.git", cl.GitHost, cl.GitAuth.Owner), + WorkloadClusterTerraformModuleURL: fmt.Sprintf("git::https://%s/%s/gitops.git//terraform/%s/modules/workload-cluster?ref=main", cl.GitHost, cl.GitAuth.Owner, cl.CloudProvider), + WorkloadClusterBootstrapTerraformModuleURL: fmt.Sprintf("git::https://%s/%s/gitops.git//terraform/%s/modules/bootstrap?ref=main", cl.GitHost, cl.GitAuth.Owner, cl.CloudProvider), + ClusterId: cl.ClusterID, + + // external-dns optionality to provide cloudflare support regardless of cloud provider + ExternalDNSProviderName: cl.DnsProvider, + ExternalDNSProviderTokenEnvName: externalDNSProviderTokenEnvName, + ExternalDNSProviderSecretName: fmt.Sprintf("%s-auth", cl.CloudProvider), + ExternalDNSProviderSecretKey: externalDNSProviderSecretKey, + + ContainerRegistryURL: fmt.Sprintf("%s/%s", containerRegistryHost, cl.GitAuth.Owner), // Not Supported for AWS ECR + } + + //Handle provider specific tokens + switch cl.CloudProvider { + case "vultr": + gitopsTemplateTokens.StateStoreBucketHostname = cl.StateStoreDetails.Hostname + case "google": + gitopsTemplateTokens.GoogleAuth = cl.GoogleAuth.KeyFile + gitopsTemplateTokens.GoogleProject = cl.GoogleAuth.ProjectId + gitopsTemplateTokens.GoogleUniqueness = strings.ToLower(randstr.String(5)) + gitopsTemplateTokens.ForceDestroy = strconv.FormatBool(true) //TODO make this optional + gitopsTemplateTokens.KubefirstArtifactsBucket = cl.StateStoreDetails.Name + gitopsTemplateTokens.VaultDataBucketName = fmt.Sprintf("%s-vault-data-%s", cl.GoogleAuth.ProjectId, cl.ClusterName) + case "aws": + gitopsTemplateTokens.KubefirstArtifactsBucket = cl.StateStoreDetails.Name + gitopsTemplateTokens.AtlantisWebhookURL = cl.AtlantisWebhookURL + } + + return gitopsTemplateTokens +}