package recipe import ( "fmt" "path" "strconv" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/config" "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/sirupsen/logrus" "github.com/urfave/cli" ) var recipeSyncCommand = cli.Command{ Name: "sync", Aliases: []string{"s"}, Usage: "Sync recipe version label", ArgsUsage: " []", Flags: []cli.Flag{ internal.DebugFlag, internal.NoInputFlag, internal.DryFlag, internal.MajorFlag, internal.MinorFlag, internal.PatchFlag, }, Before: internal.SubCommandBefore, Description: ` This command will generate labels for the main recipe service (i.e. by convention, the service named 'app') which corresponds to the following format: coop-cloud.${STACK_NAME}.version= Where can be specifed on the command-line or Abra can attempt to auto-generate it for you. The configuration will be updated on the local file system. `, Action: func(c *cli.Context) error { recipe := internal.ValidateRecipeWithPrompt(c) mainApp, err := internal.GetMainAppImage(recipe) if err != nil { logrus.Fatal(err) } imagesTmp, err := getImageVersions(recipe) if err != nil { logrus.Fatal(err) } mainAppVersion := imagesTmp[mainApp] tags, err := recipe.Tags() if err != nil { logrus.Fatal(err) } nextTag := c.Args().Get(1) if len(tags) == 0 && nextTag == "" { logrus.Warnf("no git tags found for %s", recipe.Name) 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 { logrus.Fatal(err) } nextTag = fmt.Sprintf("%s+%s", chosenVersion, mainAppVersion) } if nextTag == "" && (!internal.Major && !internal.Minor && !internal.Patch) { if err := internal.PromptBumpType(""); err != nil { logrus.Fatal(err) } } if nextTag == "" { recipeDir := path.Join(config.RECIPES_DIR, recipe.Name) repo, err := git.PlainOpen(recipeDir) if err != nil { logrus.Fatal(err) } var lastGitTag tagcmp.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 { 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 { logrus.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 { logrus.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 { logrus.Fatal(err) } newTag.Patch = strconv.Itoa(now + 1) } else if internal.Minor { now, err := strconv.Atoi(newTag.Minor) if err != nil { logrus.Fatal(err) } newTag.Patch = "0" newTag.Minor = strconv.Itoa(now + 1) } else if internal.Major { now, err := strconv.Atoi(newTag.Major) if err != nil { logrus.Fatal(err) } newTag.Patch = "0" newTag.Minor = "0" newTag.Major = strconv.Itoa(now + 1) } } newTag.Metadata = mainAppVersion logrus.Debugf("choosing %s as new version for %s", newTag.String(), recipe.Name) nextTag = newTag.String() } if _, err := tagcmp.Parse(nextTag); err != nil { logrus.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 { logrus.Fatal(err) } } else { logrus.Infof("dry run: not syncing label %s for recipe %s", nextTag, recipe.Name) } return nil }, BashComplete: autocomplete.RecipeNameComplete, }