feat: make release use wizard mode

Some bugs squashed while testing this extensively.
This commit is contained in:
decentral1se 2021-11-06 22:36:01 +01:00
parent f9726b6643
commit 63d9703d9d
No known key found for this signature in database
GPG Key ID: 5E2EF5A63E3718CC
4 changed files with 188 additions and 54 deletions

View File

@ -20,6 +20,10 @@ func Truncate(str string) string {
return fmt.Sprintf(`"%s"`, formatter.Ellipsis(str, 19))
}
func SmallSHA(hash string) string {
return hash[:8]
}
// RemoveSha remove image sha from a string that are added in some docker outputs
func RemoveSha(str string) string {
return strings.Split(str, "@")[0]

View File

@ -63,7 +63,14 @@ var recipeLintCommand = &cli.Command{
allImagesTagged = false
}
tag := img.(reference.NamedTagged).Tag()
var tag string
switch img.(type) {
case reference.NamedTagged:
tag = img.(reference.NamedTagged).Tag()
case reference.Named:
noUnstableTags = false
}
if tag == "latest" {
noUnstableTags = false
}

View File

@ -7,6 +7,7 @@ import (
var Major bool
var MajorFlag = &cli.BoolFlag{
Name: "major",
Usage: "Increase the major part of the version (new functionality, backwards incompatible, x of x.y.z)",
Value: false,
Aliases: []string{"ma", "x"},
Destination: &Major,
@ -15,6 +16,7 @@ var MajorFlag = &cli.BoolFlag{
var Minor bool
var MinorFlag = &cli.BoolFlag{
Name: "minor",
Usage: "Increase the minor part of the version (new functionality, backwards compatible, y of x.y.z)",
Value: false,
Aliases: []string{"mi", "y"},
Destination: &Minor,
@ -23,6 +25,7 @@ var MinorFlag = &cli.BoolFlag{
var Patch bool
var PatchFlag = &cli.BoolFlag{
Name: "patch",
Usage: "Increase the patch part of the version (bug fixes, backwards compatible, z of x.y.z)",
Value: false,
Aliases: []string{"p", "z"},
Destination: &Patch,

View File

@ -6,12 +6,14 @@ import (
"strconv"
"strings"
abraFormatter "coopcloud.tech/abra/cli/formatter"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2"
"github.com/docker/distribution/reference"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/sirupsen/logrus"
@ -21,13 +23,16 @@ import (
var Push bool
var PushFlag = &cli.BoolFlag{
Name: "push",
Usage: "Git push changes",
Value: false,
Aliases: []string{"P"},
Destination: &Push,
}
var Dry bool
var DryFlag = &cli.BoolFlag{
Name: "dry-run",
Usage: "No changes are made, only reports changes that would be made",
Value: false,
Aliases: []string{"d"},
Destination: &Dry,
@ -36,7 +41,7 @@ var DryFlag = &cli.BoolFlag{
var CommitMessage string
var CommitMessageFlag = &cli.StringFlag{
Name: "commit-message",
Usage: "commit message. Implies --commit",
Usage: "Commit message (implies --commit)",
Aliases: []string{"cm"},
Destination: &CommitMessage,
}
@ -44,7 +49,7 @@ var CommitMessageFlag = &cli.StringFlag{
var Commit bool
var CommitFlag = &cli.BoolFlag{
Name: "commit",
Usage: "add compose.yml to staging area and commit changes",
Usage: "Commits compose.**yml file changes to recipe repository",
Value: false,
Aliases: []string{"c"},
Destination: &Commit,
@ -53,7 +58,7 @@ var CommitFlag = &cli.BoolFlag{
var TagMessage string
var TagMessageFlag = &cli.StringFlag{
Name: "tag-comment",
Usage: "tag comment. If not given, user will be asked for it",
Usage: "Description for release tag",
Aliases: []string{"t", "tm"},
Destination: &TagMessage,
}
@ -83,23 +88,32 @@ updates are properly communicated.
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
`,
Flags: []cli.Flag{
DryFlag,
PatchFlag,
MinorFlag,
MajorFlag,
MinorFlag,
PatchFlag,
PushFlag,
CommitFlag,
CommitMessageFlag,
TagMessageFlag,
},
Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c)
recipe := internal.ValidateRecipeWithPrompt(c)
directory := path.Join(config.APPS_DIR, recipe.Name)
tagstring := c.Args().Get(1)
imagesTmp := getImageVersions(recipe)
tagString := c.Args().Get(1)
mainApp := getMainApp(recipe)
imagesTmp, err := getImageVersions(recipe)
if err != nil {
logrus.Fatal(err)
}
mainAppVersion := imagesTmp[mainApp]
if err := recipePkg.EnsureExists(recipe.Name); err != nil {
@ -107,18 +121,55 @@ or a rollback of an app.
}
if mainAppVersion == "" {
logrus.Fatal("main app version is empty?")
logrus.Fatalf("main 'app' service version for %s is empty?", recipe.Name)
}
if tagstring != "" {
if _, err := tagcmp.Parse(tagstring); err != nil {
if tagString != "" {
if _, err := tagcmp.Parse(tagString); err != nil {
logrus.Fatal("invalid tag specified")
}
}
if (!Major && !Minor && !Patch) && tagString != "" {
logrus.Fatal("please specify <tag> or bump type (--major/--minor/--patch)")
}
if (Major || Minor || Patch) && tagString != "" {
logrus.Fatal("cannot specify tag and bump type at the same time")
}
// bumpType is used to decide what part of the tag should be incremented
bumpType := btoi(Major)*4 + btoi(Minor)*2 + btoi(Patch)
if bumpType != 0 {
// a bitwise check if the number is a power of 2
if (bumpType & (bumpType - 1)) != 0 {
logrus.Fatal("you can only use one of: --major, --minor, --patch.")
}
}
if (!Major && !Minor && !Patch) && tagString == "" {
fmt.Printf(`
semver cheat sheet (more via semver.org):
major: new features/bug fixes, backwards incompatible
minor: new features/bug fixes, backwards compatible
patch: bug fixes, backwards compatible
`)
var chosenBumpType string
prompt := &survey.Select{
Message: fmt.Sprintf("select recipe version increment type"),
Options: []string{"major", "minor", "patch"},
}
if err := survey.AskOne(prompt, &chosenBumpType); err != nil {
logrus.Fatal(err)
}
setBumpType(chosenBumpType)
}
if TagMessage == "" {
prompt := &survey.Input{
Message: "tag message",
Default: fmt.Sprintf("chore: publish new %s version", getBumpType()),
}
if err := survey.AskOne(prompt, &TagMessage); err != nil {
logrus.Fatal(err)
@ -128,7 +179,25 @@ or a rollback of an app.
var createTagOptions git.CreateTagOptions
createTagOptions.Message = TagMessage
if Commit || (CommitMessage != "") {
if !Commit {
prompt := &survey.Confirm{
Message: "git commit changes also?",
}
if err := survey.AskOne(prompt, &Commit); err != nil {
return err
}
}
if !Push {
prompt := &survey.Confirm{
Message: "git push changes also?",
}
if err := survey.AskOne(prompt, &Push); err != nil {
return err
}
}
if Commit || CommitMessage != "" {
commitRepo, err := git.PlainOpen(directory)
if err != nil {
logrus.Fatal(err)
@ -141,22 +210,28 @@ or a rollback of an app.
if CommitMessage == "" {
prompt := &survey.Input{
Message: "commit message",
Default: fmt.Sprintf("chore: publish new %s version", getBumpType()),
}
if err := survey.AskOne(prompt, &CommitMessage); err != nil {
logrus.Fatal(err)
}
}
err = commitWorktree.AddGlob("compose.**yml")
if err != nil {
logrus.Fatal(err)
}
logrus.Debug("staged compose.**yml for commit")
_, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{})
if err != nil {
logrus.Fatal(err)
if !Dry {
_, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{})
if err != nil {
logrus.Fatal(err)
}
logrus.Info("changes commited")
} else {
logrus.Info("dry run only: NOT committing changes")
}
logrus.Info("changes commited")
}
repo, err := git.PlainOpen(directory)
@ -168,20 +243,8 @@ or a rollback of an app.
logrus.Fatal(err)
}
// bumpType is used to decide what part of the tag should be incremented
bumpType := btoi(Major)*4 + btoi(Minor)*2 + btoi(Patch)
if bumpType != 0 {
// a bitwise check if the number is a power of 2
if (bumpType & (bumpType - 1)) != 0 {
logrus.Fatal("you can only use one of: --major, --minor, --patch.")
}
}
if tagstring != "" {
if bumpType > 0 {
logrus.Warn("user specified a version number and --major/--minor/--patch at the same time! using version number...")
}
tag, err := tagcmp.Parse(tagstring)
if tagString != "" {
tag, err := tagcmp.Parse(tagString)
if err != nil {
logrus.Fatal(err)
}
@ -193,19 +256,23 @@ or a rollback of an app.
tag.Patch = "0"
tag.MissingPatch = false
}
tagstring = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion)
tagString = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion)
if Dry {
logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", tagstring, head.Hash()))
hash := abraFormatter.SmallSHA(head.Hash().String())
logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", tagString, hash))
return nil
}
repo.CreateTag(tagstring, head.Hash(), &createTagOptions)
logrus.Info(fmt.Sprintf("created tag %s at %s", tagstring, head.Hash()))
if Push {
repo.CreateTag(tagString, head.Hash(), &createTagOptions)
hash := abraFormatter.SmallSHA(head.Hash().String())
logrus.Info(fmt.Sprintf("created tag %s at %s", tagString, hash))
if Push && !Dry {
if err := repo.Push(&git.PushOptions{}); err != nil {
logrus.Fatal(err)
}
logrus.Info(fmt.Sprintf("pushed tag %s to remote", tagstring))
logrus.Info(fmt.Sprintf("pushed tag %s to remote", tagString))
} else {
logrus.Info("dry run only: NOT pushing changes")
}
return nil
@ -236,10 +303,8 @@ or a rollback of an app.
logrus.Fatal(err)
}
fmt.Println(lastGitTag)
newTag := lastGitTag
var newTagString string
var newtagString string
if bumpType > 0 {
if Patch {
now, err := strconv.Atoi(newTag.Patch)
@ -263,44 +328,66 @@ or a rollback of an app.
newTag.Minor = "0"
newTag.Major = strconv.Itoa(now + 1)
}
} else {
logrus.Fatal("we don't support automatic tag generation yet - specify a version or use one of: --major --minor --patch")
}
newTag.Metadata = mainAppVersion
newTagString = newTag.String()
newtagString = newTag.String()
if Dry {
logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newTagString, head.Hash()))
hash := abraFormatter.SmallSHA(head.Hash().String())
logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newtagString, hash))
return nil
}
repo.CreateTag(newTagString, head.Hash(), &createTagOptions)
logrus.Info(fmt.Sprintf("created tag %s at %s", newTagString, head.Hash()))
if Push {
repo.CreateTag(newtagString, head.Hash(), &createTagOptions)
hash := abraFormatter.SmallSHA(head.Hash().String())
logrus.Info(fmt.Sprintf("created tag %s at %s", newtagString, hash))
if Push && !Dry {
if err := repo.Push(&git.PushOptions{}); err != nil {
logrus.Fatal(err)
}
logrus.Info(fmt.Sprintf("pushed tag %s to remote", newTagString))
logrus.Info(fmt.Sprintf("pushed tag %s to remote", newtagString))
} else {
logrus.Info("dry run only: NOT pushing changes")
}
return nil
},
}
func getImageVersions(recipe recipe.Recipe) map[string]string {
// getImageVersions retrieves image versions for a recipe
func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
var services = make(map[string]string)
for _, service := range recipe.Config.Services {
if service.Image == "" {
continue
}
srv := strings.Split(service.Image, ":")
services[srv[0]] = srv[1]
img, err := reference.ParseNormalizedNamed(service.Image)
if err != nil {
return services, err
}
path := reference.Path(img)
if strings.Contains(path, "library") {
path = strings.Split(path, "/")[1]
}
var tag string
switch img.(type) {
case reference.NamedTagged:
tag = img.(reference.NamedTagged).Tag()
case reference.Named:
logrus.Fatalf("%s service is missing image tag?", path)
}
services[path] = tag
}
return services
return services, nil
}
// getMainApp retrieves the main 'app' image name
func getMainApp(recipe recipe.Recipe) string {
for _, service := range recipe.Config.Services {
name := service.Name
@ -308,12 +395,45 @@ func getMainApp(recipe recipe.Recipe) string {
return strings.Split(service.Image, ":")[0]
}
}
return ""
}
// btoi converts a boolean value into an integer
func btoi(b bool) int {
if b {
return 1
}
return 0
}
// getBumpType figures out which bump type is specified
func getBumpType() string {
var bumpType string
if Major {
bumpType = "major"
} else if Minor {
bumpType = "minor"
} else if Patch {
bumpType = "patch"
} else {
logrus.Fatal("no version bump type specififed?")
}
return bumpType
}
// setBumpType figures out which bump type is specified
func setBumpType(bumpType string) {
if bumpType == "major" {
Major = true
} else if bumpType == "minor" {
Minor = true
} else if bumpType == "patch" {
Patch = true
} else {
logrus.Fatal("no version bump type specififed?")
}
}