From f02ea7ca0d2a67d3c72abbaa99962f4a2cf3cb44 Mon Sep 17 00:00:00 2001 From: knoflook Date: Mon, 1 Nov 2021 11:32:47 +0100 Subject: [PATCH] feat: add recipe version pinning closes: https://git.coopcloud.tech/coop-cloud/organising/issues/186 --- cli/recipe/upgrade.go | 131 ++++++++++++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 31 deletions(-) diff --git a/cli/recipe/upgrade.go b/cli/recipe/upgrade.go index 479ad21e..fa536520 100644 --- a/cli/recipe/upgrade.go +++ b/cli/recipe/upgrade.go @@ -1,13 +1,17 @@ package recipe import ( + "bufio" "fmt" + "os" + "path" "sort" "strings" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/catalogue" "coopcloud.tech/abra/pkg/client" + "coopcloud.tech/abra/pkg/config" "coopcloud.tech/tagcmp" "github.com/AlecAivazis/survey/v2" "github.com/docker/distribution/reference" @@ -15,6 +19,11 @@ import ( "github.com/urfave/cli/v2" ) +type imgPin struct { + image string + version tagcmp.Tag +} + var recipeUpgradeCommand = &cli.Command{ Name: "upgrade", Usage: "Upgrade recipe image tags", @@ -45,6 +54,43 @@ is up to the end-user to decide. } } + // check for versions file and load pinned versions + versionsPresent := false + recipeDir := path.Join(config.ABRA_DIR, "apps", recipe.Name) + versionsPath := path.Join(recipeDir, "versions") + var servicePins = make(map[string]imgPin) + if _, err := os.Stat(versionsPath); err == nil { + logrus.Debugf("found versions file for %s", recipe.Name) + file, err := os.Open(versionsPath) + if err != nil { + logrus.Fatal(err) + } + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + splitLine := strings.Split(line, " ") + if splitLine[0] != "pin" || len(splitLine) != 3 { + logrus.Fatalf("malformed version pin specification: %s", line) + } + pinSlice := strings.Split(splitLine[2], ":") + pinTag, err := tagcmp.Parse(pinSlice[1]) + if err != nil { + logrus.Fatal(err) + } + pin := imgPin{ + image: pinSlice[0], + version: pinTag, + } + servicePins[splitLine[1]] = pin + } + if err := scanner.Err(); err != nil { + logrus.Error(err) + } + versionsPresent = true + } else { + logrus.Debugf("did not find versions file for %s", recipe.Name) + } + for _, service := range recipe.Config.Services { catlVersions, err := catalogue.VersionsOfService(recipe.Name, service.Name) if err != nil { @@ -69,7 +115,6 @@ is up to the end-user to decide. // 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()) @@ -81,7 +126,6 @@ is up to the end-user to decide. 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) @@ -117,44 +161,69 @@ is up to the end-user to decide. } logrus.Debugf("detected compatible upgradable tags '%s' for '%s'", compatibleStrings, service.Name) + var upgradeTag string - if bumpType != 0 { - for _, upTag := range compatible { - upElement, err := tag.UpgradeDelta(upTag) - if err != nil { - return err + _, ok := servicePins[service.Name] + if versionsPresent && ok { + pinnedTag := servicePins[service.Name].version + if tag.IsLessThan(pinnedTag) { + pinnedTagString := pinnedTag.String() + contains := false + for _, v := range compatible { + if pinnedTag.IsUpgradeCompatible(v) { + contains = true + upgradeTag = v.String() + break + } } - delta := upElement.UpgradeType() - if delta <= bumpType { - upgradeTag = upTag.String() - break + if contains { + logrus.Infof("Upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString) + } else { + logrus.Infof("service %s, image %s pinned to %s. No compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString) + continue } - } - if upgradeTag == "" { - logrus.Warnf("not upgrading from '%s' to '%s' for '%s', because the upgrade type is more serious than what user wants.", tag.String(), compatible[0].String(), image) + } 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()) continue } } else { - 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) + if bumpType != 0 { + for _, upTag := range compatible { + upElement, err := tag.UpgradeDelta(upTag) + if err != nil { + return err + } + delta := upElement.UpgradeType() + if delta <= bumpType { + upgradeTag = upTag.String() + break + } + } + if upgradeTag == "" { + logrus.Warnf("not upgrading from '%s' to '%s' for '%s', because the upgrade type is more serious than what user wants.", tag.String(), compatible[0].String(), image) + continue + } + } else { + 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) + } + } + + prompt := &survey.Select{ + Message: msg, + Options: compatibleStrings, + } + if err := survey.AskOne(prompt, &upgradeTag); err != nil { + logrus.Fatal(err) } } - - 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) }