forked from coop-cloud/abra
feat: make release use wizard mode
Some bugs squashed while testing this extensively.
This commit is contained in:
parent
f9726b6643
commit
63d9703d9d
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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?")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue