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/v2" ) var recipeSyncCommand = &cli.Command{ Name: "sync", Usage: "Ensure recipe version labels are up-to-date", Aliases: []string{"s"}, ArgsUsage: " []", Flags: []cli.Flag{ internal.DryFlag, internal.MajorFlag, internal.MinorFlag, internal.PatchFlag, }, 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= The is determined by the recipe maintainer and is specified on the command-line. The configuration will be updated on the local file system. You may invoke this command in "wizard" mode and be prompted for input: abra recipe sync `, Action: func(c *cli.Context) error { recipe := internal.ValidateRecipeWithPrompt(c) mainApp := internal.GetMainApp(recipe) 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 tags found for %s", 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.APPS_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 of: --major, --minor, --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" var services []string hasAppService := false for _, service := range recipe.Config.Services { services = append(services, service.Name) if service.Name == "app" { hasAppService = true logrus.Debugf("detected app service in %s", recipe.Name) } } if !hasAppService { logrus.Fatalf("%s has no main 'app' service?", recipe.Name) } logrus.Debugf("selecting %s as the service to sync version label", mainService) label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", nextTag) if !internal.Dry { if err := recipe.UpdateLabel(mainService, label); err != nil { logrus.Fatal(err) } logrus.Infof("synced label '%s' to service '%s'", label, mainService) } else { logrus.Infof("dry run only: NOT syncing label %s for recipe %s", nextTag, recipe.Name) } return nil }, BashComplete: autocomplete.RecipeNameComplete, }