diff --git a/cli/catalogue/catalogue.go b/cli/catalogue/catalogue.go index 6138d3b1..02f13cd1 100644 --- a/cli/catalogue/catalogue.go +++ b/cli/catalogue/catalogue.go @@ -13,7 +13,6 @@ import ( gitPkg "coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/limit" "coopcloud.tech/abra/pkg/recipe" - "github.com/AlecAivazis/survey/v2" "github.com/go-git/go-git/v5" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -59,11 +58,9 @@ var CatalogueSkipList = map[string]bool{ var catalogueGenerateCommand = &cli.Command{ Name: "generate", Aliases: []string{"g"}, - Usage: "Generate a new copy of the recipe catalogue", + Usage: "Generate the recipe catalogue", Flags: []cli.Flag{ - internal.PushFlag, - internal.CommitFlag, - internal.CommitMessageFlag, + internal.PublishFlag, internal.DryFlag, internal.SkipUpdatesFlag, internal.RegistryUsernameFlag, @@ -81,14 +78,13 @@ metadata and produces a recipes JSON file. 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 - It is quite easy to get rate limited by Docker Hub when running this command. If you have a Hub account you can have Abra log you in to avoid this. Pass -"--username" and "--password". +"--user" and "--pass". + +Push your new release git.coopcloud.tech with "-p/--publish". This requires +that you have permission to git push to these repositories and have your SSH +keys configured on your account. `, ArgsUsage: "[]", Action: func(c *cli.Context) error { @@ -197,37 +193,43 @@ If you have a Hub account you can have Abra log you in to avoid this. Pass logrus.Infof("generated new recipe catalogue in %s", config.RECIPES_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) - } - } - + if internal.Publish { cataloguePath := path.Join(config.ABRA_DIR, "catalogue") - if err := gitPkg.Commit(cataloguePath, "**.json", internal.CommitMessage, internal.Dry); err != nil { + + isClean, err := gitPkg.IsClean(cataloguePath) + if err != nil { logrus.Fatal(err) } - if internal.Push { - repo, err := git.PlainOpen(cataloguePath) - if err != nil { - logrus.Fatal(err) - } - - sshURL := fmt.Sprintf(config.SSH_URL_TEMPLATE, "recipes") - if err := gitPkg.CreateRemote(repo, "origin-ssh", sshURL, internal.Dry); err != nil { - logrus.Fatal(err) - } - - if err := gitPkg.Push(cataloguePath, "origin-ssh", false, internal.Dry); err != nil { - logrus.Fatal(err) - } + if isClean { + logrus.Fatalf("no changes discovered in %s, nothing to publish?", cataloguePath) } + + msg := "chore: publish new catalogue release changes" + if err := gitPkg.Commit(cataloguePath, "**.json", msg, internal.Dry); err != nil { + logrus.Fatal(err) + } + + repo, err := git.PlainOpen(cataloguePath) + if err != nil { + logrus.Fatal(err) + } + + sshURL := fmt.Sprintf(config.SSH_URL_TEMPLATE, "recipes") + if err := gitPkg.CreateRemote(repo, "origin-ssh", sshURL, internal.Dry); err != nil { + logrus.Fatal(err) + } + + if err := gitPkg.Push(cataloguePath, "origin-ssh", false, internal.Dry); err != nil { + logrus.Fatal(err) + } + } + + if internal.Dry { + logrus.Info("dry run: no changes published") + } else { + url := fmt.Sprintf("%s/recipes", config.REPOS_BASE_URL) + logrus.Infof("new changes published: %s", url) } return nil @@ -281,7 +283,7 @@ func updateRepositories(repos recipe.RepoCatalogue, recipeName string) error { logrus.Fatal(err) } - isClean, err := gitPkg.IsClean(rm.Name) + isClean, err := gitPkg.IsClean(recipeDir) if err != nil { logrus.Fatal(err) } diff --git a/cli/internal/common.go b/cli/internal/common.go index b3833e9b..17a02fef 100644 --- a/cli/internal/common.go +++ b/cli/internal/common.go @@ -306,7 +306,7 @@ var PatchFlag = &cli.BoolFlag{ Name: "patch", Usage: "Increase the patch part of the version", Value: false, - Aliases: []string{"p", "z"}, + Aliases: []string{"pa", "z"}, Destination: &Patch, } @@ -319,38 +319,13 @@ var DryFlag = &cli.BoolFlag{ Destination: &Dry, } -var Push bool -var PushFlag = &cli.BoolFlag{ - Name: "push", - Usage: "Git push changes", +var Publish bool +var PublishFlag = &cli.BoolFlag{ + Name: "publish", + Usage: "Publish changes to git.coopcloud.tech", Value: false, - Aliases: []string{"P"}, - Destination: &Push, -} - -var CommitMessage string -var CommitMessageFlag = &cli.StringFlag{ - Name: "commit-message", - Usage: "Commit message (implies --commit)", - Aliases: []string{"cm"}, - Destination: &CommitMessage, -} - -var Commit bool -var CommitFlag = &cli.BoolFlag{ - Name: "commit", - Usage: "Commit new changes", - Value: false, - Aliases: []string{"c"}, - Destination: &Commit, -} - -var TagMessage string -var TagMessageFlag = &cli.StringFlag{ - Name: "tag-comment", - Usage: "Description for release tag", - Aliases: []string{"t", "tm"}, - Destination: &TagMessage, + Aliases: []string{"p"}, + Destination: &Publish, } var Domain string @@ -439,14 +414,14 @@ var SkipUpdatesFlag = &cli.BoolFlag{ Name: "skip-updates", Aliases: []string{"s"}, Value: false, - Usage: "Skip updating git repositories", + Usage: "Skip updating recipe repositories", Destination: &SkipUpdates, } var RegistryUsername string var RegistryUsernameFlag = &cli.StringFlag{ Name: "username", - Aliases: []string{"u"}, + Aliases: []string{"user"}, Value: "", Usage: "Registry username", EnvVars: []string{"REGISTRY_USERNAME"}, @@ -456,7 +431,7 @@ var RegistryUsernameFlag = &cli.StringFlag{ var RegistryPassword string var RegistryPasswordFlag = &cli.StringFlag{ Name: "password", - Aliases: []string{"p"}, + Aliases: []string{"pass"}, Value: "", Usage: "Registry password", EnvVars: []string{"REGISTRY_PASSWORD"}, diff --git a/cli/internal/recipe.go b/cli/internal/recipe.go index 889bdc1d..154afdcc 100644 --- a/cli/internal/recipe.go +++ b/cli/internal/recipe.go @@ -6,6 +6,7 @@ import ( "coopcloud.tech/abra/pkg/recipe" "github.com/AlecAivazis/survey/v2" + "github.com/docker/distribution/reference" "github.com/sirupsen/logrus" ) @@ -64,20 +65,29 @@ func SetBumpType(bumpType string) { } } -// GetMainApp retrieves the main 'app' image name -func GetMainApp(recipe recipe.Recipe) string { - var app string +// GetMainAppImage retrieves the main 'app' image name +func GetMainAppImage(recipe recipe.Recipe) (string, error) { + var path string for _, service := range recipe.Config.Services { - name := service.Name - if name == "app" { - app = strings.Split(service.Image, ":")[0] + if service.Name == "app" { + img, err := reference.ParseNormalizedNamed(service.Image) + if err != nil { + return "", err + } + + path = reference.Path(img) + if strings.Contains(path, "library") { + path = strings.Split(path, "/")[1] + } + + return path, nil } } - if app == "" { - logrus.Fatalf("%s has no main 'app' service?", recipe.Name) + if path == "" { + return path, fmt.Errorf("%s has no main 'app' service?", recipe.Name) } - return app + return path, nil } diff --git a/cli/recipe/release.go b/cli/recipe/release.go index 723a4052..97f6fcca 100644 --- a/cli/recipe/release.go +++ b/cli/recipe/release.go @@ -47,20 +47,16 @@ Abra does its best to read the "a.b.c" version scheme and communicate what action needs to be taken when performing different operations such as an update or a rollback of an app. -You may invoke this command in "wizard" mode and be prompted for input: - - abra recipe release gitea - +Publish your new release git.coopcloud.tech with "-p/--publish". This requires +that you have permission to git push to these repositories and have your SSH +keys configured on your account. `, Flags: []cli.Flag{ internal.DryFlag, internal.MajorFlag, internal.MinorFlag, internal.PatchFlag, - internal.PushFlag, - internal.CommitFlag, - internal.CommitMessageFlag, - internal.TagMessageFlag, + internal.PublishFlag, }, BashComplete: autocomplete.RecipeNameComplete, Action: func(c *cli.Context) error { @@ -71,7 +67,11 @@ You may invoke this command in "wizard" mode and be prompted for input: logrus.Fatal(err) } - mainApp := internal.GetMainApp(recipe) + mainApp, err := internal.GetMainAppImage(recipe) + if err != nil { + logrus.Fatal(err) + } + mainAppVersion := imagesTmp[mainApp] if mainAppVersion == "" { logrus.Fatalf("main app service version for %s is empty?", recipe.Name) @@ -201,14 +201,14 @@ func createReleaseFromTag(recipe recipe.Recipe, tagString, mainAppVersion string tag.MissingPatch = false } - if err := commitRelease(recipe); err != nil { - logrus.Fatal(err) - } - if tagString == "" { tagString = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion) } + if err := commitRelease(recipe, tagString); err != nil { + logrus.Fatal(err) + } + if err := tagRelease(tagString, repo); err != nil { logrus.Fatal(err) } @@ -230,50 +230,30 @@ func btoi(b bool) int { } // getTagCreateOptions constructs git tag create options -func getTagCreateOptions() (git.CreateTagOptions, error) { - if internal.TagMessage == "" && !internal.NoInput { - prompt := &survey.Input{ - Message: "git tag message?", - Default: "chore: publish new release", - } - - if err := survey.AskOne(prompt, &internal.TagMessage); err != nil { - return git.CreateTagOptions{}, err - } - } - - return git.CreateTagOptions{Message: internal.TagMessage}, nil +func getTagCreateOptions(tag string) (git.CreateTagOptions, error) { + msg := fmt.Sprintf("chore: publish %s release", tag) + return git.CreateTagOptions{Message: msg}, nil } -func commitRelease(recipe recipe.Recipe) error { +func commitRelease(recipe recipe.Recipe, tag string) error { if internal.Dry { - logrus.Info("dry run: no changed committed") + logrus.Debugf("dry run: no changes committed") return nil } - if !internal.Commit && !internal.NoInput { - prompt := &survey.Confirm{ - Message: "git commit changes?", - } - - if err := survey.AskOne(prompt, &internal.Commit); err != nil { - return err - } + isClean, err := gitPkg.IsClean(recipe.Dir()) + if err != nil { + return err } - if internal.CommitMessage == "" && !internal.NoInput && internal.Commit { - prompt := &survey.Input{ - Message: "git commit message?", - Default: "chore: publish new version", - } - if err := survey.AskOne(prompt, &internal.CommitMessage); err != nil { - return err - } + if isClean { + return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir()) } - if internal.Commit { + if internal.Publish { + msg := fmt.Sprintf("chore: publish %s release", tag) repoPath := path.Join(config.RECIPES_DIR, recipe.Name) - if err := gitPkg.Commit(repoPath, "compose.**yml", internal.CommitMessage, internal.Dry); err != nil { + if err := gitPkg.Commit(repoPath, "compose.**yml", msg, internal.Dry); err != nil { return err } } @@ -283,7 +263,7 @@ func commitRelease(recipe recipe.Recipe) error { func tagRelease(tagString string, repo *git.Repository) error { if internal.Dry { - logrus.Infof("dry run: no git tag created (%s)", tagString) + logrus.Debugf("dry run: no git tag created (%s)", tagString) return nil } @@ -292,7 +272,7 @@ func tagRelease(tagString string, repo *git.Repository) error { return err } - createTagOptions, err := getTagCreateOptions() + createTagOptions, err := getTagCreateOptions(tagString) if err != nil { return err } @@ -303,33 +283,36 @@ func tagRelease(tagString string, repo *git.Repository) error { } hash := abraFormatter.SmallSHA(head.Hash().String()) - logrus.Info(fmt.Sprintf("created tag %s at %s", tagString, hash)) + logrus.Debugf(fmt.Sprintf("created tag %s at %s", tagString, hash)) return nil } func pushRelease(recipe recipe.Recipe) error { if internal.Dry { - logrus.Info("dry run: no changes pushed") + logrus.Info("dry run: no changes published") return nil } - if !internal.Push && !internal.NoInput { + if !internal.Publish && !internal.NoInput { prompt := &survey.Confirm{ - Message: "git push changes?", + Message: "publish new release?", } - if err := survey.AskOne(prompt, &internal.Push); err != nil { + if err := survey.AskOne(prompt, &internal.Publish); err != nil { return err } } - if internal.Push { + if internal.Publish { if err := recipe.Push(internal.Dry); err != nil { return err } } + url := fmt.Sprintf("%s/%s/tags", config.REPOS_BASE_URL, recipe.Name) + logrus.Infof("new release published: %s", url) + return nil } @@ -392,13 +375,13 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip newTag.Major = strconv.Itoa(now + 1) } - if err := commitRelease(recipe); err != nil { - logrus.Fatal(err) - } - newTag.Metadata = mainAppVersion newTagString := newTag.String() + if err := commitRelease(recipe, newTagString); err != nil { + logrus.Fatal(err) + } + if err := tagRelease(newTagString, repo); err != nil { logrus.Fatal(err) } @@ -422,7 +405,7 @@ func cleanUpTag(tag, recipeName string) error { return err } - logrus.Warnf("removed freshly created tag %s", tag) + logrus.Debugf("removed freshly created tag %s", tag) return nil } diff --git a/cli/recipe/sync.go b/cli/recipe/sync.go index aff55824..002759e4 100644 --- a/cli/recipe/sync.go +++ b/cli/recipe/sync.go @@ -18,7 +18,7 @@ import ( var recipeSyncCommand = &cli.Command{ Name: "sync", - Usage: "Ensure recipe version labels are up-to-date", + Usage: "Sync recipe version label", Aliases: []string{"s"}, ArgsUsage: " []", Flags: []cli.Flag{ @@ -29,23 +29,21 @@ var recipeSyncCommand = &cli.Command{ }, Description: ` This command will generate labels for the main recipe service (i.e. by -convention, the service named "app") which corresponds to the following format: +convention, the service named 'app') which corresponds to the following format: coop-cloud.${STACK_NAME}.version= -The is determined by the recipe maintainer and is specified on the -command-line. The configuration will be updated on the local file -system. - -You may invoke this command in "wizard" mode and be prompted for input: - - abra recipe sync - +Where can be specifed on the command-line or Abra can attempt to +auto-generate it for you. The configuration will be updated on the +local file system. `, Action: func(c *cli.Context) error { recipe := internal.ValidateRecipeWithPrompt(c) - mainApp := internal.GetMainApp(recipe) + mainApp, err := internal.GetMainAppImage(recipe) + if err != nil { + logrus.Fatal(err) + } imagesTmp, err := getImageVersions(recipe) if err != nil { diff --git a/cli/recipe/upgrade.go b/cli/recipe/upgrade.go index 02e12a5b..908d5235 100644 --- a/cli/recipe/upgrade.go +++ b/cli/recipe/upgrade.go @@ -9,6 +9,7 @@ import ( "strings" "coopcloud.tech/abra/cli/internal" + "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" recipePkg "coopcloud.tech/abra/pkg/recipe" @@ -42,7 +43,8 @@ You may invoke this command in "wizard" mode and be prompted for input: abra recipe upgrade `, - ArgsUsage: "", + BashComplete: autocomplete.RecipeNameComplete, + ArgsUsage: "", Flags: []cli.Flag{ internal.PatchFlag, internal.MinorFlag, diff --git a/pkg/git/commit.go b/pkg/git/commit.go index 3e186717..a3c6433f 100644 --- a/pkg/git/commit.go +++ b/pkg/git/commit.go @@ -47,9 +47,9 @@ func Commit(repoPath, glob, commitMessage string, dryRun bool) error { if err != nil { return err } - logrus.Info("changes commited") + logrus.Debug("git changes commited") } else { - logrus.Info("dry run: no changes commited") + logrus.Debug("dry run: no changes commited") } return nil diff --git a/pkg/git/push.go b/pkg/git/push.go index d0eeae9d..0430d57f 100644 --- a/pkg/git/push.go +++ b/pkg/git/push.go @@ -9,7 +9,7 @@ import ( // Push pushes the latest changes & optionally tags to the default remote func Push(repoDir string, remote string, tags bool, dryRun bool) error { if dryRun { - logrus.Infof("dry run: no git changes pushed in %s", repoDir) + logrus.Debugf("dry run: no git changes pushed in %s", repoDir) return nil } @@ -27,7 +27,7 @@ func Push(repoDir string, remote string, tags bool, dryRun bool) error { return err } - logrus.Info("git changes pushed") + logrus.Debugf("git changes pushed") if tags { opts.RefSpecs = append(opts.RefSpecs, config.RefSpec("+refs/tags/*:refs/tags/*")) @@ -36,7 +36,7 @@ func Push(repoDir string, remote string, tags bool, dryRun bool) error { return err } - logrus.Info("git tags pushed") + logrus.Debugf("git tags pushed") } return nil diff --git a/pkg/git/read.go b/pkg/git/read.go index 4081a6f2..47d74d33 100644 --- a/pkg/git/read.go +++ b/pkg/git/read.go @@ -34,10 +34,8 @@ func GetRecipeHead(recipeName string) (*plumbing.Reference, error) { } // IsClean checks if a repo has unstaged changes -func IsClean(recipeName string) (bool, error) { - recipeDir := path.Join(config.RECIPES_DIR, recipeName) - - repo, err := git.PlainOpen(recipeDir) +func IsClean(repoPath string) (bool, error) { + repo, err := git.PlainOpen(repoPath) if err != nil { return false, err } @@ -62,9 +60,9 @@ func IsClean(recipeName string) (bool, error) { } if status.String() != "" { - logrus.Debugf("discovered git status for %s repository: %s", recipeName, status.String()) + logrus.Debugf("discovered git status in %s: %s", repoPath, status.String()) } else { - logrus.Debugf("discovered clean git status for %s repository", recipeName) + logrus.Debugf("discovered clean git status in %s", repoPath) } return status.IsClean(), nil diff --git a/pkg/recipe/recipe.go b/pkg/recipe/recipe.go index eff3d971..3622262f 100644 --- a/pkg/recipe/recipe.go +++ b/pkg/recipe/recipe.go @@ -260,7 +260,7 @@ func EnsureExists(recipeName string) error { func EnsureVersion(recipeName, version string) error { recipeDir := path.Join(config.RECIPES_DIR, recipeName) - isClean, err := gitPkg.IsClean(recipeName) + isClean, err := gitPkg.IsClean(recipeDir) if err != nil { return err } @@ -325,7 +325,7 @@ func EnsureVersion(recipeName, version string) error { func EnsureLatest(recipeName string) error { recipeDir := path.Join(config.RECIPES_DIR, recipeName) - isClean, err := gitPkg.IsClean(recipeName) + isClean, err := gitPkg.IsClean(recipeDir) if err != nil { return err } @@ -380,7 +380,8 @@ func ChaosVersion(recipeName string) (string, error) { version = head.String()[:8] - isClean, err := gitPkg.IsClean(recipeName) + recipeDir := path.Join(config.RECIPES_DIR, recipeName) + isClean, err := gitPkg.IsClean(recipeDir) if err != nil { return version, err } @@ -556,7 +557,7 @@ func GetStringInBetween(recipeName, str, start, end string) (result string, err func EnsureUpToDate(recipeName string) error { recipeDir := path.Join(config.RECIPES_DIR, recipeName) - isClean, err := gitPkg.IsClean(recipeName) + isClean, err := gitPkg.IsClean(recipeDir) if err != nil { return err }