refactor!: simplifying publish logic
continuous-integration/drone/push Build is passing Details

This commit is contained in:
decentral1se 2021-12-27 19:56:27 +01:00
parent eb1b6be4c5
commit 0aa37fcee8
Signed by: decentral1se
GPG Key ID: 03789458B3D0C410
10 changed files with 137 additions and 168 deletions

View File

@ -13,7 +13,6 @@ import (
gitPkg "coopcloud.tech/abra/pkg/git" gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/limit" "coopcloud.tech/abra/pkg/limit"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -59,11 +58,9 @@ var CatalogueSkipList = map[string]bool{
var catalogueGenerateCommand = &cli.Command{ var catalogueGenerateCommand = &cli.Command{
Name: "generate", Name: "generate",
Aliases: []string{"g"}, Aliases: []string{"g"},
Usage: "Generate a new copy of the recipe catalogue", Usage: "Generate the recipe catalogue",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.PushFlag, internal.PublishFlag,
internal.CommitFlag,
internal.CommitMessageFlag,
internal.DryFlag, internal.DryFlag,
internal.SkipUpdatesFlag, internal.SkipUpdatesFlag,
internal.RegistryUsernameFlag, 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 It is possible to generate new metadata for a single recipe by passing
<recipe>. The existing local catalogue will be updated, not overwritten. <recipe>. 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. 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 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: "[<recipe>]", ArgsUsage: "[<recipe>]",
Action: func(c *cli.Context) error { 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) logrus.Infof("generated new recipe catalogue in %s", config.RECIPES_JSON)
if internal.Commit { if internal.Publish {
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") 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) logrus.Fatal(err)
} }
if internal.Push { if isClean {
repo, err := git.PlainOpen(cataloguePath) logrus.Fatalf("no changes discovered in %s, nothing to publish?", 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)
}
} }
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 return nil
@ -281,7 +283,7 @@ func updateRepositories(repos recipe.RepoCatalogue, recipeName string) error {
logrus.Fatal(err) logrus.Fatal(err)
} }
isClean, err := gitPkg.IsClean(rm.Name) isClean, err := gitPkg.IsClean(recipeDir)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -306,7 +306,7 @@ var PatchFlag = &cli.BoolFlag{
Name: "patch", Name: "patch",
Usage: "Increase the patch part of the version", Usage: "Increase the patch part of the version",
Value: false, Value: false,
Aliases: []string{"p", "z"}, Aliases: []string{"pa", "z"},
Destination: &Patch, Destination: &Patch,
} }
@ -319,38 +319,13 @@ var DryFlag = &cli.BoolFlag{
Destination: &Dry, Destination: &Dry,
} }
var Push bool var Publish bool
var PushFlag = &cli.BoolFlag{ var PublishFlag = &cli.BoolFlag{
Name: "push", Name: "publish",
Usage: "Git push changes", Usage: "Publish changes to git.coopcloud.tech",
Value: false, Value: false,
Aliases: []string{"P"}, Aliases: []string{"p"},
Destination: &Push, Destination: &Publish,
}
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,
} }
var Domain string var Domain string
@ -439,14 +414,14 @@ var SkipUpdatesFlag = &cli.BoolFlag{
Name: "skip-updates", Name: "skip-updates",
Aliases: []string{"s"}, Aliases: []string{"s"},
Value: false, Value: false,
Usage: "Skip updating git repositories", Usage: "Skip updating recipe repositories",
Destination: &SkipUpdates, Destination: &SkipUpdates,
} }
var RegistryUsername string var RegistryUsername string
var RegistryUsernameFlag = &cli.StringFlag{ var RegistryUsernameFlag = &cli.StringFlag{
Name: "username", Name: "username",
Aliases: []string{"u"}, Aliases: []string{"user"},
Value: "", Value: "",
Usage: "Registry username", Usage: "Registry username",
EnvVars: []string{"REGISTRY_USERNAME"}, EnvVars: []string{"REGISTRY_USERNAME"},
@ -456,7 +431,7 @@ var RegistryUsernameFlag = &cli.StringFlag{
var RegistryPassword string var RegistryPassword string
var RegistryPasswordFlag = &cli.StringFlag{ var RegistryPasswordFlag = &cli.StringFlag{
Name: "password", Name: "password",
Aliases: []string{"p"}, Aliases: []string{"pass"},
Value: "", Value: "",
Usage: "Registry password", Usage: "Registry password",
EnvVars: []string{"REGISTRY_PASSWORD"}, EnvVars: []string{"REGISTRY_PASSWORD"},

View File

@ -6,6 +6,7 @@ import (
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/distribution/reference"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -64,20 +65,29 @@ func SetBumpType(bumpType string) {
} }
} }
// GetMainApp retrieves the main 'app' image name // GetMainAppImage retrieves the main 'app' image name
func GetMainApp(recipe recipe.Recipe) string { func GetMainAppImage(recipe recipe.Recipe) (string, error) {
var app string var path string
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
name := service.Name if service.Name == "app" {
if name == "app" { img, err := reference.ParseNormalizedNamed(service.Image)
app = strings.Split(service.Image, ":")[0] if err != nil {
return "", err
}
path = reference.Path(img)
if strings.Contains(path, "library") {
path = strings.Split(path, "/")[1]
}
return path, nil
} }
} }
if app == "" { if path == "" {
logrus.Fatalf("%s has no main 'app' service?", recipe.Name) return path, fmt.Errorf("%s has no main 'app' service?", recipe.Name)
} }
return app return path, nil
} }

View File

@ -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 action needs to be taken when performing different operations such as an update
or a rollback of an app. or a rollback of an app.
You may invoke this command in "wizard" mode and be prompted for input: 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
abra recipe release gitea keys configured on your account.
`, `,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DryFlag, internal.DryFlag,
internal.MajorFlag, internal.MajorFlag,
internal.MinorFlag, internal.MinorFlag,
internal.PatchFlag, internal.PatchFlag,
internal.PushFlag, internal.PublishFlag,
internal.CommitFlag,
internal.CommitMessageFlag,
internal.TagMessageFlag,
}, },
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { 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) logrus.Fatal(err)
} }
mainApp := internal.GetMainApp(recipe) mainApp, err := internal.GetMainAppImage(recipe)
if err != nil {
logrus.Fatal(err)
}
mainAppVersion := imagesTmp[mainApp] mainAppVersion := imagesTmp[mainApp]
if mainAppVersion == "" { if mainAppVersion == "" {
logrus.Fatalf("main app service version for %s is empty?", recipe.Name) 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 tag.MissingPatch = false
} }
if err := commitRelease(recipe); err != nil {
logrus.Fatal(err)
}
if tagString == "" { if tagString == "" {
tagString = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion) 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 { if err := tagRelease(tagString, repo); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -230,50 +230,30 @@ func btoi(b bool) int {
} }
// getTagCreateOptions constructs git tag create options // getTagCreateOptions constructs git tag create options
func getTagCreateOptions() (git.CreateTagOptions, error) { func getTagCreateOptions(tag string) (git.CreateTagOptions, error) {
if internal.TagMessage == "" && !internal.NoInput { msg := fmt.Sprintf("chore: publish %s release", tag)
prompt := &survey.Input{ return git.CreateTagOptions{Message: msg}, nil
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 commitRelease(recipe recipe.Recipe) error { func commitRelease(recipe recipe.Recipe, tag string) error {
if internal.Dry { if internal.Dry {
logrus.Info("dry run: no changed committed") logrus.Debugf("dry run: no changes committed")
return nil return nil
} }
if !internal.Commit && !internal.NoInput { isClean, err := gitPkg.IsClean(recipe.Dir())
prompt := &survey.Confirm{ if err != nil {
Message: "git commit changes?", return err
}
if err := survey.AskOne(prompt, &internal.Commit); err != nil {
return err
}
} }
if internal.CommitMessage == "" && !internal.NoInput && internal.Commit { if isClean {
prompt := &survey.Input{ return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir())
Message: "git commit message?",
Default: "chore: publish new version",
}
if err := survey.AskOne(prompt, &internal.CommitMessage); err != nil {
return err
}
} }
if internal.Commit { if internal.Publish {
msg := fmt.Sprintf("chore: publish %s release", tag)
repoPath := path.Join(config.RECIPES_DIR, recipe.Name) 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 return err
} }
} }
@ -283,7 +263,7 @@ func commitRelease(recipe recipe.Recipe) error {
func tagRelease(tagString string, repo *git.Repository) error { func tagRelease(tagString string, repo *git.Repository) error {
if internal.Dry { 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 return nil
} }
@ -292,7 +272,7 @@ func tagRelease(tagString string, repo *git.Repository) error {
return err return err
} }
createTagOptions, err := getTagCreateOptions() createTagOptions, err := getTagCreateOptions(tagString)
if err != nil { if err != nil {
return err return err
} }
@ -303,33 +283,36 @@ func tagRelease(tagString string, repo *git.Repository) error {
} }
hash := abraFormatter.SmallSHA(head.Hash().String()) 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 return nil
} }
func pushRelease(recipe recipe.Recipe) error { func pushRelease(recipe recipe.Recipe) error {
if internal.Dry { if internal.Dry {
logrus.Info("dry run: no changes pushed") logrus.Info("dry run: no changes published")
return nil return nil
} }
if !internal.Push && !internal.NoInput { if !internal.Publish && !internal.NoInput {
prompt := &survey.Confirm{ 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 return err
} }
} }
if internal.Push { if internal.Publish {
if err := recipe.Push(internal.Dry); err != nil { if err := recipe.Push(internal.Dry); err != nil {
return err return err
} }
} }
url := fmt.Sprintf("%s/%s/tags", config.REPOS_BASE_URL, recipe.Name)
logrus.Infof("new release published: %s", url)
return nil return nil
} }
@ -392,13 +375,13 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
newTag.Major = strconv.Itoa(now + 1) newTag.Major = strconv.Itoa(now + 1)
} }
if err := commitRelease(recipe); err != nil {
logrus.Fatal(err)
}
newTag.Metadata = mainAppVersion newTag.Metadata = mainAppVersion
newTagString := newTag.String() newTagString := newTag.String()
if err := commitRelease(recipe, newTagString); err != nil {
logrus.Fatal(err)
}
if err := tagRelease(newTagString, repo); err != nil { if err := tagRelease(newTagString, repo); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -422,7 +405,7 @@ func cleanUpTag(tag, recipeName string) error {
return err return err
} }
logrus.Warnf("removed freshly created tag %s", tag) logrus.Debugf("removed freshly created tag %s", tag)
return nil return nil
} }

View File

@ -18,7 +18,7 @@ import (
var recipeSyncCommand = &cli.Command{ var recipeSyncCommand = &cli.Command{
Name: "sync", Name: "sync",
Usage: "Ensure recipe version labels are up-to-date", Usage: "Sync recipe version label",
Aliases: []string{"s"}, Aliases: []string{"s"},
ArgsUsage: "<recipe> [<version>]", ArgsUsage: "<recipe> [<version>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -29,23 +29,21 @@ var recipeSyncCommand = &cli.Command{
}, },
Description: ` Description: `
This command will generate labels for the main recipe service (i.e. by 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=<version> coop-cloud.${STACK_NAME}.version=<version>
The <version> is determined by the recipe maintainer and is specified on the Where <version> can be specifed on the command-line or Abra can attempt to
command-line. The <recipe> configuration will be updated on the local file auto-generate it for you. The <recipe> configuration will be updated on the
system. local file system.
You may invoke this command in "wizard" mode and be prompted for input:
abra recipe sync
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipeWithPrompt(c) recipe := internal.ValidateRecipeWithPrompt(c)
mainApp := internal.GetMainApp(recipe) mainApp, err := internal.GetMainAppImage(recipe)
if err != nil {
logrus.Fatal(err)
}
imagesTmp, err := getImageVersions(recipe) imagesTmp, err := getImageVersions(recipe)
if err != nil { if err != nil {

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
recipePkg "coopcloud.tech/abra/pkg/recipe" 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 abra recipe upgrade
`, `,
ArgsUsage: "<recipe>", BashComplete: autocomplete.RecipeNameComplete,
ArgsUsage: "<recipe>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.PatchFlag, internal.PatchFlag,
internal.MinorFlag, internal.MinorFlag,

View File

@ -47,9 +47,9 @@ func Commit(repoPath, glob, commitMessage string, dryRun bool) error {
if err != nil { if err != nil {
return err return err
} }
logrus.Info("changes commited") logrus.Debug("git changes commited")
} else { } else {
logrus.Info("dry run: no changes commited") logrus.Debug("dry run: no changes commited")
} }
return nil return nil

View File

@ -9,7 +9,7 @@ import (
// Push pushes the latest changes & optionally tags to the default remote // Push pushes the latest changes & optionally tags to the default remote
func Push(repoDir string, remote string, tags bool, dryRun bool) error { func Push(repoDir string, remote string, tags bool, dryRun bool) error {
if dryRun { 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 return nil
} }
@ -27,7 +27,7 @@ func Push(repoDir string, remote string, tags bool, dryRun bool) error {
return err return err
} }
logrus.Info("git changes pushed") logrus.Debugf("git changes pushed")
if tags { if tags {
opts.RefSpecs = append(opts.RefSpecs, config.RefSpec("+refs/tags/*:refs/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 return err
} }
logrus.Info("git tags pushed") logrus.Debugf("git tags pushed")
} }
return nil return nil

View File

@ -34,10 +34,8 @@ func GetRecipeHead(recipeName string) (*plumbing.Reference, error) {
} }
// IsClean checks if a repo has unstaged changes // IsClean checks if a repo has unstaged changes
func IsClean(recipeName string) (bool, error) { func IsClean(repoPath string) (bool, error) {
recipeDir := path.Join(config.RECIPES_DIR, recipeName) repo, err := git.PlainOpen(repoPath)
repo, err := git.PlainOpen(recipeDir)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -62,9 +60,9 @@ func IsClean(recipeName string) (bool, error) {
} }
if status.String() != "" { 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 { } else {
logrus.Debugf("discovered clean git status for %s repository", recipeName) logrus.Debugf("discovered clean git status in %s", repoPath)
} }
return status.IsClean(), nil return status.IsClean(), nil

View File

@ -260,7 +260,7 @@ func EnsureExists(recipeName string) error {
func EnsureVersion(recipeName, version string) error { func EnsureVersion(recipeName, version string) error {
recipeDir := path.Join(config.RECIPES_DIR, recipeName) recipeDir := path.Join(config.RECIPES_DIR, recipeName)
isClean, err := gitPkg.IsClean(recipeName) isClean, err := gitPkg.IsClean(recipeDir)
if err != nil { if err != nil {
return err return err
} }
@ -325,7 +325,7 @@ func EnsureVersion(recipeName, version string) error {
func EnsureLatest(recipeName string) error { func EnsureLatest(recipeName string) error {
recipeDir := path.Join(config.RECIPES_DIR, recipeName) recipeDir := path.Join(config.RECIPES_DIR, recipeName)
isClean, err := gitPkg.IsClean(recipeName) isClean, err := gitPkg.IsClean(recipeDir)
if err != nil { if err != nil {
return err return err
} }
@ -380,7 +380,8 @@ func ChaosVersion(recipeName string) (string, error) {
version = head.String()[:8] version = head.String()[:8]
isClean, err := gitPkg.IsClean(recipeName) recipeDir := path.Join(config.RECIPES_DIR, recipeName)
isClean, err := gitPkg.IsClean(recipeDir)
if err != nil { if err != nil {
return version, err return version, err
} }
@ -556,7 +557,7 @@ func GetStringInBetween(recipeName, str, start, end string) (result string, err
func EnsureUpToDate(recipeName string) error { func EnsureUpToDate(recipeName string) error {
recipeDir := path.Join(config.RECIPES_DIR, recipeName) recipeDir := path.Join(config.RECIPES_DIR, recipeName)
isClean, err := gitPkg.IsClean(recipeName) isClean, err := gitPkg.IsClean(recipeDir)
if err != nil { if err != nil {
return err return err
} }