forked from toolshed/abra
		
	refactor!: simplifying publish logic
This commit is contained in:
		| @ -13,7 +13,6 @@ import ( | ||||
| 	gitPkg "coopcloud.tech/abra/pkg/git" | ||||
| 	"coopcloud.tech/abra/pkg/limit" | ||||
| 	"coopcloud.tech/abra/pkg/recipe" | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| 	"github.com/go-git/go-git/v5" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| @ -59,11 +58,9 @@ var CatalogueSkipList = map[string]bool{ | ||||
| var catalogueGenerateCommand = &cli.Command{ | ||||
| 	Name:    "generate", | ||||
| 	Aliases: []string{"g"}, | ||||
| 	Usage:   "Generate a new copy of the recipe catalogue", | ||||
| 	Usage:   "Generate the recipe catalogue", | ||||
| 	Flags: []cli.Flag{ | ||||
| 		internal.PushFlag, | ||||
| 		internal.CommitFlag, | ||||
| 		internal.CommitMessageFlag, | ||||
| 		internal.PublishFlag, | ||||
| 		internal.DryFlag, | ||||
| 		internal.SkipUpdatesFlag, | ||||
| 		internal.RegistryUsernameFlag, | ||||
| @ -81,14 +78,13 @@ metadata and produces a recipes JSON file. | ||||
| It is possible to generate new metadata for a single recipe by passing | ||||
| <recipe>. The existing local catalogue will be updated, not overwritten. | ||||
|  | ||||
| A new catalogue copy can be published to the recipes repository by passing the | ||||
| "--commit" and "--push" flags. The recipes repository is available here: | ||||
|  | ||||
|     https://git.coopcloud.tech/coop-cloud/recipes | ||||
|  | ||||
| It is quite easy to get rate limited by Docker Hub when running this command. | ||||
| If you have a Hub account you can have Abra log you in to avoid this. Pass | ||||
| "--username" and "--password". | ||||
| "--user" and "--pass". | ||||
|  | ||||
| Push your new release git.coopcloud.tech with "-p/--publish". This requires | ||||
| that you have permission to git push to these repositories and have your SSH | ||||
| keys configured on your account. | ||||
| `, | ||||
| 	ArgsUsage: "[<recipe>]", | ||||
| 	Action: func(c *cli.Context) error { | ||||
| @ -197,37 +193,43 @@ If you have a Hub account you can have Abra log you in to avoid this. Pass | ||||
|  | ||||
| 		logrus.Infof("generated new recipe catalogue in %s", config.RECIPES_JSON) | ||||
|  | ||||
| 		if internal.Commit { | ||||
| 			if internal.CommitMessage == "" && !internal.NoInput { | ||||
| 				prompt := &survey.Input{ | ||||
| 					Message: "commit message", | ||||
| 					Default: fmt.Sprintf("chore: publish catalogue changes"), | ||||
| 				} | ||||
| 				if err := survey.AskOne(prompt, &internal.CommitMessage); err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 		if internal.Publish { | ||||
| 			cataloguePath := path.Join(config.ABRA_DIR, "catalogue") | ||||
| 			if err := gitPkg.Commit(cataloguePath, "**.json", internal.CommitMessage, internal.Dry); err != nil { | ||||
|  | ||||
| 			isClean, err := gitPkg.IsClean(cataloguePath) | ||||
| 			if err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			if internal.Push { | ||||
| 				repo, err := git.PlainOpen(cataloguePath) | ||||
| 				if err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
|  | ||||
| 				sshURL := fmt.Sprintf(config.SSH_URL_TEMPLATE, "recipes") | ||||
| 				if err := gitPkg.CreateRemote(repo, "origin-ssh", sshURL, internal.Dry); err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
|  | ||||
| 				if err := gitPkg.Push(cataloguePath, "origin-ssh", false, internal.Dry); err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
| 			if isClean { | ||||
| 				logrus.Fatalf("no changes discovered in %s, nothing to publish?", cataloguePath) | ||||
| 			} | ||||
|  | ||||
| 			msg := "chore: publish new catalogue release changes" | ||||
| 			if err := gitPkg.Commit(cataloguePath, "**.json", msg, internal.Dry); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			repo, err := git.PlainOpen(cataloguePath) | ||||
| 			if err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			sshURL := fmt.Sprintf(config.SSH_URL_TEMPLATE, "recipes") | ||||
| 			if err := gitPkg.CreateRemote(repo, "origin-ssh", sshURL, internal.Dry); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			if err := gitPkg.Push(cataloguePath, "origin-ssh", false, internal.Dry); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if internal.Dry { | ||||
| 			logrus.Info("dry run: no changes published") | ||||
| 		} else { | ||||
| 			url := fmt.Sprintf("%s/recipes", config.REPOS_BASE_URL) | ||||
| 			logrus.Infof("new changes published: %s", url) | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| @ -281,7 +283,7 @@ func updateRepositories(repos recipe.RepoCatalogue, recipeName string) error { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			isClean, err := gitPkg.IsClean(rm.Name) | ||||
| 			isClean, err := gitPkg.IsClean(recipeDir) | ||||
| 			if err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| @ -306,7 +306,7 @@ var PatchFlag = &cli.BoolFlag{ | ||||
| 	Name:        "patch", | ||||
| 	Usage:       "Increase the patch part of the version", | ||||
| 	Value:       false, | ||||
| 	Aliases:     []string{"p", "z"}, | ||||
| 	Aliases:     []string{"pa", "z"}, | ||||
| 	Destination: &Patch, | ||||
| } | ||||
|  | ||||
| @ -319,38 +319,13 @@ var DryFlag = &cli.BoolFlag{ | ||||
| 	Destination: &Dry, | ||||
| } | ||||
|  | ||||
| var Push bool | ||||
| var PushFlag = &cli.BoolFlag{ | ||||
| 	Name:        "push", | ||||
| 	Usage:       "Git push changes", | ||||
| var Publish bool | ||||
| var PublishFlag = &cli.BoolFlag{ | ||||
| 	Name:        "publish", | ||||
| 	Usage:       "Publish changes to git.coopcloud.tech", | ||||
| 	Value:       false, | ||||
| 	Aliases:     []string{"P"}, | ||||
| 	Destination: &Push, | ||||
| } | ||||
|  | ||||
| var CommitMessage string | ||||
| var CommitMessageFlag = &cli.StringFlag{ | ||||
| 	Name:        "commit-message", | ||||
| 	Usage:       "Commit message (implies --commit)", | ||||
| 	Aliases:     []string{"cm"}, | ||||
| 	Destination: &CommitMessage, | ||||
| } | ||||
|  | ||||
| var Commit bool | ||||
| var CommitFlag = &cli.BoolFlag{ | ||||
| 	Name:        "commit", | ||||
| 	Usage:       "Commit new changes", | ||||
| 	Value:       false, | ||||
| 	Aliases:     []string{"c"}, | ||||
| 	Destination: &Commit, | ||||
| } | ||||
|  | ||||
| var TagMessage string | ||||
| var TagMessageFlag = &cli.StringFlag{ | ||||
| 	Name:        "tag-comment", | ||||
| 	Usage:       "Description for release tag", | ||||
| 	Aliases:     []string{"t", "tm"}, | ||||
| 	Destination: &TagMessage, | ||||
| 	Aliases:     []string{"p"}, | ||||
| 	Destination: &Publish, | ||||
| } | ||||
|  | ||||
| var Domain string | ||||
| @ -439,14 +414,14 @@ var SkipUpdatesFlag = &cli.BoolFlag{ | ||||
| 	Name:        "skip-updates", | ||||
| 	Aliases:     []string{"s"}, | ||||
| 	Value:       false, | ||||
| 	Usage:       "Skip updating git repositories", | ||||
| 	Usage:       "Skip updating recipe repositories", | ||||
| 	Destination: &SkipUpdates, | ||||
| } | ||||
|  | ||||
| var RegistryUsername string | ||||
| var RegistryUsernameFlag = &cli.StringFlag{ | ||||
| 	Name:        "username", | ||||
| 	Aliases:     []string{"u"}, | ||||
| 	Aliases:     []string{"user"}, | ||||
| 	Value:       "", | ||||
| 	Usage:       "Registry username", | ||||
| 	EnvVars:     []string{"REGISTRY_USERNAME"}, | ||||
| @ -456,7 +431,7 @@ var RegistryUsernameFlag = &cli.StringFlag{ | ||||
| var RegistryPassword string | ||||
| var RegistryPasswordFlag = &cli.StringFlag{ | ||||
| 	Name:        "password", | ||||
| 	Aliases:     []string{"p"}, | ||||
| 	Aliases:     []string{"pass"}, | ||||
| 	Value:       "", | ||||
| 	Usage:       "Registry password", | ||||
| 	EnvVars:     []string{"REGISTRY_PASSWORD"}, | ||||
|  | ||||
| @ -6,6 +6,7 @@ import ( | ||||
|  | ||||
| 	"coopcloud.tech/abra/pkg/recipe" | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| 	"github.com/docker/distribution/reference" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| @ -64,20 +65,29 @@ func SetBumpType(bumpType string) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetMainApp retrieves the main 'app' image name | ||||
| func GetMainApp(recipe recipe.Recipe) string { | ||||
| 	var app string | ||||
| // GetMainAppImage retrieves the main 'app' image name | ||||
| func GetMainAppImage(recipe recipe.Recipe) (string, error) { | ||||
| 	var path string | ||||
|  | ||||
| 	for _, service := range recipe.Config.Services { | ||||
| 		name := service.Name | ||||
| 		if name == "app" { | ||||
| 			app = strings.Split(service.Image, ":")[0] | ||||
| 		if service.Name == "app" { | ||||
| 			img, err := reference.ParseNormalizedNamed(service.Image) | ||||
| 			if err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
|  | ||||
| 			path = reference.Path(img) | ||||
| 			if strings.Contains(path, "library") { | ||||
| 				path = strings.Split(path, "/")[1] | ||||
| 			} | ||||
|  | ||||
| 			return path, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if app == "" { | ||||
| 		logrus.Fatalf("%s has no main 'app' service?", recipe.Name) | ||||
| 	if path == "" { | ||||
| 		return path, fmt.Errorf("%s has no main 'app' service?", recipe.Name) | ||||
| 	} | ||||
|  | ||||
| 	return app | ||||
| 	return path, nil | ||||
| } | ||||
|  | ||||
| @ -47,20 +47,16 @@ Abra does its best to read the "a.b.c" version scheme and communicate what | ||||
| action needs to be taken when performing different operations such as an update | ||||
| or a rollback of an app. | ||||
|  | ||||
| You may invoke this command in "wizard" mode and be prompted for input: | ||||
|  | ||||
|     abra recipe release gitea | ||||
|  | ||||
| Publish your new release git.coopcloud.tech with "-p/--publish". This requires | ||||
| that you have permission to git push to these repositories and have your SSH | ||||
| keys configured on your account. | ||||
| `, | ||||
| 	Flags: []cli.Flag{ | ||||
| 		internal.DryFlag, | ||||
| 		internal.MajorFlag, | ||||
| 		internal.MinorFlag, | ||||
| 		internal.PatchFlag, | ||||
| 		internal.PushFlag, | ||||
| 		internal.CommitFlag, | ||||
| 		internal.CommitMessageFlag, | ||||
| 		internal.TagMessageFlag, | ||||
| 		internal.PublishFlag, | ||||
| 	}, | ||||
| 	BashComplete: autocomplete.RecipeNameComplete, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| @ -71,7 +67,11 @@ You may invoke this command in "wizard" mode and be prompted for input: | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		mainApp := internal.GetMainApp(recipe) | ||||
| 		mainApp, err := internal.GetMainAppImage(recipe) | ||||
| 		if err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		mainAppVersion := imagesTmp[mainApp] | ||||
| 		if mainAppVersion == "" { | ||||
| 			logrus.Fatalf("main app service version for %s is empty?", recipe.Name) | ||||
| @ -201,14 +201,14 @@ func createReleaseFromTag(recipe recipe.Recipe, tagString, mainAppVersion string | ||||
| 		tag.MissingPatch = false | ||||
| 	} | ||||
|  | ||||
| 	if err := commitRelease(recipe); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if tagString == "" { | ||||
| 		tagString = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion) | ||||
| 	} | ||||
|  | ||||
| 	if err := commitRelease(recipe, tagString); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := tagRelease(tagString, repo); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
| @ -230,50 +230,30 @@ func btoi(b bool) int { | ||||
| } | ||||
|  | ||||
| // getTagCreateOptions constructs git tag create options | ||||
| func getTagCreateOptions() (git.CreateTagOptions, error) { | ||||
| 	if internal.TagMessage == "" && !internal.NoInput { | ||||
| 		prompt := &survey.Input{ | ||||
| 			Message: "git tag message?", | ||||
| 			Default: "chore: publish new release", | ||||
| 		} | ||||
|  | ||||
| 		if err := survey.AskOne(prompt, &internal.TagMessage); err != nil { | ||||
| 			return git.CreateTagOptions{}, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return git.CreateTagOptions{Message: internal.TagMessage}, nil | ||||
| func getTagCreateOptions(tag string) (git.CreateTagOptions, error) { | ||||
| 	msg := fmt.Sprintf("chore: publish %s release", tag) | ||||
| 	return git.CreateTagOptions{Message: msg}, nil | ||||
| } | ||||
|  | ||||
| func commitRelease(recipe recipe.Recipe) error { | ||||
| func commitRelease(recipe recipe.Recipe, tag string) error { | ||||
| 	if internal.Dry { | ||||
| 		logrus.Info("dry run: no changed committed") | ||||
| 		logrus.Debugf("dry run: no changes committed") | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if !internal.Commit && !internal.NoInput { | ||||
| 		prompt := &survey.Confirm{ | ||||
| 			Message: "git commit changes?", | ||||
| 		} | ||||
|  | ||||
| 		if err := survey.AskOne(prompt, &internal.Commit); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	isClean, err := gitPkg.IsClean(recipe.Dir()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if internal.CommitMessage == "" && !internal.NoInput && internal.Commit { | ||||
| 		prompt := &survey.Input{ | ||||
| 			Message: "git commit message?", | ||||
| 			Default: "chore: publish new version", | ||||
| 		} | ||||
| 		if err := survey.AskOne(prompt, &internal.CommitMessage); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	if isClean { | ||||
| 		return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir()) | ||||
| 	} | ||||
|  | ||||
| 	if internal.Commit { | ||||
| 	if internal.Publish { | ||||
| 		msg := fmt.Sprintf("chore: publish %s release", tag) | ||||
| 		repoPath := path.Join(config.RECIPES_DIR, recipe.Name) | ||||
| 		if err := gitPkg.Commit(repoPath, "compose.**yml", internal.CommitMessage, internal.Dry); err != nil { | ||||
| 		if err := gitPkg.Commit(repoPath, "compose.**yml", msg, internal.Dry); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| @ -283,7 +263,7 @@ func commitRelease(recipe recipe.Recipe) error { | ||||
|  | ||||
| func tagRelease(tagString string, repo *git.Repository) error { | ||||
| 	if internal.Dry { | ||||
| 		logrus.Infof("dry run: no git tag created (%s)", tagString) | ||||
| 		logrus.Debugf("dry run: no git tag created (%s)", tagString) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| @ -292,7 +272,7 @@ func tagRelease(tagString string, repo *git.Repository) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	createTagOptions, err := getTagCreateOptions() | ||||
| 	createTagOptions, err := getTagCreateOptions(tagString) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @ -303,33 +283,36 @@ func tagRelease(tagString string, repo *git.Repository) error { | ||||
| 	} | ||||
|  | ||||
| 	hash := abraFormatter.SmallSHA(head.Hash().String()) | ||||
| 	logrus.Info(fmt.Sprintf("created tag %s at %s", tagString, hash)) | ||||
| 	logrus.Debugf(fmt.Sprintf("created tag %s at %s", tagString, hash)) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func pushRelease(recipe recipe.Recipe) error { | ||||
| 	if internal.Dry { | ||||
| 		logrus.Info("dry run: no changes pushed") | ||||
| 		logrus.Info("dry run: no changes published") | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if !internal.Push && !internal.NoInput { | ||||
| 	if !internal.Publish && !internal.NoInput { | ||||
| 		prompt := &survey.Confirm{ | ||||
| 			Message: "git push changes?", | ||||
| 			Message: "publish new release?", | ||||
| 		} | ||||
|  | ||||
| 		if err := survey.AskOne(prompt, &internal.Push); err != nil { | ||||
| 		if err := survey.AskOne(prompt, &internal.Publish); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if internal.Push { | ||||
| 	if internal.Publish { | ||||
| 		if err := recipe.Push(internal.Dry); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	url := fmt.Sprintf("%s/%s/tags", config.REPOS_BASE_URL, recipe.Name) | ||||
| 	logrus.Infof("new release published: %s", url) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -392,13 +375,13 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip | ||||
| 		newTag.Major = strconv.Itoa(now + 1) | ||||
| 	} | ||||
|  | ||||
| 	if err := commitRelease(recipe); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	newTag.Metadata = mainAppVersion | ||||
| 	newTagString := newTag.String() | ||||
|  | ||||
| 	if err := commitRelease(recipe, newTagString); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := tagRelease(newTagString, repo); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
| @ -422,7 +405,7 @@ func cleanUpTag(tag, recipeName string) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	logrus.Warnf("removed freshly created tag %s", tag) | ||||
| 	logrus.Debugf("removed freshly created tag %s", tag) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @ -18,7 +18,7 @@ import ( | ||||
|  | ||||
| var recipeSyncCommand = &cli.Command{ | ||||
| 	Name:      "sync", | ||||
| 	Usage:     "Ensure recipe version labels are up-to-date", | ||||
| 	Usage:     "Sync recipe version label", | ||||
| 	Aliases:   []string{"s"}, | ||||
| 	ArgsUsage: "<recipe> [<version>]", | ||||
| 	Flags: []cli.Flag{ | ||||
| @ -29,23 +29,21 @@ var recipeSyncCommand = &cli.Command{ | ||||
| 	}, | ||||
| 	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: | ||||
| convention, the service named 'app') which corresponds to the following format: | ||||
|  | ||||
|     coop-cloud.${STACK_NAME}.version=<version> | ||||
|  | ||||
| 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 | ||||
| system. | ||||
|  | ||||
| You may invoke this command in "wizard" mode and be prompted for input: | ||||
|  | ||||
|     abra recipe sync | ||||
|  | ||||
| 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. | ||||
| `, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		recipe := internal.ValidateRecipeWithPrompt(c) | ||||
|  | ||||
| 		mainApp := internal.GetMainApp(recipe) | ||||
| 		mainApp, err := internal.GetMainAppImage(recipe) | ||||
| 		if err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		imagesTmp, err := getImageVersions(recipe) | ||||
| 		if err != nil { | ||||
|  | ||||
| @ -9,6 +9,7 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"coopcloud.tech/abra/cli/internal" | ||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | ||||
| 	"coopcloud.tech/abra/pkg/client" | ||||
| 	"coopcloud.tech/abra/pkg/config" | ||||
| 	recipePkg "coopcloud.tech/abra/pkg/recipe" | ||||
| @ -42,7 +43,8 @@ You may invoke this command in "wizard" mode and be prompted for input: | ||||
|     abra recipe upgrade | ||||
|  | ||||
| `, | ||||
| 	ArgsUsage: "<recipe>", | ||||
| 	BashComplete: autocomplete.RecipeNameComplete, | ||||
| 	ArgsUsage:    "<recipe>", | ||||
| 	Flags: []cli.Flag{ | ||||
| 		internal.PatchFlag, | ||||
| 		internal.MinorFlag, | ||||
|  | ||||
		Reference in New Issue
	
	Block a user