forked from toolshed/abra
		
	
		
			
				
	
	
		
			141 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package recipe
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"coopcloud.tech/abra/cli/internal"
 | 
						|
	"coopcloud.tech/abra/pkg/catalogue"
 | 
						|
	"coopcloud.tech/abra/pkg/client"
 | 
						|
	"coopcloud.tech/tagcmp"
 | 
						|
	"github.com/AlecAivazis/survey/v2"
 | 
						|
	"github.com/docker/distribution/reference"
 | 
						|
	"github.com/sirupsen/logrus"
 | 
						|
	"github.com/urfave/cli/v2"
 | 
						|
)
 | 
						|
 | 
						|
var recipeUpgradeCommand = &cli.Command{
 | 
						|
	Name:    "upgrade",
 | 
						|
	Usage:   "Upgrade recipe image tags",
 | 
						|
	Aliases: []string{"u"},
 | 
						|
	Description: `
 | 
						|
This command reads and attempts to parse all image tags within the given
 | 
						|
<recipe> configuration and prompt with more recent tags to upgrade to. It will
 | 
						|
update the relevant compose file tags on the local file system.
 | 
						|
 | 
						|
Some image tags cannot be parsed because they do not follow some sort of
 | 
						|
semver-like convention. In this case, all possible tags will be listed and it
 | 
						|
is up to the end-user to decide.
 | 
						|
 | 
						|
This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync
 | 
						|
<recipe>".
 | 
						|
`,
 | 
						|
	ArgsUsage: "<recipe>",
 | 
						|
	Action: func(c *cli.Context) error {
 | 
						|
		recipe := internal.ValidateRecipe(c)
 | 
						|
 | 
						|
		for _, service := range recipe.Config.Services {
 | 
						|
			catlVersions, err := catalogue.VersionsOfService(recipe.Name, service.Name)
 | 
						|
			if err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
			logrus.Debugf("read '%s' from the recipe catalogue for '%s'", catlVersions, service.Name)
 | 
						|
 | 
						|
			img, err := reference.ParseNormalizedNamed(service.Image)
 | 
						|
			if err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
 | 
						|
			image := reference.Path(img)
 | 
						|
			regVersions, err := client.GetRegistryTags(image)
 | 
						|
			if err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
			logrus.Debugf("retrieved '%s' from remote registry for '%s'", regVersions, image)
 | 
						|
 | 
						|
			if strings.Contains(image, "library") {
 | 
						|
				// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>,
 | 
						|
				// postgres:<tag>, i.e. images which do not have a username in the
 | 
						|
				// first position of the string
 | 
						|
				image = strings.Split(image, "/")[1]
 | 
						|
			}
 | 
						|
 | 
						|
			semverLikeTag := true
 | 
						|
			if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
 | 
						|
				logrus.Debugf("'%s' not considered semver-like", img.(reference.NamedTagged).Tag())
 | 
						|
				semverLikeTag = false
 | 
						|
			}
 | 
						|
 | 
						|
			tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag())
 | 
						|
			if err != nil && semverLikeTag {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
			logrus.Debugf("parsed '%s' for '%s'", tag, service.Name)
 | 
						|
 | 
						|
			var compatible []tagcmp.Tag
 | 
						|
			for _, regVersion := range regVersions {
 | 
						|
				other, err := tagcmp.Parse(regVersion.Name)
 | 
						|
				if err != nil {
 | 
						|
					continue // skip tags that cannot be parsed
 | 
						|
				}
 | 
						|
 | 
						|
				if tag.IsCompatible(other) && tag.IsLessThan(other) && !tag.Equals(other) {
 | 
						|
					compatible = append(compatible, other)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			logrus.Debugf("detected potential upgradable tags '%s' for '%s'", compatible, service.Name)
 | 
						|
 | 
						|
			sort.Sort(tagcmp.ByTagDesc(compatible))
 | 
						|
 | 
						|
			if len(compatible) == 0 && semverLikeTag {
 | 
						|
				logrus.Info(fmt.Sprintf("no new versions available for '%s', '%s' is the latest", image, tag))
 | 
						|
				continue // skip on to the next tag and don't update any compose files
 | 
						|
			}
 | 
						|
 | 
						|
			var compatibleStrings []string
 | 
						|
			for _, compat := range compatible {
 | 
						|
				skip := false
 | 
						|
				for _, catlVersion := range catlVersions {
 | 
						|
					if compat.String() == catlVersion {
 | 
						|
						skip = true
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if !skip {
 | 
						|
					compatibleStrings = append(compatibleStrings, compat.String())
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			logrus.Debugf("detected compatible upgradable tags '%s' for '%s'", compatibleStrings, service.Name)
 | 
						|
 | 
						|
			msg := fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
 | 
						|
			if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
 | 
						|
				tag := img.(reference.NamedTagged).Tag()
 | 
						|
				logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of '%s', listing all tags", tag))
 | 
						|
				msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
 | 
						|
				compatibleStrings = []string{}
 | 
						|
				for _, regVersion := range regVersions {
 | 
						|
					compatibleStrings = append(compatibleStrings, regVersion.Name)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			var upgradeTag string
 | 
						|
			prompt := &survey.Select{
 | 
						|
				Message: msg,
 | 
						|
				Options: compatibleStrings,
 | 
						|
			}
 | 
						|
			if err := survey.AskOne(prompt, &upgradeTag); err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
 | 
						|
			if err := recipe.UpdateTag(image, upgradeTag); err != nil {
 | 
						|
				logrus.Fatal(err)
 | 
						|
			}
 | 
						|
			logrus.Debugf("tag updated from '%s' to '%s' for '%s'", image, upgradeTag, recipe.Name)
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	},
 | 
						|
}
 |