forked from toolshed/abra
		
	Compare commits
	
		
			44 Commits
		
	
	
		
			0.7.0-rc2-
			...
			detailed_a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| edff63b446 | |||
| d5979436c1 | |||
| cb33edaac3 | |||
| e9879e2226 | |||
| 5428ebf43b | |||
| d120299929 | |||
| 3753357ef8 | |||
| 611430aab2 | |||
| 
						
						
							
						
						f29278f80a
	
				 | 
					
					
						|||
| a9a294cbb7 | |||
| 
						
						
							
						
						73004789a4
	
				 | 
					
					
						|||
| 440aba77d5 | |||
| e4a89bcc4f | |||
| 
						
						
							
						
						eb07617e73
	
				 | 
					
					
						|||
| 
						
						
							
						
						9fca4e56fb
	
				 | 
					
					
						|||
| 
						
						
							
						
						f17523010a
	
				 | 
					
					
						|||
| 
						
						
							
						
						3058178d84
	
				 | 
					
					
						|||
| 
						
						
							
						
						d62c4e3400
	
				 | 
					
					
						|||
| 
						
						
							
						
						5739758c3a
	
				 | 
					
					
						|||
| 
						
						
							
						
						a6b5566fa6
	
				 | 
					
					
						|||
| 
						
						
							
						
						4dbe1362a8
	
				 | 
					
					
						|||
| 
						
						
							
						
						98fc36c830
	
				 | 
					
					
						|||
| 
						
						
							
						
						b8abc8705c
	
				 | 
					
					
						|||
| 
						
						
							
						
						636261934f
	
				 | 
					
					
						|||
| 
						
						
							
						
						6381b73a6a
	
				 | 
					
					
						|||
| 
						
						
							
						
						1a72e27045
	
				 | 
					
					
						|||
| 
						
						
							
						
						9754c1b2d1
	
				 | 
					
					
						|||
| b14ec0cda4 | |||
| c7730ba604 | |||
| 
						
						
							
						
						47c61df444
	
				 | 
					
					
						|||
| 
						
						
							
						
						312b93e794
	
				 | 
					
					
						|||
| 
						
						
							
						
						992e675921
	
				 | 
					
					
						|||
| 
						
						
							
						
						d4f3a7be31
	
				 | 
					
					
						|||
| d619f399e7 | |||
| 
						
						
							
						
						96a8cb7aff
	
				 | 
					
					
						|||
| 9b51d22c20 | |||
| d789830ce4 | |||
| 
						
						
							
						
						e4b4084dfd
	
				 | 
					
					
						|||
| 
						
						
							
						
						ff58646cfc
	
				 | 
					
					
						|||
| 
						
						
							
						
						eec6469ba1
	
				 | 
					
					
						|||
| 
						
						
							
						
						e94f947d20
	
				 | 
					
					
						|||
| 
						
						
							
						
						cccbe4a2ec
	
				 | 
					
					
						|||
| 
						
						
							
						
						f53cfb6c36
	
				 | 
					
					
						|||
| 
						
						
							
						
						f55f01a25c
	
				 | 
					
					
						
@ -5,9 +5,12 @@
 | 
			
		||||
 | 
			
		||||
- 3wordchant
 | 
			
		||||
- cassowary
 | 
			
		||||
- codegod100
 | 
			
		||||
- decentral1se
 | 
			
		||||
- frando
 | 
			
		||||
- kawaiipunk
 | 
			
		||||
- knoflook
 | 
			
		||||
- moritz
 | 
			
		||||
- roxxers
 | 
			
		||||
- vera
 | 
			
		||||
- yksflip
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								Makefile
									
									
									
									
									
								
							@ -16,11 +16,11 @@ install:
 | 
			
		||||
	@go install -ldflags=$(LDFLAGS) $(ABRA)
 | 
			
		||||
 | 
			
		||||
build-dev:
 | 
			
		||||
	@go build -ldflags=$(LDFLAGS) $(ABRA)
 | 
			
		||||
	@go build -v -ldflags=$(LDFLAGS) $(ABRA)
 | 
			
		||||
 | 
			
		||||
build:
 | 
			
		||||
	@go build -ldflags=$(DIST_LDFLAGS) $(ABRA)
 | 
			
		||||
	@go build -ldflags=$(DIST_LDFLAGS) $(KADABRA)
 | 
			
		||||
	@go build -v -ldflags=$(DIST_LDFLAGS) $(ABRA)
 | 
			
		||||
	@go build -v -ldflags=$(DIST_LDFLAGS) $(KADABRA)
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	@rm '$(GOPATH)/bin/abra'
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	containerPkg "coopcloud.tech/abra/pkg/container"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/upstream/container"
 | 
			
		||||
	"github.com/docker/cli/cli/command"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
@ -72,13 +73,14 @@ This single file can be used to restore your app. See "abra app restore" for mor
 | 
			
		||||
`,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		recipe, err := recipe.Get(app.Recipe)
 | 
			
		||||
		recipe, err := recipe.Get(app.Recipe, conf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
@ -55,6 +56,7 @@ the logs.
 | 
			
		||||
	BashComplete: autocomplete.AppNameComplete,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -71,14 +73,14 @@ the logs.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !internal.Watch {
 | 
			
		||||
			if err := checkErrors(c, cl, app); err != nil {
 | 
			
		||||
			if err := checkErrors(c, cl, app, conf); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for {
 | 
			
		||||
			if err := checkErrors(c, cl, app); err != nil {
 | 
			
		||||
			if err := checkErrors(c, cl, app, conf); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			time.Sleep(2 * time.Second)
 | 
			
		||||
@ -88,8 +90,8 @@ the logs.
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error {
 | 
			
		||||
	recipe, err := recipe.Get(app.Recipe)
 | 
			
		||||
func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App, conf *runtime.Config) error {
 | 
			
		||||
	recipe, err := recipe.Get(app.Recipe, conf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -39,13 +39,16 @@ var listAppServerFlag = &cli.StringFlag{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type appStatus struct {
 | 
			
		||||
	Server  string `json:"server"`
 | 
			
		||||
	Recipe  string `json:"recipe"`
 | 
			
		||||
	AppName string `json:"appName"`
 | 
			
		||||
	Domain  string `json:"domain"`
 | 
			
		||||
	Status  string `json:"status"`
 | 
			
		||||
	Version string `json:"version"`
 | 
			
		||||
	Upgrade string `json:"upgrade"`
 | 
			
		||||
	Server       string `json:"server"`
 | 
			
		||||
	Recipe       string `json:"recipe"`
 | 
			
		||||
	AppName      string `json:"appName"`
 | 
			
		||||
	Domain       string `json:"domain"`
 | 
			
		||||
	Status       string `json:"status"`
 | 
			
		||||
	Chaos        string `json:"chaos"`
 | 
			
		||||
	ChaosVersion string `json:"chaosVersion"`
 | 
			
		||||
	AutoUpdate   string `json:"autoUpdate"`
 | 
			
		||||
	Version      string `json:"version"`
 | 
			
		||||
	Upgrade      string `json:"upgrade"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type serverStatus struct {
 | 
			
		||||
@ -138,12 +141,24 @@ can take some time.
 | 
			
		||||
				if status {
 | 
			
		||||
					status := "unknown"
 | 
			
		||||
					version := "unknown"
 | 
			
		||||
					chaos := "unknown"
 | 
			
		||||
					chaosVersion := "unknown"
 | 
			
		||||
					autoUpdate := "unknown"
 | 
			
		||||
					if statusMeta, ok := statuses[app.StackName()]; ok {
 | 
			
		||||
						if currentVersion, exists := statusMeta["version"]; exists {
 | 
			
		||||
							if currentVersion != "" {
 | 
			
		||||
								version = currentVersion
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						if chaosDeploy, exists := statusMeta["chaos"]; exists {
 | 
			
		||||
							chaos = chaosDeploy
 | 
			
		||||
						}
 | 
			
		||||
						if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists {
 | 
			
		||||
							chaosVersion = chaosDeployVersion
 | 
			
		||||
						}
 | 
			
		||||
						if autoUpdateState, exists := statusMeta["autoUpdate"]; exists {
 | 
			
		||||
							autoUpdate = autoUpdateState
 | 
			
		||||
						}
 | 
			
		||||
						if statusMeta["status"] != "" {
 | 
			
		||||
							status = statusMeta["status"]
 | 
			
		||||
						}
 | 
			
		||||
@ -153,7 +168,10 @@ can take some time.
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					appStats.Status = status
 | 
			
		||||
					appStats.Chaos = chaos
 | 
			
		||||
					appStats.ChaosVersion = chaosVersion
 | 
			
		||||
					appStats.Version = version
 | 
			
		||||
					appStats.AutoUpdate = autoUpdate
 | 
			
		||||
 | 
			
		||||
					var newUpdates []string
 | 
			
		||||
					if version != "unknown" {
 | 
			
		||||
@ -223,7 +241,7 @@ can take some time.
 | 
			
		||||
 | 
			
		||||
			tableCol := []string{"recipe", "domain"}
 | 
			
		||||
			if status {
 | 
			
		||||
				tableCol = append(tableCol, []string{"status", "version", "upgrade"}...)
 | 
			
		||||
				tableCol = append(tableCol, []string{"status", "chaos", "version", "upgrade", "autoupdate"}...)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			table := formatter.CreateTable(tableCol)
 | 
			
		||||
@ -231,7 +249,13 @@ can take some time.
 | 
			
		||||
			for _, appStat := range serverStat.Apps {
 | 
			
		||||
				tableRow := []string{appStat.Recipe, appStat.Domain}
 | 
			
		||||
				if status {
 | 
			
		||||
					tableRow = append(tableRow, []string{appStat.Status, appStat.Version, appStat.Upgrade}...)
 | 
			
		||||
					chaosStatus := "unknown"
 | 
			
		||||
					if appStat.ChaosVersion != "unknown" {
 | 
			
		||||
						chaosStatus = appStat.Chaos + appStat.ChaosVersion
 | 
			
		||||
					} else {
 | 
			
		||||
						chaosStatus = appStat.Chaos
 | 
			
		||||
					}
 | 
			
		||||
					tableRow = append(tableRow, []string{appStat.Status, chaosStatus, appStat.Version, appStat.Upgrade, appStat.AutoUpdate}...)
 | 
			
		||||
				}
 | 
			
		||||
				table.Append(tableRow)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/service"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
@ -20,12 +21,14 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var logOpts = types.ContainerLogsOptions{
 | 
			
		||||
	Details:    false,
 | 
			
		||||
	Follow:     true,
 | 
			
		||||
	ShowStderr: true,
 | 
			
		||||
	ShowStdout: true,
 | 
			
		||||
	Tail:       "20",
 | 
			
		||||
	Since:      "",
 | 
			
		||||
	Until:      "",
 | 
			
		||||
	Timestamps: true,
 | 
			
		||||
	Follow:     true,
 | 
			
		||||
	Tail:       "20",
 | 
			
		||||
	Details:    false,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// stackLogs lists logs for all stack services
 | 
			
		||||
@ -74,18 +77,21 @@ var appLogsCommand = cli.Command{
 | 
			
		||||
	Usage:     "Tail app logs",
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		internal.StdErrOnlyFlag,
 | 
			
		||||
		internal.SinceLogsFlag,
 | 
			
		||||
		internal.DebugFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Before:       internal.SubCommandBefore,
 | 
			
		||||
	BashComplete: autocomplete.AppNameComplete,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		app := internal.ValidateApp(c, runtime.WithEnsureRecipeExists(false))
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logOpts.Since = internal.SinceLogs
 | 
			
		||||
 | 
			
		||||
		serviceName := c.Args().Get(1)
 | 
			
		||||
		if serviceName == "" {
 | 
			
		||||
			logrus.Debugf("tailing logs for all %s services", app.Recipe)
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	containerPkg "coopcloud.tech/abra/pkg/container"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/upstream/container"
 | 
			
		||||
	"github.com/docker/cli/cli/command"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
@ -55,6 +56,7 @@ Example:
 | 
			
		||||
`,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -77,7 +79,7 @@ Example:
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		recipe, err := recipe.Get(app.Recipe)
 | 
			
		||||
		recipe, err := recipe.Get(app.Recipe, conf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/lint"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
 | 
			
		||||
@ -49,14 +50,15 @@ recipes.
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		if !internal.Chaos {
 | 
			
		||||
			if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
			
		||||
			if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r, err := recipe.Get(app.Recipe)
 | 
			
		||||
		r, err := recipe.Get(app.Recipe, conf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
@ -181,6 +183,7 @@ recipes.
 | 
			
		||||
		config.ExposeAllEnv(stackName, compose, app.Env)
 | 
			
		||||
		config.SetRecipeLabel(compose, stackName, app.Recipe)
 | 
			
		||||
		config.SetChaosLabel(compose, stackName, internal.Chaos)
 | 
			
		||||
		config.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
 | 
			
		||||
		config.SetUpdateLabel(compose, stackName, app.Env)
 | 
			
		||||
 | 
			
		||||
		if !internal.Force {
 | 
			
		||||
@ -189,7 +192,7 @@ recipes.
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := stack.RunDeploy(cl, deployOpts, compose, app.StackName(), internal.DontWaitConverge); err != nil {
 | 
			
		||||
		if err := stack.RunDeploy(cl, deployOpts, compose, stackName, internal.DontWaitConverge); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,15 +2,82 @@ package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	dockerClient "github.com/docker/docker/client"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var prune bool
 | 
			
		||||
 | 
			
		||||
var pruneFlag = &cli.BoolFlag{
 | 
			
		||||
	Name:        "prune, p",
 | 
			
		||||
	Destination: &prune,
 | 
			
		||||
	Usage:       "Prunes unused containers, networks, and dangling images for an app",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// pruneApp runs the equivalent of a "docker system prune" but only filtering
 | 
			
		||||
// against resources connected with the app deployment. It is not a system wide
 | 
			
		||||
// prune. Volumes are not pruned to avoid unwated data loss.
 | 
			
		||||
func pruneApp(c *cli.Context, cl *dockerClient.Client, app config.App) error {
 | 
			
		||||
	stackName := app.StackName()
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		logrus.Debugf("polling for %s stack, waiting to be undeployed...", stackName)
 | 
			
		||||
 | 
			
		||||
		services, err := stack.GetStackServices(ctx, cl, stackName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(services) == 0 {
 | 
			
		||||
			logrus.Debugf("%s undeployed, moving on with pruning logic", stackName)
 | 
			
		||||
			time.Sleep(time.Second) // give runtime more time to tear down related state
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		time.Sleep(time.Second)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pruneFilters := filters.NewArgs()
 | 
			
		||||
	stackSearch := fmt.Sprintf("%s*", stackName)
 | 
			
		||||
	pruneFilters.Add("label", stackSearch)
 | 
			
		||||
	cr, err := cl.ContainersPrune(ctx, pruneFilters)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
 | 
			
		||||
	logrus.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
 | 
			
		||||
 | 
			
		||||
	nr, err := cl.NetworksPrune(ctx, pruneFilters)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Infof("networks pruned: %d", len(nr.NetworksDeleted))
 | 
			
		||||
 | 
			
		||||
	ir, err := cl.ImagesPrune(ctx, pruneFilters)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
 | 
			
		||||
	logrus.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var appUndeployCommand = cli.Command{
 | 
			
		||||
	Name:      "undeploy",
 | 
			
		||||
	Aliases:   []string{"un"},
 | 
			
		||||
@ -18,18 +85,22 @@ var appUndeployCommand = cli.Command{
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		internal.DebugFlag,
 | 
			
		||||
		internal.NoInputFlag,
 | 
			
		||||
		pruneFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Before: internal.SubCommandBefore,
 | 
			
		||||
	Usage:  "Undeploy an app",
 | 
			
		||||
	Before:       internal.SubCommandBefore,
 | 
			
		||||
	Usage:        "Undeploy an app",
 | 
			
		||||
	BashComplete: autocomplete.AppNameComplete,
 | 
			
		||||
	Description: `
 | 
			
		||||
This does not destroy any of the application data. However, you should remain
 | 
			
		||||
vigilant, as your swarm installation will consider any previously attached
 | 
			
		||||
volumes as eligiblef or pruning once undeployed.
 | 
			
		||||
This does not destroy any of the application data.
 | 
			
		||||
 | 
			
		||||
However, you should remain vigilant, as your swarm installation will consider
 | 
			
		||||
any previously attached volumes as eligible for pruning once undeployed.
 | 
			
		||||
 | 
			
		||||
Passing "-p/--prune" does not remove those volumes.
 | 
			
		||||
`,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
@ -55,7 +126,12 @@ volumes as eligiblef or pruning once undeployed.
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if prune {
 | 
			
		||||
			if err := pruneApp(c, cl, app); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
	BashComplete: autocomplete.AppNameComplete,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/lint"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	stack "coopcloud.tech/abra/pkg/upstream/stack"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
@ -52,6 +53,7 @@ recipes.
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -59,12 +61,12 @@ recipes.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !internal.Chaos {
 | 
			
		||||
			if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
			
		||||
			if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r, err := recipe.Get(app.Recipe)
 | 
			
		||||
		r, err := recipe.Get(app.Recipe, conf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
@ -192,13 +194,14 @@ recipes.
 | 
			
		||||
		config.ExposeAllEnv(stackName, compose, app.Env)
 | 
			
		||||
		config.SetRecipeLabel(compose, stackName, app.Recipe)
 | 
			
		||||
		config.SetChaosLabel(compose, stackName, internal.Chaos)
 | 
			
		||||
		config.SetChaosVersionLabel(compose, stackName, chosenUpgrade)
 | 
			
		||||
		config.SetUpdateLabel(compose, stackName, app.Env)
 | 
			
		||||
 | 
			
		||||
		if err := internal.NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := stack.RunDeploy(cl, deployOpts, compose, app.StackName(), internal.DontWaitConverge); err != nil {
 | 
			
		||||
		if err := stack.RunDeploy(cl, deployOpts, compose, stackName, internal.DontWaitConverge); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/upstream/stack"
 | 
			
		||||
	"github.com/docker/distribution/reference"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
@ -48,6 +49,7 @@ version.
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -69,7 +71,7 @@ version.
 | 
			
		||||
			logrus.Fatalf("%s is not deployed?", app.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		recipeMeta, err := recipe.GetRecipeMeta(app.Recipe)
 | 
			
		||||
		recipeMeta, err := recipe.GetRecipeMeta(app.Recipe, conf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	gitPkg "coopcloud.tech/abra/pkg/git"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"github.com/go-git/go-git/v5"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
@ -54,8 +55,10 @@ keys configured on your account.
 | 
			
		||||
	ArgsUsage: "[<recipe>]",
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		recipeName := c.Args().First()
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		if recipeName != "" {
 | 
			
		||||
			internal.ValidateRecipe(c, true)
 | 
			
		||||
			internal.ValidateRecipe(c)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := catalogue.EnsureUpToDate(); err != nil {
 | 
			
		||||
@ -79,7 +82,7 @@ keys configured on your account.
 | 
			
		||||
 | 
			
		||||
		if !internal.SkipUpdates {
 | 
			
		||||
			logrus.Warn(logMsg)
 | 
			
		||||
			if err := recipe.UpdateRepositories(repos, recipeName); err != nil {
 | 
			
		||||
			if err := recipe.UpdateRepositories(repos, recipeName, conf); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@ -97,7 +100,7 @@ keys configured on your account.
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			versions, err := recipe.GetRecipeVersions(recipeMeta.Name)
 | 
			
		||||
			versions, err := recipe.GetRecipeVersions(recipeMeta.Name, conf)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Warn(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -328,6 +328,14 @@ var StdErrOnlyFlag = &cli.BoolFlag{
 | 
			
		||||
	Destination: &StdErrOnly,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var SinceLogs string
 | 
			
		||||
var SinceLogsFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "since, S",
 | 
			
		||||
	Value:       "",
 | 
			
		||||
	Usage:       "tail logs since YYYY-MM-DDTHH:MM:SSZ",
 | 
			
		||||
	Destination: &SinceLogs,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var DontWaitConverge bool
 | 
			
		||||
var DontWaitConvergeFlag = &cli.BoolFlag{
 | 
			
		||||
	Name:        "no-converge-checks, c",
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/git"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/lint"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/upstream/stack"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
@ -24,6 +25,8 @@ import (
 | 
			
		||||
// DeployAction is the main command-line action for this package
 | 
			
		||||
func DeployAction(c *cli.Context) error {
 | 
			
		||||
	app := ValidateApp(c)
 | 
			
		||||
	stackName := app.StackName()
 | 
			
		||||
	conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
	cl, err := client.New(app.Server)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -31,12 +34,12 @@ func DeployAction(c *cli.Context) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !Chaos {
 | 
			
		||||
		if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
 | 
			
		||||
		if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, err := recipe.Get(app.Recipe)
 | 
			
		||||
	r, err := recipe.Get(app.Recipe, conf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
@ -45,9 +48,9 @@ func DeployAction(c *cli.Context) error {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("checking whether %s is already deployed", app.StackName())
 | 
			
		||||
	logrus.Debugf("checking whether %s is already deployed", stackName)
 | 
			
		||||
 | 
			
		||||
	isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, app.StackName())
 | 
			
		||||
	isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
@ -83,7 +86,7 @@ func DeployAction(c *cli.Context) error {
 | 
			
		||||
			}
 | 
			
		||||
			version = formatter.SmallSHA(head.String())
 | 
			
		||||
			logrus.Warn("no versions detected, using latest commit")
 | 
			
		||||
			if err := recipe.EnsureLatest(app.Recipe); err != nil {
 | 
			
		||||
			if err := recipe.EnsureLatest(app.Recipe, conf); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@ -126,7 +129,7 @@ func DeployAction(c *cli.Context) error {
 | 
			
		||||
	}
 | 
			
		||||
	deployOpts := stack.Deploy{
 | 
			
		||||
		Composefiles: composeFiles,
 | 
			
		||||
		Namespace:    app.StackName(),
 | 
			
		||||
		Namespace:    stackName,
 | 
			
		||||
		Prune:        false,
 | 
			
		||||
		ResolveImage: stack.ResolveImageAlways,
 | 
			
		||||
	}
 | 
			
		||||
@ -134,10 +137,11 @@ func DeployAction(c *cli.Context) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	config.ExposeAllEnv(app.StackName(), compose, app.Env)
 | 
			
		||||
	config.SetRecipeLabel(compose, app.StackName(), app.Recipe)
 | 
			
		||||
	config.SetChaosLabel(compose, app.StackName(), Chaos)
 | 
			
		||||
	config.SetUpdateLabel(compose, app.StackName(), app.Env)
 | 
			
		||||
	config.ExposeAllEnv(stackName, compose, app.Env)
 | 
			
		||||
	config.SetRecipeLabel(compose, stackName, app.Recipe)
 | 
			
		||||
	config.SetChaosLabel(compose, stackName, Chaos)
 | 
			
		||||
	config.SetChaosVersionLabel(compose, stackName, version)
 | 
			
		||||
	config.SetUpdateLabel(compose, stackName, app.Env)
 | 
			
		||||
 | 
			
		||||
	if err := DeployOverview(app, version, "continue with deployment?"); err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/jsontable"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/secret"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	dockerClient "github.com/docker/docker/client"
 | 
			
		||||
@ -119,9 +120,10 @@ func ensureServerFlag() error {
 | 
			
		||||
 | 
			
		||||
// NewAction is the new app creation logic
 | 
			
		||||
func NewAction(c *cli.Context) error {
 | 
			
		||||
	recipe := ValidateRecipeWithPrompt(c, false)
 | 
			
		||||
	recipe := ValidateRecipeWithPrompt(c, runtime.WithEnsureRecipeLatest(false))
 | 
			
		||||
 | 
			
		||||
	if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
 | 
			
		||||
	conf := runtime.New(runtime.WithEnsureRecipeLatest(false))
 | 
			
		||||
	if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/app"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
@ -18,14 +19,15 @@ import (
 | 
			
		||||
var AppName string
 | 
			
		||||
 | 
			
		||||
// ValidateRecipe ensures the recipe arg is valid.
 | 
			
		||||
func ValidateRecipe(c *cli.Context, ensureLatest bool) recipe.Recipe {
 | 
			
		||||
func ValidateRecipe(c *cli.Context, opts ...runtime.Option) recipe.Recipe {
 | 
			
		||||
	recipeName := c.Args().First()
 | 
			
		||||
	conf := runtime.New(opts...)
 | 
			
		||||
 | 
			
		||||
	if recipeName == "" {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chosenRecipe, err := recipe.Get(recipeName)
 | 
			
		||||
	chosenRecipe, err := recipe.Get(recipeName, conf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if c.Command.Name == "generate" {
 | 
			
		||||
			if strings.Contains(err.Error(), "missing a compose") {
 | 
			
		||||
@ -40,10 +42,8 @@ func ValidateRecipe(c *cli.Context, ensureLatest bool) recipe.Recipe {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ensureLatest {
 | 
			
		||||
		if err := recipe.EnsureLatest(recipeName); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	if err := recipe.EnsureLatest(recipeName, conf); err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("validated %s as recipe argument", recipeName)
 | 
			
		||||
@ -53,8 +53,9 @@ func ValidateRecipe(c *cli.Context, ensureLatest bool) recipe.Recipe {
 | 
			
		||||
 | 
			
		||||
// ValidateRecipeWithPrompt ensures a recipe argument is present before
 | 
			
		||||
// validating, asking for input if required.
 | 
			
		||||
func ValidateRecipeWithPrompt(c *cli.Context, ensureLatest bool) recipe.Recipe {
 | 
			
		||||
func ValidateRecipeWithPrompt(c *cli.Context, opts ...runtime.Option) recipe.Recipe {
 | 
			
		||||
	recipeName := c.Args().First()
 | 
			
		||||
	conf := runtime.New(opts...)
 | 
			
		||||
 | 
			
		||||
	if recipeName == "" && !NoInput {
 | 
			
		||||
		var recipes []string
 | 
			
		||||
@ -102,15 +103,13 @@ func ValidateRecipeWithPrompt(c *cli.Context, ensureLatest bool) recipe.Recipe {
 | 
			
		||||
		ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chosenRecipe, err := recipe.Get(recipeName)
 | 
			
		||||
	chosenRecipe, err := recipe.Get(recipeName, conf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ensureLatest {
 | 
			
		||||
		if err := recipe.EnsureLatest(recipeName); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	if err := recipe.EnsureLatest(recipeName, conf); err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("validated %s as recipe argument", recipeName)
 | 
			
		||||
@ -119,8 +118,9 @@ func ValidateRecipeWithPrompt(c *cli.Context, ensureLatest bool) recipe.Recipe {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateApp ensures the app name arg is valid.
 | 
			
		||||
func ValidateApp(c *cli.Context) config.App {
 | 
			
		||||
func ValidateApp(c *cli.Context, opts ...runtime.Option) config.App {
 | 
			
		||||
	appName := c.Args().First()
 | 
			
		||||
	conf := runtime.New(opts...)
 | 
			
		||||
 | 
			
		||||
	if AppName != "" {
 | 
			
		||||
		appName = AppName
 | 
			
		||||
@ -136,7 +136,7 @@ func ValidateApp(c *cli.Context) config.App {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := recipe.EnsureExists(app.Recipe); err != nil {
 | 
			
		||||
	if err := recipe.EnsureExists(app.Recipe, conf); err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
@ -21,8 +22,10 @@ var recipeFetchCommand = cli.Command{
 | 
			
		||||
	BashComplete: autocomplete.RecipeNameComplete,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		recipeName := c.Args().First()
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		if recipeName != "" {
 | 
			
		||||
			internal.ValidateRecipe(c, true)
 | 
			
		||||
			internal.ValidateRecipe(c)
 | 
			
		||||
			return nil // ValidateRecipe ensures latest checkout
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -31,7 +34,7 @@ var recipeFetchCommand = cli.Command{
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := recipe.UpdateRepositories(repos, recipeName); err != nil {
 | 
			
		||||
		if err := recipe.UpdateRepositories(repos, recipeName, conf); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/lint"
 | 
			
		||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
@ -24,9 +25,10 @@ var recipeLintCommand = cli.Command{
 | 
			
		||||
	Before:       internal.SubCommandBefore,
 | 
			
		||||
	BashComplete: autocomplete.RecipeNameComplete,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		recipe := internal.ValidateRecipe(c, true)
 | 
			
		||||
		recipe := internal.ValidateRecipe(c)
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
 | 
			
		||||
		if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ import (
 | 
			
		||||
	gitPkg "coopcloud.tech/abra/pkg/git"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/docker/distribution/reference"
 | 
			
		||||
@ -58,7 +59,7 @@ your SSH keys configured on your account.
 | 
			
		||||
	Before:       internal.SubCommandBefore,
 | 
			
		||||
	BashComplete: autocomplete.RecipeNameComplete,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		recipe := internal.ValidateRecipeWithPrompt(c, false)
 | 
			
		||||
		recipe := internal.ValidateRecipeWithPrompt(c, runtime.WithEnsureRecipeLatest(false))
 | 
			
		||||
 | 
			
		||||
		imagesTmp, err := getImageVersions(recipe)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/go-git/go-git/v5"
 | 
			
		||||
@ -41,7 +42,7 @@ 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, false)
 | 
			
		||||
		recipe := internal.ValidateRecipeWithPrompt(c, runtime.WithEnsureRecipeLatest(false))
 | 
			
		||||
 | 
			
		||||
		mainApp, err := internal.GetMainAppImage(recipe)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/docker/distribution/reference"
 | 
			
		||||
@ -59,9 +60,10 @@ You may invoke this command in "wizard" mode and be prompted for input:
 | 
			
		||||
	},
 | 
			
		||||
	Before: internal.SubCommandBefore,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		recipe := internal.ValidateRecipeWithPrompt(c, true)
 | 
			
		||||
		recipe := internal.ValidateRecipeWithPrompt(c)
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
 | 
			
		||||
		if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
@ -20,7 +21,7 @@ var recipeVersionCommand = cli.Command{
 | 
			
		||||
	Before:       internal.SubCommandBefore,
 | 
			
		||||
	BashComplete: autocomplete.RecipeNameComplete,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		recipe := internal.ValidateRecipe(c, false)
 | 
			
		||||
		recipe := internal.ValidateRecipe(c, runtime.WithEnsureRecipeLatest(false))
 | 
			
		||||
 | 
			
		||||
		catalogue, err := recipePkg.ReadRecipeCatalogue()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
 | 
			
		||||
@ -39,11 +39,7 @@ var serverListCommand = cli.Command{
 | 
			
		||||
 | 
			
		||||
		tableColumns := []string{"name", "host", "user", "port"}
 | 
			
		||||
		table := formatter.CreateTable(tableColumns)
 | 
			
		||||
		if internal.MachineReadable {
 | 
			
		||||
			defer table.JSONRender()
 | 
			
		||||
		} else {
 | 
			
		||||
			defer table.Render()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		serverNames, err := config.ReadServerNames()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
@ -84,6 +80,16 @@ var serverListCommand = cli.Command{
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if internal.MachineReadable {
 | 
			
		||||
			table.JSONRender()
 | 
			
		||||
		} else {
 | 
			
		||||
			if problemsFilter && table.NumLines() == 0 {
 | 
			
		||||
				logrus.Info("all servers wired up correctly 👏")
 | 
			
		||||
			} else {
 | 
			
		||||
				table.Render()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										100
									
								
								cli/server/prune.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								cli/server/prune.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,100 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var allFilter bool
 | 
			
		||||
 | 
			
		||||
var allFilterFlag = &cli.BoolFlag{
 | 
			
		||||
	Name:        "all, a",
 | 
			
		||||
	Usage:       "Remove all unused images not just dangling ones",
 | 
			
		||||
	Destination: &allFilter,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var volunesFilter bool
 | 
			
		||||
 | 
			
		||||
var volumesFilterFlag = &cli.BoolFlag{
 | 
			
		||||
	Name:        "volumes, v",
 | 
			
		||||
	Usage:       "Prune volumes. This will remove app data, Be Careful!",
 | 
			
		||||
	Destination: &volunesFilter,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var serverPruneCommand = cli.Command{
 | 
			
		||||
	Name:    "prune",
 | 
			
		||||
	Aliases: []string{"p"},
 | 
			
		||||
	Usage:   "Prune a managed server; Runs a docker system prune",
 | 
			
		||||
	Description: `
 | 
			
		||||
Prunes unused containers, networks, and dangling images.
 | 
			
		||||
 | 
			
		||||
If passing "-v/--volumes" then volumes not connected with a deployed app will
 | 
			
		||||
also be removed. This can result in unwanted data loss if not used carefully.
 | 
			
		||||
	`,
 | 
			
		||||
	ArgsUsage: "[<server>]",
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		allFilterFlag,
 | 
			
		||||
		volumesFilterFlag,
 | 
			
		||||
		internal.DebugFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Before:       internal.SubCommandBefore,
 | 
			
		||||
	BashComplete: autocomplete.ServerNameComplete,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		var args filters.Args
 | 
			
		||||
 | 
			
		||||
		serverName := internal.ValidateServer(c)
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(serverName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx := context.Background()
 | 
			
		||||
		cr, err := cl.ContainersPrune(ctx, args)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
 | 
			
		||||
		logrus.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
 | 
			
		||||
 | 
			
		||||
		nr, err := cl.NetworksPrune(ctx, args)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Infof("networks pruned: %d", len(nr.NetworksDeleted))
 | 
			
		||||
 | 
			
		||||
		pruneFilters := filters.NewArgs()
 | 
			
		||||
		if allFilter {
 | 
			
		||||
			pruneFilters.Add("dangling", "false")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ir, err := cl.ImagesPrune(ctx, pruneFilters)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
 | 
			
		||||
		logrus.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
 | 
			
		||||
 | 
			
		||||
		if volunesFilter {
 | 
			
		||||
			vr, err := cl.VolumesPrune(ctx, args)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			volSpaceReclaimed := formatter.ByteCountSI(vr.SpaceReclaimed)
 | 
			
		||||
			logrus.Infof("volumes pruned: %d; space reclaimed: %s", len(vr.VolumesDeleted), volSpaceReclaimed)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
@ -124,7 +125,8 @@ like tears in rain.
 | 
			
		||||
		internal.HetznerCloudNameFlag,
 | 
			
		||||
		internal.HetznerCloudAPITokenFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Before: internal.SubCommandBefore,
 | 
			
		||||
	Before:       internal.SubCommandBefore,
 | 
			
		||||
	BashComplete: autocomplete.ServerNameComplete,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		serverName := internal.ValidateServer(c)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -22,5 +22,6 @@ recipes, see available flags on "abra server add" for more.
 | 
			
		||||
		serverAddCommand,
 | 
			
		||||
		serverListCommand,
 | 
			
		||||
		serverRemoveCommand,
 | 
			
		||||
		serverPruneCommand,
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/lint"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/upstream/convert"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/upstream/stack"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
@ -113,10 +114,12 @@ update chaos deployments use the "--chaos" flag. Use it with care.
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		conf := runtime.New()
 | 
			
		||||
 | 
			
		||||
		if !updateAll {
 | 
			
		||||
			stackName := c.Args().Get(0)
 | 
			
		||||
			recipeName := c.Args().Get(1)
 | 
			
		||||
			err = tryUpgrade(cl, stackName, recipeName)
 | 
			
		||||
			err = tryUpgrade(cl, stackName, recipeName, conf)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
@ -136,7 +139,7 @@ update chaos deployments use the "--chaos" flag. Use it with care.
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			err = tryUpgrade(cl, stackName, recipeName)
 | 
			
		||||
			err = tryUpgrade(cl, stackName, recipeName, conf)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
@ -281,7 +284,8 @@ func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(versions) == 0 {
 | 
			
		||||
		return nil, fmt.Errorf("no published releases for %s in the recipe catalogue?", recipeName)
 | 
			
		||||
		logrus.Warnf("no published releases for %s in the recipe catalogue?", recipeName)
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var availableUpgrades []string
 | 
			
		||||
@ -313,12 +317,12 @@ func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName
 | 
			
		||||
 | 
			
		||||
// processRecipeRepoVersion clones, pulls, checks out the version and lints the
 | 
			
		||||
// recipe repository.
 | 
			
		||||
func processRecipeRepoVersion(recipeName string, version string) error {
 | 
			
		||||
	if err := recipe.EnsureExists(recipeName); err != nil {
 | 
			
		||||
func processRecipeRepoVersion(recipeName, version string, conf *runtime.Config) error {
 | 
			
		||||
	if err := recipe.EnsureExists(recipeName, conf); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := recipe.EnsureUpToDate(recipeName); err != nil {
 | 
			
		||||
	if err := recipe.EnsureUpToDate(recipeName, conf); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -326,7 +330,7 @@ func processRecipeRepoVersion(recipeName string, version string) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r, err := recipe.Get(recipeName); err != nil {
 | 
			
		||||
	if r, err := recipe.Get(recipeName, conf); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if err := lint.LintForErrors(r); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@ -383,7 +387,7 @@ func createDeployConfig(recipeName string, stackName string, env config.AppEnv)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tryUpgrade performs the upgrade if all the requirements are fulfilled.
 | 
			
		||||
func tryUpgrade(cl *dockerclient.Client, stackName string, recipeName string) error {
 | 
			
		||||
func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *runtime.Config) error {
 | 
			
		||||
	if recipeName == "" {
 | 
			
		||||
		logrus.Debugf("Don't update %s due to missing recipe name", stackName)
 | 
			
		||||
		return nil
 | 
			
		||||
@ -419,13 +423,13 @@ func tryUpgrade(cl *dockerclient.Client, stackName string, recipeName string) er
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = upgrade(cl, stackName, recipeName, upgradeVersion)
 | 
			
		||||
	err = upgrade(cl, stackName, recipeName, upgradeVersion, conf)
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// upgrade performs all necessary steps to upgrade an app.
 | 
			
		||||
func upgrade(cl *dockerclient.Client, stackName string, recipeName string, upgradeVersion string) error {
 | 
			
		||||
func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion string, conf *runtime.Config) error {
 | 
			
		||||
	env, err := getEnv(cl, stackName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@ -438,7 +442,7 @@ func upgrade(cl *dockerclient.Client, stackName string, recipeName string, upgra
 | 
			
		||||
		Env:    env,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = processRecipeRepoVersion(recipeName, upgradeVersion); err != nil {
 | 
			
		||||
	if err = processRecipeRepoVersion(recipeName, upgradeVersion, conf); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							@ -11,8 +11,8 @@ require (
 | 
			
		||||
	github.com/docker/distribution v2.8.1+incompatible
 | 
			
		||||
	github.com/docker/docker v20.10.23+incompatible
 | 
			
		||||
	github.com/docker/go-units v0.5.0
 | 
			
		||||
	github.com/go-git/go-git/v5 v5.5.2
 | 
			
		||||
	github.com/hetznercloud/hcloud-go v1.39.0
 | 
			
		||||
	github.com/go-git/go-git/v5 v5.6.0
 | 
			
		||||
	github.com/hetznercloud/hcloud-go v1.40.0
 | 
			
		||||
	github.com/moby/sys/signal v0.7.0
 | 
			
		||||
	github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.5
 | 
			
		||||
@ -46,5 +46,5 @@ require (
 | 
			
		||||
	github.com/urfave/cli v1.22.9
 | 
			
		||||
	github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b // indirect
 | 
			
		||||
	golang.org/x/crypto v0.5.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.5.0
 | 
			
		||||
	golang.org/x/sys v0.6.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								go.sum
									
									
									
									
									
								
							@ -414,8 +414,8 @@ github.com/go-git/go-billy/v5 v5.4.0 h1:Vaw7LaSTRJOUric7pe4vnzBSgyuf2KrLsu2Y4ZpQ
 | 
			
		||||
github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
 | 
			
		||||
github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ=
 | 
			
		||||
github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.5.2 h1:v8lgZa5k9ylUw+OR/roJHTxR4QItsNFI5nKtAXFuynw=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.6.0 h1:JvBdYfcttd+0kdpuWO7KTu0FYgCf5W0t5VwkWGobaa4=
 | 
			
		||||
github.com/go-git/go-git/v5 v5.6.0/go.mod h1:6nmJ0tJ3N4noMV1Omv7rC5FG3/o8Cm51TB4CJp7mRmE=
 | 
			
		||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 | 
			
		||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 | 
			
		||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 | 
			
		||||
@ -605,8 +605,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
 | 
			
		||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
 | 
			
		||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
 | 
			
		||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
 | 
			
		||||
github.com/hetznercloud/hcloud-go v1.39.0 h1:RUlzI458nGnPR6dlcZlrsGXYC1hQlFbKdm8tVtEQQB0=
 | 
			
		||||
github.com/hetznercloud/hcloud-go v1.39.0/go.mod h1:mepQwR6va27S3UQthaEPGS86jtzSY9xWL1e9dyxXpgA=
 | 
			
		||||
github.com/hetznercloud/hcloud-go v1.40.0 h1:rchS+LVd80GuAOQUM5pLs0Fn7tb4PflPCuxskLYc1JQ=
 | 
			
		||||
github.com/hetznercloud/hcloud-go v1.40.0/go.mod h1:jcKomw7kDIjrJ9FI72S/RdRPK8X8arYzEnXjRM/uXT8=
 | 
			
		||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
 | 
			
		||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
@ -744,6 +744,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 | 
			
		||||
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
 | 
			
		||||
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
 | 
			
		||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
 | 
			
		||||
github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM=
 | 
			
		||||
github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM=
 | 
			
		||||
@ -833,8 +834,8 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap
 | 
			
		||||
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
 | 
			
		||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 | 
			
		||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
 | 
			
		||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
 | 
			
		||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
@ -1045,6 +1046,7 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
 | 
			
		||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 | 
			
		||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
			
		||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 | 
			
		||||
golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
@ -1069,6 +1071,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 | 
			
		||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
			
		||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
 | 
			
		||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 | 
			
		||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
 | 
			
		||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
 | 
			
		||||
@ -1109,6 +1112,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
			
		||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
 | 
			
		||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
@ -1164,6 +1168,7 @@ golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qx
 | 
			
		||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 | 
			
		||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 | 
			
		||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 | 
			
		||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
 | 
			
		||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 | 
			
		||||
@ -1310,16 +1315,18 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
			
		||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 | 
			
		||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 | 
			
		||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 | 
			
		||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
 | 
			
		||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
 | 
			
		||||
@ -1407,6 +1414,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
 | 
			
		||||
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=
 | 
			
		||||
@ -1652,6 +1660,7 @@ k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAG
 | 
			
		||||
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
 | 
			
		||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 | 
			
		||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 | 
			
		||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 | 
			
		||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 | 
			
		||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 | 
			
		||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import (
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AppNameComplete copletes app names
 | 
			
		||||
// AppNameComplete copletes app names.
 | 
			
		||||
func AppNameComplete(c *cli.Context) {
 | 
			
		||||
	appNames, err := config.GetAppNames()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -25,7 +25,7 @@ func AppNameComplete(c *cli.Context) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RecipeNameComplete completes recipe names
 | 
			
		||||
// RecipeNameComplete completes recipe names.
 | 
			
		||||
func RecipeNameComplete(c *cli.Context) {
 | 
			
		||||
	catl, err := recipe.ReadRecipeCatalogue()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -41,7 +41,23 @@ func RecipeNameComplete(c *cli.Context) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SubcommandComplete completes subcommands.
 | 
			
		||||
// ServerNameComplete completes server names.
 | 
			
		||||
func ServerNameComplete(c *cli.Context) {
 | 
			
		||||
	files, err := config.LoadAppFiles("")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.NArg() > 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, appFile := range files {
 | 
			
		||||
		fmt.Println(appFile.Server)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SubcommandComplete completes sub-commands.
 | 
			
		||||
func SubcommandComplete(c *cli.Context) {
 | 
			
		||||
	if c.NArg() > 0 {
 | 
			
		||||
		return
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ func New(serverName string) (*client.Client, error) {
 | 
			
		||||
	if serverName != "default" {
 | 
			
		||||
		context, err := GetContext(serverName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
			return nil, fmt.Errorf("unknown server, run \"abra server add %s\"?", serverName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctxEndpoint, err := contextPkg.GetContextEndpoint(context)
 | 
			
		||||
 | 
			
		||||
@ -173,7 +173,7 @@ func newApp(env AppEnv, name string, appFile AppFile) (App, error) {
 | 
			
		||||
	if !exists {
 | 
			
		||||
		recipe, exists = env["TYPE"]
 | 
			
		||||
		if !exists {
 | 
			
		||||
			return App{}, fmt.Errorf("%s is missing the RECIPE env var", name)
 | 
			
		||||
			return App{}, fmt.Errorf("%s is missing the TYPE env var?", name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -369,13 +369,6 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for server := range servers {
 | 
			
		||||
		// validate that all server connections work
 | 
			
		||||
		if _, err := client.New(server); err != nil {
 | 
			
		||||
			return statuses, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var bar *progressbar.ProgressBar
 | 
			
		||||
	if !MachineReadable {
 | 
			
		||||
		bar = formatter.CreateProgressbar(len(servers), "querying remote servers...")
 | 
			
		||||
@ -410,7 +403,25 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
 | 
			
		||||
				result["status"] = "deployed"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			labelKey := fmt.Sprintf("coop-cloud.%s.version", name)
 | 
			
		||||
			labelKey := fmt.Sprintf("coop-cloud.%s.chaos", name)
 | 
			
		||||
			chaos, ok := service.Spec.Labels[labelKey]
 | 
			
		||||
			if ok {
 | 
			
		||||
				result["chaos"] = chaos
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			labelKey = fmt.Sprintf("coop-cloud.%s.chaos-version", name)
 | 
			
		||||
			if chaosVersion, ok := service.Spec.Labels[labelKey]; ok {
 | 
			
		||||
				result["chaosVersion"] = chaosVersion
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			labelKey = fmt.Sprintf("coop-cloud.%s.autoupdate", name)
 | 
			
		||||
			if autoUpdate, ok := service.Spec.Labels[labelKey]; ok {
 | 
			
		||||
				result["autoUpdate"] = autoUpdate
 | 
			
		||||
			} else {
 | 
			
		||||
				result["autoUpdate"] = "false"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			labelKey = fmt.Sprintf("coop-cloud.%s.version", name)
 | 
			
		||||
			if version, ok := service.Spec.Labels[labelKey]; ok {
 | 
			
		||||
				result["version"] = version
 | 
			
		||||
			} else {
 | 
			
		||||
@ -506,6 +517,17 @@ func SetChaosLabel(compose *composetypes.Config, stackName string, chaos bool) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetChaosVersionLabel adds the label 'coop-cloud.${STACK_NAME}.chaos-version=$(GIT_COMMIT)' to the app container
 | 
			
		||||
func SetChaosVersionLabel(compose *composetypes.Config, stackName string, chaosVersion string) {
 | 
			
		||||
	for _, service := range compose.Services {
 | 
			
		||||
		if service.Name == "app" {
 | 
			
		||||
			logrus.Debugf("set label 'coop-cloud.%s.chaos-version' to %v for %s", stackName, chaosVersion, stackName)
 | 
			
		||||
			labelKey := fmt.Sprintf("coop-cloud.%s.chaos-version", stackName)
 | 
			
		||||
			service.Deploy.Labels[labelKey] = chaosVersion
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetUpdateLabel adds env ENABLE_AUTO_UPDATE as label to enable/disable the
 | 
			
		||||
// auto update process for this app. The default if this variable is not set is to disable
 | 
			
		||||
// the auto update process.
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
package formatter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
@ -70,3 +71,21 @@ func StripTagMeta(image string) string {
 | 
			
		||||
 | 
			
		||||
	return image
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ByteCountSI presents a human friendly representation of a byte count. See
 | 
			
		||||
// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format.
 | 
			
		||||
func ByteCountSI(b uint64) string {
 | 
			
		||||
	const unit = 1000
 | 
			
		||||
 | 
			
		||||
	if b < unit {
 | 
			
		||||
		return fmt.Sprintf("%d B", b)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	div, exp := uint64(unit), 0
 | 
			
		||||
	for n := b / unit; n >= unit; n /= unit {
 | 
			
		||||
		div *= unit
 | 
			
		||||
		exp++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	gitPkg "coopcloud.tech/abra/pkg/git"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/limit"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/runtime"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/upstream/stack"
 | 
			
		||||
	loader "coopcloud.tech/abra/pkg/upstream/stack"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/web"
 | 
			
		||||
@ -206,8 +207,8 @@ func (r Recipe) Tags() ([]string, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get retrieves a recipe.
 | 
			
		||||
func Get(recipeName string) (Recipe, error) {
 | 
			
		||||
	if err := EnsureExists(recipeName); err != nil {
 | 
			
		||||
func Get(recipeName string, conf *runtime.Config) (Recipe, error) {
 | 
			
		||||
	if err := EnsureExists(recipeName, conf); err != nil {
 | 
			
		||||
		return Recipe{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -233,7 +234,7 @@ func Get(recipeName string) (Recipe, error) {
 | 
			
		||||
		return Recipe{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	meta, err := GetRecipeMeta(recipeName)
 | 
			
		||||
	meta, err := GetRecipeMeta(recipeName, conf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if strings.Contains(err.Error(), "does not exist") {
 | 
			
		||||
			meta = RecipeMeta{}
 | 
			
		||||
@ -250,7 +251,11 @@ func Get(recipeName string) (Recipe, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureExists ensures that a recipe is locally cloned
 | 
			
		||||
func EnsureExists(recipeName string) error {
 | 
			
		||||
func EnsureExists(recipeName string, conf *runtime.Config) error {
 | 
			
		||||
	if !conf.EnsureRecipeExists {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
			
		||||
 | 
			
		||||
	if _, err := os.Stat(recipeDir); os.IsNotExist(err) {
 | 
			
		||||
@ -333,7 +338,11 @@ func EnsureVersion(recipeName, version string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureLatest makes sure the latest commit is checked out for a local recipe repository
 | 
			
		||||
func EnsureLatest(recipeName string) error {
 | 
			
		||||
func EnsureLatest(recipeName string, conf *runtime.Config) error {
 | 
			
		||||
	if !conf.EnsureRecipeLatest {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
			
		||||
 | 
			
		||||
	isClean, err := gitPkg.IsClean(recipeDir)
 | 
			
		||||
@ -361,7 +370,7 @@ func EnsureLatest(recipeName string) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	meta, err := GetRecipeMeta(recipeName)
 | 
			
		||||
	meta, err := GetRecipeMeta(recipeName, conf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@ -578,7 +587,11 @@ func GetStringInBetween(recipeName, str, start, end string) (result string, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureUpToDate ensures that the local repo is synced to the remote
 | 
			
		||||
func EnsureUpToDate(recipeName string) error {
 | 
			
		||||
func EnsureUpToDate(recipeName string, conf *runtime.Config) error {
 | 
			
		||||
	if !conf.EnsureRecipeLatest {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
			
		||||
 | 
			
		||||
	isClean, err := gitPkg.IsClean(recipeDir)
 | 
			
		||||
@ -790,7 +803,7 @@ func VersionsOfService(recipe, serviceName string) ([]string, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRecipeMeta retrieves the recipe metadata from the recipe catalogue.
 | 
			
		||||
func GetRecipeMeta(recipeName string) (RecipeMeta, error) {
 | 
			
		||||
func GetRecipeMeta(recipeName string, conf *runtime.Config) (RecipeMeta, error) {
 | 
			
		||||
	catl, err := ReadRecipeCatalogue()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return RecipeMeta{}, err
 | 
			
		||||
@ -801,7 +814,7 @@ func GetRecipeMeta(recipeName string) (RecipeMeta, error) {
 | 
			
		||||
		return RecipeMeta{}, fmt.Errorf("recipe %s does not exist?", recipeName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := EnsureExists(recipeName); err != nil {
 | 
			
		||||
	if err := EnsureExists(recipeName, conf); err != nil {
 | 
			
		||||
		return RecipeMeta{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -923,9 +936,8 @@ func ReadReposMetadata() (RepoCatalogue, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRecipeVersions retrieves all recipe versions.
 | 
			
		||||
func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
 | 
			
		||||
func GetRecipeVersions(recipeName string, conf *runtime.Config) (RecipeVersions, error) {
 | 
			
		||||
	versions := RecipeVersions{}
 | 
			
		||||
 | 
			
		||||
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("attempting to open git repository in %s", recipeDir)
 | 
			
		||||
@ -962,7 +974,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
 | 
			
		||||
 | 
			
		||||
		logrus.Debugf("successfully checked out %s in %s", ref.Name(), recipeDir)
 | 
			
		||||
 | 
			
		||||
		recipe, err := Get(recipeName)
 | 
			
		||||
		recipe, err := Get(recipeName, conf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
@ -1027,7 +1039,7 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateRepositories clones and updates all recipe repositories locally.
 | 
			
		||||
func UpdateRepositories(repos RepoCatalogue, recipeName string) error {
 | 
			
		||||
func UpdateRepositories(repos RepoCatalogue, recipeName string, conf *runtime.Config) error {
 | 
			
		||||
	var barLength int
 | 
			
		||||
	if recipeName != "" {
 | 
			
		||||
		barLength = 1
 | 
			
		||||
@ -1061,7 +1073,7 @@ func UpdateRepositories(repos RepoCatalogue, recipeName string) error {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := EnsureUpToDate(rm.Name); err != nil {
 | 
			
		||||
			if err := EnsureUpToDate(rm.Name, conf); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										61
									
								
								pkg/runtime/runtime.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								pkg/runtime/runtime.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
package runtime
 | 
			
		||||
 | 
			
		||||
import "github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
// Config is an internal configuration modifier. It can be instantiated on a
 | 
			
		||||
// command call and can be changed on the fly to help make decisions further
 | 
			
		||||
// down in the internals, e.g. whether or not to clone the recipe locally or
 | 
			
		||||
// not.
 | 
			
		||||
type Config struct {
 | 
			
		||||
	EnsureRecipeExists bool // ensure that the recipe is cloned locally
 | 
			
		||||
	EnsureRecipeLatest bool // ensure the local recipe has latest changes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Option modified a Config. The convetion for passing Options to functions is
 | 
			
		||||
// so far, only used in internal/validate.go. A Config is then constructed and
 | 
			
		||||
// passed down further into the code. This may change in the future but this is
 | 
			
		||||
// at least the abstraction so far.
 | 
			
		||||
type Option func(c *Config)
 | 
			
		||||
 | 
			
		||||
// New instantiates a new Config.
 | 
			
		||||
func New(opts ...Option) *Config {
 | 
			
		||||
	conf := &Config{
 | 
			
		||||
		EnsureRecipeExists: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, optFunc := range opts {
 | 
			
		||||
		optFunc(conf)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return conf
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithEnsureRecipeExists determines whether or not we should be cloning the
 | 
			
		||||
// local recipe or not. This can be useful for being more adaptable to offline
 | 
			
		||||
// scenarios.
 | 
			
		||||
func WithEnsureRecipeExists(ensureRecipeExists bool) Option {
 | 
			
		||||
	return func(c *Config) {
 | 
			
		||||
		if ensureRecipeExists {
 | 
			
		||||
			logrus.Debugf("runtime config: EnsureRecipeExists = %v, ensuring recipes are cloned", ensureRecipeExists)
 | 
			
		||||
		} else {
 | 
			
		||||
			logrus.Debugf("runtime config: EnsureRecipeExists = %v, not cloning recipes", ensureRecipeExists)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.EnsureRecipeExists = ensureRecipeExists
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithEnsureRecipeLatest determines whether we should update the local recipes
 | 
			
		||||
// remotely via Git. This can be useful when e.g. ensuring we have the latest
 | 
			
		||||
// changes before making new ones.
 | 
			
		||||
func WithEnsureRecipeLatest(ensureRecipeLatest bool) Option {
 | 
			
		||||
	return func(c *Config) {
 | 
			
		||||
		if ensureRecipeLatest {
 | 
			
		||||
			logrus.Debugf("runtime config: EnsureRecipeLatest = %v, ensuring recipes have latest changes", ensureRecipeLatest)
 | 
			
		||||
		} else {
 | 
			
		||||
			logrus.Debugf("runtime config: EnsureRecipeLatest = %v, leaving recipes alone", ensureRecipeLatest)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.EnsureRecipeLatest = ensureRecipeLatest
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -77,6 +77,8 @@ func Fatal(hostname string, err error) error {
 | 
			
		||||
		return fmt.Errorf("connection timed out for %s", hostname)
 | 
			
		||||
	} else if strings.Contains(out, "Permission denied") {
 | 
			
		||||
		return fmt.Errorf("ssh auth: permission denied for %s", hostname)
 | 
			
		||||
	} else if strings.Contains(out, "Network is unreachable") {
 | 
			
		||||
		return fmt.Errorf("unable to connect to %s, network is unreachable?", hostname)
 | 
			
		||||
	} else {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
ABRA_VERSION="0.6.0-beta"
 | 
			
		||||
ABRA_VERSION="0.7.0-beta"
 | 
			
		||||
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
 | 
			
		||||
RC_VERSION="0.7.0-rc2-beta"
 | 
			
		||||
RC_VERSION="0.7.0-beta"
 | 
			
		||||
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
 | 
			
		||||
 | 
			
		||||
for arg in "$@"; do
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user