diff --git a/internal/db/gitopsCatalog.go b/internal/db/gitopsCatalog.go index f90df01a..d2c60c64 100644 --- a/internal/db/gitopsCatalog.go +++ b/internal/db/gitopsCatalog.go @@ -14,6 +14,7 @@ import ( log "github.com/rs/zerolog/log" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" + "golang.org/x/exp/slices" ) // GetGitopsCatalogApps @@ -28,6 +29,29 @@ func (mdbcl *MongoDBClient) GetGitopsCatalogApps() (types.GitopsCatalogApps, err return result, nil } +// GetGitopsCatalogAppsByCloudProvider +func (mdbcl *MongoDBClient) GetGitopsCatalogAppsByCloudProvider(cloudProvider string, gitProvider string) (types.GitopsCatalogApps, error) { + // Find + var result types.GitopsCatalogApps + + err := mdbcl.GitopsCatalogCollection.FindOne(mdbcl.Context, bson.D{}).Decode(&result) + if err != nil { + return types.GitopsCatalogApps{}, fmt.Errorf("error getting gitops catalog apps: %s", err) + } + + filteredApps := []types.GitopsCatalogApp{} + + for _, app := range result.Apps { + if !slices.Contains(app.CloudDenylist, cloudProvider) && !slices.Contains(app.GitDenylist, gitProvider) { + filteredApps = append(filteredApps, app) + } + } + + result.Apps = filteredApps + + return result, nil +} + // UpdateGitopsCatalogApps func (mdbcl *MongoDBClient) UpdateGitopsCatalogApps() error { mpapps, err := gitopsCatalog.ReadActiveApplications() diff --git a/internal/router/api/v1/gitopsCatalog.go b/internal/router/api/v1/gitopsCatalog.go index cb2fd57b..de479dd2 100644 --- a/internal/router/api/v1/gitopsCatalog.go +++ b/internal/router/api/v1/gitopsCatalog.go @@ -22,11 +22,35 @@ import ( // @Produce json // @Success 200 {object} types.GitopsCatalogApps // @Failure 400 {object} types.JSONFailureResponse -// @Router /gitops-catalog/apps [get] +// @Router /gitops-catalog/:cluster_name/:cloud_provider/apps [get] // @Param Authorization header string true "API key" default(Bearer ) // GetGitopsCatalogApps returns a list of available Kubefirst gitops catalog applications func GetGitopsCatalogApps(c *gin.Context) { - apps, err := db.Client.GetGitopsCatalogApps() + cloudProvider, param := c.Params.Get("cloud_provider") + if !param { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: ":cloud_provider not provided", + }) + return + } + + clusterName, param := c.Params.Get("cluster_name") + if !param { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: ":cluster_name not provided", + }) + return + } + + cluster, err := db.Client.GetCluster(clusterName) + if err != nil { + c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ + Message: "cluster not found", + }) + return + } + + apps, err := db.Client.GetGitopsCatalogAppsByCloudProvider(cloudProvider, cluster.GitProvider) if err != nil { c.JSON(http.StatusBadRequest, types.JSONFailureResponse{ Message: err.Error(), diff --git a/internal/router/router.go b/internal/router/router.go index 65d05609..7a12f1a3 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -62,7 +62,7 @@ func SetupRouter() *gin.Engine { v1.POST("/secret/:cluster_name/:secret", router.UpdateClusterSecret) // Gitops Catalog - v1.GET("/gitops-catalog/apps", middleware.ValidateAPIKey(), router.GetGitopsCatalogApps) + v1.GET("/gitops-catalog/:cluster_name/:cloud_provider/apps", middleware.ValidateAPIKey(), router.GetGitopsCatalogApps) v1.GET("/gitops-catalog/apps/update", middleware.ValidateAPIKey(), router.UpdateGitopsCatalogApps) // Services diff --git a/internal/services/services.go b/internal/services/services.go index 4c17c442..72d0c96a 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -26,6 +26,7 @@ import ( "github.com/kubefirst/kubefirst-api/internal/db" "github.com/kubefirst/kubefirst-api/internal/env" "github.com/kubefirst/kubefirst-api/internal/gitShim" + "github.com/kubefirst/kubefirst-api/pkg/common" "github.com/kubefirst/kubefirst-api/pkg/providerConfigs" pkgtypes "github.com/kubefirst/kubefirst-api/pkg/types" utils "github.com/kubefirst/kubefirst-api/pkg/utils" @@ -182,6 +183,9 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi } } + // Get Ingress links + links := common.GetIngressLinks(catalogServiceFolder, fullDomainName) + err = cp.Copy(catalogServiceFolder, clusterRegistryPath, cp.Options{}) if err != nil { log.Error().Msgf("Error populating gitops repository with catalog components content: %s. error: %s", serviceName, err.Error()) @@ -221,7 +225,7 @@ func CreateService(cl *pkgtypes.Cluster, serviceName string, appDef *pkgtypes.Gi Default: false, Description: appDef.Description, Image: appDef.ImageURL, - Links: []string{}, + Links: links, Status: "", CreatedBy: req.User, }) diff --git a/pkg/common/common.go b/pkg/common/common.go new file mode 100644 index 00000000..ee31457a --- /dev/null +++ b/pkg/common/common.go @@ -0,0 +1,73 @@ +package common + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" +) + +func GetIngressLinks(path string, domainName string) []string { + links := []string{} + + regexPattern := regexp.MustCompile(fmt.Sprintf(`\b(?:[a-zA-Z0-9-]+\.)*%s\b[^ ]*`, domainName)) + + filepath.Walk(path, func(path string, info os.FileInfo, err error) error { + if err != nil { + fmt.Println("Error accessing path:", path, err) + return err + } + if !info.IsDir() { + // Process each file + fmt.Println("Processing file:", path) + matches := processFile(path, regexPattern) + links = append(links, matches...) + } + return nil + }) + + return RemoveDuplicatesLinks(links) +} + +// processFile reads the file at given path and return all links matching the domain +func processFile(filePath string, regexPattern *regexp.Regexp) []string { + links := []string{} + + file, err := os.Open(filePath) + if err != nil { + fmt.Println("Error opening file:", err) + return links + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + matches := regexPattern.FindAllString(line, -1) + + links = append(links, matches...) + } + + if err := scanner.Err(); err != nil { + fmt.Println("Error reading file:", err) + } + + return links +} + +// removeDuplicates returns a new slice with duplicates removed +func RemoveDuplicatesLinks(items []string) []string { + seen := make(map[string]struct{}) // map to keep track of seen items + var result []string + + for _, item := range items { + if _, ok := seen[item]; !ok && !strings.Contains(item, ".git") { + seen[item] = struct{}{} // Mark item as seen + result = append(result, fmt.Sprintf("https://%s", item)) + } + } + + return result +} diff --git a/pkg/types/gitopsCatalog.go b/pkg/types/gitopsCatalog.go index 015d5d5e..de4383de 100644 --- a/pkg/types/gitopsCatalog.go +++ b/pkg/types/gitopsCatalog.go @@ -14,14 +14,16 @@ type GitopsCatalogApps struct { // GitopsCatalogApp describes a Kubefirst gitops catalog application type GitopsCatalogApp struct { - Category string `bson:"category" json:"category" yaml:"category"` - ConfigKeys []GitopsCatalogAppKeys `bson:"config_keys" json:"config_keys" yaml:"configKeys"` - Description string `bson:"description" json:"description" yaml:"description"` - 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"` + Category string `bson:"category" json:"category" yaml:"category"` + ConfigKeys []GitopsCatalogAppKeys `bson:"config_keys" json:"config_keys" yaml:"configKeys"` + Description string `bson:"description" json:"description" yaml:"description"` + 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"` + CloudDenylist []string `bson:"cloudDenylist" json:"cloudDenylist" yaml:"cloudDenylist"` + GitDenylist []string `bson:"gitDenylist" json:"gitDenylist" yaml:"gitDenylist"` } // GitopsCatalogAppSecretKey describes a required secret value when creating a