diff --git a/cli/catalogue/catalogue.go b/cli/catalogue/catalogue.go index 6b21bc50..675043bc 100644 --- a/cli/catalogue/catalogue.go +++ b/cli/catalogue/catalogue.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "os" "path" "slices" @@ -56,9 +57,17 @@ It is quite easy to get rate limited by Docker Hub when running this command. If you have a Hub account you can "docker login" and Abra will automatically use those details. -Push your new release to git.coopcloud.tech with "--publish/-p". This requires -that you have permission to git push to these repositories and have your SSH -keys configured on your account.`), +Publish your new release to git.coopcloud.tech with "--publish/-p". This +requires that you have permission to git push to these repositories and have +your SSH keys configured on your account. Enable ssh-agent and make sure to add +your private key and enter your passphrase beforehand. + + eval ` + "`ssh-agent`" + ` + ssh-add ~/.ssh/`), + Example: ` # publish catalogue + eval ` + "`ssh-agent`" + ` + ssh-add ~/.ssh/id_ed25519 + abra catalogue generate -p`, Args: cobra.RangeArgs(0, 1), ValidArgsFunction: func( cmd *cobra.Command, @@ -72,6 +81,10 @@ keys configured on your account.`), recipeName = args[0] } + if os.Getenv("SSH_AUTH_SOCK") == "" { + log.Warn(i18n.G("ssh: SSH_AUTH_SOCK missing, --publish/-p will fail. see \"abra catalogue generate --help\"")) + } + if recipeName != "" { internal.ValidateRecipe(args, cmd.Name()) } diff --git a/cli/recipe/release.go b/cli/recipe/release.go index b4bda80a..4c650d20 100644 --- a/cli/recipe/release.go +++ b/cli/recipe/release.go @@ -19,6 +19,7 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/distribution/reference" "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" "github.com/spf13/cobra" ) @@ -45,7 +46,15 @@ major and therefore require intervention while doing the upgrade work. Publish your new release to git.coopcloud.tech with "--publish/-p". This requires that you have permission to git push to these repositories and have -your SSH keys configured on your account.`), +your SSH keys configured on your account. Enable ssh-agent and make sure to add +your private key and enter your passphrase beforehand. + + eval ` + "`ssh-agent`" + ` + ssh-add ~/.ssh/`), + Example: ` # publish release + eval ` + "`ssh-agent`" + ` + ssh-add ~/.ssh/id_ed25519 + abra recipe release gitea -p`, Args: cobra.RangeArgs(1, 2), ValidArgsFunction: func( cmd *cobra.Command, @@ -93,8 +102,24 @@ your SSH keys configured on your account.`), log.Fatal(i18n.G("cannot specify tag and bump type at the same time")) } + repo, err := git.PlainOpen(recipe.Dir) + if err != nil { + log.Fatal(err) + } + + preCommitHead, err := repo.Head() + if err != nil { + log.Fatal(err) + } + if tagString != "" { if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil { + if cleanErr := cleanTag(recipe, tagString); cleanErr != nil { + log.Fatal(cleanErr) + } + if cleanErr := cleanCommit(recipe, preCommitHead); cleanErr != nil { + log.Fatal(cleanErr) + } log.Fatal(err) } } @@ -125,16 +150,25 @@ your SSH keys configured on your account.`), } if len(tags) > 0 { - log.Warn(i18n.G("previous git tags detected, assuming this is a new semver release")) + log.Warn(i18n.G("previous git tags detected, assuming new semver release")) if err := createReleaseFromPreviousTag(tagString, mainAppVersion, recipe, tags); err != nil { + if cleanErr := cleanTag(recipe, tagString); cleanErr != nil { + log.Fatal(cleanErr) + } + if cleanErr := cleanCommit(recipe, preCommitHead); cleanErr != nil { + log.Fatal(cleanErr) + } log.Fatal(err) } } else { - log.Warn(i18n.G("no tag specified and no previous tag available for %s, assuming this is the initial release", recipe.Name)) + log.Warn(i18n.G("no tag specified and no previous tag available for %s, assuming initial release", recipe.Name)) if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil { - if cleanUpErr := cleanUpTag(recipe, tagString); err != nil { - log.Fatal(cleanUpErr) + if cleanErr := cleanTag(recipe, tagString); cleanErr != nil { + log.Fatal(cleanErr) + } + if cleanErr := cleanCommit(recipe, preCommitHead); cleanErr != nil { + log.Fatal(cleanErr) } log.Fatal(err) } @@ -217,19 +251,19 @@ func createReleaseFromTag(recipe recipe.Recipe, tagString, mainAppVersion string } if err := addReleaseNotes(recipe, tagString); err != nil { - log.Fatal(err) + return errors.New(i18n.G("failed to add release notes: %s", err.Error())) } if err := commitRelease(recipe, tagString); err != nil { - log.Fatal(err) + return errors.New(i18n.G("failed to commit changes: %s", err.Error())) } if err := tagRelease(tagString, repo); err != nil { - log.Fatal(err) + return errors.New(i18n.G("failed to tag release: %s", err.Error())) } if err := pushRelease(recipe, tagString); err != nil { - log.Fatal(err) + return errors.New(i18n.G("failed to publish new release: %s", err.Error())) } return nil @@ -314,7 +348,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error { } prompt := &survey.Input{ - Message: i18n.G("Release Note (leave empty for no release note)"), + Message: i18n.G("add release note? (leave empty to skip)"), } var releaseNote string @@ -406,9 +440,14 @@ func pushRelease(recipe recipe.Recipe, tagString string) error { } if publish { + if os.Getenv("SSH_AUTH_SOCK") == "" { + return errors.New(i18n.G("ssh-agent not found. see \"abra recipe release --help\" and try again")) + } + if err := recipe.Push(internal.Dry); err != nil { return err } + url := fmt.Sprintf("%s/src/tag/%s", recipe.GitURL, tagString) log.Info(i18n.G("new release published: %s", url)) } else { @@ -484,7 +523,7 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip } if lastGitTag.String() == tagString { - log.Fatal(i18n.G("latest git tag (%s) and synced label (%s) are the same?", lastGitTag, tagString)) + return errors.New(i18n.G("latest git tag (%s) and synced label (%s) are the same?", lastGitTag, tagString)) } if !internal.NoInput { @@ -494,43 +533,66 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip var ok bool if err := survey.AskOne(prompt, &ok); err != nil { - log.Fatal(err) + return err } if !ok { - log.Fatal(i18n.G("exiting as requested")) + return errors.New(i18n.G("exiting as requested")) } } if err := addReleaseNotes(recipe, tagString); err != nil { - log.Fatal(err) + return errors.New(i18n.G("failed to add release notes: %s", err.Error())) } if err := commitRelease(recipe, tagString); err != nil { - log.Fatal(i18n.G("failed to commit changes: %s", err.Error())) + return errors.New(i18n.G("failed to commit changes: %s", err.Error())) } if err := tagRelease(tagString, repo); err != nil { - log.Fatal(i18n.G("failed to tag release: %s", err.Error())) + return errors.New(i18n.G("failed to tag release: %s", err.Error())) } if err := pushRelease(recipe, tagString); err != nil { - log.Fatal(i18n.G("failed to publish new release: %s", err.Error())) + return errors.New(i18n.G("failed to publish new release: %s", err.Error())) } return nil } -// cleanUpTag removes a freshly created tag -func cleanUpTag(recipe recipe.Recipe, tag string) error { +// cleanCommit soft removes the latest release commit. No change are lost the +// the commit itself is removed. This is the equivalent of `git reset HEAD~1`. +func cleanCommit(recipe recipe.Recipe, head *plumbing.Reference) error { repo, err := git.PlainOpen(recipe.Dir) if err != nil { - return err + return errors.New(i18n.G("unable to open repo in %s: %s", recipe.Dir, err)) + } + + worktree, err := repo.Worktree() + if err != nil { + return errors.New(i18n.G("unable to open work tree in %s: %s", recipe.Dir, err)) + } + + opts := &git.ResetOptions{Commit: head.Hash(), Mode: git.MixedReset} + if err := worktree.Reset(opts); err != nil { + return errors.New(i18n.G("unable to soft reset %s: %s", recipe.Dir, err)) + } + + log.Debug(i18n.G("removed freshly created commit")) + + return nil +} + +// cleanTag removes a freshly created tag +func cleanTag(recipe recipe.Recipe, tag string) error { + repo, err := git.PlainOpen(recipe.Dir) + if err != nil { + return errors.New(i18n.G("unable to open repo in %s: %s", recipe.Dir, err)) } if err := repo.DeleteTag(tag); err != nil { if !strings.Contains(err.Error(), "not found") { - return err + return errors.New(i18n.G("unable to delete tag %s: %s", tag, err)) } } @@ -546,7 +608,7 @@ func getLabelVersion(recipe recipe.Recipe, prompt bool) (string, error) { } if initTag == "" { - log.Fatal(i18n.G("unable to read version for %s from synced label. Did you try running \"abra recipe sync %s\" already?", recipe.Name, recipe.Name)) + return "", errors.New(i18n.G("unable to read version for %s from synced label. Did you try running \"abra recipe sync %s\" already?", recipe.Name, recipe.Name)) } log.Warn(i18n.G("discovered %s as currently synced recipe label", initTag))