251 lines
6.2 KiB
Go
251 lines
6.2 KiB
Go
package recipe
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"coopcloud.tech/abra/cli/internal"
|
|
"coopcloud.tech/abra/pkg/autocomplete"
|
|
gitPkg "coopcloud.tech/abra/pkg/git"
|
|
"coopcloud.tech/abra/pkg/log"
|
|
"coopcloud.tech/tagcmp"
|
|
"github.com/AlecAivazis/survey/v2"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var RecipeSyncCommand = &cobra.Command{
|
|
Use: "sync <recipe> [version] [flags]",
|
|
Aliases: []string{"s"},
|
|
Short: "Sync recipe version label",
|
|
Long: `Generate labels for the main recipe service.
|
|
|
|
By convention, the service named "app" using the following format:
|
|
|
|
coop-cloud.${STACK_NAME}.version=<version>
|
|
|
|
Where [version] can be specifed on the command-line or Abra can attempt to
|
|
auto-generate it for you. The <recipe> configuration will be updated on the
|
|
local file system.`,
|
|
Args: cobra.RangeArgs(1, 2),
|
|
ValidArgsFunction: func(
|
|
cmd *cobra.Command,
|
|
args []string,
|
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
switch l := len(args); l {
|
|
case 0:
|
|
return autocomplete.RecipeNameComplete()
|
|
case 1:
|
|
return autocomplete.RecipeVersionComplete(args[0])
|
|
default:
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
},
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
|
|
|
mainApp, err := internal.GetMainAppImage(recipe)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
imagesTmp, err := getImageVersions(recipe)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
mainAppVersion := imagesTmp[mainApp]
|
|
|
|
tags, err := recipe.Tags()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
var nextTag string
|
|
if len(args) == 2 {
|
|
nextTag = args[1]
|
|
}
|
|
|
|
if len(tags) == 0 && nextTag == "" {
|
|
log.Warnf("no git tags found for %s", recipe.Name)
|
|
if internal.NoInput {
|
|
log.Fatalf("unable to continue, input required for initial version")
|
|
}
|
|
fmt.Println(fmt.Sprintf(`
|
|
The following options are two types of initial semantic version that you can
|
|
pick for %s that will be published in the recipe catalogue. This follows the
|
|
semver convention (more on https://semver.org), here is a short cheatsheet
|
|
|
|
0.1.0: development release, still hacking. when you make a major upgrade
|
|
you increment the "y" part (i.e. 0.1.0 -> 0.2.0) and only move to
|
|
using the "x" part when things are stable.
|
|
|
|
1.0.0: public release, assumed to be working. you already have a stable
|
|
and reliable deployment of this app and feel relatively confident
|
|
about it.
|
|
|
|
If you want people to be able alpha test your current config for %s but don't
|
|
think it is quite reliable, go with 0.1.0 and people will know that things are
|
|
likely to change.
|
|
|
|
`, recipe.Name, recipe.Name))
|
|
var chosenVersion string
|
|
edPrompt := &survey.Select{
|
|
Message: "which version do you want to begin with?",
|
|
Options: []string{"0.1.0", "1.0.0"},
|
|
}
|
|
|
|
if err := survey.AskOne(edPrompt, &chosenVersion); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
nextTag = fmt.Sprintf("%s+%s", chosenVersion, mainAppVersion)
|
|
}
|
|
|
|
if nextTag == "" && (!internal.Major && !internal.Minor && !internal.Patch) {
|
|
latestRelease := tags[len(tags)-1]
|
|
if err := internal.PromptBumpType("", latestRelease); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if nextTag == "" {
|
|
repo, err := git.PlainOpen(recipe.Dir)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
var lastGitTag tagcmp.Tag
|
|
iter, err := repo.Tags()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if err := iter.ForEach(func(ref *plumbing.Reference) error {
|
|
obj, err := repo.TagObject(ref.Hash())
|
|
if err != nil {
|
|
log.Fatal("Tag at commit ", ref.Hash(), " is unannotated or otherwise broken. Please fix it.")
|
|
return err
|
|
}
|
|
|
|
tagcmpTag, err := tagcmp.Parse(obj.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if (lastGitTag == tagcmp.Tag{}) {
|
|
lastGitTag = tagcmpTag
|
|
} else if tagcmpTag.IsGreaterThan(lastGitTag) {
|
|
lastGitTag = tagcmpTag
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// bumpType is used to decide what part of the tag should be incremented
|
|
bumpType := btoi(internal.Major)*4 + btoi(internal.Minor)*2 + btoi(internal.Patch)
|
|
if bumpType != 0 {
|
|
// a bitwise check if the number is a power of 2
|
|
if (bumpType & (bumpType - 1)) != 0 {
|
|
log.Fatal("you can only use one version flag: --major, --minor or --patch")
|
|
}
|
|
}
|
|
|
|
newTag := lastGitTag
|
|
if bumpType > 0 {
|
|
if internal.Patch {
|
|
now, err := strconv.Atoi(newTag.Patch)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
newTag.Patch = strconv.Itoa(now + 1)
|
|
} else if internal.Minor {
|
|
now, err := strconv.Atoi(newTag.Minor)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
newTag.Patch = "0"
|
|
newTag.Minor = strconv.Itoa(now + 1)
|
|
} else if internal.Major {
|
|
now, err := strconv.Atoi(newTag.Major)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
newTag.Patch = "0"
|
|
newTag.Minor = "0"
|
|
newTag.Major = strconv.Itoa(now + 1)
|
|
}
|
|
}
|
|
|
|
newTag.Metadata = mainAppVersion
|
|
log.Debugf("choosing %s as new version for %s", newTag.String(), recipe.Name)
|
|
nextTag = newTag.String()
|
|
}
|
|
|
|
if _, err := tagcmp.Parse(nextTag); err != nil {
|
|
log.Fatalf("invalid version %s specified", nextTag)
|
|
}
|
|
|
|
mainService := "app"
|
|
label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", nextTag)
|
|
if !internal.Dry {
|
|
if err := recipe.UpdateLabel("compose.y*ml", mainService, label); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
log.Infof("dry run: not syncing label %s for recipe %s", nextTag, recipe.Name)
|
|
}
|
|
|
|
isClean, err := gitPkg.IsClean(recipe.Dir)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if !isClean {
|
|
log.Infof("%s currently has these unstaged changes 👇", recipe.Name)
|
|
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
RecipeSyncCommand.Flags().BoolVarP(
|
|
&internal.Dry,
|
|
"dry-run",
|
|
"r",
|
|
false,
|
|
"report changes that would be made",
|
|
)
|
|
|
|
RecipeSyncCommand.Flags().BoolVarP(
|
|
&internal.Major,
|
|
"major",
|
|
"x",
|
|
false,
|
|
"increase the major part of the version",
|
|
)
|
|
|
|
RecipeSyncCommand.Flags().BoolVarP(
|
|
&internal.Minor,
|
|
"minor",
|
|
"y",
|
|
false,
|
|
"increase the minor part of the version",
|
|
)
|
|
|
|
RecipeSyncCommand.Flags().BoolVarP(
|
|
&internal.Patch,
|
|
"patch",
|
|
"z",
|
|
false,
|
|
"increase the patch part of the version",
|
|
)
|
|
}
|