From 48d28c8dd1f83ac66c2267342706db26ae966322 Mon Sep 17 00:00:00 2001 From: knoflook Date: Wed, 22 Sep 2021 16:03:56 +0200 Subject: [PATCH 1/5] feat: tag recipes with abra --- cli/recipe/recipe.go | 1 + cli/recipe/release.go | 123 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 cli/recipe/release.go diff --git a/cli/recipe/recipe.go b/cli/recipe/recipe.go index d349c2d2..0fd3ef4d 100644 --- a/cli/recipe/recipe.go +++ b/cli/recipe/recipe.go @@ -18,6 +18,7 @@ Cloud community and you can use Abra to read them and create apps for you. Subcommands: []*cli.Command{ recipeListCommand, recipeVersionCommand, + recipeReleaseCommand, recipeNewCommand, recipeUpgradeCommand, recipeSyncCommand, diff --git a/cli/recipe/release.go b/cli/recipe/release.go new file mode 100644 index 00000000..d675ca41 --- /dev/null +++ b/cli/recipe/release.go @@ -0,0 +1,123 @@ +package recipe + +import ( + "fmt" + "os" + "path" + "strconv" + "strings" + + "coopcloud.tech/abra/cli/internal" + "coopcloud.tech/abra/pkg/config" + "coopcloud.tech/abra/pkg/recipe" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +var Dry bool +var DryFlag = &cli.BoolFlag{ + Name: "dry-run", + Value: false, + Destination: &Dry, +} + +var recipeReleaseCommand = &cli.Command{ + Name: "release", + Usage: "tag a recipe", + Aliases: []string{"rl"}, + ArgsUsage: " []", + Flags: []cli.Flag{ + DryFlag, + }, + Action: func(c *cli.Context) error { + recipe := internal.ValidateRecipe(c) + + directory := path.Join(config.APPS_DIR, recipe.Name) + if _, err := os.Stat(directory); os.IsNotExist(err) { + logrus.Fatalf("recipe doesn't exist at path %s.", directory) + return nil + } + + images := getImageVersions(recipe) + tag := c.Args().Get(1) + if tag == "" { + for name, version := range images { + if !isSemver(version) { + logrus.Fatal(fmt.Sprintf("app %s: version number %s is not in the format x.y.z (where x,y,z are integers) - unable to generate tags, please specify a tag yourself.", name, version)) + } + } + } + + repo, err := git.PlainOpen(directory) + if err != nil { + logrus.Fatal(err) + } + head, err := repo.Head() + if err != nil { + logrus.Fatal(err) + } + // get the latest tag with its hash, name etc + if tag == "" { + var lastTag *object.Tag + iter, err := repo.Tags() + if err != nil { + logrus.Fatal(err) + } + // TODO: This is some magic and I have no idea what's going on but it does the job. Re-write if this looks stupid to you. Copied from the docs /knoflook + if err := iter.ForEach(func(ref *plumbing.Reference) error { + obj, err := repo.TagObject(ref.Hash()) + switch err { + case nil: + lastTag = obj + break + case plumbing.ErrObjectNotFound: + logrus.Fatal(err) + default: + return err + } + return nil + }); err != nil { + logrus.Fatal(err) + } + } + + if Dry { + logrus.Info(fmt.Sprintf("Dry run only. NOT creating tag %s at %s", tag, head.Hash())) + return nil + } + + repo.CreateTag(tag, head.Hash(), nil) /* &git.CreateTagOptions{ + Message: tag, + })*/ + logrus.Info(fmt.Sprintf("Created tag %s at %s.", tag, head.Hash())) + + return nil + }, +} + +func getImageVersions(recipe recipe.Recipe) map[string]string { + + var services = make(map[string]string) + for _, service := range recipe.Config.Services { + srv := strings.Split(service.Image, ":") + services[srv[0]] = srv[1] + } + + return services +} + +func isSemver(ver string) bool { + numbers := strings.Split(ver, ".") + if len(numbers) > 3 { + return false + } + for _, part := range numbers { + if _, err := strconv.Atoi(part); err != nil { + return false + } + } + return true +} From c0f92ca13d5325d74d31139a1c3624a3f7dc6df0 Mon Sep 17 00:00:00 2001 From: knoflook Date: Thu, 23 Sep 2021 18:27:19 +0200 Subject: [PATCH 2/5] feat: support --major/-x --minor/-y --patch/-z for tag calculation --- cli/recipe/release.go | 168 ++++++++++++++++++++++++++++++------------ 1 file changed, 121 insertions(+), 47 deletions(-) diff --git a/cli/recipe/release.go b/cli/recipe/release.go index d675ca41..6569d635 100644 --- a/cli/recipe/release.go +++ b/cli/recipe/release.go @@ -2,7 +2,6 @@ package recipe import ( "fmt" - "os" "path" "strconv" "strings" @@ -10,6 +9,7 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/recipe" + "coopcloud.tech/tagcmp" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" @@ -21,9 +21,34 @@ var Dry bool var DryFlag = &cli.BoolFlag{ Name: "dry-run", Value: false, + Aliases: []string{"d"}, Destination: &Dry, } +var Major bool +var MajorFlag = &cli.BoolFlag{ + Name: "major", + Value: false, + Aliases: []string{"ma", "x"}, + Destination: &Major, +} + +var Minor bool +var MinorFlag = &cli.BoolFlag{ + Name: "minor", + Value: false, + Aliases: []string{"mi", "y"}, + Destination: &Minor, +} + +var Patch bool +var PatchFlag = &cli.BoolFlag{ + Name: "patch", + Value: false, + Aliases: []string{"p", "z"}, + Destination: &Patch, +} + var recipeReleaseCommand = &cli.Command{ Name: "release", Usage: "tag a recipe", @@ -31,25 +56,15 @@ var recipeReleaseCommand = &cli.Command{ ArgsUsage: " []", Flags: []cli.Flag{ DryFlag, + PatchFlag, + MinorFlag, + MajorFlag, }, Action: func(c *cli.Context) error { recipe := internal.ValidateRecipe(c) - directory := path.Join(config.APPS_DIR, recipe.Name) - if _, err := os.Stat(directory); os.IsNotExist(err) { - logrus.Fatalf("recipe doesn't exist at path %s.", directory) - return nil - } - - images := getImageVersions(recipe) - tag := c.Args().Get(1) - if tag == "" { - for name, version := range images { - if !isSemver(version) { - logrus.Fatal(fmt.Sprintf("app %s: version number %s is not in the format x.y.z (where x,y,z are integers) - unable to generate tags, please specify a tag yourself.", name, version)) - } - } - } + tagstring := c.Args().Get(1) + imagesTmp := getImageVersions(recipe) repo, err := git.PlainOpen(directory) if err != nil { @@ -59,42 +74,107 @@ var recipeReleaseCommand = &cli.Command{ if err != nil { logrus.Fatal(err) } - // get the latest tag with its hash, name etc - if tag == "" { - var lastTag *object.Tag - iter, err := repo.Tags() + + if tagstring != "" { + repo, err := git.PlainOpen(directory) if err != nil { logrus.Fatal(err) } - // TODO: This is some magic and I have no idea what's going on but it does the job. Re-write if this looks stupid to you. Copied from the docs /knoflook - if err := iter.ForEach(func(ref *plumbing.Reference) error { - obj, err := repo.TagObject(ref.Hash()) - switch err { - case nil: - lastTag = obj - break - case plumbing.ErrObjectNotFound: - logrus.Fatal(err) - default: - return err - } - return nil - }); err != nil { + head, err := repo.Head() + if err != nil { logrus.Fatal(err) } + if Dry { + logrus.Info(fmt.Sprintf("Dry run only. NOT creating tag %s at %s", tagstring, head.Hash())) + return nil + } + + repo.CreateTag(tagstring, head.Hash(), nil) /* &git.CreateTagOptions{ + Message: tag, + })*/ + logrus.Info(fmt.Sprintf("Created tag %s at %s.", tagstring, head.Hash())) + + return nil + } + + // 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.") + } + } + + // get the latest tag with its hash, name etc + var lastGitTag *object.Tag + iter, err := repo.Tags() + if err != nil { + logrus.Fatal(err) + } + if err := iter.ForEach(func(ref *plumbing.Reference) error { + obj, err := repo.TagObject(ref.Hash()) + if err == nil { + lastGitTag = obj + return nil + } + return err + + }); err != nil { + logrus.Fatal(err) + } + + newTag, err := tagcmp.Parse(lastGitTag.Name) + if err != nil { + logrus.Fatal(err) + } + + var newTagString string + if bumpType > 0 { + if Patch { + now, err := strconv.Atoi(newTag.Patch) + if err != nil { + logrus.Fatal(err) + } + newTag.Patch = strconv.Itoa(now + 1) + } else if Minor { + now, err := strconv.Atoi(newTag.Minor) + if err != nil { + logrus.Fatal(err) + } + newTag.Minor = strconv.Itoa(now + 1) + } else if Major { + now, err := strconv.Atoi(newTag.Major) + if err != nil { + logrus.Fatal(err) + } + newTag.Major = strconv.Itoa(now + 1) + } + newTagString = newTag.String() + } else { + // calculate the new tag + var images = make(map[string]tagcmp.Tag) + for name, version := range imagesTmp { + t, err := tagcmp.Parse(version) + if err != nil { + logrus.Fatal(err) + } + images[name] = t + } } if Dry { - logrus.Info(fmt.Sprintf("Dry run only. NOT creating tag %s at %s", tag, head.Hash())) + logrus.Info(fmt.Sprintf("Dry run only. NOT creating tag %s at %s", newTagString, head.Hash())) return nil } - repo.CreateTag(tag, head.Hash(), nil) /* &git.CreateTagOptions{ + repo.CreateTag(newTagString, head.Hash(), nil) /* &git.CreateTagOptions{ Message: tag, })*/ - logrus.Info(fmt.Sprintf("Created tag %s at %s.", tag, head.Hash())) + logrus.Info(fmt.Sprintf("Created tag %s at %s.", newTagString, head.Hash())) return nil + }, } @@ -109,15 +189,9 @@ func getImageVersions(recipe recipe.Recipe) map[string]string { return services } -func isSemver(ver string) bool { - numbers := strings.Split(ver, ".") - if len(numbers) > 3 { - return false +func btoi(b bool) int { + if b { + return 1 } - for _, part := range numbers { - if _, err := strconv.Atoi(part); err != nil { - return false - } - } - return true + return 0 } From cd179175f560a281eb1daaf4b05739112439638b Mon Sep 17 00:00:00 2001 From: knoflook Date: Thu, 23 Sep 2021 18:32:58 +0200 Subject: [PATCH 3/5] refactor: dont' create the same objects twice --- cli/recipe/release.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/cli/recipe/release.go b/cli/recipe/release.go index 6569d635..5dddafb8 100644 --- a/cli/recipe/release.go +++ b/cli/recipe/release.go @@ -76,14 +76,6 @@ var recipeReleaseCommand = &cli.Command{ } if tagstring != "" { - repo, err := git.PlainOpen(directory) - if err != nil { - logrus.Fatal(err) - } - head, err := repo.Head() - if err != nil { - logrus.Fatal(err) - } if Dry { logrus.Info(fmt.Sprintf("Dry run only. NOT creating tag %s at %s", tagstring, head.Hash())) return nil From 9faefd25922b2b7acf6c035758e9b4a07d5a6245 Mon Sep 17 00:00:00 2001 From: knoflook Date: Thu, 23 Sep 2021 18:52:21 +0200 Subject: [PATCH 4/5] feat: push the new tag with --push --- cli/recipe/release.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cli/recipe/release.go b/cli/recipe/release.go index 5dddafb8..eb717fa4 100644 --- a/cli/recipe/release.go +++ b/cli/recipe/release.go @@ -17,6 +17,13 @@ import ( "github.com/urfave/cli/v2" ) +var Push bool +var PushFlag = &cli.BoolFlag{ + Name: "push", + Value: false, + Destination: &Push, +} + var Dry bool var DryFlag = &cli.BoolFlag{ Name: "dry-run", @@ -59,6 +66,7 @@ var recipeReleaseCommand = &cli.Command{ PatchFlag, MinorFlag, MajorFlag, + PushFlag, }, Action: func(c *cli.Context) error { recipe := internal.ValidateRecipe(c) @@ -85,6 +93,12 @@ var recipeReleaseCommand = &cli.Command{ Message: tag, })*/ logrus.Info(fmt.Sprintf("Created tag %s at %s.", tagstring, head.Hash())) + if Push { + if err := repo.Push(&git.PushOptions{}); err != nil { + logrus.Fatal(err) + } + logrus.Info(fmt.Sprintf("Pushed tag %s to remote.", tagstring)) + } return nil } @@ -164,6 +178,12 @@ var recipeReleaseCommand = &cli.Command{ Message: tag, })*/ logrus.Info(fmt.Sprintf("Created tag %s at %s.", newTagString, head.Hash())) + if Push { + if err := repo.Push(&git.PushOptions{}); err != nil { + logrus.Fatal(err) + } + logrus.Info(fmt.Sprintf("Pushed tag %s to remote.", newTagString)) + } return nil From e700e44363054560966103b16568f96840ceeed5 Mon Sep 17 00:00:00 2001 From: knoflook Date: Fri, 24 Sep 2021 10:48:09 +0200 Subject: [PATCH 5/5] feat: add main apps version as a semver build metadata when releasing --- cli/recipe/release.go | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/cli/recipe/release.go b/cli/recipe/release.go index eb717fa4..64ae37ce 100644 --- a/cli/recipe/release.go +++ b/cli/recipe/release.go @@ -73,6 +73,11 @@ var recipeReleaseCommand = &cli.Command{ directory := path.Join(config.APPS_DIR, recipe.Name) tagstring := c.Args().Get(1) imagesTmp := getImageVersions(recipe) + mainApp := getMainApp(recipe) + mainAppVersion := imagesTmp[mainApp] + if mainAppVersion == "" { + logrus.Fatal("Main app's version is empty.") + } repo, err := git.PlainOpen(directory) if err != nil { @@ -83,7 +88,20 @@ var recipeReleaseCommand = &cli.Command{ 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.") + } + tagstring = fmt.Sprintf("%s+%s", tagstring, mainAppVersion) if Dry { logrus.Info(fmt.Sprintf("Dry run only. NOT creating tag %s at %s", tagstring, head.Hash())) return nil @@ -103,15 +121,6 @@ var recipeReleaseCommand = &cli.Command{ return nil } - // 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.") - } - } - // get the latest tag with its hash, name etc var lastGitTag *object.Tag iter, err := repo.Tags() @@ -129,7 +138,6 @@ var recipeReleaseCommand = &cli.Command{ }); err != nil { logrus.Fatal(err) } - newTag, err := tagcmp.Parse(lastGitTag.Name) if err != nil { logrus.Fatal(err) @@ -169,6 +177,7 @@ var recipeReleaseCommand = &cli.Command{ } } + newTagString = fmt.Sprintf("%s+%s", newTagString, mainAppVersion) if Dry { logrus.Info(fmt.Sprintf("Dry run only. NOT creating tag %s at %s", newTagString, head.Hash())) return nil @@ -201,6 +210,16 @@ func getImageVersions(recipe recipe.Recipe) map[string]string { return services } +func getMainApp(recipe recipe.Recipe) string { + for _, service := range recipe.Config.Services { + name := service.Name + if name == "app" { + return strings.Split(service.Image, ":")[0] + } + } + return "" +} + func btoi(b bool) int { if b { return 1