package catalogue import ( "encoding/json" "fmt" "io/ioutil" "os" "path" "coopcloud.tech/abra/cli/formatter" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/catalogue" "coopcloud.tech/abra/pkg/config" gitPkg "coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/limit" "github.com/AlecAivazis/survey/v2" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) // CatalogueSkipList is all the repos that are not recipes. var CatalogueSkipList = map[string]bool{ "abra": true, "abra-apps": true, "abra-aur": true, "abra-bash": true, "abra-capsul": true, "abra-gandi": true, "abra-hetzner": true, "apps": true, "aur-abra-git": true, "auto-apps-json": true, "auto-mirror": true, "backup-bot": true, "backup-bot-two": true, "comrade-renovate-bot": true, "coopcloud.tech": true, "coturn": true, "docker-cp-deploy": true, "docker-dind-bats-kcov": true, "docs.coopcloud.tech": true, "drone-abra": true, "example": true, "gardening": true, "go-abra": true, "organising": true, "outline-with-patch": true, "pyabra": true, "radicle-seed-node": true, "recipes": true, "stack-ssh-deploy": true, "swarm-cronjob": true, "tagcmp": true, "traefik-cert-dumper": true, "tyop": true, } var catalogueGenerateCommand = &cli.Command{ Name: "generate", Aliases: []string{"g"}, Usage: "Generate a new copy of the catalogue", Flags: []cli.Flag{ internal.PushFlag, internal.CommitFlag, internal.CommitMessageFlag, internal.DryFlag, }, Description: ` This command generates a new copy of the recipe catalogue which can be found on: https://recipes.coopcloud.tech It polls the entire git.coopcloud.tech/coop-cloud/... recipe repository listing, parses README and tags to produce recipe metadata and produces a apps.json file which is placed in your ~/.abra/catalogue/recipes.json. It is possible to generate new metadata for a single recipe by passing . The existing local catalogue will be updated, not overwritten. A new catalogue copy can be published to the recipes repository by passing the "--commit" and "--push" flags. The recipes repository is available here: https://git.coopcloud.tech/coop-cloud/recipes `, ArgsUsage: "[]", Action: func(c *cli.Context) error { recipeName := c.Args().First() if recipeName != "" { internal.ValidateRecipe(c) } catalogueDir := path.Join(config.ABRA_DIR, "catalogue") url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes") if err := gitPkg.Clone(catalogueDir, url); err != nil { return err } repos, err := catalogue.ReadReposMetadata() if err != nil { logrus.Fatal(err) } logrus.Debugf("ensuring '%v' recipe(s) are locally present and up-to-date", len(repos)) var barLength int if recipeName != "" { barLength = 1 } else { barLength = len(repos) } cloneLimiter := limit.New(10) retrieveBar := formatter.CreateProgressbar(len(repos), "retrieving recipes from recipes.coopcloud.tech...") ch := make(chan string, barLength) for _, repoMeta := range repos { go func(rm catalogue.RepoMeta) { cloneLimiter.Begin() defer cloneLimiter.End() if recipeName != "" && recipeName != rm.Name { ch <- rm.Name retrieveBar.Add(1) return } if _, exists := CatalogueSkipList[rm.Name]; exists { ch <- rm.Name retrieveBar.Add(1) return } recipeDir := path.Join(config.ABRA_DIR, "apps", rm.Name) if err := gitPkg.Clone(recipeDir, rm.SSHURL); err != nil { logrus.Fatal(err) } isClean, err := gitPkg.IsClean(rm.Name) if err != nil { logrus.Fatal(err) } if !isClean { logrus.Fatalf("'%s' has locally unstaged changes", rm.Name) } if err := gitPkg.EnsureUpToDate(recipeDir); err != nil { logrus.Fatal(err) } ch <- rm.Name retrieveBar.Add(1) }(repoMeta) } for range repos { <-ch // wait for everything } catl := make(catalogue.RecipeCatalogue) catlBar := formatter.CreateProgressbar(barLength, "generating catalogue metadata...") for _, recipeMeta := range repos { if recipeName != "" && recipeName != recipeMeta.Name { catlBar.Add(1) continue } if _, exists := CatalogueSkipList[recipeMeta.Name]; exists { catlBar.Add(1) continue } versions, err := catalogue.GetRecipeVersions(recipeMeta.Name) if err != nil { logrus.Fatal(err) } features, category, err := catalogue.GetRecipeFeaturesAndCategory(recipeMeta.Name) if err != nil { logrus.Warn(err) } catl[recipeMeta.Name] = catalogue.RecipeMeta{ Name: recipeMeta.Name, Repository: recipeMeta.CloneURL, Icon: recipeMeta.AvatarURL, DefaultBranch: recipeMeta.DefaultBranch, Description: recipeMeta.Description, Website: recipeMeta.Website, Versions: versions, Category: category, Features: features, } catlBar.Add(1) } recipesJSON, err := json.MarshalIndent(catl, "", " ") if err != nil { logrus.Fatal(err) } if _, err := os.Stat(config.APPS_JSON); err != nil && os.IsNotExist(err) { if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0764); err != nil { logrus.Fatal(err) } } else { if recipeName != "" { catlFS, err := catalogue.ReadRecipeCatalogue() if err != nil { logrus.Fatal(err) } catlFS[recipeName] = catl[recipeName] updatedRecipesJSON, err := json.MarshalIndent(catlFS, "", " ") if err != nil { logrus.Fatal(err) } if err := ioutil.WriteFile(config.APPS_JSON, updatedRecipesJSON, 0764); err != nil { logrus.Fatal(err) } } } logrus.Infof("generated new recipe catalogue in %s", config.APPS_JSON) if internal.Commit { if internal.CommitMessage == "" && !internal.NoInput { prompt := &survey.Input{ Message: "commit message", Default: fmt.Sprintf("chore: publish catalogue changes"), } if err := survey.AskOne(prompt, &internal.CommitMessage); err != nil { logrus.Fatal(err) } } cataloguePath := path.Join(config.ABRA_DIR, "catalogue") if err := gitPkg.Commit(cataloguePath, "**.json", internal.CommitMessage, internal.Dry, internal.Push); err != nil { logrus.Fatal(err) } } return nil }, BashComplete: autocomplete.RecipeNameComplete, } // CatalogueCommand defines the `abra catalogue` command and sub-commands. var CatalogueCommand = &cli.Command{ Name: "catalogue", Usage: "Manage the recipe catalogue", Aliases: []string{"c"}, ArgsUsage: "", Description: "This command helps recipe packagers interact with the recipe catalogue", Subcommands: []*cli.Command{ catalogueGenerateCommand, }, }