diff --git a/internal/router/api/v1/services.go b/internal/router/api/v1/services.go index 6827fa93..d961a487 100644 --- a/internal/router/api/v1/services.go +++ b/internal/router/api/v1/services.go @@ -185,6 +185,100 @@ func PostAddServiceToCluster(c *gin.Context) { }) } +// PostValidateService godoc +// @Summary Validate gitops catalog application +// @Description Validate a gitops catalog application so it can be deleted +// @Tags services +// @Accept json +// @Produce json +// @Param cluster_name path string true "Cluster name" +// @Param service_name path string true "Service name to be validated" +// @Param definition body types.GitopsCatalogAppCreateRequest true "Service create request in JSON format" +// @Success 202 {object} types.GitopsCatalogAppValidateRequest +// @Failure 400 {object} types.JSONFailureResponse +// @Router /services/:cluster_name/:service_name/validate [post] +// @Param Authorization header string true "API key" default(Bearer ) +// PostValidateService handles a request to add a service to a cluster based on a gitops catalog app +func PostValidateService(c *gin.Context) { + clusterName, param := c.Params.Get("cluster_name") + if !param { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: ":cluster_name not provided", + }) + return + } + + serviceName, param := c.Params.Get("service_name") + if !param { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: ":service_name not provided", + }) + return + } + + // Verify cluster exists + _, err := db.Client.GetCluster(clusterName) + if err != nil { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: "cluster not found", + }) + return + } + + // Verify service is a valid option and determine if it requires secrets + apps, err := db.Client.GetGitopsCatalogApps() + if err != nil { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: err.Error(), + }) + return + } + valid := false + for _, app := range apps.Apps { + if app.Name == serviceName { + valid = true + } + } + + if !valid { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: fmt.Sprintf("service %s is not valid", serviceName), + }) + return + } + + // Bind to variable as application/json, handle error + var serviceDefinition pkgtypes.GitopsCatalogAppCreateRequest + err = c.Bind(&serviceDefinition) + if err != nil { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: err.Error(), + }) + return + } + + // Generate and apply + cl, err := db.Client.GetCluster(clusterName) + if err != nil { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: err.Error(), + }) + return + } + + err, canDeleteService := services.ValidateService(&cl, serviceName, &serviceDefinition) + if err != nil { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: err.Error(), + }) + return + } + + c.JSON(http.StatusOK, pkgtypes.GitopsCatalogAppValidateRequest{ + CanDeleteService: canDeleteService, + }) +} + // DeleteServiceFromCluster godoc // @Summary Remove a gitops catalog application from a cluster // @Description Remove a gitops catalog application from a cluster diff --git a/internal/router/router.go b/internal/router/router.go index 7a12f1a3..d0554266 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -68,6 +68,7 @@ func SetupRouter() *gin.Engine { // Services v1.GET("/services/:cluster_name", middleware.ValidateAPIKey(), router.GetServices) v1.POST("/services/:cluster_name/:service_name", middleware.ValidateAPIKey(), router.PostAddServiceToCluster) + v1.POST("/services/:cluster_name/:service_name/validate", middleware.ValidateAPIKey(), router.PostValidateService) v1.DELETE("/services/:cluster_name/:service_name", middleware.ValidateAPIKey(), router.DeleteServiceFromCluster) // Domains diff --git a/internal/services/services.go b/internal/services/services.go index 72d0c96a..d0a1ebdc 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -46,12 +46,12 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi return fmt.Errorf("cluster %s - unable to deploy service %s to cluster: cannot deploy services to a cluster in %s state", cl.ClusterName, serviceName, cl.Status) } - homeDir, err := os.UserHomeDir() + homeDir, _ := os.UserHomeDir() tmpGitopsDir := fmt.Sprintf("%s/.k1/%s/%s/gitops", homeDir, cl.ClusterName, serviceName) tmpGitopsCatalogDir := fmt.Sprintf("%s/.k1/%s/%s/gitops-catalog", homeDir, cl.ClusterName, serviceName) // Remove gitops dir - err = os.RemoveAll(tmpGitopsDir) + err := os.RemoveAll(tmpGitopsDir) if err != nil { log.Fatal().Msgf("error removing gitops dir %s: %s", tmpGitopsDir, err) return err @@ -209,7 +209,7 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi return fmt.Errorf("cluster %s - error pushing commit for service file: %s", clusterName, err) } - existingService, err := db.Client.GetServices(clusterName) + existingService, _ := db.Client.GetServices(clusterName) if existingService.ClusterName == "" { // Add to list @@ -311,14 +311,112 @@ func DeleteService(cl *pkgtypes.Cluster, serviceName string, def pkgtypes.Gitops return fmt.Errorf("cluster %s - error finding service: %s", clusterName, err) } - homeDir, err := os.UserHomeDir() + if !def.SkipFiles { + homeDir, _ := 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 removing gitops dir %s: %s", tmpGitopsDir, err) + return err + } + + 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) + + err = gitShim.PullWithAuth( + gitopsRepo, + cl.GitProvider, + "main", + &githttps.BasicAuth{ + Username: cl.GitAuth.User, + Password: cl.GitAuth.Token, + }, + ) + + if err != nil { + 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", 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("removing %s from the cluster %s on behalf of %s", serviceName, clusterName, def.User)) + if err != nil { + return fmt.Errorf("cluster %s - error deleting service file: %s", clusterName, err) + } + + err = gitopsRepo.Push(&git.PushOptions{ + 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", clusterName, err) + } + } + + err = db.Client.DeleteClusterServiceListEntry(clusterName, &svc) + if err != nil { + return err + } + + return nil +} + +// ValidateService +func ValidateService(cl *pkgtypes.Cluster, serviceName string, def *pkgtypes.GitopsCatalogAppCreateRequest) (error, bool) { + canDeleleteService := true + + var gitopsRepo *git.Repository + + clusterName := cl.ClusterName + + if def.WorkloadClusterName != "" { + clusterName = def.WorkloadClusterName + } + + homeDir, _ := os.UserHomeDir() tmpGitopsDir := fmt.Sprintf("%s/.k1/%s/%s/gitops", homeDir, cl.ClusterName, serviceName) // Remove gitops dir - err = os.RemoveAll(tmpGitopsDir) + err := os.RemoveAll(tmpGitopsDir) if err != nil { log.Fatal().Msgf("error removing gitops dir %s: %s", tmpGitopsDir, err) - return err + return err, false } err = gitShim.PrepareGitEnvironment(cl, tmpGitopsDir) @@ -331,7 +429,6 @@ func DeleteService(cl *pkgtypes.Cluster, serviceName string, def pkgtypes.Gitops 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) err = gitShim.PullWithAuth( gitopsRepo, @@ -350,49 +447,10 @@ func DeleteService(cl *pkgtypes.Cluster, serviceName string, def pkgtypes.Gitops // 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", 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("removing %s from the cluster %s on behalf of %s", serviceName, clusterName, def.User)) - if err != nil { - return fmt.Errorf("cluster %s - error deleting service file: %s", clusterName, err) + canDeleleteService = false } - err = gitopsRepo.Push(&git.PushOptions{ - 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", clusterName, err) - } - - err = db.Client.DeleteClusterServiceListEntry(clusterName, &svc) - if err != nil { - return err - } - - return nil + return nil, canDeleleteService } // AddDefaultServices diff --git a/pkg/types/gitopsCatalog.go b/pkg/types/gitopsCatalog.go index de4383de..beb116d6 100644 --- a/pkg/types/gitopsCatalog.go +++ b/pkg/types/gitopsCatalog.go @@ -46,8 +46,14 @@ type GitopsCatalogAppCreateRequest struct { Environment string `bson:"environment" json:"environment"` } +// GitopsCatalogAppValidateRequest +type GitopsCatalogAppValidateRequest struct { + CanDeleteService bool `bson:"can_delete_service" json:"can_delete_service"` +} + type GitopsCatalogAppDeleteRequest struct { User string `bson:"user" json:"user"` IsTemplate bool `bson:"is_template" json:"is_template"` WorkloadClusterName string `bson:"workload_cluster_name" json:"workload_cluster_name"` + SkipFiles bool `bson:"skip_files" json:"skip_files"` }