forked from toolshed/abra
		
	Compare commits
	
		
			25 Commits
		
	
	
		
			0.4.0-alph
			...
			0.4.0-alph
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						c6db9ee355
	
				 | 
					
					
						|||
| 
						
						
							
						
						7733637767
	
				 | 
					
					
						|||
| 
						
						
							
						
						88f9796aaf
	
				 | 
					
					
						|||
| 
						
						
							
						
						6cdba0f9de
	
				 | 
					
					
						|||
| 
						
						
							
						
						199aa5f4e3
	
				 | 
					
					
						|||
| 
						
						
							
						
						9b26c24a5f
	
				 | 
					
					
						|||
| 
						
						
							
						
						ca75654769
	
				 | 
					
					
						|||
| 
						
						
							
						
						fc2d83d203
	
				 | 
					
					
						|||
| 
						
						
							
						
						2f4f288a46
	
				 | 
					
					
						|||
| 
						
						
							
						
						e98f00d354
	
				 | 
					
					
						|||
| b4c2773b87 | |||
| 
						
						
							
						
						3aec5d1d7e
	
				 | 
					
					
						|||
| 
						
						
							
						
						e0fa1b6995
	
				 | 
					
					
						|||
| 
						
						
							
						
						b69ab0df65
	
				 | 
					
					
						|||
| 
						
						
							
						
						69a7d37fb7
	
				 | 
					
					
						|||
| 
						
						
							
						
						87649cbbd0
	
				 | 
					
					
						|||
| 
						
						
							
						
						4b7ec6384c
	
				 | 
					
					
						|||
| 
						
						
							
						
						b22b63c2ba
	
				 | 
					
					
						|||
| 
						
						
							
						
						d9f3a11265
	
				 | 
					
					
						|||
| 
						
						
							
						
						d7cf11b876
	
				 | 
					
					
						|||
| 
						
						
							
						
						d7e1b2947a
	
				 | 
					
					
						|||
| 
						
						
							
						
						1b37d2d5f5
	
				 | 
					
					
						|||
| 
						
						
							
						
						74dfb12fd6
	
				 | 
					
					
						|||
| 
						
						
							
						
						49ccf2d204
	
				 | 
					
					
						|||
| 
						
						
							
						
						76adc45431
	
				 | 
					
					
						
@ -66,9 +66,7 @@ We maintain a fork of [godotenv](https://github.com/Autonomic-Cooperative/godote
 | 
			
		||||
1. multi-line env var support
 | 
			
		||||
2. inline comment parsing
 | 
			
		||||
 | 
			
		||||
You can upgrade the version here by running `go get github.com/Autonomic-Cooperative/godotenv@<commit>` where `<commit>` is the
 | 
			
		||||
latest commit you want to pin to. We are aiming to migrate to YAML format for the environment configuration, so this should only
 | 
			
		||||
be a temporary thing.
 | 
			
		||||
You can upgrade the version here by running `go get github.com/Autonomic-Cooperative/godotenv@<commit>` where `<commit>` is the latest commit you want to pin to. At time of writing, `go get github.com/Autonomic-Cooperative/godotenv@b031ea1211e7fd297af4c7747ffb562ebe00cd33` is the command you want to run to maintain the above functionality.
 | 
			
		||||
 | 
			
		||||
#### `docker/client`
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -141,7 +141,9 @@ var appRemoveCommand = &cli.Command{
 | 
			
		||||
				logrus.Info("no volumes were removed")
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			logrus.Info("no volumes to remove")
 | 
			
		||||
			if Volumes {
 | 
			
		||||
				logrus.Info("no volumes to remove")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = os.Remove(app.Path)
 | 
			
		||||
 | 
			
		||||
@ -45,8 +45,10 @@ recipes.
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
 | 
			
		||||
		if err := recipe.EnsureUpToDate(app.Type); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		if !internal.Chaos {
 | 
			
		||||
			if err := recipe.EnsureUpToDate(app.Type); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r, err := recipe.Get(app.Type)
 | 
			
		||||
 | 
			
		||||
@ -186,20 +186,29 @@ Example:
 | 
			
		||||
				if err := cl.SecretRemove(c.Context, secretName); err != nil {
 | 
			
		||||
					logrus.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
				logrus.Infof("deleted %s successfully from server", secretName)
 | 
			
		||||
 | 
			
		||||
				if internal.Pass {
 | 
			
		||||
					if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
 | 
			
		||||
						logrus.Fatal(err)
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					logrus.Infof("deleted %s successfully from local pass store", secretName)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if parsed == secretToRm {
 | 
			
		||||
					if err := cl.SecretRemove(c.Context, secretName); err != nil {
 | 
			
		||||
						logrus.Fatal(err)
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					logrus.Infof("deleted %s successfully from server", secretName)
 | 
			
		||||
 | 
			
		||||
					if internal.Pass {
 | 
			
		||||
						if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
 | 
			
		||||
							logrus.Fatal(err)
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						logrus.Infof("deleted %s successfully from local pass store", secretName)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -48,8 +48,10 @@ recipes.
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
 | 
			
		||||
		if err := recipe.EnsureUpToDate(app.Type); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		if !internal.Chaos {
 | 
			
		||||
			if err := recipe.EnsureUpToDate(app.Type); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r, err := recipe.Get(app.Type)
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,6 @@
 | 
			
		||||
package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
@ -22,9 +20,8 @@ func getImagePath(image string) (string, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path := reference.Path(img)
 | 
			
		||||
	if strings.Contains(path, "library") {
 | 
			
		||||
		path = strings.Split(path, "/")[1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path = recipe.StripTagMeta(path)
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("parsed %s from %s", path, image)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@ var CatalogueSkipList = map[string]bool{
 | 
			
		||||
	"auto-mirror":           true,
 | 
			
		||||
	"backup-bot":            true,
 | 
			
		||||
	"backup-bot-two":        true,
 | 
			
		||||
	"beta.coopcloud.tech":   true,
 | 
			
		||||
	"comrade-renovate-bot":  true,
 | 
			
		||||
	"coopcloud.tech":        true,
 | 
			
		||||
	"coturn":                true,
 | 
			
		||||
@ -93,12 +94,6 @@ keys configured on your account.
 | 
			
		||||
			internal.ValidateRecipe(c)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
 | 
			
		||||
		url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
 | 
			
		||||
		if err := gitPkg.Clone(catalogueDir, url); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		repos, err := recipe.ReadReposMetadata()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
 | 
			
		||||
@ -24,8 +24,10 @@ import (
 | 
			
		||||
func DeployAction(c *cli.Context) error {
 | 
			
		||||
	app := ValidateApp(c)
 | 
			
		||||
 | 
			
		||||
	if err := recipe.EnsureUpToDate(app.Type); err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	if !Chaos {
 | 
			
		||||
		if err := recipe.EnsureUpToDate(app.Type); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, err := recipe.Get(app.Type)
 | 
			
		||||
 | 
			
		||||
@ -438,6 +438,15 @@ var RegistryPasswordFlag = &cli.StringFlag{
 | 
			
		||||
	Destination: &RegistryUsername,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var AllTags bool
 | 
			
		||||
var AllTagsFlag = &cli.BoolFlag{
 | 
			
		||||
	Name:        "all-tags",
 | 
			
		||||
	Aliases:     []string{"a"},
 | 
			
		||||
	Value:       false,
 | 
			
		||||
	Usage:       "List all tags, not just upgrades",
 | 
			
		||||
	Destination: &AllTags,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SSHFailMsg is a hopefully helpful SSH failure message
 | 
			
		||||
var SSHFailMsg = `
 | 
			
		||||
Woops, Abra is unable to connect to connect to %s.
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@ var RecipeName string
 | 
			
		||||
 | 
			
		||||
// createSecrets creates all secrets for a new app.
 | 
			
		||||
func createSecrets(sanitisedAppName string) (AppSecrets, error) {
 | 
			
		||||
	appEnvPath := path.Join(config.ABRA_DIR, "servers", NewAppServer, fmt.Sprintf("%s.env", sanitisedAppName))
 | 
			
		||||
	appEnvPath := path.Join(config.ABRA_DIR, "servers", NewAppServer, fmt.Sprintf("%s.env", NewAppName))
 | 
			
		||||
	appEnv, err := config.ReadEnv(appEnvPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,9 @@ package internal
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/docker/distribution/reference"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
@ -94,9 +94,7 @@ func GetMainAppImage(recipe recipe.Recipe) (string, error) {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			path = reference.Path(img)
 | 
			
		||||
			if strings.Contains(path, "library") {
 | 
			
		||||
				path = strings.Split(path, "/")[1]
 | 
			
		||||
			}
 | 
			
		||||
			path = recipePkg.StripTagMeta(path)
 | 
			
		||||
 | 
			
		||||
			return path, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -2,14 +2,11 @@ package recipe
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	gitPkg "coopcloud.tech/abra/pkg/git"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
@ -32,12 +29,6 @@ var recipeListCommand = &cli.Command{
 | 
			
		||||
		patternFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
 | 
			
		||||
		url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
 | 
			
		||||
		if err := gitPkg.Clone(catalogueDir, url); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		catl, err := recipe.ReadRecipeCatalogue()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err.Error())
 | 
			
		||||
 | 
			
		||||
@ -127,6 +127,7 @@ your SSH keys configured on your account.
 | 
			
		||||
func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
 | 
			
		||||
	var services = make(map[string]string)
 | 
			
		||||
 | 
			
		||||
	missingTag := false
 | 
			
		||||
	for _, service := range recipe.Config.Services {
 | 
			
		||||
		if service.Image == "" {
 | 
			
		||||
			continue
 | 
			
		||||
@ -138,21 +139,27 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path := reference.Path(img)
 | 
			
		||||
		if strings.Contains(path, "library") {
 | 
			
		||||
			path = strings.Split(path, "/")[1]
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path = recipePkg.StripTagMeta(path)
 | 
			
		||||
 | 
			
		||||
		var tag string
 | 
			
		||||
		switch img.(type) {
 | 
			
		||||
		case reference.NamedTagged:
 | 
			
		||||
			tag = img.(reference.NamedTagged).Tag()
 | 
			
		||||
		case reference.Named:
 | 
			
		||||
			return services, fmt.Errorf("%s service is missing image tag?", path)
 | 
			
		||||
			if service.Name == "app" {
 | 
			
		||||
				missingTag = true
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		services[path] = tag
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if missingTag {
 | 
			
		||||
		return services, fmt.Errorf("app service is missing image tag?")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return services, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -232,12 +239,10 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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", msg, internal.Dry); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	msg := fmt.Sprintf("chore: publish %s release", tag)
 | 
			
		||||
	repoPath := path.Join(config.RECIPES_DIR, recipe.Name)
 | 
			
		||||
	if err := gitPkg.Commit(repoPath, ".", msg, internal.Dry); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
@ -290,13 +295,10 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
 | 
			
		||||
		if err := recipe.Push(internal.Dry); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !internal.Dry {
 | 
			
		||||
			url := fmt.Sprintf("%s/%s/src/tag/%s", config.REPOS_BASE_URL, recipe.Name, tagString)
 | 
			
		||||
			logrus.Infof("new release published: %s", url)
 | 
			
		||||
		} else {
 | 
			
		||||
			logrus.Info("dry run: no changes published")
 | 
			
		||||
		}
 | 
			
		||||
		url := fmt.Sprintf("%s/%s/src/tag/%s", config.REPOS_BASE_URL, recipe.Name, tagString)
 | 
			
		||||
		logrus.Infof("new release published: %s", url)
 | 
			
		||||
	} else {
 | 
			
		||||
		logrus.Info("no -p/--publish passed, not publishing")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
@ -53,6 +53,7 @@ You may invoke this command in "wizard" mode and be prompted for input:
 | 
			
		||||
		internal.PatchFlag,
 | 
			
		||||
		internal.MinorFlag,
 | 
			
		||||
		internal.MajorFlag,
 | 
			
		||||
		internal.AllTagsFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		recipe := internal.ValidateRecipeWithPrompt(c)
 | 
			
		||||
@ -115,23 +116,26 @@ You may invoke this command in "wizard" mode and be prompted for input:
 | 
			
		||||
			}
 | 
			
		||||
			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
 | 
			
		||||
			image = recipePkg.StripTagMeta(image)
 | 
			
		||||
 | 
			
		||||
			switch img.(type) {
 | 
			
		||||
			case reference.NamedTagged:
 | 
			
		||||
				if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
 | 
			
		||||
					logrus.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag())
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				logrus.Warnf("unable to read tag for image %s, is it missing? skipping upgrade for %s", image, service.Name)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag())
 | 
			
		||||
			if err != nil && semverLikeTag {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Warnf("unable to parse %s, error was: %s, skipping upgrade for %s", image, err.Error(), service.Name)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			logrus.Debugf("parsed %s for %s", tag, service.Name)
 | 
			
		||||
 | 
			
		||||
			var compatible []tagcmp.Tag
 | 
			
		||||
			for _, regVersion := range regVersions {
 | 
			
		||||
				other, err := tagcmp.Parse(regVersion.Name)
 | 
			
		||||
@ -148,7 +152,7 @@ You may invoke this command in "wizard" mode and be prompted for input:
 | 
			
		||||
 | 
			
		||||
			sort.Sort(tagcmp.ByTagDesc(compatible))
 | 
			
		||||
 | 
			
		||||
			if len(compatible) == 0 && semverLikeTag {
 | 
			
		||||
			if len(compatible) == 0 && !internal.AllTags {
 | 
			
		||||
				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
 | 
			
		||||
			}
 | 
			
		||||
@ -188,13 +192,13 @@ You may invoke this command in "wizard" mode and be prompted for input:
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					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 {
 | 
			
		||||
						logrus.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString)
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				} 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
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
@ -211,16 +215,18 @@ You may invoke this command in "wizard" mode and be prompted for input:
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					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)
 | 
			
		||||
						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, image: %s, tag: %s)", service.Name, image, tag)
 | 
			
		||||
					if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
 | 
			
		||||
					if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) || internal.AllTags {
 | 
			
		||||
						tag := img.(reference.NamedTagged).Tag()
 | 
			
		||||
						logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", tag))
 | 
			
		||||
						if !internal.AllTags {
 | 
			
		||||
							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{}
 | 
			
		||||
						compatibleStrings = []string{"skip"}
 | 
			
		||||
						for _, regVersion := range regVersions {
 | 
			
		||||
							compatibleStrings = append(compatibleStrings, regVersion.Name)
 | 
			
		||||
						}
 | 
			
		||||
@ -238,10 +244,13 @@ You may invoke this command in "wizard" mode and be prompted for input:
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if upgradeTag != "skip" {
 | 
			
		||||
				if err := recipe.UpdateTag(image, upgradeTag); err != nil {
 | 
			
		||||
				ok, err := recipe.UpdateTag(image, upgradeTag)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logrus.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
				logrus.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image)
 | 
			
		||||
				if ok {
 | 
			
		||||
					logrus.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				logrus.Warnf("not upgrading %s, skipping as requested", image)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,9 @@
 | 
			
		||||
package recipe
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	gitPkg "coopcloud.tech/abra/pkg/git"
 | 
			
		||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
@ -23,12 +18,6 @@ var recipeVersionCommand = &cli.Command{
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		recipe := internal.ValidateRecipe(c)
 | 
			
		||||
 | 
			
		||||
		catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
 | 
			
		||||
		url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
 | 
			
		||||
		if err := gitPkg.Clone(catalogueDir, url); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		catalogue, err := recipePkg.ReadRecipeCatalogue()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							@ -5,7 +5,7 @@ go 1.16
 | 
			
		||||
require (
 | 
			
		||||
	coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52
 | 
			
		||||
	github.com/AlecAivazis/survey/v2 v2.3.2
 | 
			
		||||
	github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4
 | 
			
		||||
	github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731094149-b031ea1211e7
 | 
			
		||||
	github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
 | 
			
		||||
	github.com/docker/cli v20.10.12+incompatible
 | 
			
		||||
	github.com/docker/distribution v2.7.1+incompatible
 | 
			
		||||
@ -21,7 +21,7 @@ require (
 | 
			
		||||
	github.com/schultz-is/passgen v1.0.1
 | 
			
		||||
	github.com/sirupsen/logrus v1.8.1
 | 
			
		||||
	github.com/urfave/cli/v2 v2.3.0
 | 
			
		||||
	gotest.tools/v3 v3.0.3
 | 
			
		||||
	gotest.tools/v3 v3.1.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							@ -28,8 +28,8 @@ coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52/go.mod h1:ESVm0wQKcbcFi
 | 
			
		||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
			
		||||
github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8=
 | 
			
		||||
github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
 | 
			
		||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4 h1:aYUdiI42a4fWfPoUr25XlaJrFEICv24+o/gWhqYS/jk=
 | 
			
		||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4/go.mod h1:oZRCMMRS318l07ei4DTqbZoOawfJlJ4yyo8juk2v4Rk=
 | 
			
		||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731094149-b031ea1211e7 h1:asQtdXYbxEYWcwAQqJTVYC/RltB4eqoWKvqWg/LFPOg=
 | 
			
		||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731094149-b031ea1211e7/go.mod h1:oZRCMMRS318l07ei4DTqbZoOawfJlJ4yyo8juk2v4Rk=
 | 
			
		||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 | 
			
		||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
 | 
			
		||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
 | 
			
		||||
@ -1045,6 +1045,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
 | 
			
		||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
@ -1160,8 +1161,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
 | 
			
		||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 | 
			
		||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 | 
			
		||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 | 
			
		||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
 | 
			
		||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
 | 
			
		||||
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
 | 
			
		||||
gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
 | 
			
		||||
@ -16,10 +16,10 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UpdateTag updates an image tag in-place on file system local compose files.
 | 
			
		||||
func UpdateTag(pattern, image, tag, recipeName string) error {
 | 
			
		||||
func UpdateTag(pattern, image, tag, recipeName string) (bool, error) {
 | 
			
		||||
	composeFiles, err := filepath.Glob(pattern)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", "))
 | 
			
		||||
@ -30,12 +30,12 @@ func UpdateTag(pattern, image, tag, recipeName string) error {
 | 
			
		||||
		envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
 | 
			
		||||
		sampleEnv, err := config.ReadEnv(envSamplePath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		compose, err := loader.LoadComposefile(opts, sampleEnv)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, service := range compose.Services {
 | 
			
		||||
@ -45,24 +45,26 @@ func UpdateTag(pattern, image, tag, recipeName string) error {
 | 
			
		||||
 | 
			
		||||
			img, _ := reference.ParseNormalizedNamed(service.Image)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
				return false, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var composeTag string
 | 
			
		||||
			switch img.(type) {
 | 
			
		||||
			case reference.NamedTagged:
 | 
			
		||||
				composeTag = img.(reference.NamedTagged).Tag()
 | 
			
		||||
			default:
 | 
			
		||||
				// unable to parse, typically image missing tag
 | 
			
		||||
				return false, nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			composeImage := reference.Path(img)
 | 
			
		||||
			if strings.Contains(composeImage, "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
 | 
			
		||||
				composeImage = strings.Split(composeImage, "/")[1]
 | 
			
		||||
			}
 | 
			
		||||
			composeTag := img.(reference.NamedTagged).Tag()
 | 
			
		||||
 | 
			
		||||
			logrus.Debugf("parsed %s from %s", composeTag, service.Image)
 | 
			
		||||
 | 
			
		||||
			if image == composeImage {
 | 
			
		||||
				bytes, err := ioutil.ReadFile(composeFile)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
					return false, err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				old := fmt.Sprintf("%s:%s", composeImage, composeTag)
 | 
			
		||||
@ -72,13 +74,13 @@ func UpdateTag(pattern, image, tag, recipeName string) error {
 | 
			
		||||
				logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename)
 | 
			
		||||
 | 
			
		||||
				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
					return true, err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateLabel updates a label in-place on file system local compose files.
 | 
			
		||||
 | 
			
		||||
@ -153,7 +153,7 @@ func LoadAppFiles(servers ...string) (AppFiles, error) {
 | 
			
		||||
		serverDir := path.Join(SERVERS_DIR, server)
 | 
			
		||||
		files, err := getAllFilesInDirectory(serverDir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
			return nil, fmt.Errorf("server %s doesn't exist? Run \"abra server ls\" to check", server)
 | 
			
		||||
		}
 | 
			
		||||
		for _, file := range files {
 | 
			
		||||
			appName := strings.TrimSuffix(file.Name(), ".env")
 | 
			
		||||
 | 
			
		||||
@ -163,12 +163,17 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateTag updates a recipe tag
 | 
			
		||||
func (r Recipe) UpdateTag(image, tag string) error {
 | 
			
		||||
func (r Recipe) UpdateTag(image, tag string) (bool, error) {
 | 
			
		||||
	pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, r.Name)
 | 
			
		||||
	if err := compose.UpdateTag(pattern, image, tag, r.Name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
 | 
			
		||||
	image = StripTagMeta(image)
 | 
			
		||||
 | 
			
		||||
	ok, err := compose.UpdateTag(pattern, image, tag, r.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
	return ok, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Tags list the recipe tags
 | 
			
		||||
@ -693,6 +698,10 @@ func recipeCatalogueFSIsLatest() (bool, error) {
 | 
			
		||||
func ReadRecipeCatalogue() (RecipeCatalogue, error) {
 | 
			
		||||
	recipes := make(RecipeCatalogue)
 | 
			
		||||
 | 
			
		||||
	if err := EnsureCatalogue(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	recipeFSIsLatest, err := recipeCatalogueFSIsLatest()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@ -973,9 +982,8 @@ func GetRecipeVersions(recipeName, registryUsername, registryPassword string) (R
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			path := reference.Path(img)
 | 
			
		||||
			if strings.Contains(path, "library") {
 | 
			
		||||
				path = strings.Split(path, "/")[1]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			path = StripTagMeta(path)
 | 
			
		||||
 | 
			
		||||
			var tag string
 | 
			
		||||
			switch img.(type) {
 | 
			
		||||
@ -1041,3 +1049,37 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri
 | 
			
		||||
 | 
			
		||||
	return versions, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StripTagMeta strips front-matter image tag data that we don't need for parsing.
 | 
			
		||||
func StripTagMeta(image string) string {
 | 
			
		||||
	originalImage := image
 | 
			
		||||
 | 
			
		||||
	if strings.Contains(image, "docker.io") {
 | 
			
		||||
		image = strings.Split(image, "/")[1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.Contains(image, "library") {
 | 
			
		||||
		image = strings.Split(image, "/")[1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if originalImage != image {
 | 
			
		||||
		logrus.Debugf("stripped %s to %s for parsing", originalImage, image)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return image
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureCatalogue ensures that the catalogue is cloned locally & present.
 | 
			
		||||
func EnsureCatalogue() error {
 | 
			
		||||
	catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
 | 
			
		||||
	if _, err := os.Stat(catalogueDir); err != nil && os.IsNotExist(err) {
 | 
			
		||||
		url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
 | 
			
		||||
		if err := gitPkg.Clone(catalogueDir, url); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Debugf("cloned catalogue repository to %s", catalogueDir)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
ABRA_VERSION="0.3.0-alpha"
 | 
			
		||||
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
 | 
			
		||||
RC_VERSION="0.4.0-alpha-rc3"
 | 
			
		||||
RC_VERSION="0.4.0-alpha-rc5"
 | 
			
		||||
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
 | 
			
		||||
 | 
			
		||||
for arg in "$@"; do
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,8 @@ wire up for testing in an automated way.
 | 
			
		||||
## deploy, upgrade, rollback
 | 
			
		||||
 | 
			
		||||
- `abra app deploy <app>`
 | 
			
		||||
- `abra app deploy --force <app>`
 | 
			
		||||
- `abra app deploy --chaos <app>`
 | 
			
		||||
- `abra app upgrade <app>`
 | 
			
		||||
- `abra app rollback <app>`
 | 
			
		||||
 | 
			
		||||
@ -45,6 +47,7 @@ wire up for testing in an automated way.
 | 
			
		||||
- `abra app run <app>`
 | 
			
		||||
- `abra app secret ls <app>`
 | 
			
		||||
- `abra app volume ls <app>`
 | 
			
		||||
- `abra app new --secrets <recipe>`
 | 
			
		||||
 | 
			
		||||
### hard mode
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user