forked from toolshed/abra
		
	
		
			
				
	
	
		
			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",
 | |
| 	)
 | |
| }
 |