forked from toolshed/abra
		
	
		
			
				
	
	
		
			279 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			279 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package recipe
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"path"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"coopcloud.tech/abra/cli/internal"
 | 
						|
	"coopcloud.tech/abra/pkg/config"
 | 
						|
	"coopcloud.tech/abra/pkg/recipe"
 | 
						|
	"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/go-git/go-git/v5/plumbing/object"
 | 
						|
	"github.com/sirupsen/logrus"
 | 
						|
	"github.com/urfave/cli/v2"
 | 
						|
)
 | 
						|
 | 
						|
var Push bool
 | 
						|
var PushFlag = &cli.BoolFlag{
 | 
						|
	Name:        "push",
 | 
						|
	Value:       false,
 | 
						|
	Destination: &Push,
 | 
						|
}
 | 
						|
 | 
						|
var Dry bool
 | 
						|
var DryFlag = &cli.BoolFlag{
 | 
						|
	Name:        "dry-run",
 | 
						|
	Value:       false,
 | 
						|
	Aliases:     []string{"d"},
 | 
						|
	Destination: &Dry,
 | 
						|
}
 | 
						|
 | 
						|
var CommitMessage string
 | 
						|
var CommitMessageFlag = &cli.StringFlag{
 | 
						|
	Name:        "commit-message",
 | 
						|
	Usage:       "commit message",
 | 
						|
	Aliases:     []string{"cm"},
 | 
						|
	Destination: &CommitMessage,
 | 
						|
}
 | 
						|
 | 
						|
var Commit bool
 | 
						|
var CommitFlag = &cli.BoolFlag{
 | 
						|
	Name:        "commit",
 | 
						|
	Value:       false,
 | 
						|
	Aliases:     []string{"c"},
 | 
						|
	Destination: &Commit,
 | 
						|
}
 | 
						|
 | 
						|
var recipeReleaseCommand = &cli.Command{
 | 
						|
	Name:      "release",
 | 
						|
	Usage:     "tag a recipe",
 | 
						|
	Aliases:   []string{"rl"},
 | 
						|
	ArgsUsage: "<recipe> [<tag>]",
 | 
						|
	Description: `
 | 
						|
This command is used to specify a new tag for a recipe. These tags are used to
 | 
						|
identify different versions of the recipe and are published on the Co-op Cloud
 | 
						|
recipe catalogue.
 | 
						|
 | 
						|
These tags take the following form:
 | 
						|
 | 
						|
    a.b.c+x.y.z
 | 
						|
 | 
						|
Where the "a.b.c" part is maintained as a semantic version of the recipe by the
 | 
						|
recipe maintainer. And the "x.y.z" part is the image tag of the recipe "app"
 | 
						|
service (the main container which contains the software to be used).
 | 
						|
 | 
						|
We maintain a semantic versioning scheme ("a.b.c") alongside the libre app
 | 
						|
versioning scheme in order to maximise the chances that the nature of recipe
 | 
						|
updates are properly communicated.
 | 
						|
 | 
						|
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.
 | 
						|
`,
 | 
						|
	Flags: []cli.Flag{
 | 
						|
		DryFlag,
 | 
						|
		PatchFlag,
 | 
						|
		MinorFlag,
 | 
						|
		MajorFlag,
 | 
						|
		PushFlag,
 | 
						|
		CommitFlag,
 | 
						|
		CommitMessageFlag,
 | 
						|
	},
 | 
						|
	Action: func(c *cli.Context) error {
 | 
						|
		recipe := internal.ValidateRecipe(c)
 | 
						|
		directory := path.Join(config.APPS_DIR, recipe.Name)
 | 
						|
		tagstring := c.Args().Get(1)
 | 
						|
		imagesTmp := getImageVersions(recipe)
 | 
						|
		mainApp := getMainApp(recipe)
 | 
						|
		mainAppVersion := imagesTmp[mainApp]
 | 
						|
		if mainAppVersion == "" {
 | 
						|
			logrus.Fatal("main app version is empty?")
 | 
						|
		}
 | 
						|
 | 
						|
		if tagstring != "" {
 | 
						|
			_, err := tagcmp.Parse(tagstring)
 | 
						|
			if err != nil {
 | 
						|
				logrus.Fatal("invalid tag specified")
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if Commit || (CommitMessage != "") {
 | 
						|
			commitRepo, err := git.PlainOpen(directory)
 | 
						|
			if err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
			commitWorktree, err := commitRepo.Worktree()
 | 
						|
			if err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
 | 
						|
			if CommitMessage == "" {
 | 
						|
				prompt := &survey.Input{
 | 
						|
					Message: "commit message",
 | 
						|
				}
 | 
						|
				survey.AskOne(prompt, &CommitMessage)
 | 
						|
			}
 | 
						|
			_, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{})
 | 
						|
			if err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
			logrus.Info("changes commited")
 | 
						|
		}
 | 
						|
 | 
						|
		repo, err := git.PlainOpen(directory)
 | 
						|
		if err != nil {
 | 
						|
			logrus.Fatal(err)
 | 
						|
		}
 | 
						|
		head, err := repo.Head()
 | 
						|
		if err != nil {
 | 
						|
			logrus.Fatal(err)
 | 
						|
		}
 | 
						|
 | 
						|
		// bumpType is used to decide what part of the tag should be incremented
 | 
						|
		bumpType := btoi(Major)*4 + btoi(Minor)*2 + btoi(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.")
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if tagstring != "" {
 | 
						|
			if bumpType > 0 {
 | 
						|
				logrus.Warn("user specified a version number and --major/--minor/--patch at the same time! using version number...")
 | 
						|
			}
 | 
						|
			tag, err := tagcmp.Parse(tagstring)
 | 
						|
			if err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
			if tag.MissingMinor {
 | 
						|
				tag.Minor = "0"
 | 
						|
				tag.MissingMinor = false
 | 
						|
			}
 | 
						|
			if tag.MissingPatch {
 | 
						|
				tag.Patch = "0"
 | 
						|
				tag.MissingPatch = false
 | 
						|
			}
 | 
						|
			tagstring = fmt.Sprintf("%s+%s", tag.String(), mainAppVersion)
 | 
						|
			if Dry {
 | 
						|
				logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", tagstring, head.Hash()))
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
 | 
						|
			repo.CreateTag(tagstring, head.Hash(), nil) /* &git.CreateTagOptions{
 | 
						|
				Message: tag,
 | 
						|
			})*/
 | 
						|
			logrus.Info(fmt.Sprintf("created tag %s at %s", tagstring, head.Hash()))
 | 
						|
			if Push {
 | 
						|
				if err := repo.Push(&git.PushOptions{}); err != nil {
 | 
						|
					logrus.Fatal(err)
 | 
						|
				}
 | 
						|
				logrus.Info(fmt.Sprintf("pushed tag %s to remote", tagstring))
 | 
						|
			}
 | 
						|
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// get the latest tag with its hash, name etc
 | 
						|
		var lastGitTag *object.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 {
 | 
						|
				lastGitTag = obj
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
			return err
 | 
						|
 | 
						|
		}); err != nil {
 | 
						|
			logrus.Fatal(err)
 | 
						|
		}
 | 
						|
		newTag, err := tagcmp.Parse(lastGitTag.Name)
 | 
						|
		if err != nil {
 | 
						|
			logrus.Fatal(err)
 | 
						|
		}
 | 
						|
 | 
						|
		var newTagString string
 | 
						|
		if bumpType > 0 {
 | 
						|
			if Patch {
 | 
						|
				now, err := strconv.Atoi(newTag.Patch)
 | 
						|
				if err != nil {
 | 
						|
					logrus.Fatal(err)
 | 
						|
				}
 | 
						|
				newTag.Patch = strconv.Itoa(now + 1)
 | 
						|
			} else if Minor {
 | 
						|
				now, err := strconv.Atoi(newTag.Minor)
 | 
						|
				if err != nil {
 | 
						|
					logrus.Fatal(err)
 | 
						|
				}
 | 
						|
				newTag.Minor = strconv.Itoa(now + 1)
 | 
						|
			} else if Major {
 | 
						|
				now, err := strconv.Atoi(newTag.Major)
 | 
						|
				if err != nil {
 | 
						|
					logrus.Fatal(err)
 | 
						|
				}
 | 
						|
				newTag.Major = strconv.Itoa(now + 1)
 | 
						|
			}
 | 
						|
			newTagString = newTag.String()
 | 
						|
		} else {
 | 
						|
			logrus.Fatal("we don't support automatic tag generation yet - specify a version or use one of: --major --minor --patch")
 | 
						|
		}
 | 
						|
 | 
						|
		newTagString = fmt.Sprintf("%s+%s", newTagString, mainAppVersion)
 | 
						|
		if Dry {
 | 
						|
			logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newTagString, head.Hash()))
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		repo.CreateTag(newTagString, head.Hash(), nil) /* &git.CreateTagOptions{
 | 
						|
			Message: tag,
 | 
						|
		})*/
 | 
						|
		logrus.Info(fmt.Sprintf("created tag %s at %s", newTagString, head.Hash()))
 | 
						|
		if Push {
 | 
						|
			if err := repo.Push(&git.PushOptions{}); err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
			logrus.Info(fmt.Sprintf("pushed tag %s to remote", newTagString))
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
func getImageVersions(recipe recipe.Recipe) map[string]string {
 | 
						|
 | 
						|
	var services = make(map[string]string)
 | 
						|
	for _, service := range recipe.Config.Services {
 | 
						|
		srv := strings.Split(service.Image, ":")
 | 
						|
		services[srv[0]] = srv[1]
 | 
						|
	}
 | 
						|
 | 
						|
	return services
 | 
						|
}
 | 
						|
 | 
						|
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 ""
 | 
						|
}
 | 
						|
 | 
						|
func btoi(b bool) int {
 | 
						|
	if b {
 | 
						|
		return 1
 | 
						|
	}
 | 
						|
	return 0
 | 
						|
}
 |