forked from toolshed/abra
		
	feat: make sync use wizard mode
Some bugs squashed while testing this extensively.
This commit is contained in:
		
							
								
								
									
										118
									
								
								cli/internal/recipe.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								cli/internal/recipe.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,118 @@ | |||||||
|  | package internal | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"coopcloud.tech/abra/pkg/recipe" | ||||||
|  | 	"github.com/AlecAivazis/survey/v2" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/urfave/cli/v2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var Major bool | ||||||
|  | var MajorFlag = &cli.BoolFlag{ | ||||||
|  | 	Name:        "major", | ||||||
|  | 	Usage:       "Increase the major part of the version", | ||||||
|  | 	Value:       false, | ||||||
|  | 	Aliases:     []string{"ma", "x"}, | ||||||
|  | 	Destination: &Major, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var Minor bool | ||||||
|  | var MinorFlag = &cli.BoolFlag{ | ||||||
|  | 	Name:        "minor", | ||||||
|  | 	Usage:       "Increase the minor part of the version", | ||||||
|  | 	Value:       false, | ||||||
|  | 	Aliases:     []string{"mi", "y"}, | ||||||
|  | 	Destination: &Minor, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var Patch bool | ||||||
|  | var PatchFlag = &cli.BoolFlag{ | ||||||
|  | 	Name:        "patch", | ||||||
|  | 	Usage:       "Increase the patch part of the version", | ||||||
|  | 	Value:       false, | ||||||
|  | 	Aliases:     []string{"p", "z"}, | ||||||
|  | 	Destination: &Patch, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var Dry bool | ||||||
|  | var DryFlag = &cli.BoolFlag{ | ||||||
|  | 	Name:        "dry-run", | ||||||
|  | 	Usage:       "No changes are made, only reports changes that would be made", | ||||||
|  | 	Value:       false, | ||||||
|  | 	Aliases:     []string{"d"}, | ||||||
|  | 	Destination: &Dry, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PromptBumpType prompts for version bump type | ||||||
|  | func PromptBumpType(tagString string) error { | ||||||
|  | 	if (!Major && !Minor && !Patch) && tagString == "" { | ||||||
|  | 		fmt.Printf(` | ||||||
|  | semver cheat sheet (more via semver.org): | ||||||
|  |   major: new features/bug fixes, backwards incompatible | ||||||
|  |   minor: new features/bug fixes, backwards compatible | ||||||
|  |   patch: bug fixes, backwards compatible | ||||||
|  |  | ||||||
|  | `) | ||||||
|  | 		var chosenBumpType string | ||||||
|  | 		prompt := &survey.Select{ | ||||||
|  | 			Message: fmt.Sprintf("select recipe version increment type"), | ||||||
|  | 			Options: []string{"major", "minor", "patch"}, | ||||||
|  | 		} | ||||||
|  | 		if err := survey.AskOne(prompt, &chosenBumpType); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		SetBumpType(chosenBumpType) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetBumpType figures out which bump type is specified | ||||||
|  | func GetBumpType() string { | ||||||
|  | 	var bumpType string | ||||||
|  |  | ||||||
|  | 	if Major { | ||||||
|  | 		bumpType = "major" | ||||||
|  | 	} else if Minor { | ||||||
|  | 		bumpType = "minor" | ||||||
|  | 	} else if Patch { | ||||||
|  | 		bumpType = "patch" | ||||||
|  | 	} else { | ||||||
|  | 		logrus.Fatal("no version bump type specififed?") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return bumpType | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SetBumpType figures out which bump type is specified | ||||||
|  | func SetBumpType(bumpType string) { | ||||||
|  | 	if bumpType == "major" { | ||||||
|  | 		Major = true | ||||||
|  | 	} else if bumpType == "minor" { | ||||||
|  | 		Minor = true | ||||||
|  | 	} else if bumpType == "patch" { | ||||||
|  | 		Patch = true | ||||||
|  | 	} else { | ||||||
|  | 		logrus.Fatal("no version bump type specififed?") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetMainApp retrieves the main 'app' image name | ||||||
|  | func GetMainApp(recipe recipe.Recipe) string { | ||||||
|  | 	var app string | ||||||
|  |  | ||||||
|  | 	for _, service := range recipe.Config.Services { | ||||||
|  | 		name := service.Name | ||||||
|  | 		if name == "app" { | ||||||
|  | 			app = strings.Split(service.Image, ":")[0] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if app == "" { | ||||||
|  | 		logrus.Fatalf("%s has no main 'app' service?", recipe.Name) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return app | ||||||
|  | } | ||||||
| @ -4,33 +4,6 @@ import ( | |||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var Major bool |  | ||||||
| var MajorFlag = &cli.BoolFlag{ |  | ||||||
| 	Name:        "major", |  | ||||||
| 	Usage:       "Increase the major part of the version (new functionality, backwards incompatible, x of x.y.z)", |  | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"ma", "x"}, |  | ||||||
| 	Destination: &Major, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var Minor bool |  | ||||||
| var MinorFlag = &cli.BoolFlag{ |  | ||||||
| 	Name:        "minor", |  | ||||||
| 	Usage:       "Increase the minor part of the version (new functionality, backwards compatible, y of x.y.z)", |  | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"mi", "y"}, |  | ||||||
| 	Destination: &Minor, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var Patch bool |  | ||||||
| var PatchFlag = &cli.BoolFlag{ |  | ||||||
| 	Name:        "patch", |  | ||||||
| 	Usage:       "Increase the patch part of the version (bug fixes, backwards compatible, z of x.y.z)", |  | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"p", "z"}, |  | ||||||
| 	Destination: &Patch, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // RecipeCommand defines all recipe related sub-commands. | // RecipeCommand defines all recipe related sub-commands. | ||||||
| var RecipeCommand = &cli.Command{ | var RecipeCommand = &cli.Command{ | ||||||
| 	Name:      "recipe", | 	Name:      "recipe", | ||||||
|  | |||||||
| @ -29,15 +29,6 @@ var PushFlag = &cli.BoolFlag{ | |||||||
| 	Destination: &Push, | 	Destination: &Push, | ||||||
| } | } | ||||||
|  |  | ||||||
| var Dry bool |  | ||||||
| var DryFlag = &cli.BoolFlag{ |  | ||||||
| 	Name:        "dry-run", |  | ||||||
| 	Usage:       "No changes are made, only reports changes that would be made", |  | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"d"}, |  | ||||||
| 	Destination: &Dry, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var CommitMessage string | var CommitMessage string | ||||||
| var CommitMessageFlag = &cli.StringFlag{ | var CommitMessageFlag = &cli.StringFlag{ | ||||||
| 	Name:        "commit-message", | 	Name:        "commit-message", | ||||||
| @ -95,10 +86,10 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
|  |  | ||||||
| `, | `, | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
| 		DryFlag, | 		internal.DryFlag, | ||||||
| 		MajorFlag, | 		internal.MajorFlag, | ||||||
| 		MinorFlag, | 		internal.MinorFlag, | ||||||
| 		PatchFlag, | 		internal.PatchFlag, | ||||||
| 		PushFlag, | 		PushFlag, | ||||||
| 		CommitFlag, | 		CommitFlag, | ||||||
| 		CommitMessageFlag, | 		CommitMessageFlag, | ||||||
| @ -108,7 +99,7 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 		recipe := internal.ValidateRecipeWithPrompt(c) | 		recipe := internal.ValidateRecipeWithPrompt(c) | ||||||
| 		directory := path.Join(config.APPS_DIR, recipe.Name) | 		directory := path.Join(config.APPS_DIR, recipe.Name) | ||||||
| 		tagString := c.Args().Get(1) | 		tagString := c.Args().Get(1) | ||||||
| 		mainApp := getMainApp(recipe) | 		mainApp := internal.GetMainApp(recipe) | ||||||
|  |  | ||||||
| 		imagesTmp, err := getImageVersions(recipe) | 		imagesTmp, err := getImageVersions(recipe) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @ -130,16 +121,16 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (!Major && !Minor && !Patch) && tagString != "" { | 		if (!internal.Major && !internal.Minor && !internal.Patch) && tagString != "" { | ||||||
| 			logrus.Fatal("please specify <version> or bump type (--major/--minor/--patch)") | 			logrus.Fatal("please specify <version> or bump type (--major/--minor/--patch)") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (Major || Minor || Patch) && tagString != "" { | 		if (internal.Major || internal.Minor || internal.Patch) && tagString != "" { | ||||||
| 			logrus.Fatal("cannot specify tag and bump type at the same time") | 			logrus.Fatal("cannot specify tag and bump type at the same time") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// bumpType is used to decide what part of the tag should be incremented | 		// bumpType is used to decide what part of the tag should be incremented | ||||||
| 		bumpType := btoi(Major)*4 + btoi(Minor)*2 + btoi(Patch) | 		bumpType := btoi(internal.Major)*4 + btoi(internal.Minor)*2 + btoi(internal.Patch) | ||||||
| 		if bumpType != 0 { | 		if bumpType != 0 { | ||||||
| 			// a bitwise check if the number is a power of 2 | 			// a bitwise check if the number is a power of 2 | ||||||
| 			if (bumpType & (bumpType - 1)) != 0 { | 			if (bumpType & (bumpType - 1)) != 0 { | ||||||
| @ -147,29 +138,14 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (!Major && !Minor && !Patch) && tagString == "" { | 		if err := internal.PromptBumpType(tagString); err != nil { | ||||||
| 			fmt.Printf(` |  | ||||||
| semver cheat sheet (more via semver.org): |  | ||||||
|   major: new features/bug fixes, backwards incompatible |  | ||||||
|   minor: new features/bug fixes, backwards compatible |  | ||||||
|   patch: bug fixes, backwards compatible |  | ||||||
|  |  | ||||||
| `) |  | ||||||
| 			var chosenBumpType string |  | ||||||
| 			prompt := &survey.Select{ |  | ||||||
| 				Message: fmt.Sprintf("select recipe version increment type"), |  | ||||||
| 				Options: []string{"major", "minor", "patch"}, |  | ||||||
| 			} |  | ||||||
| 			if err := survey.AskOne(prompt, &chosenBumpType); err != nil { |  | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 			setBumpType(chosenBumpType) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if TagMessage == "" { | 		if TagMessage == "" { | ||||||
| 			prompt := &survey.Input{ | 			prompt := &survey.Input{ | ||||||
| 				Message: "tag message", | 				Message: "tag message", | ||||||
| 				Default: fmt.Sprintf("chore: publish new %s version", getBumpType()), | 				Default: fmt.Sprintf("chore: publish new %s version", internal.GetBumpType()), | ||||||
| 			} | 			} | ||||||
| 			if err := survey.AskOne(prompt, &TagMessage); err != nil { | 			if err := survey.AskOne(prompt, &TagMessage); err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| @ -210,7 +186,7 @@ semver cheat sheet (more via semver.org): | |||||||
| 			if CommitMessage == "" { | 			if CommitMessage == "" { | ||||||
| 				prompt := &survey.Input{ | 				prompt := &survey.Input{ | ||||||
| 					Message: "commit message", | 					Message: "commit message", | ||||||
| 					Default: fmt.Sprintf("chore: publish new %s version", getBumpType()), | 					Default: fmt.Sprintf("chore: publish new %s version", internal.GetBumpType()), | ||||||
| 				} | 				} | ||||||
| 				if err := survey.AskOne(prompt, &CommitMessage); err != nil { | 				if err := survey.AskOne(prompt, &CommitMessage); err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| @ -223,7 +199,7 @@ semver cheat sheet (more via semver.org): | |||||||
| 			} | 			} | ||||||
| 			logrus.Debug("staged compose.**yml for commit") | 			logrus.Debug("staged compose.**yml for commit") | ||||||
|  |  | ||||||
| 			if !Dry { | 			if !internal.Dry { | ||||||
| 				_, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{}) | 				_, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{}) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| @ -257,7 +233,7 @@ semver cheat sheet (more via semver.org): | |||||||
| 				tag.MissingPatch = false | 				tag.MissingPatch = false | ||||||
| 			} | 			} | ||||||
| 			tagString = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion) | 			tagString = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion) | ||||||
| 			if Dry { | 			if internal.Dry { | ||||||
| 				hash := abraFormatter.SmallSHA(head.Hash().String()) | 				hash := abraFormatter.SmallSHA(head.Hash().String()) | ||||||
| 				logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", tagString, hash)) | 				logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", tagString, hash)) | ||||||
| 				return nil | 				return nil | ||||||
| @ -266,7 +242,7 @@ semver cheat sheet (more via semver.org): | |||||||
| 			repo.CreateTag(tagString, head.Hash(), &createTagOptions) | 			repo.CreateTag(tagString, head.Hash(), &createTagOptions) | ||||||
| 			hash := abraFormatter.SmallSHA(head.Hash().String()) | 			hash := abraFormatter.SmallSHA(head.Hash().String()) | ||||||
| 			logrus.Info(fmt.Sprintf("created tag %s at %s", tagString, hash)) | 			logrus.Info(fmt.Sprintf("created tag %s at %s", tagString, hash)) | ||||||
| 			if Push && !Dry { | 			if Push && !internal.Dry { | ||||||
| 				if err := repo.Push(&git.PushOptions{}); err != nil { | 				if err := repo.Push(&git.PushOptions{}); err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| 				} | 				} | ||||||
| @ -306,20 +282,20 @@ semver cheat sheet (more via semver.org): | |||||||
| 		newTag := lastGitTag | 		newTag := lastGitTag | ||||||
| 		var newtagString string | 		var newtagString string | ||||||
| 		if bumpType > 0 { | 		if bumpType > 0 { | ||||||
| 			if Patch { | 			if internal.Patch { | ||||||
| 				now, err := strconv.Atoi(newTag.Patch) | 				now, err := strconv.Atoi(newTag.Patch) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| 				} | 				} | ||||||
| 				newTag.Patch = strconv.Itoa(now + 1) | 				newTag.Patch = strconv.Itoa(now + 1) | ||||||
| 			} else if Minor { | 			} else if internal.Minor { | ||||||
| 				now, err := strconv.Atoi(newTag.Minor) | 				now, err := strconv.Atoi(newTag.Minor) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| 				} | 				} | ||||||
| 				newTag.Patch = "0" | 				newTag.Patch = "0" | ||||||
| 				newTag.Minor = strconv.Itoa(now + 1) | 				newTag.Minor = strconv.Itoa(now + 1) | ||||||
| 			} else if Major { | 			} else if internal.Major { | ||||||
| 				now, err := strconv.Atoi(newTag.Major) | 				now, err := strconv.Atoi(newTag.Major) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| @ -332,7 +308,7 @@ semver cheat sheet (more via semver.org): | |||||||
|  |  | ||||||
| 		newTag.Metadata = mainAppVersion | 		newTag.Metadata = mainAppVersion | ||||||
| 		newtagString = newTag.String() | 		newtagString = newTag.String() | ||||||
| 		if Dry { | 		if internal.Dry { | ||||||
| 			hash := abraFormatter.SmallSHA(head.Hash().String()) | 			hash := abraFormatter.SmallSHA(head.Hash().String()) | ||||||
| 			logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newtagString, hash)) | 			logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newtagString, hash)) | ||||||
| 			return nil | 			return nil | ||||||
| @ -341,13 +317,13 @@ semver cheat sheet (more via semver.org): | |||||||
| 		repo.CreateTag(newtagString, head.Hash(), &createTagOptions) | 		repo.CreateTag(newtagString, head.Hash(), &createTagOptions) | ||||||
| 		hash := abraFormatter.SmallSHA(head.Hash().String()) | 		hash := abraFormatter.SmallSHA(head.Hash().String()) | ||||||
| 		logrus.Info(fmt.Sprintf("created tag %s at %s", newtagString, hash)) | 		logrus.Info(fmt.Sprintf("created tag %s at %s", newtagString, hash)) | ||||||
| 		if Push && !Dry { | 		if Push && !internal.Dry { | ||||||
| 			if err := repo.Push(&git.PushOptions{}); err != nil { | 			if err := repo.Push(&git.PushOptions{}); err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| 			} | 			} | ||||||
| 			logrus.Info(fmt.Sprintf("pushed tag %s to remote", newtagString)) | 			logrus.Info(fmt.Sprintf("pushed tag %s to remote", newtagString)) | ||||||
| 		} else { | 		} else { | ||||||
| 			logrus.Info("dry run only: NOT pushing changes") | 			logrus.Info("gry run only: NOT pushing changes") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return nil | 		return nil | ||||||
| @ -387,18 +363,6 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) { | |||||||
| 	return services, nil | 	return services, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // getMainApp retrieves the main 'app' image name |  | ||||||
| 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 "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // btoi converts a boolean value into an integer | // btoi converts a boolean value into an integer | ||||||
| func btoi(b bool) int { | func btoi(b bool) int { | ||||||
| 	if b { | 	if b { | ||||||
| @ -407,33 +371,3 @@ func btoi(b bool) int { | |||||||
|  |  | ||||||
| 	return 0 | 	return 0 | ||||||
| } | } | ||||||
|  |  | ||||||
| // getBumpType figures out which bump type is specified |  | ||||||
| func getBumpType() string { |  | ||||||
| 	var bumpType string |  | ||||||
|  |  | ||||||
| 	if Major { |  | ||||||
| 		bumpType = "major" |  | ||||||
| 	} else if Minor { |  | ||||||
| 		bumpType = "minor" |  | ||||||
| 	} else if Patch { |  | ||||||
| 		bumpType = "patch" |  | ||||||
| 	} else { |  | ||||||
| 		logrus.Fatal("no version bump type specififed?") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return bumpType |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // setBumpType figures out which bump type is specified |  | ||||||
| func setBumpType(bumpType string) { |  | ||||||
| 	if bumpType == "major" { |  | ||||||
| 		Major = true |  | ||||||
| 	} else if bumpType == "minor" { |  | ||||||
| 		Minor = true |  | ||||||
| 	} else if bumpType == "patch" { |  | ||||||
| 		Patch = true |  | ||||||
| 	} else { |  | ||||||
| 		logrus.Fatal("no version bump type specififed?") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,12 +1,17 @@ | |||||||
| package recipe | package recipe | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"path" | ||||||
|  | 	"strconv" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/catalogue" | 	"coopcloud.tech/abra/pkg/catalogue" | ||||||
|  | 	"coopcloud.tech/abra/pkg/config" | ||||||
|  | 	"coopcloud.tech/tagcmp" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"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/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| ) | ) | ||||||
| @ -16,17 +21,166 @@ var recipeSyncCommand = &cli.Command{ | |||||||
| 	Usage:     "Ensure recipe version labels are up-to-date", | 	Usage:     "Ensure recipe version labels are up-to-date", | ||||||
| 	Aliases:   []string{"s"}, | 	Aliases:   []string{"s"}, | ||||||
| 	ArgsUsage: "<recipe> [<version>]", | 	ArgsUsage: "<recipe> [<version>]", | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DryFlag, | ||||||
|  | 		internal.MajorFlag, | ||||||
|  | 		internal.MinorFlag, | ||||||
|  | 		internal.PatchFlag, | ||||||
|  | 	}, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command will generate labels for the main recipe service (i.e. by | This command will generate labels for the main recipe service (i.e. by | ||||||
| convention, typically the service named "app") which corresponds to the | convention, the service named "app") which corresponds to the following format: | ||||||
| following format: |  | ||||||
|  |  | ||||||
|     coop-cloud.${STACK_NAME}.version=<version> |     coop-cloud.${STACK_NAME}.version=<version> | ||||||
|  |  | ||||||
| The <version> is determined by the recipe maintainer and is specified on the | The <version> is determined by the recipe maintainer and is specified on the | ||||||
| command-line. The <recipe> configuration will be updated on the local file | command-line. The <recipe> configuration will be updated on the local file | ||||||
| system. | system. | ||||||
|  |  | ||||||
|  | You may invoke this command in "wizard" mode and be prompted for input: | ||||||
|  |  | ||||||
|  |     abra recipe sync gitea | ||||||
|  |  | ||||||
| `, | `, | ||||||
|  | 	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: func(c *cli.Context) { | 	BashComplete: func(c *cli.Context) { | ||||||
| 		catl, err := catalogue.ReadRecipeCatalogue() | 		catl, err := catalogue.ReadRecipeCatalogue() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @ -39,50 +193,4 @@ system. | |||||||
| 			fmt.Println(name) | 			fmt.Println(name) | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	Action: func(c *cli.Context) error { |  | ||||||
| 		if c.Args().Len() != 2 { |  | ||||||
| 			internal.ShowSubcommandHelpAndError(c, errors.New("missing <recipe>/<version> arguments?")) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		recipe := internal.ValidateRecipe(c) |  | ||||||
|  |  | ||||||
| 		// TODO: validate with tagcmp when new commits come in |  | ||||||
| 		// See https://git.coopcloud.tech/coop-cloud/abra/pulls/109 |  | ||||||
| 		nextTag := c.Args().Get(1) |  | ||||||
|  |  | ||||||
| 		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.Warnf("no 'app' service defined in '%s'", recipe.Name) |  | ||||||
| 			var chosenService string |  | ||||||
| 			prompt := &survey.Select{ |  | ||||||
| 				Message: fmt.Sprintf("what is the main service name for '%s'?", recipe.Name), |  | ||||||
| 				Options: services, |  | ||||||
| 			} |  | ||||||
| 			if err := survey.AskOne(prompt, &chosenService); err != nil { |  | ||||||
| 				logrus.Fatal(err) |  | ||||||
| 			} |  | ||||||
| 			mainService = chosenService |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		logrus.Debugf("selecting '%s' as the service to sync version labels", mainService) |  | ||||||
|  |  | ||||||
| 		label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", nextTag) |  | ||||||
| 		if err := recipe.UpdateLabel(mainService, label); err != nil { |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		logrus.Infof("synced label '%s' to service '%s'", label, mainService) |  | ||||||
|  |  | ||||||
| 		return nil |  | ||||||
| 	}, |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -39,14 +39,14 @@ is up to the end-user to decide. | |||||||
| `, | `, | ||||||
| 	ArgsUsage: "<recipe>", | 	ArgsUsage: "<recipe>", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
| 		PatchFlag, | 		internal.PatchFlag, | ||||||
| 		MinorFlag, | 		internal.MinorFlag, | ||||||
| 		MajorFlag, | 		internal.MajorFlag, | ||||||
| 	}, | 	}, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		recipe := internal.ValidateRecipe(c) | 		recipe := internal.ValidateRecipe(c) | ||||||
|  |  | ||||||
| 		bumpType := btoi(Major)*4 + btoi(Minor)*2 + btoi(Patch) | 		bumpType := btoi(internal.Major)*4 + btoi(internal.Minor)*2 + btoi(internal.Patch) | ||||||
| 		if bumpType != 0 { | 		if bumpType != 0 { | ||||||
| 			// a bitwise check if the number is a power of 2 | 			// a bitwise check if the number is a power of 2 | ||||||
| 			if (bumpType & (bumpType - 1)) != 0 { | 			if (bumpType & (bumpType - 1)) != 0 { | ||||||
| @ -179,11 +179,11 @@ is up to the end-user to decide. | |||||||
| 					if contains { | 					if contains { | ||||||
| 						logrus.Infof("Upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString) | 						logrus.Infof("Upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString) | ||||||
| 					} else { | 					} else { | ||||||
| 						logrus.Infof("service %s, image %s pinned to %s. No compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString) | 						logrus.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString) | ||||||
| 						continue | 						continue | ||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| 					logrus.Fatalf("Service %s is at version %s, but pinned to %s. Please correct your compose.yml file manually!", service.Name, tag.String(), pinnedTag.String()) | 					logrus.Fatalf("Service %s is at version %s, but pinned to %s, please correct your compose.yml file manually!", service.Name, tag.String(), pinnedTag.String()) | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
|  | |||||||
| @ -117,8 +117,11 @@ func UpdateLabel(pattern, serviceName, label, recipeName string) error { | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		discovered := false | ||||||
| 		for oldLabel, value := range service.Deploy.Labels { | 		for oldLabel, value := range service.Deploy.Labels { | ||||||
| 			if strings.HasPrefix(oldLabel, "coop-cloud") { | 			if strings.HasPrefix(oldLabel, "coop-cloud") { | ||||||
|  | 				discovered = true | ||||||
|  |  | ||||||
| 				bytes, err := ioutil.ReadFile(composeFile) | 				bytes, err := ioutil.ReadFile(composeFile) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return err | 					return err | ||||||
| @ -127,13 +130,19 @@ func UpdateLabel(pattern, serviceName, label, recipeName string) error { | |||||||
| 				old := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", value) | 				old := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", value) | ||||||
| 				replacedBytes := strings.Replace(string(bytes), old, label, -1) | 				replacedBytes := strings.Replace(string(bytes), old, label, -1) | ||||||
|  |  | ||||||
| 				logrus.Debugf("updating '%s' to '%s' in '%s'", old, label, compose.Filename) | 				logrus.Debugf("updating %s to %s in %s", old, label, compose.Filename) | ||||||
|  |  | ||||||
| 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil { | 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil { | ||||||
| 					return err | 					return err | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if !discovered { | ||||||
|  | 			logrus.Warn("no existing label found, cannot continue...") | ||||||
|  | 			logrus.Fatalf("add '%s' manually, automagic insertion not supported yet", label) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user