forked from toolshed/abra
		
	Compare commits
	
		
			61 Commits
		
	
	
		
			0.4.0-alph
			...
			0.4.0-alph
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cff7534bf9 | |||
| 13e582349c | |||
| b1b9612e01 | |||
| afeee1270e | |||
| cb210d0c81 | |||
| 9f2bb3f74f | |||
| a33767f848 | |||
| a1abe5c6be | |||
| 672b44f965 | |||
| 6d9573ec7e | |||
| 53cd3b8b71 | |||
| b9ec41647b | |||
| f4b563528f | |||
| f9a2c1d58f | |||
| 7a66a90ecb | |||
| 0e688f1407 | |||
| 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 | |||
| e38a0078f3 | |||
| 25b44dc54e | |||
| 0c2f6fb676 | |||
| 10e4a8b97f | |||
| eed2756784 | |||
| b61b8f0d2a | |||
| 763e7b5bff | |||
| d5ab9aedbf | |||
| 2ebb00c9d4 | |||
| 6d76b3646a | |||
| 636dc82258 | |||
| 66d5453248 | |||
| ba9abcb0d7 | |||
| a1cbf21f61 | |||
| bd1da39374 | |||
| 8b90519bc9 | |||
| 65feda7f1d | |||
| 64e223a810 | |||
| 379e01d855 | |||
| a421c0dca5 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -4,4 +4,5 @@ | |||||||
| .vscode/ | .vscode/ | ||||||
| abra | abra | ||||||
| dist/ | dist/ | ||||||
|  | tests/integration/.abra/catalogue | ||||||
| vendor/ | vendor/ | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Makefile
									
									
									
									
									
								
							| @ -43,15 +43,3 @@ loc-author: | |||||||
|    	sort -f | \ |    	sort -f | \ | ||||||
|    	uniq -ic | \ |    	uniq -ic | \ | ||||||
|    	sort -n |    	sort -n | ||||||
|  |  | ||||||
| int-core: |  | ||||||
| 	@docker run \ |  | ||||||
| 		-v $$(pwd):/src \ |  | ||||||
| 		--env-file .e2e.env \ |  | ||||||
| 		debian:bullseye-slim \ |  | ||||||
| 		sh -c "\ |  | ||||||
| 			apt update && apt install -y wget curl git; echo ""; echo ""; \ |  | ||||||
| 			git config --global user.email 'e2e@coopcloud.tech'; \ |  | ||||||
| 			git config --global user.name 'e2e'; \ |  | ||||||
| 			cd /src/tests/integration && bash core.sh -- --dev \ |  | ||||||
| 		" |  | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ | |||||||
|  |  | ||||||
| The Co-op Cloud utility belt 🎩🐇 | The Co-op Cloud utility belt 🎩🐇 | ||||||
|  |  | ||||||
| `abra` is a command-line tool for managing your own [Co-op Cloud](https://coopcloud.tech). It can provision new servers, create apps, deploy them, run backup and restore operations and a whole lot of other things. Please see [docs.coopcloud.tech](https://docs.coopcloud.tech) for more extensive documentation. | `abra` is a command-line tool for managing your own [Co-op Cloud](https://coopcloud.tech). It can provision new servers, create apps, deploy them and a whole lot of other things. Please see [docs.coopcloud.tech](https://docs.coopcloud.tech) for more extensive documentation. | ||||||
|  |  | ||||||
| ## Quick install | ## Quick install | ||||||
|  |  | ||||||
| @ -66,9 +66,7 @@ We maintain a fork of [godotenv](https://github.com/Autonomic-Cooperative/godote | |||||||
| 1. multi-line env var support | 1. multi-line env var support | ||||||
| 2. inline comment parsing | 2. inline comment parsing | ||||||
|  |  | ||||||
| You can upgrade the version here by running `go get github.com/Autonomic-Cooperative/godotenv@<commit>` where `<commit>` is the | 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. | ||||||
| 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. |  | ||||||
|  |  | ||||||
| #### `docker/client` | #### `docker/client` | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,29 +1,22 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // AppCommand defines the `abra app` command and ets subcommands | var AppCommand = cli.Command{ | ||||||
| var AppCommand = &cli.Command{ |  | ||||||
| 	Name:        "app", | 	Name:        "app", | ||||||
| 	Usage:     "Manage apps", |  | ||||||
| 	Aliases:     []string{"a"}, | 	Aliases:     []string{"a"}, | ||||||
|  | 	Usage:       "Manage apps", | ||||||
| 	ArgsUsage:   "<app>", | 	ArgsUsage:   "<app>", | ||||||
| 	Description: ` | 	Description: "This command provides functionality for managing the life cycle of your apps", | ||||||
| This command provides all the functionality you need to manage the life cycle | 	Subcommands: []cli.Command{ | ||||||
| of your apps. From initial deployment, day-2 operations (e.g. backup/restore) |  | ||||||
| to scaling apps up and spinning them down. |  | ||||||
| `, |  | ||||||
| 	Subcommands: []*cli.Command{ |  | ||||||
| 		appNewCommand, | 		appNewCommand, | ||||||
| 		appConfigCommand, | 		appConfigCommand, | ||||||
| 		appRestartCommand, | 		appRestartCommand, | ||||||
| 		appDeployCommand, | 		appDeployCommand, | ||||||
| 		appUpgradeCommand, | 		appUpgradeCommand, | ||||||
| 		appUndeployCommand, | 		appUndeployCommand, | ||||||
| 		appBackupCommand, |  | ||||||
| 		appRestoreCommand, |  | ||||||
| 		appRemoveCommand, | 		appRemoveCommand, | ||||||
| 		appCheckCommand, | 		appCheckCommand, | ||||||
| 		appListCommand, | 		appListCommand, | ||||||
|  | |||||||
| @ -1,77 +0,0 @@ | |||||||
| package app |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| 	"os/exec" |  | ||||||
| 	"path" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" |  | ||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" |  | ||||||
| 	"coopcloud.tech/abra/pkg/config" |  | ||||||
| 	"github.com/sirupsen/logrus" |  | ||||||
| 	"github.com/urfave/cli/v2" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| var backupAllServices bool |  | ||||||
| var backupAllServicesFlag = &cli.BoolFlag{ |  | ||||||
| 	Name:        "all", |  | ||||||
| 	Value:       false, |  | ||||||
| 	Destination: &backupAllServices, |  | ||||||
| 	Aliases:     []string{"a"}, |  | ||||||
| 	Usage:       "Backup all services", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var appBackupCommand = &cli.Command{ |  | ||||||
| 	Name:      "backup", |  | ||||||
| 	Usage:     "Backup an app", |  | ||||||
| 	Aliases:   []string{"b"}, |  | ||||||
| 	Flags:     []cli.Flag{backupAllServicesFlag}, |  | ||||||
| 	ArgsUsage: "<service>", |  | ||||||
| 	Action: func(c *cli.Context) error { |  | ||||||
| 		app := internal.ValidateApp(c) |  | ||||||
|  |  | ||||||
| 		if c.Args().Get(1) != "" && backupAllServices { |  | ||||||
| 			internal.ShowSubcommandHelpAndError(c, errors.New("cannot use '<service>' and '--all' together")) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		abraSh := path.Join(config.RECIPES_DIR, app.Type, "abra.sh") |  | ||||||
| 		if _, err := os.Stat(abraSh); err != nil { |  | ||||||
| 			if os.IsNotExist(err) { |  | ||||||
| 				logrus.Fatalf("%s does not exist?", abraSh) |  | ||||||
| 			} |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		sourceCmd := fmt.Sprintf("source %s", abraSh) |  | ||||||
|  |  | ||||||
| 		execCmd := "abra_backup" |  | ||||||
| 		if !backupAllServices { |  | ||||||
| 			serviceName := c.Args().Get(1) |  | ||||||
| 			if serviceName == "" { |  | ||||||
| 				internal.ShowSubcommandHelpAndError(c, errors.New("no service(s) target provided")) |  | ||||||
| 			} |  | ||||||
| 			execCmd = fmt.Sprintf("abra_backup_%s", serviceName) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bytes, err := ioutil.ReadFile(abraSh) |  | ||||||
| 		if err != nil { |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
| 		if !strings.Contains(string(bytes), execCmd) { |  | ||||||
| 			logrus.Fatalf("%s doesn't have a %s function", app.Type, execCmd) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		sourceAndExec := fmt.Sprintf("%s; %s", sourceCmd, execCmd) |  | ||||||
| 		cmd := exec.Command("bash", "-c", sourceAndExec) |  | ||||||
| 		if err := internal.RunCmd(cmd); err != nil { |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return nil |  | ||||||
| 	}, |  | ||||||
| 	BashComplete: autocomplete.AppNameComplete, |  | ||||||
| } |  | ||||||
| @ -9,14 +9,19 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appCheckCommand = &cli.Command{ | var appCheckCommand = cli.Command{ | ||||||
| 	Name:      "check", | 	Name:      "check", | ||||||
| 	Usage:     "Check if app is configured correctly", |  | ||||||
| 	Aliases:   []string{"c"}, | 	Aliases:   []string{"c"}, | ||||||
|  | 	Usage:     "Check if app is configured correctly", | ||||||
| 	ArgsUsage: "<service>", | 	ArgsUsage: "<service>", | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
|  |  | ||||||
|  | |||||||
| @ -10,13 +10,18 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appConfigCommand = &cli.Command{ | var appConfigCommand = cli.Command{ | ||||||
| 	Name:    "config", | 	Name:    "config", | ||||||
| 	Aliases: []string{"c"}, | 	Aliases: []string{"c"}, | ||||||
| 	Usage:   "Edit app config", | 	Usage:   "Edit app config", | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		appName := c.Args().First() | 		appName := c.Args().First() | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| @ -15,13 +16,18 @@ import ( | |||||||
| 	"github.com/docker/docker/api/types/filters" | 	"github.com/docker/docker/api/types/filters" | ||||||
| 	"github.com/docker/docker/pkg/archive" | 	"github.com/docker/docker/pkg/archive" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appCpCommand = &cli.Command{ | var appCpCommand = cli.Command{ | ||||||
| 	Name:      "cp", | 	Name:      "cp", | ||||||
| 	Aliases:   []string{"c"}, | 	Aliases:   []string{"c"}, | ||||||
| 	ArgsUsage: "<src> <dst>", | 	ArgsUsage: "<src> <dst>", | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Usage:  "Copy files to/from a running app service", | 	Usage:  "Copy files to/from a running app service", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command supports copying files to and from any app service file system. | This command supports copying files to and from any app service file system. | ||||||
| @ -118,7 +124,7 @@ func configureAndCp( | |||||||
| 	filters := filters.NewArgs() | 	filters := filters.NewArgs() | ||||||
| 	filters.Add("name", fmt.Sprintf("%s_%s", appEnv.StackName(), service)) | 	filters.Add("name", fmt.Sprintf("%s_%s", appEnv.StackName(), service)) | ||||||
|  |  | ||||||
| 	container, err := container.GetContainer(c.Context, cl, filters, true) | 	container, err := container.GetContainer(context.Background(), cl, filters, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logrus.Fatal(err) | 		logrus.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @ -137,11 +143,11 @@ func configureAndCp( | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false} | 		copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false} | ||||||
| 		if err := cl.CopyToContainer(c.Context, container.ID, dstPath, content, copyOpts); err != nil { | 		if err := cl.CopyToContainer(context.Background(), container.ID, dstPath, content, copyOpts); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
| 		content, _, err := cl.CopyFromContainer(c.Context, container.ID, srcPath) | 		content, _, err := cl.CopyFromContainer(context.Background(), container.ID, srcPath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -3,19 +3,22 @@ package app | |||||||
| import ( | import ( | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appDeployCommand = &cli.Command{ | var appDeployCommand = cli.Command{ | ||||||
| 	Name:    "deploy", | 	Name:    "deploy", | ||||||
| 	Aliases: []string{"d"}, | 	Aliases: []string{"d"}, | ||||||
| 	Usage:   "Deploy an app", | 	Usage:   "Deploy an app", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.ForceFlag, | 		internal.ForceFlag, | ||||||
| 		internal.ChaosFlag, | 		internal.ChaosFlag, | ||||||
| 		internal.NoDomainChecksFlag, | 		internal.NoDomainChecksFlag, | ||||||
| 		internal.DontWaitConvergeFlag, | 		internal.DontWaitConvergeFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command deploys an app. It does not support incrementing the version of a | This command deploys an app. It does not support incrementing the version of a | ||||||
| deployed app, for this you need to look at the "abra app upgrade <app>" | deployed app, for this you need to look at the "abra app upgrade <app>" | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| @ -15,10 +16,10 @@ import ( | |||||||
| 	"github.com/docker/docker/api/types/filters" | 	"github.com/docker/docker/api/types/filters" | ||||||
| 	dockerClient "github.com/docker/docker/client" | 	dockerClient "github.com/docker/docker/client" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appErrorsCommand = &cli.Command{ | var appErrorsCommand = cli.Command{ | ||||||
| 	Name:  "errors", | 	Name:  "errors", | ||||||
| 	Usage: "List errors for a deployed app", | 	Usage: "List errors for a deployed app", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| @ -45,7 +46,12 @@ the logs. | |||||||
|  |  | ||||||
| `, | `, | ||||||
| 	Aliases: []string{"e"}, | 	Aliases: []string{"e"}, | ||||||
| 	Flags:        []cli.Flag{internal.WatchFlag}, | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 		internal.WatchFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	BashComplete: autocomplete.AppNameComplete, | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
| @ -55,7 +61,7 @@ the logs. | |||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName()) | 		isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -91,7 +97,7 @@ func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error | |||||||
| 	for _, service := range recipe.Config.Services { | 	for _, service := range recipe.Config.Services { | ||||||
| 		filters := filters.NewArgs() | 		filters := filters.NewArgs() | ||||||
| 		filters.Add("name", service.Name) | 		filters.Add("name", service.Name) | ||||||
| 		containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters}) | 		containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @ -102,7 +108,7 @@ func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		container := containers[0] | 		container := containers[0] | ||||||
| 		containerState, err := cl.ContainerInspect(c.Context, container.ID) | 		containerState, err := cl.ContainerInspect(context.Background(), container.ID) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -12,22 +12,19 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/ssh" | 	"coopcloud.tech/abra/pkg/ssh" | ||||||
| 	"coopcloud.tech/tagcmp" | 	"coopcloud.tech/tagcmp" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var status bool | var status bool | ||||||
| var statusFlag = &cli.BoolFlag{ | var statusFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "status", | 	Name:        "status, S", | ||||||
| 	Aliases:     []string{"S"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Show app deployment status", | 	Usage:       "Show app deployment status", | ||||||
| 	Destination: &status, | 	Destination: &status, | ||||||
| } | } | ||||||
|  |  | ||||||
| var appType string | var appType string | ||||||
| var typeFlag = &cli.StringFlag{ | var typeFlag = &cli.StringFlag{ | ||||||
| 	Name:        "type", | 	Name:        "type, t", | ||||||
| 	Aliases:     []string{"t"}, |  | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Usage:       "Show apps of a specific type", | 	Usage:       "Show apps of a specific type", | ||||||
| 	Destination: &appType, | 	Destination: &appType, | ||||||
| @ -35,8 +32,7 @@ var typeFlag = &cli.StringFlag{ | |||||||
|  |  | ||||||
| var listAppServer string | var listAppServer string | ||||||
| var listAppServerFlag = &cli.StringFlag{ | var listAppServerFlag = &cli.StringFlag{ | ||||||
| 	Name:        "server", | 	Name:        "server, s", | ||||||
| 	Aliases:     []string{"s"}, |  | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Usage:       "Show apps of a specific server", | 	Usage:       "Show apps of a specific server", | ||||||
| 	Destination: &listAppServer, | 	Destination: &listAppServer, | ||||||
| @ -61,8 +57,9 @@ type serverStatus struct { | |||||||
| 	upgradeCount     int | 	upgradeCount     int | ||||||
| } | } | ||||||
|  |  | ||||||
| var appListCommand = &cli.Command{ | var appListCommand = cli.Command{ | ||||||
| 	Name:    "list", | 	Name:    "list", | ||||||
|  | 	Aliases: []string{"ls"}, | ||||||
| 	Usage:   "List all managed apps", | 	Usage:   "List all managed apps", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command looks at your local file system listing of apps and servers (e.g. | This command looks at your local file system listing of apps and servers (e.g. | ||||||
| @ -72,12 +69,14 @@ By passing the "--status/-S" flag, you can query all your servers for the | |||||||
| actual live deployment status. Depending on how many servers you manage, this | actual live deployment status. Depending on how many servers you manage, this | ||||||
| can take some time. | can take some time. | ||||||
| 	`, | 	`, | ||||||
| 	Aliases: []string{"ls"}, |  | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		statusFlag, | 		statusFlag, | ||||||
| 		listAppServerFlag, | 		listAppServerFlag, | ||||||
| 		typeFlag, | 		typeFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		appFiles, err := config.LoadAppFiles(listAppServer) | 		appFiles, err := config.LoadAppFiles(listAppServer) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @ -145,8 +144,10 @@ can take some time. | |||||||
| 					version := "unknown" | 					version := "unknown" | ||||||
| 					if statusMeta, ok := statuses[app.StackName()]; ok { | 					if statusMeta, ok := statuses[app.StackName()]; ok { | ||||||
| 						if currentVersion, exists := statusMeta["version"]; exists { | 						if currentVersion, exists := statusMeta["version"]; exists { | ||||||
|  | 							if currentVersion != "" { | ||||||
| 								version = currentVersion | 								version = currentVersion | ||||||
| 							} | 							} | ||||||
|  | 						} | ||||||
| 						if statusMeta["status"] != "" { | 						if statusMeta["status"] != "" { | ||||||
| 							status = statusMeta["status"] | 							status = statusMeta["status"] | ||||||
| 						} | 						} | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| @ -15,7 +16,7 @@ import ( | |||||||
| 	"github.com/docker/docker/api/types/filters" | 	"github.com/docker/docker/api/types/filters" | ||||||
| 	dockerClient "github.com/docker/docker/client" | 	dockerClient "github.com/docker/docker/client" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var logOpts = types.ContainerLogsOptions{ | var logOpts = types.ContainerLogsOptions{ | ||||||
| @ -32,7 +33,7 @@ func stackLogs(c *cli.Context, stackName string, client *dockerClient.Client) { | |||||||
| 	filters := filters.NewArgs() | 	filters := filters.NewArgs() | ||||||
| 	filters.Add("name", stackName) | 	filters.Add("name", stackName) | ||||||
| 	serviceOpts := types.ServiceListOptions{Filters: filters} | 	serviceOpts := types.ServiceListOptions{Filters: filters} | ||||||
| 	services, err := client.ServiceList(c.Context, serviceOpts) | 	services, err := client.ServiceList(context.Background(), serviceOpts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logrus.Fatal(err) | 		logrus.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @ -45,7 +46,7 @@ func stackLogs(c *cli.Context, stackName string, client *dockerClient.Client) { | |||||||
| 				logOpts.ShowStdout = false | 				logOpts.ShowStdout = false | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			logs, err := client.ServiceLogs(c.Context, s, logOpts) | 			logs, err := client.ServiceLogs(context.Background(), s, logOpts) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| 			} | 			} | ||||||
| @ -63,14 +64,17 @@ func stackLogs(c *cli.Context, stackName string, client *dockerClient.Client) { | |||||||
| 	os.Exit(0) | 	os.Exit(0) | ||||||
| } | } | ||||||
|  |  | ||||||
| var appLogsCommand = &cli.Command{ | var appLogsCommand = cli.Command{ | ||||||
| 	Name:      "logs", | 	Name:      "logs", | ||||||
| 	Aliases:   []string{"l"}, | 	Aliases:   []string{"l"}, | ||||||
| 	ArgsUsage: "[<service>]", | 	ArgsUsage: "[<service>]", | ||||||
| 	Usage:     "Tail app logs", | 	Usage:     "Tail app logs", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
| 		internal.StdErrOnlyFlag, | 		internal.StdErrOnlyFlag, | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	BashComplete: autocomplete.AppNameComplete, | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
| @ -98,7 +102,7 @@ var appLogsCommand = &cli.Command{ | |||||||
| func tailServiceLogs(c *cli.Context, cl *dockerClient.Client, app config.App, serviceName string) error { | func tailServiceLogs(c *cli.Context, cl *dockerClient.Client, app config.App, serviceName string) error { | ||||||
| 	filters := filters.NewArgs() | 	filters := filters.NewArgs() | ||||||
| 	filters.Add("name", fmt.Sprintf("%s_%s", app.StackName(), serviceName)) | 	filters.Add("name", fmt.Sprintf("%s_%s", app.StackName(), serviceName)) | ||||||
| 	chosenService, err := service.GetService(c.Context, cl, filters, internal.NoInput) | 	chosenService, err := service.GetService(context.Background(), cl, filters, internal.NoInput) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logrus.Fatal(err) | 		logrus.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @ -107,7 +111,7 @@ func tailServiceLogs(c *cli.Context, cl *dockerClient.Client, app config.App, se | |||||||
| 		logOpts.ShowStdout = false | 		logOpts.ShowStdout = false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	logs, err := cl.ServiceLogs(c.Context, chosenService.ID, logOpts) | 	logs, err := cl.ServiceLogs(context.Background(), chosenService.ID, logOpts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logrus.Fatal(err) | 		logrus.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ package app | |||||||
| import ( | import ( | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appNewDescription = ` | var appNewDescription = ` | ||||||
| @ -26,18 +26,21 @@ pass store (see passwordstore.org for more). The pass command must be available | |||||||
| on your $PATH. | on your $PATH. | ||||||
| ` | ` | ||||||
|  |  | ||||||
| var appNewCommand = &cli.Command{ | var appNewCommand = cli.Command{ | ||||||
| 	Name:        "new", | 	Name:        "new", | ||||||
| 	Usage:       "Create a new app", |  | ||||||
| 	Aliases:     []string{"n"}, | 	Aliases:     []string{"n"}, | ||||||
|  | 	Usage:       "Create a new app", | ||||||
| 	Description: appNewDescription, | 	Description: appNewDescription, | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.NewAppServerFlag, | 		internal.NewAppServerFlag, | ||||||
| 		internal.DomainFlag, | 		internal.DomainFlag, | ||||||
| 		internal.NewAppNameFlag, | 		internal.NewAppNameFlag, | ||||||
| 		internal.PassFlag, | 		internal.PassFlag, | ||||||
| 		internal.SecretsFlag, | 		internal.SecretsFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	ArgsUsage:    "<recipe>", | 	ArgsUsage:    "<recipe>", | ||||||
| 	Action:       internal.NewAction, | 	Action:       internal.NewAction, | ||||||
| 	BashComplete: autocomplete.RecipeNameComplete, | 	BashComplete: autocomplete.RecipeNameComplete, | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @ -17,17 +18,20 @@ import ( | |||||||
| 	"github.com/docker/docker/api/types/filters" | 	"github.com/docker/docker/api/types/filters" | ||||||
| 	dockerClient "github.com/docker/docker/client" | 	dockerClient "github.com/docker/docker/client" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appPsCommand = &cli.Command{ | var appPsCommand = cli.Command{ | ||||||
| 	Name:        "ps", | 	Name:        "ps", | ||||||
|  | 	Aliases:     []string{"p"}, | ||||||
| 	Usage:       "Check app status", | 	Usage:       "Check app status", | ||||||
| 	Description: "This command shows a more detailed status output of a specific deployed app.", | 	Description: "This command shows a more detailed status output of a specific deployed app.", | ||||||
| 	Aliases:     []string{"p"}, |  | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
| 		internal.WatchFlag, | 		internal.WatchFlag, | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	BashComplete: autocomplete.AppNameComplete, | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
| @ -37,7 +41,7 @@ var appPsCommand = &cli.Command{ | |||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName()) | 		isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -66,7 +70,7 @@ func showPSOutput(c *cli.Context, app config.App, cl *dockerClient.Client) { | |||||||
| 	filters := filters.NewArgs() | 	filters := filters.NewArgs() | ||||||
| 	filters.Add("name", app.StackName()) | 	filters.Add("name", app.StackName()) | ||||||
|  |  | ||||||
| 	containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters}) | 	containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logrus.Fatal(err) | 		logrus.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
| @ -12,7 +13,7 @@ import ( | |||||||
| 	"github.com/docker/docker/api/types" | 	"github.com/docker/docker/api/types" | ||||||
| 	"github.com/docker/docker/api/types/filters" | 	"github.com/docker/docker/api/types/filters" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Volumes stores the variable from VolumesFlag | // Volumes stores the variable from VolumesFlag | ||||||
| @ -21,31 +22,33 @@ var Volumes bool | |||||||
| // VolumesFlag is used to specify if volumes should be deleted when deleting an app | // VolumesFlag is used to specify if volumes should be deleted when deleting an app | ||||||
| var VolumesFlag = &cli.BoolFlag{ | var VolumesFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "volumes", | 	Name:        "volumes", | ||||||
| 	Value:       false, |  | ||||||
| 	Destination: &Volumes, | 	Destination: &Volumes, | ||||||
| } | } | ||||||
|  |  | ||||||
| var appRemoveCommand = &cli.Command{ | var appRemoveCommand = cli.Command{ | ||||||
| 	Name:    "remove", | 	Name:    "remove", | ||||||
| 	Usage:   "Remove an already undeployed app", |  | ||||||
| 	Aliases: []string{"rm"}, | 	Aliases: []string{"rm"}, | ||||||
|  | 	Usage:   "Remove an already undeployed app", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
| 		VolumesFlag, | 		VolumesFlag, | ||||||
| 		internal.ForceFlag, | 		internal.ForceFlag, | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
|  |  | ||||||
| 		if !internal.Force { | 		if !internal.Force { | ||||||
| 			response := false | 			response := false | ||||||
| 			prompt := &survey.Confirm{ | 			prompt := &survey.Confirm{ | ||||||
| 				Message: fmt.Sprintf("about to delete %s, are you sure?", app.Name), | 				Message: fmt.Sprintf("about to remove %s, are you sure?", app.Name), | ||||||
| 			} | 			} | ||||||
| 			if err := survey.AskOne(prompt, &response); err != nil { | 			if err := survey.AskOne(prompt, &response); err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| 			} | 			} | ||||||
| 			if !response { | 			if !response { | ||||||
| 				logrus.Fatal("user aborted app removal") | 				logrus.Fatal("aborting as requested") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @ -54,19 +57,17 @@ var appRemoveCommand = &cli.Command{ | |||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if !internal.Force { | 		isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) | ||||||
| 			isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName()) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		if isDeployed { | 		if isDeployed { | ||||||
| 			logrus.Fatalf("%s is still deployed. Run \"abra app undeploy %s \" or pass --force", app.Name, app.Name) | 			logrus.Fatalf("%s is still deployed. Run \"abra app undeploy %s \" or pass --force", app.Name, app.Name) | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		fs := filters.NewArgs() | 		fs := filters.NewArgs() | ||||||
| 		fs.Add("name", app.Name) | 		fs.Add("name", app.StackName()) | ||||||
| 		secretList, err := cl.SecretList(c.Context, types.SecretListOptions{Filters: fs}) | 		secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: fs}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -81,6 +82,7 @@ var appRemoveCommand = &cli.Command{ | |||||||
|  |  | ||||||
| 		if len(secrets) > 0 { | 		if len(secrets) > 0 { | ||||||
| 			var secretNamesToRemove []string | 			var secretNamesToRemove []string | ||||||
|  |  | ||||||
| 			if !internal.Force { | 			if !internal.Force { | ||||||
| 				secretsPrompt := &survey.MultiSelect{ | 				secretsPrompt := &survey.MultiSelect{ | ||||||
| 					Message: "which secrets do you want to remove?", | 					Message: "which secrets do you want to remove?", | ||||||
| @ -95,7 +97,7 @@ var appRemoveCommand = &cli.Command{ | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for _, name := range secretNamesToRemove { | 			for _, name := range secretNamesToRemove { | ||||||
| 				err := cl.SecretRemove(c.Context, secrets[name]) | 				err := cl.SecretRemove(context.Background(), secrets[name]) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| 				} | 				} | ||||||
| @ -105,7 +107,7 @@ var appRemoveCommand = &cli.Command{ | |||||||
| 			logrus.Info("no secrets to remove") | 			logrus.Info("no secrets to remove") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		volumeListOKBody, err := cl.VolumeList(c.Context, fs) | 		volumeListOKBody, err := cl.VolumeList(context.Background(), fs) | ||||||
| 		volumeList := volumeListOKBody.Volumes | 		volumeList := volumeListOKBody.Volumes | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| @ -132,7 +134,7 @@ var appRemoveCommand = &cli.Command{ | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 				for _, vol := range removeVols { | 				for _, vol := range removeVols { | ||||||
| 					err := cl.VolumeRemove(c.Context, vol, internal.Force) // last argument is for force removing | 					err := cl.VolumeRemove(context.Background(), vol, internal.Force) // last argument is for force removing | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						logrus.Fatal(err) | 						logrus.Fatal(err) | ||||||
| 					} | 					} | ||||||
| @ -142,8 +144,10 @@ var appRemoveCommand = &cli.Command{ | |||||||
| 				logrus.Info("no volumes were removed") | 				logrus.Info("no volumes were removed") | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
|  | 			if Volumes { | ||||||
| 				logrus.Info("no volumes to remove") | 				logrus.Info("no volumes to remove") | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		err = os.Remove(app.Path) | 		err = os.Remove(app.Path) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
| @ -10,14 +11,19 @@ import ( | |||||||
| 	upstream "coopcloud.tech/abra/pkg/upstream/service" | 	upstream "coopcloud.tech/abra/pkg/upstream/service" | ||||||
| 	stack "coopcloud.tech/abra/pkg/upstream/stack" | 	stack "coopcloud.tech/abra/pkg/upstream/stack" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appRestartCommand = &cli.Command{ | var appRestartCommand = cli.Command{ | ||||||
| 	Name:      "restart", | 	Name:      "restart", | ||||||
| 	Usage:        "Restart an app", |  | ||||||
| 	Aliases:   []string{"re"}, | 	Aliases:   []string{"re"}, | ||||||
|  | 	Usage:     "Restart an app", | ||||||
| 	ArgsUsage: "<service>", | 	ArgsUsage: "<service>", | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	Description:  `This command restarts a service within a deployed app.`, | 	Description:  `This command restarts a service within a deployed app.`, | ||||||
| 	BashComplete: autocomplete.AppNameComplete, | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| @ -37,22 +43,22 @@ var appRestartCommand = &cli.Command{ | |||||||
| 		serviceName := fmt.Sprintf("%s_%s", app.StackName(), serviceNameShort) | 		serviceName := fmt.Sprintf("%s_%s", app.StackName(), serviceNameShort) | ||||||
|  |  | ||||||
| 		logrus.Debugf("attempting to scale %s to 0 (restart logic)", serviceName) | 		logrus.Debugf("attempting to scale %s to 0 (restart logic)", serviceName) | ||||||
| 		if err := upstream.RunServiceScale(c.Context, cl, serviceName, 0); err != nil { | 		if err := upstream.RunServiceScale(context.Background(), cl, serviceName, 0); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if err := stack.WaitOnService(c.Context, cl, serviceName, app.Name); err != nil { | 		if err := stack.WaitOnService(context.Background(), cl, serviceName, app.Name); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		logrus.Debugf("%s has been scaled to 0 (restart logic)", serviceName) | 		logrus.Debugf("%s has been scaled to 0 (restart logic)", serviceName) | ||||||
|  |  | ||||||
| 		logrus.Debugf("attempting to scale %s to 1 (restart logic)", serviceName) | 		logrus.Debugf("attempting to scale %s to 1 (restart logic)", serviceName) | ||||||
| 		if err := upstream.RunServiceScale(c.Context, cl, serviceName, 1); err != nil { | 		if err := upstream.RunServiceScale(context.Background(), cl, serviceName, 1); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if err := stack.WaitOnService(c.Context, cl, serviceName, app.Name); err != nil { | 		if err := stack.WaitOnService(context.Background(), cl, serviceName, app.Name); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,79 +0,0 @@ | |||||||
| package app |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"os" |  | ||||||
| 	"os/exec" |  | ||||||
| 	"path" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" |  | ||||||
| 	"coopcloud.tech/abra/pkg/config" |  | ||||||
| 	"github.com/sirupsen/logrus" |  | ||||||
| 	"github.com/urfave/cli/v2" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| var restoreAllServices bool |  | ||||||
| var restoreAllServicesFlag = &cli.BoolFlag{ |  | ||||||
| 	Name:        "all", |  | ||||||
| 	Value:       false, |  | ||||||
| 	Destination: &restoreAllServices, |  | ||||||
| 	Aliases:     []string{"a"}, |  | ||||||
| 	Usage:       "Restore all services", |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var appRestoreCommand = &cli.Command{ |  | ||||||
| 	Name:      "restore", |  | ||||||
| 	Usage:     "Restore an app from a backup", |  | ||||||
| 	Aliases:   []string{"rs"}, |  | ||||||
| 	Flags:     []cli.Flag{restoreAllServicesFlag}, |  | ||||||
| 	ArgsUsage: "<service> [<backup file>]", |  | ||||||
| 	Action: func(c *cli.Context) error { |  | ||||||
| 		app := internal.ValidateApp(c) |  | ||||||
|  |  | ||||||
| 		if c.Args().Len() > 1 && restoreAllServices { |  | ||||||
| 			internal.ShowSubcommandHelpAndError(c, errors.New("cannot use <service>/<backup file> and '--all' together")) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		abraSh := path.Join(config.RECIPES_DIR, app.Type, "abra.sh") |  | ||||||
| 		if _, err := os.Stat(abraSh); err != nil { |  | ||||||
| 			if os.IsNotExist(err) { |  | ||||||
| 				logrus.Fatalf("%s does not exist?", abraSh) |  | ||||||
| 			} |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		sourceCmd := fmt.Sprintf("source %s", abraSh) |  | ||||||
| 		execCmd := "abra_restore" |  | ||||||
| 		if !restoreAllServices { |  | ||||||
| 			serviceName := c.Args().Get(1) |  | ||||||
| 			if serviceName == "" { |  | ||||||
| 				internal.ShowSubcommandHelpAndError(c, errors.New("no service(s) target provided")) |  | ||||||
| 			} |  | ||||||
| 			execCmd = fmt.Sprintf("abra_restore_%s", serviceName) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		bytes, err := ioutil.ReadFile(abraSh) |  | ||||||
| 		if err != nil { |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
| 		if !strings.Contains(string(bytes), execCmd) { |  | ||||||
| 			logrus.Fatalf("%s doesn't have a %s function", app.Type, execCmd) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		backupFile := c.Args().Get(2) |  | ||||||
| 		if backupFile != "" { |  | ||||||
| 			execCmd = fmt.Sprintf("%s %s", execCmd, backupFile) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		sourceAndExec := fmt.Sprintf("%s; %s", sourceCmd, execCmd) |  | ||||||
| 		cmd := exec.Command("bash", "-c", sourceAndExec) |  | ||||||
| 		if err := internal.RunCmd(cmd); err != nil { |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return nil |  | ||||||
| 	}, |  | ||||||
| } |  | ||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| @ -14,19 +15,22 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/client" | 	"coopcloud.tech/abra/pkg/client" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appRollbackCommand = &cli.Command{ | var appRollbackCommand = cli.Command{ | ||||||
| 	Name:      "rollback", | 	Name:      "rollback", | ||||||
| 	Usage:     "Roll an app back to a previous version", |  | ||||||
| 	Aliases:   []string{"rl"}, | 	Aliases:   []string{"rl"}, | ||||||
|  | 	Usage:     "Roll an app back to a previous version", | ||||||
| 	ArgsUsage: "<app>", | 	ArgsUsage: "<app>", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.ForceFlag, | 		internal.ForceFlag, | ||||||
| 		internal.ChaosFlag, | 		internal.ChaosFlag, | ||||||
| 		internal.DontWaitConvergeFlag, | 		internal.DontWaitConvergeFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command rolls an app back to a previous version if one exists. | This command rolls an app back to a previous version if one exists. | ||||||
|  |  | ||||||
| @ -34,7 +38,7 @@ You may pass "--force/-f" to downgrade to the same version again. This can be | |||||||
| useful if the container runtime has gotten into a weird state. | useful if the container runtime has gotten into a weird state. | ||||||
|  |  | ||||||
| This action could be destructive, please ensure you have a copy of your app | This action could be destructive, please ensure you have a copy of your app | ||||||
| data beforehand - see "abra app backup <app>" for more. | data beforehand. | ||||||
|  |  | ||||||
| Chas mode ("--chaos") will deploy your local checkout of a recipe as-is, | Chas mode ("--chaos") will deploy your local checkout of a recipe as-is, | ||||||
| including unstaged changes and can be useful for live hacking and testing new | including unstaged changes and can be useful for live hacking and testing new | ||||||
| @ -45,9 +49,11 @@ recipes. | |||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
| 		stackName := app.StackName() | 		stackName := app.StackName() | ||||||
|  |  | ||||||
|  | 		if !internal.Chaos { | ||||||
| 			if err := recipe.EnsureUpToDate(app.Type); err != nil { | 			if err := recipe.EnsureUpToDate(app.Type); err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		r, err := recipe.Get(app.Type) | 		r, err := recipe.Get(app.Type) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @ -65,7 +71,7 @@ recipes. | |||||||
|  |  | ||||||
| 		logrus.Debugf("checking whether %s is already deployed", stackName) | 		logrus.Debugf("checking whether %s is already deployed", stackName) | ||||||
|  |  | ||||||
| 		isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName) | 		isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
| @ -13,7 +14,7 @@ import ( | |||||||
| 	"github.com/docker/docker/api/types" | 	"github.com/docker/docker/api/types" | ||||||
| 	"github.com/docker/docker/api/types/filters" | 	"github.com/docker/docker/api/types/filters" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var user string | var user string | ||||||
| @ -26,28 +27,30 @@ var userFlag = &cli.StringFlag{ | |||||||
| var noTTY bool | var noTTY bool | ||||||
| var noTTYFlag = &cli.BoolFlag{ | var noTTYFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "no-tty", | 	Name:        "no-tty", | ||||||
| 	Value:       false, |  | ||||||
| 	Destination: &noTTY, | 	Destination: &noTTY, | ||||||
| } | } | ||||||
|  |  | ||||||
| var appRunCommand = &cli.Command{ | var appRunCommand = cli.Command{ | ||||||
| 	Name:    "run", | 	Name:    "run", | ||||||
|  | 	Aliases: []string{"r"}, | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		noTTYFlag, | 		noTTYFlag, | ||||||
| 		userFlag, | 		userFlag, | ||||||
| 	}, | 	}, | ||||||
| 	Aliases:      []string{"r"}, | 	Before:       internal.SubCommandBefore, | ||||||
| 	ArgsUsage:    "<service> <args>...", | 	ArgsUsage:    "<service> <args>...", | ||||||
| 	Usage:        "Run a command in a service container", | 	Usage:        "Run a command in a service container", | ||||||
| 	BashComplete: autocomplete.AppNameComplete, | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
|  |  | ||||||
| 		if c.Args().Len() < 2 { | 		if len(c.Args()) < 2 { | ||||||
| 			internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?")) | 			internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?")) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if c.Args().Len() < 3 { | 		if len(c.Args()) < 3 { | ||||||
| 			internal.ShowSubcommandHelpAndError(c, errors.New("no <args> provided?")) | 			internal.ShowSubcommandHelpAndError(c, errors.New("no <args> provided?")) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @ -61,12 +64,12 @@ var appRunCommand = &cli.Command{ | |||||||
| 		filters := filters.NewArgs() | 		filters := filters.NewArgs() | ||||||
| 		filters.Add("name", stackAndServiceName) | 		filters.Add("name", stackAndServiceName) | ||||||
|  |  | ||||||
| 		targetContainer, err := containerPkg.GetContainer(c.Context, cl, filters, true) | 		targetContainer, err := containerPkg.GetContainer(context.Background(), cl, filters, true) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		cmd := c.Args().Slice()[2:] | 		cmd := c.Args()[2:] | ||||||
| 		execCreateOpts := types.ExecConfig{ | 		execCreateOpts := types.ExecConfig{ | ||||||
| 			AttachStderr: true, | 			AttachStderr: true, | ||||||
| 			AttachStdin:  true, | 			AttachStdin:  true, | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| @ -14,28 +15,32 @@ import ( | |||||||
| 	"github.com/docker/docker/api/types" | 	"github.com/docker/docker/api/types" | ||||||
| 	"github.com/docker/docker/api/types/filters" | 	"github.com/docker/docker/api/types/filters" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var allSecrets bool | var allSecrets bool | ||||||
| var allSecretsFlag = &cli.BoolFlag{ | var allSecretsFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "all", | 	Name:        "all, a", | ||||||
| 	Aliases:     []string{"A"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Destination: &allSecrets, | 	Destination: &allSecrets, | ||||||
| 	Usage:       "Generate all secrets", | 	Usage:       "Generate all secrets", | ||||||
| } | } | ||||||
|  |  | ||||||
| var appSecretGenerateCommand = &cli.Command{ | var appSecretGenerateCommand = cli.Command{ | ||||||
| 	Name:      "generate", | 	Name:      "generate", | ||||||
| 	Aliases:   []string{"g"}, | 	Aliases:   []string{"g"}, | ||||||
| 	Usage:     "Generate secrets", | 	Usage:     "Generate secrets", | ||||||
| 	ArgsUsage: "<secret> <version>", | 	ArgsUsage: "<secret> <version>", | ||||||
| 	Flags:     []cli.Flag{allSecretsFlag, internal.PassFlag}, | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 		allSecretsFlag, internal.PassFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
|  | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
|  |  | ||||||
| 		if c.Args().Len() == 1 && !allSecrets { | 		if len(c.Args()) == 1 && !allSecrets { | ||||||
| 			err := errors.New("missing arguments <secret>/<version> or '--all'") | 			err := errors.New("missing arguments <secret>/<version> or '--all'") | ||||||
| 			internal.ShowSubcommandHelpAndError(c, err) | 			internal.ShowSubcommandHelpAndError(c, err) | ||||||
| 		} | 		} | ||||||
| @ -94,12 +99,18 @@ var appSecretGenerateCommand = &cli.Command{ | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| var appSecretInsertCommand = &cli.Command{ | var appSecretInsertCommand = cli.Command{ | ||||||
| 	Name:    "insert", | 	Name:    "insert", | ||||||
| 	Aliases: []string{"i"}, | 	Aliases: []string{"i"}, | ||||||
| 	Usage:   "Insert secret", | 	Usage:   "Insert secret", | ||||||
| 	Flags:     []cli.Flag{internal.PassFlag}, | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 		internal.PassFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	ArgsUsage:    "<app> <secret-name> <version> <data>", | 	ArgsUsage:    "<app> <secret-name> <version> <data>", | ||||||
|  | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command inserts a secret into an app environment. | This command inserts a secret into an app environment. | ||||||
|  |  | ||||||
| @ -115,7 +126,7 @@ Example: | |||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
|  |  | ||||||
| 		if c.Args().Len() != 4 { | 		if len(c.Args()) != 4 { | ||||||
| 			internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?")) | 			internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?")) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @ -138,12 +149,18 @@ Example: | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| var appSecretRmCommand = &cli.Command{ | var appSecretRmCommand = cli.Command{ | ||||||
| 	Name:    "remove", | 	Name:    "remove", | ||||||
| 	Usage:     "Remove a secret", |  | ||||||
| 	Aliases: []string{"rm"}, | 	Aliases: []string{"rm"}, | ||||||
| 	Flags:     []cli.Flag{allSecretsFlag, internal.PassFlag}, | 	Usage:   "Remove a secret", | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 		allSecretsFlag, internal.PassFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	ArgsUsage:    "<app> <secret-name>", | 	ArgsUsage:    "<app> <secret-name>", | ||||||
|  | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command removes a secret from an app environment. | This command removes a secret from an app environment. | ||||||
|  |  | ||||||
| @ -170,7 +187,7 @@ Example: | |||||||
|  |  | ||||||
| 		filters := filters.NewArgs() | 		filters := filters.NewArgs() | ||||||
| 		filters.Add("name", app.StackName()) | 		filters.Add("name", app.StackName()) | ||||||
| 		secretList, err := cl.SecretList(c.Context, types.SecretListOptions{Filters: filters}) | 		secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filters}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -180,23 +197,32 @@ Example: | |||||||
| 			secretName := cont.Spec.Annotations.Name | 			secretName := cont.Spec.Annotations.Name | ||||||
| 			parsed := secret.ParseGeneratedSecretName(secretName, app) | 			parsed := secret.ParseGeneratedSecretName(secretName, app) | ||||||
| 			if allSecrets { | 			if allSecrets { | ||||||
| 				if err := cl.SecretRemove(c.Context, secretName); err != nil { | 				if err := cl.SecretRemove(context.Background(), secretName); err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| 				} | 				} | ||||||
|  | 				logrus.Infof("deleted %s successfully from server", secretName) | ||||||
|  |  | ||||||
| 				if internal.Pass { | 				if internal.Pass { | ||||||
| 					if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil { | 					if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil { | ||||||
| 						logrus.Fatal(err) | 						logrus.Fatal(err) | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | 					logrus.Infof("deleted %s successfully from local pass store", secretName) | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				if parsed == secretToRm { | 				if parsed == secretToRm { | ||||||
| 					if err := cl.SecretRemove(c.Context, secretName); err != nil { | 					if err := cl.SecretRemove(context.Background(), secretName); err != nil { | ||||||
| 						logrus.Fatal(err) | 						logrus.Fatal(err) | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
|  | 					logrus.Infof("deleted %s successfully from server", secretName) | ||||||
|  |  | ||||||
| 					if internal.Pass { | 					if internal.Pass { | ||||||
| 						if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil { | 						if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil { | ||||||
| 							logrus.Fatal(err) | 							logrus.Fatal(err) | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
|  | 						logrus.Infof("deleted %s successfully from local pass store", secretName) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @ -206,10 +232,15 @@ Example: | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| var appSecretLsCommand = &cli.Command{ | var appSecretLsCommand = cli.Command{ | ||||||
| 	Name:    "list", | 	Name:    "list", | ||||||
| 	Usage:   "List all secrets", |  | ||||||
| 	Aliases: []string{"ls"}, | 	Aliases: []string{"ls"}, | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
|  | 	Usage:  "List all secrets", | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
| 		secrets := secret.ReadSecretEnvVars(app.Env) | 		secrets := secret.ReadSecretEnvVars(app.Env) | ||||||
| @ -224,7 +255,7 @@ var appSecretLsCommand = &cli.Command{ | |||||||
|  |  | ||||||
| 		filters := filters.NewArgs() | 		filters := filters.NewArgs() | ||||||
| 		filters.Add("name", app.StackName()) | 		filters.Add("name", app.StackName()) | ||||||
| 		secretList, err := cl.SecretList(c.Context, types.SecretListOptions{Filters: filters}) | 		secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filters}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -260,12 +291,12 @@ var appSecretLsCommand = &cli.Command{ | |||||||
| 	BashComplete: autocomplete.AppNameComplete, | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| } | } | ||||||
|  |  | ||||||
| var appSecretCommand = &cli.Command{ | var appSecretCommand = cli.Command{ | ||||||
| 	Name:      "secret", | 	Name:      "secret", | ||||||
| 	Aliases:   []string{"s"}, | 	Aliases:   []string{"s"}, | ||||||
| 	Usage:     "Manage app secrets", | 	Usage:     "Manage app secrets", | ||||||
| 	ArgsUsage: "<command>", | 	ArgsUsage: "<command>", | ||||||
| 	Subcommands: []*cli.Command{ | 	Subcommands: []cli.Command{ | ||||||
| 		appSecretGenerateCommand, | 		appSecretGenerateCommand, | ||||||
| 		appSecretInsertCommand, | 		appSecretInsertCommand, | ||||||
| 		appSecretRmCommand, | 		appSecretRmCommand, | ||||||
|  | |||||||
| @ -1,17 +1,24 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| 	"coopcloud.tech/abra/pkg/client" | 	"coopcloud.tech/abra/pkg/client" | ||||||
| 	stack "coopcloud.tech/abra/pkg/upstream/stack" | 	stack "coopcloud.tech/abra/pkg/upstream/stack" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appUndeployCommand = &cli.Command{ | var appUndeployCommand = cli.Command{ | ||||||
| 	Name:    "undeploy", | 	Name:    "undeploy", | ||||||
| 	Aliases: []string{"un"}, | 	Aliases: []string{"un"}, | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Usage:  "Undeploy an app", | 	Usage:  "Undeploy an app", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This does not destroy any of the application data. However, you should remain | This does not destroy any of the application data. However, you should remain | ||||||
| @ -29,7 +36,7 @@ volumes as eligiblef or pruning once undeployed. | |||||||
|  |  | ||||||
| 		logrus.Debugf("checking whether %s is already deployed", stackName) | 		logrus.Debugf("checking whether %s is already deployed", stackName) | ||||||
|  |  | ||||||
| 		isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName) | 		isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -43,7 +50,7 @@ volumes as eligiblef or pruning once undeployed. | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		rmOpts := stack.Remove{Namespaces: []string{app.StackName()}} | 		rmOpts := stack.Remove{Namespaces: []string{app.StackName()}} | ||||||
| 		if err := stack.RunRemove(c.Context, cl, rmOpts); err != nil { | 		if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| @ -13,19 +14,22 @@ import ( | |||||||
| 	"coopcloud.tech/tagcmp" | 	"coopcloud.tech/tagcmp" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appUpgradeCommand = &cli.Command{ | var appUpgradeCommand = cli.Command{ | ||||||
| 	Name:      "upgrade", | 	Name:      "upgrade", | ||||||
| 	Aliases:   []string{"up"}, | 	Aliases:   []string{"up"}, | ||||||
| 	Usage:     "Upgrade an app", | 	Usage:     "Upgrade an app", | ||||||
| 	ArgsUsage: "<app>", | 	ArgsUsage: "<app>", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.ForceFlag, | 		internal.ForceFlag, | ||||||
| 		internal.ChaosFlag, | 		internal.ChaosFlag, | ||||||
| 		internal.NoDomainChecksFlag, | 		internal.NoDomainChecksFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command supports upgrading an app. You can use it to choose and roll out a | This command supports upgrading an app. You can use it to choose and roll out a | ||||||
| new upgrade to an existing app. | new upgrade to an existing app. | ||||||
| @ -38,7 +42,7 @@ You may pass "--force/-f" to upgrade to the same version again. This can be | |||||||
| useful if the container runtime has gotten into a weird state. | useful if the container runtime has gotten into a weird state. | ||||||
|  |  | ||||||
| This action could be destructive, please ensure you have a copy of your app | This action could be destructive, please ensure you have a copy of your app | ||||||
| data beforehand - see "abra app backup <app>" for more. | data beforehand. | ||||||
|  |  | ||||||
| Chas mode ("--chaos") will deploy your local checkout of a recipe as-is, | Chas mode ("--chaos") will deploy your local checkout of a recipe as-is, | ||||||
| including unstaged changes and can be useful for live hacking and testing new | including unstaged changes and can be useful for live hacking and testing new | ||||||
| @ -48,9 +52,11 @@ recipes. | |||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
| 		stackName := app.StackName() | 		stackName := app.StackName() | ||||||
|  |  | ||||||
|  | 		if !internal.Chaos { | ||||||
| 			if err := recipe.EnsureUpToDate(app.Type); err != nil { | 			if err := recipe.EnsureUpToDate(app.Type); err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		r, err := recipe.Get(app.Type) | 		r, err := recipe.Get(app.Type) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @ -68,7 +74,7 @@ recipes. | |||||||
|  |  | ||||||
| 		logrus.Debugf("checking whether %s is already deployed", stackName) | 		logrus.Debugf("checking whether %s is already deployed", stackName) | ||||||
|  |  | ||||||
| 		isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName) | 		isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -113,7 +119,7 @@ recipes. | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if len(availableUpgrades) == 0 && !internal.Force { | 			if len(availableUpgrades) == 0 && !internal.Force { | ||||||
| 				logrus.Info("no available upgrades, you're on latest ✌️") | 				logrus.Infof("no available upgrades, you're on latest (%s) ✌️", deployedVersion) | ||||||
| 				return nil | 				return nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"strings" | 	"context" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| @ -11,7 +11,7 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/upstream/stack" | 	"coopcloud.tech/abra/pkg/upstream/stack" | ||||||
| 	"github.com/docker/distribution/reference" | 	"github.com/docker/distribution/reference" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // getImagePath returns the image name | // getImagePath returns the image name | ||||||
| @ -22,18 +22,22 @@ func getImagePath(image string) (string, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	path := reference.Path(img) | 	path := reference.Path(img) | ||||||
| 	if strings.Contains(path, "library") { |  | ||||||
| 		path = strings.Split(path, "/")[1] | 	path = formatter.StripTagMeta(path) | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	logrus.Debugf("parsed %s from %s", path, image) | 	logrus.Debugf("parsed %s from %s", path, image) | ||||||
|  |  | ||||||
| 	return path, nil | 	return path, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| var appVersionCommand = &cli.Command{ | var appVersionCommand = cli.Command{ | ||||||
| 	Name:    "version", | 	Name:    "version", | ||||||
| 	Aliases: []string{"v"}, | 	Aliases: []string{"v"}, | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Usage:  "Show app versions", | 	Usage:  "Show app versions", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command shows all information about versioning related to a deployed app. | This command shows all information about versioning related to a deployed app. | ||||||
| @ -51,7 +55,7 @@ Cloud recipe version. | |||||||
|  |  | ||||||
| 		logrus.Debugf("checking whether %s is already deployed", stackName) | 		logrus.Debugf("checking whether %s is already deployed", stackName) | ||||||
|  |  | ||||||
| 		isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName) | 		isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -1,24 +1,31 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| 	"coopcloud.tech/abra/pkg/client" | 	"coopcloud.tech/abra/pkg/client" | ||||||
| 	"coopcloud.tech/abra/pkg/formatter" | 	"coopcloud.tech/abra/pkg/formatter" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var appVolumeListCommand = &cli.Command{ | var appVolumeListCommand = cli.Command{ | ||||||
| 	Name:    "list", | 	Name:    "list", | ||||||
| 	Usage:        "List volumes associated with an app", |  | ||||||
| 	Aliases: []string{"ls"}, | 	Aliases: []string{"ls"}, | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
|  | 	Usage:        "List volumes associated with an app", | ||||||
| 	BashComplete: autocomplete.AppNameComplete, | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
|  |  | ||||||
| 		volumeList, err := client.GetVolumes(c.Context, app.Server, app.Name) | 		volumeList, err := client.GetVolumes(context.Background(), app.Server, app.Name) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -45,7 +52,7 @@ var appVolumeListCommand = &cli.Command{ | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| var appVolumeRemoveCommand = &cli.Command{ | var appVolumeRemoveCommand = cli.Command{ | ||||||
| 	Name:  "remove", | 	Name:  "remove", | ||||||
| 	Usage: "Remove volume(s) associated with an app", | 	Usage: "Remove volume(s) associated with an app", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| @ -62,12 +69,15 @@ Passing "--force" will select all volumes for removal. Be careful. | |||||||
| 	ArgsUsage: "<app>", | 	ArgsUsage: "<app>", | ||||||
| 	Aliases:   []string{"rm"}, | 	Aliases:   []string{"rm"}, | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.ForceFlag, | 		internal.ForceFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
|  |  | ||||||
| 		volumeList, err := client.GetVolumes(c.Context, app.Server, app.Name) | 		volumeList, err := client.GetVolumes(context.Background(), app.Server, app.Name) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -89,7 +99,7 @@ Passing "--force" will select all volumes for removal. Be careful. | |||||||
| 			volumesToRemove = volumeNames | 			volumesToRemove = volumeNames | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		err = client.RemoveVolumes(c.Context, app.Server, volumesToRemove, internal.Force) | 		err = client.RemoveVolumes(context.Background(), app.Server, volumesToRemove, internal.Force) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -101,12 +111,12 @@ Passing "--force" will select all volumes for removal. Be careful. | |||||||
| 	BashComplete: autocomplete.AppNameComplete, | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| } | } | ||||||
|  |  | ||||||
| var appVolumeCommand = &cli.Command{ | var appVolumeCommand = cli.Command{ | ||||||
| 	Name:      "volume", | 	Name:      "volume", | ||||||
| 	Aliases:   []string{"vl"}, | 	Aliases:   []string{"vl"}, | ||||||
| 	Usage:     "Manage app volumes", | 	Usage:     "Manage app volumes", | ||||||
| 	ArgsUsage: "<command>", | 	ArgsUsage: "<command>", | ||||||
| 	Subcommands: []*cli.Command{ | 	Subcommands: []cli.Command{ | ||||||
| 		appVolumeListCommand, | 		appVolumeListCommand, | ||||||
| 		appVolumeRemoveCommand, | 		appVolumeRemoveCommand, | ||||||
| 	}, | 	}, | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/recipe" | 	"coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"github.com/go-git/go-git/v5" | 	"github.com/go-git/go-git/v5" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // CatalogueSkipList is all the repos that are not recipes. | // CatalogueSkipList is all the repos that are not recipes. | ||||||
| @ -33,6 +33,7 @@ var CatalogueSkipList = map[string]bool{ | |||||||
| 	"auto-mirror":           true, | 	"auto-mirror":           true, | ||||||
| 	"backup-bot":            true, | 	"backup-bot":            true, | ||||||
| 	"backup-bot-two":        true, | 	"backup-bot-two":        true, | ||||||
|  | 	"beta.coopcloud.tech":   true, | ||||||
| 	"comrade-renovate-bot":  true, | 	"comrade-renovate-bot":  true, | ||||||
| 	"coopcloud.tech":        true, | 	"coopcloud.tech":        true, | ||||||
| 	"coturn":                true, | 	"coturn":                true, | ||||||
| @ -55,17 +56,20 @@ var CatalogueSkipList = map[string]bool{ | |||||||
| 	"tyop":                  true, | 	"tyop":                  true, | ||||||
| } | } | ||||||
|  |  | ||||||
| var catalogueGenerateCommand = &cli.Command{ | var catalogueGenerateCommand = cli.Command{ | ||||||
| 	Name:    "generate", | 	Name:    "generate", | ||||||
| 	Aliases: []string{"g"}, | 	Aliases: []string{"g"}, | ||||||
| 	Usage:   "Generate the recipe catalogue", | 	Usage:   "Generate the recipe catalogue", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.PublishFlag, | 		internal.PublishFlag, | ||||||
| 		internal.DryFlag, | 		internal.DryFlag, | ||||||
| 		internal.SkipUpdatesFlag, | 		internal.SkipUpdatesFlag, | ||||||
| 		internal.RegistryUsernameFlag, | 		internal.RegistryUsernameFlag, | ||||||
| 		internal.RegistryPasswordFlag, | 		internal.RegistryPasswordFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command generates a new copy of the recipe catalogue which can be found on: | This command generates a new copy of the recipe catalogue which can be found on: | ||||||
|  |  | ||||||
| @ -93,12 +97,6 @@ keys configured on your account. | |||||||
| 			internal.ValidateRecipe(c) | 			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() | 		repos, err := recipe.ReadReposMetadata() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| @ -252,13 +250,13 @@ keys configured on your account. | |||||||
| } | } | ||||||
|  |  | ||||||
| // CatalogueCommand defines the `abra catalogue` command and sub-commands. | // CatalogueCommand defines the `abra catalogue` command and sub-commands. | ||||||
| var CatalogueCommand = &cli.Command{ | var CatalogueCommand = cli.Command{ | ||||||
| 	Name:        "catalogue", | 	Name:        "catalogue", | ||||||
| 	Usage:       "Manage the recipe catalogue", | 	Usage:       "Manage the recipe catalogue", | ||||||
| 	Aliases:     []string{"c"}, | 	Aliases:     []string{"c"}, | ||||||
| 	ArgsUsage:   "<recipe>", | 	ArgsUsage:   "<recipe>", | ||||||
| 	Description: "This command helps recipe packagers interact with the recipe catalogue", | 	Description: "This command helps recipe packagers interact with the recipe catalogue", | ||||||
| 	Subcommands: []*cli.Command{ | 	Subcommands: []cli.Command{ | ||||||
| 		catalogueGenerateCommand, | 		catalogueGenerateCommand, | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								cli/cli.go
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								cli/cli.go
									
									
									
									
									
								
							| @ -16,16 +16,15 @@ import ( | |||||||
| 	"coopcloud.tech/abra/cli/server" | 	"coopcloud.tech/abra/cli/server" | ||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
| 	"coopcloud.tech/abra/pkg/web" | 	"coopcloud.tech/abra/pkg/web" | ||||||
| 	logrusStack "github.com/Gurpartap/logrus-stack" |  | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // AutoCompleteCommand helps people set up auto-complete in their shells | // AutoCompleteCommand helps people set up auto-complete in their shells | ||||||
| var AutoCompleteCommand = &cli.Command{ | var AutoCompleteCommand = cli.Command{ | ||||||
| 	Name:    "autocomplete", | 	Name:    "autocomplete", | ||||||
| 	Usage:   "Configure shell autocompletion (recommended)", |  | ||||||
| 	Aliases: []string{"ac"}, | 	Aliases: []string{"ac"}, | ||||||
|  | 	Usage:   "Configure shell autocompletion (recommended)", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command helps set up autocompletion in your shell by downloading the | This command helps set up autocompletion in your shell by downloading the | ||||||
| relevant autocompletion files and laying out what additional information must | relevant autocompletion files and laying out what additional information must | ||||||
| @ -43,6 +42,10 @@ Supported shells are as follows: | |||||||
|  |  | ||||||
| `, | `, | ||||||
| 	ArgsUsage: "<shell>", | 	ArgsUsage: "<shell>", | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		shellType := c.Args().First() | 		shellType := c.Args().First() | ||||||
|  |  | ||||||
| @ -105,10 +108,10 @@ echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra | |||||||
| } | } | ||||||
|  |  | ||||||
| // UpgradeCommand upgrades abra in-place. | // UpgradeCommand upgrades abra in-place. | ||||||
| var UpgradeCommand = &cli.Command{ | var UpgradeCommand = cli.Command{ | ||||||
| 	Name:    "upgrade", | 	Name:    "upgrade", | ||||||
| 	Usage:   "Upgrade Abra itself", |  | ||||||
| 	Aliases: []string{"u"}, | 	Aliases: []string{"u"}, | ||||||
|  | 	Usage:   "Upgrade Abra itself", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command allows you to upgrade Abra in-place with the latest stable or | This command allows you to upgrade Abra in-place with the latest stable or | ||||||
| release candidate. | release candidate. | ||||||
| @ -150,7 +153,7 @@ func newAbraApp(version, commit string) *cli.App { | |||||||
|                          |_| |                          |_| | ||||||
| `, | `, | ||||||
| 		Version: fmt.Sprintf("%s-%s", version, commit[:7]), | 		Version: fmt.Sprintf("%s-%s", version, commit[:7]), | ||||||
| 		Commands: []*cli.Command{ | 		Commands: []cli.Command{ | ||||||
| 			app.AppCommand, | 			app.AppCommand, | ||||||
| 			server.ServerCommand, | 			server.ServerCommand, | ||||||
| 			recipe.RecipeCommand, | 			recipe.RecipeCommand, | ||||||
| @ -159,11 +162,7 @@ func newAbraApp(version, commit string) *cli.App { | |||||||
| 			UpgradeCommand, | 			UpgradeCommand, | ||||||
| 			AutoCompleteCommand, | 			AutoCompleteCommand, | ||||||
| 		}, | 		}, | ||||||
| 		Flags: []cli.Flag{ | 		Authors: []cli.Author{ | ||||||
| 			internal.DebugFlag, |  | ||||||
| 			internal.NoInputFlag, |  | ||||||
| 		}, |  | ||||||
| 		Authors: []*cli.Author{ |  | ||||||
| 			// If you're looking at this and you hack on Abra and you're not listed | 			// If you're looking at this and you hack on Abra and you're not listed | ||||||
| 			// here, please do add yourself! This is a community project, let's show | 			// here, please do add yourself! This is a community project, let's show | ||||||
| 			// some love | 			// some love | ||||||
| @ -178,13 +177,6 @@ func newAbraApp(version, commit string) *cli.App { | |||||||
| 	app.EnableBashCompletion = true | 	app.EnableBashCompletion = true | ||||||
|  |  | ||||||
| 	app.Before = func(c *cli.Context) error { | 	app.Before = func(c *cli.Context) error { | ||||||
| 		if internal.Debug { |  | ||||||
| 			logrus.SetLevel(logrus.DebugLevel) |  | ||||||
| 			logrus.SetFormatter(&logrus.TextFormatter{}) |  | ||||||
| 			logrus.SetOutput(os.Stderr) |  | ||||||
| 			logrus.AddHook(logrusStack.StandardHook()) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		paths := []string{ | 		paths := []string{ | ||||||
| 			config.ABRA_DIR, | 			config.ABRA_DIR, | ||||||
| 			path.Join(config.SERVERS_DIR), | 			path.Join(config.SERVERS_DIR), | ||||||
|  | |||||||
| @ -1,7 +1,11 @@ | |||||||
| package internal | package internal | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/urfave/cli/v2" | 	"os" | ||||||
|  | 
 | ||||||
|  | 	logrusStack "github.com/Gurpartap/logrus-stack" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Secrets stores the variable from SecretsFlag | // Secrets stores the variable from SecretsFlag | ||||||
| @ -9,9 +13,7 @@ var Secrets bool | |||||||
| 
 | 
 | ||||||
| // SecretsFlag turns on/off automatically generating secrets | // SecretsFlag turns on/off automatically generating secrets | ||||||
| var SecretsFlag = &cli.BoolFlag{ | var SecretsFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "secrets", | 	Name:        "secrets, ss", | ||||||
| 	Aliases:     []string{"ss"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Automatically generate secrets", | 	Usage:       "Automatically generate secrets", | ||||||
| 	Destination: &Secrets, | 	Destination: &Secrets, | ||||||
| } | } | ||||||
| @ -21,9 +23,7 @@ var Pass bool | |||||||
| 
 | 
 | ||||||
| // PassFlag turns on/off storing generated secrets in pass | // PassFlag turns on/off storing generated secrets in pass | ||||||
| var PassFlag = &cli.BoolFlag{ | var PassFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "pass", | 	Name:        "pass, p", | ||||||
| 	Aliases:     []string{"p"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Store the generated secrets in a local pass store", | 	Usage:       "Store the generated secrets in a local pass store", | ||||||
| 	Destination: &Pass, | 	Destination: &Pass, | ||||||
| } | } | ||||||
| @ -33,9 +33,8 @@ var Context string | |||||||
| 
 | 
 | ||||||
| // ContextFlag is temp | // ContextFlag is temp | ||||||
| var ContextFlag = &cli.StringFlag{ | var ContextFlag = &cli.StringFlag{ | ||||||
| 	Name:        "context", | 	Name:        "context, c", | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Aliases:     []string{"c"}, |  | ||||||
| 	Destination: &Context, | 	Destination: &Context, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -44,9 +43,7 @@ var Force bool | |||||||
| 
 | 
 | ||||||
| // ForceFlag turns on/off force functionality. | // ForceFlag turns on/off force functionality. | ||||||
| var ForceFlag = &cli.BoolFlag{ | var ForceFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "force", | 	Name:        "force, f", | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"f"}, |  | ||||||
| 	Usage:       "Perform action without further prompt. Use with care!", | 	Usage:       "Perform action without further prompt. Use with care!", | ||||||
| 	Destination: &Force, | 	Destination: &Force, | ||||||
| } | } | ||||||
| @ -56,9 +53,7 @@ var Chaos bool | |||||||
| 
 | 
 | ||||||
| // ChaosFlag turns on/off chaos functionality. | // ChaosFlag turns on/off chaos functionality. | ||||||
| var ChaosFlag = &cli.BoolFlag{ | var ChaosFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "chaos", | 	Name:        "chaos, ch", | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"ch"}, |  | ||||||
| 	Usage:       "Deploy uncommitted recipes changes. Use with care!", | 	Usage:       "Deploy uncommitted recipes changes. Use with care!", | ||||||
| 	Destination: &Chaos, | 	Destination: &Chaos, | ||||||
| } | } | ||||||
| @ -68,18 +63,15 @@ var DNSProvider string | |||||||
| 
 | 
 | ||||||
| // DNSProviderFlag selects a DNS provider. | // DNSProviderFlag selects a DNS provider. | ||||||
| var DNSProviderFlag = &cli.StringFlag{ | var DNSProviderFlag = &cli.StringFlag{ | ||||||
| 	Name:        "provider", | 	Name:        "provider, p", | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Aliases:     []string{"p"}, |  | ||||||
| 	Usage:       "DNS provider", | 	Usage:       "DNS provider", | ||||||
| 	Destination: &DNSProvider, | 	Destination: &DNSProvider, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var NoInput bool | var NoInput bool | ||||||
| var NoInputFlag = &cli.BoolFlag{ | var NoInputFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "no-input", | 	Name:        "no-input, n", | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"n"}, |  | ||||||
| 	Usage:       "Toggle non-interactive mode", | 	Usage:       "Toggle non-interactive mode", | ||||||
| 	Destination: &NoInput, | 	Destination: &NoInput, | ||||||
| } | } | ||||||
| @ -87,9 +79,8 @@ var NoInputFlag = &cli.BoolFlag{ | |||||||
| var DNSType string | var DNSType string | ||||||
| 
 | 
 | ||||||
| var DNSTypeFlag = &cli.StringFlag{ | var DNSTypeFlag = &cli.StringFlag{ | ||||||
| 	Name:        "type", | 	Name:        "type, t", | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Aliases:     []string{"t"}, |  | ||||||
| 	Usage:       "Domain name record type (e.g. A)", | 	Usage:       "Domain name record type (e.g. A)", | ||||||
| 	Destination: &DNSType, | 	Destination: &DNSType, | ||||||
| } | } | ||||||
| @ -97,9 +88,8 @@ var DNSTypeFlag = &cli.StringFlag{ | |||||||
| var DNSName string | var DNSName string | ||||||
| 
 | 
 | ||||||
| var DNSNameFlag = &cli.StringFlag{ | var DNSNameFlag = &cli.StringFlag{ | ||||||
| 	Name:        "name", | 	Name:        "name, n", | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Aliases:     []string{"n"}, |  | ||||||
| 	Usage:       "Domain name record name (e.g. mysubdomain)", | 	Usage:       "Domain name record name (e.g. mysubdomain)", | ||||||
| 	Destination: &DNSName, | 	Destination: &DNSName, | ||||||
| } | } | ||||||
| @ -107,18 +97,16 @@ var DNSNameFlag = &cli.StringFlag{ | |||||||
| var DNSValue string | var DNSValue string | ||||||
| 
 | 
 | ||||||
| var DNSValueFlag = &cli.StringFlag{ | var DNSValueFlag = &cli.StringFlag{ | ||||||
| 	Name:        "value", | 	Name:        "value, v", | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Aliases:     []string{"v"}, |  | ||||||
| 	Usage:       "Domain name record value (e.g. 192.168.1.1)", | 	Usage:       "Domain name record value (e.g. 192.168.1.1)", | ||||||
| 	Destination: &DNSValue, | 	Destination: &DNSValue, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var DNSTTL string | var DNSTTL string | ||||||
| var DNSTTLFlag = &cli.StringFlag{ | var DNSTTLFlag = &cli.StringFlag{ | ||||||
| 	Name:        "ttl", | 	Name:        "ttl, T", | ||||||
| 	Value:       "600s", | 	Value:       "600s", | ||||||
| 	Aliases:     []string{"T"}, |  | ||||||
| 	Usage:       "Domain name TTL value (seconds)", | 	Usage:       "Domain name TTL value (seconds)", | ||||||
| 	Destination: &DNSTTL, | 	Destination: &DNSTTL, | ||||||
| } | } | ||||||
| @ -126,9 +114,8 @@ var DNSTTLFlag = &cli.StringFlag{ | |||||||
| var DNSPriority int | var DNSPriority int | ||||||
| 
 | 
 | ||||||
| var DNSPriorityFlag = &cli.IntFlag{ | var DNSPriorityFlag = &cli.IntFlag{ | ||||||
| 	Name:        "priority", | 	Name:        "priority, P", | ||||||
| 	Value:       10, | 	Value:       10, | ||||||
| 	Aliases:     []string{"P"}, |  | ||||||
| 	Usage:       "Domain name priority value", | 	Usage:       "Domain name priority value", | ||||||
| 	Destination: &DNSPriority, | 	Destination: &DNSPriority, | ||||||
| } | } | ||||||
| @ -136,8 +123,7 @@ var DNSPriorityFlag = &cli.IntFlag{ | |||||||
| var ServerProvider string | var ServerProvider string | ||||||
| 
 | 
 | ||||||
| var ServerProviderFlag = &cli.StringFlag{ | var ServerProviderFlag = &cli.StringFlag{ | ||||||
| 	Name:        "provider", | 	Name:        "provider, p", | ||||||
| 	Aliases:     []string{"p"}, |  | ||||||
| 	Usage:       "3rd party server provider", | 	Usage:       "3rd party server provider", | ||||||
| 	Destination: &ServerProvider, | 	Destination: &ServerProvider, | ||||||
| } | } | ||||||
| @ -145,9 +131,8 @@ var ServerProviderFlag = &cli.StringFlag{ | |||||||
| var CapsulInstanceURL string | var CapsulInstanceURL string | ||||||
| 
 | 
 | ||||||
| var CapsulInstanceURLFlag = &cli.StringFlag{ | var CapsulInstanceURLFlag = &cli.StringFlag{ | ||||||
| 	Name:        "capsul-url", | 	Name:        "capsul-url, cu", | ||||||
| 	Value:       "yolo.servers.coop", | 	Value:       "yolo.servers.coop", | ||||||
| 	Aliases:     []string{"cu"}, |  | ||||||
| 	Usage:       "capsul instance URL", | 	Usage:       "capsul instance URL", | ||||||
| 	Destination: &CapsulInstanceURL, | 	Destination: &CapsulInstanceURL, | ||||||
| } | } | ||||||
| @ -155,9 +140,8 @@ var CapsulInstanceURLFlag = &cli.StringFlag{ | |||||||
| var CapsulName string | var CapsulName string | ||||||
| 
 | 
 | ||||||
| var CapsulNameFlag = &cli.StringFlag{ | var CapsulNameFlag = &cli.StringFlag{ | ||||||
| 	Name:        "capsul-name", | 	Name:        "capsul-name, cn", | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Aliases:     []string{"cn"}, |  | ||||||
| 	Usage:       "capsul name", | 	Usage:       "capsul name", | ||||||
| 	Destination: &CapsulName, | 	Destination: &CapsulName, | ||||||
| } | } | ||||||
| @ -165,9 +149,8 @@ var CapsulNameFlag = &cli.StringFlag{ | |||||||
| var CapsulType string | var CapsulType string | ||||||
| 
 | 
 | ||||||
| var CapsulTypeFlag = &cli.StringFlag{ | var CapsulTypeFlag = &cli.StringFlag{ | ||||||
| 	Name:        "capsul-type", | 	Name:        "capsul-type, ct", | ||||||
| 	Value:       "f1-xs", | 	Value:       "f1-xs", | ||||||
| 	Aliases:     []string{"ct"}, |  | ||||||
| 	Usage:       "capsul type", | 	Usage:       "capsul type", | ||||||
| 	Destination: &CapsulType, | 	Destination: &CapsulType, | ||||||
| } | } | ||||||
| @ -175,38 +158,33 @@ var CapsulTypeFlag = &cli.StringFlag{ | |||||||
| var CapsulImage string | var CapsulImage string | ||||||
| 
 | 
 | ||||||
| var CapsulImageFlag = &cli.StringFlag{ | var CapsulImageFlag = &cli.StringFlag{ | ||||||
| 	Name:        "capsul-image", | 	Name:        "capsul-image, ci", | ||||||
| 	Value:       "debian10", | 	Value:       "debian10", | ||||||
| 	Aliases:     []string{"ci"}, |  | ||||||
| 	Usage:       "capsul image", | 	Usage:       "capsul image", | ||||||
| 	Destination: &CapsulImage, | 	Destination: &CapsulImage, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var CapsulSSHKeys cli.StringSlice | var CapsulSSHKeys cli.StringSlice | ||||||
| 
 |  | ||||||
| var CapsulSSHKeysFlag = &cli.StringSliceFlag{ | var CapsulSSHKeysFlag = &cli.StringSliceFlag{ | ||||||
| 	Name:        "capsul-ssh-keys", | 	Name:  "capsul-ssh-keys, cs", | ||||||
| 	Aliases:     []string{"cs"}, |  | ||||||
| 	Usage: "capsul SSH key", | 	Usage: "capsul SSH key", | ||||||
| 	Destination: &CapsulSSHKeys, | 	Value: &CapsulSSHKeys, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var CapsulAPIToken string | var CapsulAPIToken string | ||||||
| 
 | 
 | ||||||
| var CapsulAPITokenFlag = &cli.StringFlag{ | var CapsulAPITokenFlag = &cli.StringFlag{ | ||||||
| 	Name:        "capsul-token", | 	Name:        "capsul-token, ca", | ||||||
| 	Aliases:     []string{"ca"}, |  | ||||||
| 	Usage:       "capsul API token", | 	Usage:       "capsul API token", | ||||||
| 	EnvVars:     []string{"CAPSUL_TOKEN"}, | 	EnvVar:      "CAPSUL_TOKEN", | ||||||
| 	Destination: &CapsulAPIToken, | 	Destination: &CapsulAPIToken, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var HetznerCloudName string | var HetznerCloudName string | ||||||
| 
 | 
 | ||||||
| var HetznerCloudNameFlag = &cli.StringFlag{ | var HetznerCloudNameFlag = &cli.StringFlag{ | ||||||
| 	Name:        "hetzner-name", | 	Name:        "hetzner-name, hn", | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Aliases:     []string{"hn"}, |  | ||||||
| 	Usage:       "hetzner cloud name", | 	Usage:       "hetzner cloud name", | ||||||
| 	Destination: &HetznerCloudName, | 	Destination: &HetznerCloudName, | ||||||
| } | } | ||||||
| @ -214,8 +192,7 @@ var HetznerCloudNameFlag = &cli.StringFlag{ | |||||||
| var HetznerCloudType string | var HetznerCloudType string | ||||||
| 
 | 
 | ||||||
| var HetznerCloudTypeFlag = &cli.StringFlag{ | var HetznerCloudTypeFlag = &cli.StringFlag{ | ||||||
| 	Name:        "hetzner-type", | 	Name:        "hetzner-type, ht", | ||||||
| 	Aliases:     []string{"ht"}, |  | ||||||
| 	Usage:       "hetzner cloud type", | 	Usage:       "hetzner cloud type", | ||||||
| 	Destination: &HetznerCloudType, | 	Destination: &HetznerCloudType, | ||||||
| 	Value:       "cx11", | 	Value:       "cx11", | ||||||
| @ -224,8 +201,7 @@ var HetznerCloudTypeFlag = &cli.StringFlag{ | |||||||
| var HetznerCloudImage string | var HetznerCloudImage string | ||||||
| 
 | 
 | ||||||
| var HetznerCloudImageFlag = &cli.StringFlag{ | var HetznerCloudImageFlag = &cli.StringFlag{ | ||||||
| 	Name:        "hetzner-image", | 	Name:        "hetzner-image, hi", | ||||||
| 	Aliases:     []string{"hi"}, |  | ||||||
| 	Usage:       "hetzner cloud image", | 	Usage:       "hetzner cloud image", | ||||||
| 	Value:       "debian-10", | 	Value:       "debian-10", | ||||||
| 	Destination: &HetznerCloudImage, | 	Destination: &HetznerCloudImage, | ||||||
| @ -234,17 +210,15 @@ var HetznerCloudImageFlag = &cli.StringFlag{ | |||||||
| var HetznerCloudSSHKeys cli.StringSlice | var HetznerCloudSSHKeys cli.StringSlice | ||||||
| 
 | 
 | ||||||
| var HetznerCloudSSHKeysFlag = &cli.StringSliceFlag{ | var HetznerCloudSSHKeysFlag = &cli.StringSliceFlag{ | ||||||
| 	Name:        "hetzner-ssh-keys", | 	Name:  "hetzner-ssh-keys, hs", | ||||||
| 	Aliases:     []string{"hs"}, |  | ||||||
| 	Usage: "hetzner cloud SSH keys (e.g. me@foo.com)", | 	Usage: "hetzner cloud SSH keys (e.g. me@foo.com)", | ||||||
| 	Destination: &HetznerCloudSSHKeys, | 	Value: &HetznerCloudSSHKeys, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var HetznerCloudLocation string | var HetznerCloudLocation string | ||||||
| 
 | 
 | ||||||
| var HetznerCloudLocationFlag = &cli.StringFlag{ | var HetznerCloudLocationFlag = &cli.StringFlag{ | ||||||
| 	Name:        "hetzner-location", | 	Name:        "hetzner-location, hl", | ||||||
| 	Aliases:     []string{"hl"}, |  | ||||||
| 	Usage:       "hetzner cloud server location", | 	Usage:       "hetzner cloud server location", | ||||||
| 	Value:       "hel1", | 	Value:       "hel1", | ||||||
| 	Destination: &HetznerCloudLocation, | 	Destination: &HetznerCloudLocation, | ||||||
| @ -253,10 +227,9 @@ var HetznerCloudLocationFlag = &cli.StringFlag{ | |||||||
| var HetznerCloudAPIToken string | var HetznerCloudAPIToken string | ||||||
| 
 | 
 | ||||||
| var HetznerCloudAPITokenFlag = &cli.StringFlag{ | var HetznerCloudAPITokenFlag = &cli.StringFlag{ | ||||||
| 	Name:        "hetzner-token", | 	Name:        "hetzner-token, ha", | ||||||
| 	Aliases:     []string{"ha"}, |  | ||||||
| 	Usage:       "hetzner cloud API token", | 	Usage:       "hetzner cloud API token", | ||||||
| 	EnvVars:     []string{"HCLOUD_TOKEN"}, | 	EnvVar:      "HCLOUD_TOKEN", | ||||||
| 	Destination: &HetznerCloudAPIToken, | 	Destination: &HetznerCloudAPIToken, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -265,9 +238,7 @@ var Debug bool | |||||||
| 
 | 
 | ||||||
| // DebugFlag turns on/off verbose logging down to the DEBUG level. | // DebugFlag turns on/off verbose logging down to the DEBUG level. | ||||||
| var DebugFlag = &cli.BoolFlag{ | var DebugFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "debug", | 	Name:        "debug, d", | ||||||
| 	Aliases:     []string{"d"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Destination: &Debug, | 	Destination: &Debug, | ||||||
| 	Usage:       "Show DEBUG messages", | 	Usage:       "Show DEBUG messages", | ||||||
| } | } | ||||||
| @ -278,60 +249,48 @@ var RC bool | |||||||
| // RCFlag chooses the latest release candidate for install | // RCFlag chooses the latest release candidate for install | ||||||
| var RCFlag = &cli.BoolFlag{ | var RCFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "rc", | 	Name:        "rc", | ||||||
| 	Value:       false, |  | ||||||
| 	Destination: &RC, | 	Destination: &RC, | ||||||
| 	Usage:       "Insatll the latest release candidate", | 	Usage:       "Insatll the latest release candidate", | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var Major bool | var Major bool | ||||||
| var MajorFlag = &cli.BoolFlag{ | var MajorFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "major", | 	Name:        "major, ma, x", | ||||||
| 	Usage:       "Increase the major part of the version", | 	Usage:       "Increase the major part of the version", | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"ma", "x"}, |  | ||||||
| 	Destination: &Major, | 	Destination: &Major, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var Minor bool | var Minor bool | ||||||
| var MinorFlag = &cli.BoolFlag{ | var MinorFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "minor", | 	Name:        "minor, mi, y", | ||||||
| 	Usage:       "Increase the minor part of the version", | 	Usage:       "Increase the minor part of the version", | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"mi", "y"}, |  | ||||||
| 	Destination: &Minor, | 	Destination: &Minor, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var Patch bool | var Patch bool | ||||||
| var PatchFlag = &cli.BoolFlag{ | var PatchFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "patch", | 	Name:        "patch, pa, z", | ||||||
| 	Usage:       "Increase the patch part of the version", | 	Usage:       "Increase the patch part of the version", | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"pa", "z"}, |  | ||||||
| 	Destination: &Patch, | 	Destination: &Patch, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var Dry bool | var Dry bool | ||||||
| var DryFlag = &cli.BoolFlag{ | var DryFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "dry-run", | 	Name:        "dry-run, dr", | ||||||
| 	Usage:       "Only reports changes that would be made", | 	Usage:       "Only reports changes that would be made", | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"d"}, |  | ||||||
| 	Destination: &Dry, | 	Destination: &Dry, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var Publish bool | var Publish bool | ||||||
| var PublishFlag = &cli.BoolFlag{ | var PublishFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "publish", | 	Name:        "publish, p", | ||||||
| 	Usage:       "Publish changes to git.coopcloud.tech", | 	Usage:       "Publish changes to git.coopcloud.tech", | ||||||
| 	Value:       false, |  | ||||||
| 	Aliases:     []string{"p"}, |  | ||||||
| 	Destination: &Publish, | 	Destination: &Publish, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var Domain string | var Domain string | ||||||
| var DomainFlag = &cli.StringFlag{ | var DomainFlag = &cli.StringFlag{ | ||||||
| 	Name:        "domain", | 	Name:        "domain, dn", | ||||||
| 	Aliases:     []string{"d"}, |  | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Usage:       "Choose a domain name", | 	Usage:       "Choose a domain name", | ||||||
| 	Destination: &Domain, | 	Destination: &Domain, | ||||||
| @ -339,8 +298,7 @@ var DomainFlag = &cli.StringFlag{ | |||||||
| 
 | 
 | ||||||
| var NewAppServer string | var NewAppServer string | ||||||
| var NewAppServerFlag = &cli.StringFlag{ | var NewAppServerFlag = &cli.StringFlag{ | ||||||
| 	Name:        "server", | 	Name:        "server, s", | ||||||
| 	Aliases:     []string{"s"}, |  | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Usage:       "Show apps of a specific server", | 	Usage:       "Show apps of a specific server", | ||||||
| 	Destination: &NewAppServer, | 	Destination: &NewAppServer, | ||||||
| @ -348,8 +306,7 @@ var NewAppServerFlag = &cli.StringFlag{ | |||||||
| 
 | 
 | ||||||
| var NewAppName string | var NewAppName string | ||||||
| var NewAppNameFlag = &cli.StringFlag{ | var NewAppNameFlag = &cli.StringFlag{ | ||||||
| 	Name:        "app-name", | 	Name:        "app-name, a", | ||||||
| 	Aliases:     []string{"a"}, |  | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Usage:       "Choose an app name", | 	Usage:       "Choose an app name", | ||||||
| 	Destination: &NewAppName, | 	Destination: &NewAppName, | ||||||
| @ -357,87 +314,71 @@ var NewAppNameFlag = &cli.StringFlag{ | |||||||
| 
 | 
 | ||||||
| var NoDomainChecks bool | var NoDomainChecks bool | ||||||
| var NoDomainChecksFlag = &cli.BoolFlag{ | var NoDomainChecksFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "no-domain-checks", | 	Name:        "no-domain-checks, nd", | ||||||
| 	Aliases:     []string{"nd"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Disable app domain sanity checks", | 	Usage:       "Disable app domain sanity checks", | ||||||
| 	Destination: &NoDomainChecks, | 	Destination: &NoDomainChecks, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var StdErrOnly bool | var StdErrOnly bool | ||||||
| var StdErrOnlyFlag = &cli.BoolFlag{ | var StdErrOnlyFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "stderr", | 	Name:        "stderr, s", | ||||||
| 	Aliases:     []string{"s"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Only tail stderr", | 	Usage:       "Only tail stderr", | ||||||
| 	Destination: &StdErrOnly, | 	Destination: &StdErrOnly, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var AutoDNSRecord bool |  | ||||||
| var AutoDNSRecordFlag = &cli.BoolFlag{ |  | ||||||
| 	Name:        "auto", |  | ||||||
| 	Aliases:     []string{"a"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Automatically configure DNS records", |  | ||||||
| 	Destination: &AutoDNSRecord, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var DontWaitConverge bool | var DontWaitConverge bool | ||||||
| var DontWaitConvergeFlag = &cli.BoolFlag{ | var DontWaitConvergeFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "no-converge-checks", | 	Name:        "no-converge-checks, nc", | ||||||
| 	Aliases:     []string{"nc"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Don't wait for converge logic checks", | 	Usage:       "Don't wait for converge logic checks", | ||||||
| 	Destination: &DontWaitConverge, | 	Destination: &DontWaitConverge, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var Watch bool | var Watch bool | ||||||
| var WatchFlag = &cli.BoolFlag{ | var WatchFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "watch", | 	Name:        "watch, w", | ||||||
| 	Aliases:     []string{"w"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Watch status by polling repeatedly", | 	Usage:       "Watch status by polling repeatedly", | ||||||
| 	Destination: &Watch, | 	Destination: &Watch, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var OnlyErrors bool | var OnlyErrors bool | ||||||
| var OnlyErrorFlag = &cli.BoolFlag{ | var OnlyErrorFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "errors", | 	Name:        "errors, e", | ||||||
| 	Aliases:     []string{"e"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Only show errors", | 	Usage:       "Only show errors", | ||||||
| 	Destination: &OnlyErrors, | 	Destination: &OnlyErrors, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var SkipUpdates bool | var SkipUpdates bool | ||||||
| var SkipUpdatesFlag = &cli.BoolFlag{ | var SkipUpdatesFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "skip-updates", | 	Name:        "skip-updates, s", | ||||||
| 	Aliases:     []string{"s"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Skip updating recipe repositories", | 	Usage:       "Skip updating recipe repositories", | ||||||
| 	Destination: &SkipUpdates, | 	Destination: &SkipUpdates, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegistryUsername string | var RegistryUsername string | ||||||
| var RegistryUsernameFlag = &cli.StringFlag{ | var RegistryUsernameFlag = &cli.StringFlag{ | ||||||
| 	Name:        "username", | 	Name:        "username, user", | ||||||
| 	Aliases:     []string{"user"}, |  | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Usage:       "Registry username", | 	Usage:       "Registry username", | ||||||
| 	EnvVars:     []string{"REGISTRY_USERNAME"}, | 	EnvVar:      "REGISTRY_USERNAME", | ||||||
| 	Destination: &RegistryUsername, | 	Destination: &RegistryUsername, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var RegistryPassword string | var RegistryPassword string | ||||||
| var RegistryPasswordFlag = &cli.StringFlag{ | var RegistryPasswordFlag = &cli.StringFlag{ | ||||||
| 	Name:        "password", | 	Name:        "password, pass", | ||||||
| 	Aliases:     []string{"pass"}, |  | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Usage:       "Registry password", | 	Usage:       "Registry password", | ||||||
| 	EnvVars:     []string{"REGISTRY_PASSWORD"}, | 	EnvVar:      "REGISTRY_PASSWORD", | ||||||
| 	Destination: &RegistryUsername, | 	Destination: &RegistryUsername, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | var AllTags bool | ||||||
|  | var AllTagsFlag = &cli.BoolFlag{ | ||||||
|  | 	Name:        "all-tags, a", | ||||||
|  | 	Usage:       "List all tags, not just upgrades", | ||||||
|  | 	Destination: &AllTags, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // SSHFailMsg is a hopefully helpful SSH failure message | // SSHFailMsg is a hopefully helpful SSH failure message | ||||||
| var SSHFailMsg = ` | var SSHFailMsg = ` | ||||||
| Woops, Abra is unable to connect to connect to %s. | Woops, Abra is unable to connect to connect to %s. | ||||||
| @ -486,3 +427,15 @@ Host foo.coopcloud.tech | |||||||
| Good luck! | Good luck! | ||||||
| 
 | 
 | ||||||
| ` | ` | ||||||
|  | 
 | ||||||
|  | // SubCommandBefore wires up pre-action machinery (e.g. --debug handling). | ||||||
|  | func SubCommandBefore(c *cli.Context) error { | ||||||
|  | 	if Debug { | ||||||
|  | 		logrus.SetLevel(logrus.DebugLevel) | ||||||
|  | 		logrus.SetFormatter(&logrus.TextFormatter{}) | ||||||
|  | 		logrus.SetOutput(os.Stderr) | ||||||
|  | 		logrus.AddHook(logrusStack.StandardHook()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @ -1,6 +1,7 @@ | |||||||
| package internal | package internal | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"os" | 	"os" | ||||||
| @ -17,16 +18,18 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/upstream/stack" | 	"coopcloud.tech/abra/pkg/upstream/stack" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // DeployAction is the main command-line action for this package | // DeployAction is the main command-line action for this package | ||||||
| func DeployAction(c *cli.Context) error { | func DeployAction(c *cli.Context) error { | ||||||
| 	app := ValidateApp(c) | 	app := ValidateApp(c) | ||||||
|  |  | ||||||
|  | 	if !Chaos { | ||||||
| 		if err := recipe.EnsureUpToDate(app.Type); err != nil { | 		if err := recipe.EnsureUpToDate(app.Type); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	r, err := recipe.Get(app.Type) | 	r, err := recipe.Get(app.Type) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -44,7 +47,7 @@ func DeployAction(c *cli.Context) error { | |||||||
|  |  | ||||||
| 	logrus.Debugf("checking whether %s is already deployed", app.StackName()) | 	logrus.Debugf("checking whether %s is already deployed", app.StackName()) | ||||||
|  |  | ||||||
| 	isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, app.StackName()) | 	isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, app.StackName()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logrus.Fatal(err) | 		logrus.Fatal(err) | ||||||
| 	} | 	} | ||||||
| @ -58,7 +61,7 @@ func DeployAction(c *cli.Context) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	version := deployedVersion | 	version := deployedVersion | ||||||
| 	if version == "" && !Chaos { | 	if version == "unknown" && !Chaos { | ||||||
| 		catl, err := recipe.ReadRecipeCatalogue() | 		catl, err := recipe.ReadRecipeCatalogue() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| @ -86,14 +89,14 @@ func DeployAction(c *cli.Context) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if version == "" && !Chaos { | 	if version == "unknown" && !Chaos { | ||||||
| 		logrus.Debugf("choosing %s as version to deploy", version) | 		logrus.Debugf("choosing %s as version to deploy", version) | ||||||
| 		if err := recipe.EnsureVersion(app.Type, version); err != nil { | 		if err := recipe.EnsureVersion(app.Type, version); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if version != "" && !Chaos { | 	if version != "unknown" && !Chaos { | ||||||
| 		if err := recipe.EnsureVersion(app.Type, version); err != nil { | 		if err := recipe.EnsureVersion(app.Type, version); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -221,7 +224,7 @@ func NewVersionOverview(app config.App, currentVersion, newVersion, releaseNotes | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if releaseNotes != "" { | 	if releaseNotes != "" && newVersion != "" { | ||||||
| 		fmt.Println() | 		fmt.Println() | ||||||
| 		fmt.Println(fmt.Sprintf("%s release notes:\n\n%s", newVersion, releaseNotes)) | 		fmt.Println(fmt.Sprintf("%s release notes:\n\n%s", newVersion, releaseNotes)) | ||||||
| 	} else { | 	} else { | ||||||
| @ -250,6 +253,10 @@ func NewVersionOverview(app config.App, currentVersion, newVersion, releaseNotes | |||||||
|  |  | ||||||
| // GetReleaseNotes prints release notes for a recipe version | // GetReleaseNotes prints release notes for a recipe version | ||||||
| func GetReleaseNotes(recipeName, version string) (string, error) { | func GetReleaseNotes(recipeName, version string) (string, error) { | ||||||
|  | 	if version == "" { | ||||||
|  | 		return "", nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	fpath := path.Join(config.RECIPES_DIR, recipeName, "release", version) | 	fpath := path.Join(config.RECIPES_DIR, recipeName, "release", version) | ||||||
|  |  | ||||||
| 	if _, err := os.Stat(fpath); !os.IsNotExist(err) { | 	if _, err := os.Stat(fpath); !os.IsNotExist(err) { | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ShowSubcommandHelpAndError exits the program on error, logs the error to the | // ShowSubcommandHelpAndError exits the program on error, logs the error to the | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/ssh" | 	"coopcloud.tech/abra/pkg/ssh" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // AppSecrets represents all app secrest | // AppSecrets represents all app secrest | ||||||
| @ -23,7 +23,7 @@ var RecipeName string | |||||||
|  |  | ||||||
| // createSecrets creates all secrets for a new app. | // createSecrets creates all secrets for a new app. | ||||||
| func createSecrets(sanitisedAppName string) (AppSecrets, error) { | 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) | 	appEnv, err := config.ReadEnv(appEnvPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @ -163,9 +163,9 @@ func NewAction(c *cli.Context) error { | |||||||
| 		NewAppServer = "local" | 		NewAppServer = "local" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tableCol := []string{"Name", "Domain", "Type", "Server"} | 	tableCol := []string{"server", "type", "domain", "app name"} | ||||||
| 	table := formatter.CreateTable(tableCol) | 	table := formatter.CreateTable(tableCol) | ||||||
| 	table.Append([]string{sanitisedAppName, Domain, recipe.Name, NewAppServer}) | 	table.Append([]string{NewAppServer, recipe.Name, Domain, NewAppName}) | ||||||
|  |  | ||||||
| 	fmt.Println("") | 	fmt.Println("") | ||||||
| 	fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name)) | 	fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name)) | ||||||
| @ -173,10 +173,10 @@ func NewAction(c *cli.Context) error { | |||||||
| 	table.Render() | 	table.Render() | ||||||
| 	fmt.Println("") | 	fmt.Println("") | ||||||
| 	fmt.Println("You can configure this app by running the following:") | 	fmt.Println("You can configure this app by running the following:") | ||||||
| 	fmt.Println(fmt.Sprintf("\n    abra app config %s", sanitisedAppName)) | 	fmt.Println(fmt.Sprintf("\n    abra app config %s", NewAppName)) | ||||||
| 	fmt.Println("") | 	fmt.Println("") | ||||||
| 	fmt.Println("You can deploy this app by running the following:") | 	fmt.Println("You can deploy this app by running the following:") | ||||||
| 	fmt.Println(fmt.Sprintf("\n    abra app deploy %s", sanitisedAppName)) | 	fmt.Println(fmt.Sprintf("\n    abra app deploy %s", NewAppName)) | ||||||
| 	fmt.Println("") | 	fmt.Println("") | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
|  | |||||||
| @ -2,8 +2,8 @@ package internal | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
|  | 	"coopcloud.tech/abra/pkg/formatter" | ||||||
| 	"coopcloud.tech/abra/pkg/recipe" | 	"coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/docker/distribution/reference" | 	"github.com/docker/distribution/reference" | ||||||
| @ -94,9 +94,7 @@ func GetMainAppImage(recipe recipe.Recipe) (string, error) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			path = reference.Path(img) | 			path = reference.Path(img) | ||||||
| 			if strings.Contains(path, "library") { | 			path = formatter.StripTagMeta(path) | ||||||
| 				path = strings.Split(path, "/")[1] |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return path, nil | 			return path, nil | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/ssh" | 	"coopcloud.tech/abra/pkg/ssh" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // AppName is used for configuring app name programmatically | // AppName is used for configuring app name programmatically | ||||||
| @ -160,9 +160,9 @@ func ValidateDomain(c *cli.Context) (string, error) { | |||||||
|  |  | ||||||
| // ValidateSubCmdFlags ensures flag order conforms to correct order | // ValidateSubCmdFlags ensures flag order conforms to correct order | ||||||
| func ValidateSubCmdFlags(c *cli.Context) bool { | func ValidateSubCmdFlags(c *cli.Context) bool { | ||||||
| 	for argIdx, arg := range c.Args().Slice() { | 	for argIdx, arg := range c.Args() { | ||||||
| 		if !strings.HasPrefix(arg, "--") { | 		if !strings.HasPrefix(arg, "--") { | ||||||
| 			for _, flag := range c.Args().Slice()[argIdx:] { | 			for _, flag := range c.Args()[argIdx:] { | ||||||
| 				if strings.HasPrefix(flag, "--") { | 				if strings.HasPrefix(flag, "--") { | ||||||
| 					return false | 					return false | ||||||
| 				} | 				} | ||||||
| @ -369,7 +369,7 @@ func EnsureNewCapsulVPSFlags(c *cli.Context) error { | |||||||
| 		if err := survey.AskOne(prompt, &CapsulSSHKeys); err != nil { | 		if err := survey.AskOne(prompt, &CapsulSSHKeys); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		CapsulSSHKeys = *cli.NewStringSlice(strings.Split(sshKeys, ",")...) | 		CapsulSSHKeys = cli.StringSlice(strings.Split(sshKeys, ",")) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if CapsulAPIToken == "" && !NoInput { | 	if CapsulAPIToken == "" && !NoInput { | ||||||
| @ -448,7 +448,7 @@ func EnsureNewHetznerCloudVPSFlags(c *cli.Context) error { | |||||||
| 		if err := survey.AskOne(prompt, &sshKeys); err != nil { | 		if err := survey.AskOne(prompt, &sshKeys); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		HetznerCloudSSHKeys = *cli.NewStringSlice(strings.Split(sshKeys, ",")...) | 		HetznerCloudSSHKeys = cli.StringSlice(strings.Split(sshKeys, ",")) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !NoInput { | 	if !NoInput { | ||||||
|  | |||||||
| @ -9,15 +9,20 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/lint" | 	"coopcloud.tech/abra/pkg/lint" | ||||||
| 	recipePkg "coopcloud.tech/abra/pkg/recipe" | 	recipePkg "coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var recipeLintCommand = &cli.Command{ | var recipeLintCommand = cli.Command{ | ||||||
| 	Name:      "lint", | 	Name:      "lint", | ||||||
| 	Usage:     "Lint a recipe", | 	Usage:     "Lint a recipe", | ||||||
| 	Aliases:   []string{"l"}, | 	Aliases:   []string{"l"}, | ||||||
| 	ArgsUsage: "<recipe>", | 	ArgsUsage: "<recipe>", | ||||||
| 	Flags:        []cli.Flag{internal.OnlyErrorFlag}, | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 		internal.OnlyErrorFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	BashComplete: autocomplete.RecipeNameComplete, | 	BashComplete: autocomplete.RecipeNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		recipe := internal.ValidateRecipe(c) | 		recipe := internal.ValidateRecipe(c) | ||||||
|  | |||||||
| @ -2,42 +2,36 @@ package recipe | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"path" |  | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/formatter" | 	"coopcloud.tech/abra/pkg/formatter" | ||||||
| 	gitPkg "coopcloud.tech/abra/pkg/git" |  | ||||||
| 	"coopcloud.tech/abra/pkg/recipe" | 	"coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var pattern string | var pattern string | ||||||
| var patternFlag = &cli.StringFlag{ | var patternFlag = &cli.StringFlag{ | ||||||
| 	Name:        "pattern", | 	Name:        "pattern, p", | ||||||
| 	Value:       "", | 	Value:       "", | ||||||
| 	Aliases:     []string{"p"}, |  | ||||||
| 	Usage:       "Simple string to filter recipes", | 	Usage:       "Simple string to filter recipes", | ||||||
| 	Destination: &pattern, | 	Destination: &pattern, | ||||||
| } | } | ||||||
|  |  | ||||||
| var recipeListCommand = &cli.Command{ | var recipeListCommand = cli.Command{ | ||||||
| 	Name:    "list", | 	Name:    "list", | ||||||
| 	Usage:   "List available recipes", | 	Usage:   "List available recipes", | ||||||
| 	Aliases: []string{"ls"}, | 	Aliases: []string{"ls"}, | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		patternFlag, | 		patternFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	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() | 		catl, err := recipe.ReadRecipeCatalogue() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err.Error()) | 			logrus.Fatal(err.Error()) | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
| 	"coopcloud.tech/abra/pkg/git" | 	"coopcloud.tech/abra/pkg/git" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // recipeMetadata is the recipe metadata for the README.md | // recipeMetadata is the recipe metadata for the README.md | ||||||
| @ -30,10 +30,15 @@ type recipeMetadata struct { | |||||||
| 	SSO         string | 	SSO         string | ||||||
| } | } | ||||||
|  |  | ||||||
| var recipeNewCommand = &cli.Command{ | var recipeNewCommand = cli.Command{ | ||||||
| 	Name:    "new", | 	Name:    "new", | ||||||
| 	Usage:     "Create a new recipe", |  | ||||||
| 	Aliases: []string{"n"}, | 	Aliases: []string{"n"}, | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before:    internal.SubCommandBefore, | ||||||
|  | 	Usage:     "Create a new recipe", | ||||||
| 	ArgsUsage: "<recipe>", | 	ArgsUsage: "<recipe>", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command creates a new recipe. | This command creates a new recipe. | ||||||
|  | |||||||
| @ -1,15 +1,15 @@ | |||||||
| package recipe | package recipe | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // RecipeCommand defines all recipe related sub-commands. | // RecipeCommand defines all recipe related sub-commands. | ||||||
| var RecipeCommand = &cli.Command{ | var RecipeCommand = cli.Command{ | ||||||
| 	Name:      "recipe", | 	Name:      "recipe", | ||||||
|  | 	Aliases:   []string{"r"}, | ||||||
| 	Usage:     "Manage recipes", | 	Usage:     "Manage recipes", | ||||||
| 	ArgsUsage: "<recipe>", | 	ArgsUsage: "<recipe>", | ||||||
| 	Aliases:   []string{"r"}, |  | ||||||
| 	Description: ` | 	Description: ` | ||||||
| A recipe is a blueprint for an app. It is a bunch of config files which | A recipe is a blueprint for an app. It is a bunch of config files which | ||||||
| describe how to deploy and maintain an app. Recipes are maintained by the Co-op | describe how to deploy and maintain an app. Recipes are maintained by the Co-op | ||||||
| @ -20,7 +20,7 @@ sure the recipe is in good working order and the config upgraded in a timely | |||||||
| manner. Abra supports convenient automation for recipe maintainenace, see the | manner. Abra supports convenient automation for recipe maintainenace, see the | ||||||
| "abra recipe upgrade", "abra recipe sync" and "abra recipe release" commands. | "abra recipe upgrade", "abra recipe sync" and "abra recipe release" commands. | ||||||
| `, | `, | ||||||
| 	Subcommands: []*cli.Command{ | 	Subcommands: []cli.Command{ | ||||||
| 		recipeListCommand, | 		recipeListCommand, | ||||||
| 		recipeVersionCommand, | 		recipeVersionCommand, | ||||||
| 		recipeReleaseCommand, | 		recipeReleaseCommand, | ||||||
|  | |||||||
| @ -18,13 +18,13 @@ import ( | |||||||
| 	"github.com/docker/distribution/reference" | 	"github.com/docker/distribution/reference" | ||||||
| 	"github.com/go-git/go-git/v5" | 	"github.com/go-git/go-git/v5" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var recipeReleaseCommand = &cli.Command{ | var recipeReleaseCommand = cli.Command{ | ||||||
| 	Name:      "release", | 	Name:      "release", | ||||||
| 	Usage:     "Release a new recipe version", |  | ||||||
| 	Aliases:   []string{"rl"}, | 	Aliases:   []string{"rl"}, | ||||||
|  | 	Usage:     "Release a new recipe version", | ||||||
| 	ArgsUsage: "<recipe> [<version>]", | 	ArgsUsage: "<recipe> [<version>]", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command is used to specify a new version of a recipe. These versions are | This command is used to specify a new version of a recipe. These versions are | ||||||
| @ -48,12 +48,15 @@ requires that you have permission to git push to these repositories and have | |||||||
| your SSH keys configured on your account. | your SSH keys configured on your account. | ||||||
| `, | `, | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.DryFlag, | 		internal.DryFlag, | ||||||
| 		internal.MajorFlag, | 		internal.MajorFlag, | ||||||
| 		internal.MinorFlag, | 		internal.MinorFlag, | ||||||
| 		internal.PatchFlag, | 		internal.PatchFlag, | ||||||
| 		internal.PublishFlag, | 		internal.PublishFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	BashComplete: autocomplete.RecipeNameComplete, | 	BashComplete: autocomplete.RecipeNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		recipe := internal.ValidateRecipeWithPrompt(c) | 		recipe := internal.ValidateRecipeWithPrompt(c) | ||||||
| @ -127,6 +130,7 @@ your SSH keys configured on your account. | |||||||
| func getImageVersions(recipe recipe.Recipe) (map[string]string, error) { | func getImageVersions(recipe recipe.Recipe) (map[string]string, error) { | ||||||
| 	var services = make(map[string]string) | 	var services = make(map[string]string) | ||||||
|  |  | ||||||
|  | 	missingTag := false | ||||||
| 	for _, service := range recipe.Config.Services { | 	for _, service := range recipe.Config.Services { | ||||||
| 		if service.Image == "" { | 		if service.Image == "" { | ||||||
| 			continue | 			continue | ||||||
| @ -138,21 +142,27 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		path := reference.Path(img) | 		path := reference.Path(img) | ||||||
| 		if strings.Contains(path, "library") { |  | ||||||
| 			path = strings.Split(path, "/")[1] | 		path = formatter.StripTagMeta(path) | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		var tag string | 		var tag string | ||||||
| 		switch img.(type) { | 		switch img.(type) { | ||||||
| 		case reference.NamedTagged: | 		case reference.NamedTagged: | ||||||
| 			tag = img.(reference.NamedTagged).Tag() | 			tag = img.(reference.NamedTagged).Tag() | ||||||
| 		case reference.Named: | 		case reference.Named: | ||||||
| 			return services, fmt.Errorf("%s service is missing image tag?", path) | 			if service.Name == "app" { | ||||||
|  | 				missingTag = true | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		services[path] = tag | 		services[path] = tag | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if missingTag { | ||||||
|  | 		return services, fmt.Errorf("app service is missing image tag?") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return services, nil | 	return services, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -232,13 +242,11 @@ func commitRelease(recipe recipe.Recipe, tag string) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if internal.Publish { |  | ||||||
| 	msg := fmt.Sprintf("chore: publish %s release", tag) | 	msg := fmt.Sprintf("chore: publish %s release", tag) | ||||||
| 	repoPath := path.Join(config.RECIPES_DIR, recipe.Name) | 	repoPath := path.Join(config.RECIPES_DIR, recipe.Name) | ||||||
| 		if err := gitPkg.Commit(repoPath, "compose.**yml", msg, internal.Dry); err != nil { | 	if err := gitPkg.Commit(repoPath, ".", msg, internal.Dry); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| @ -290,13 +298,10 @@ func pushRelease(recipe recipe.Recipe, tagString string) error { | |||||||
| 		if err := recipe.Push(internal.Dry); err != nil { | 		if err := recipe.Push(internal.Dry); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if !internal.Dry { |  | ||||||
| 		url := fmt.Sprintf("%s/%s/src/tag/%s", config.REPOS_BASE_URL, recipe.Name, tagString) | 		url := fmt.Sprintf("%s/%s/src/tag/%s", config.REPOS_BASE_URL, recipe.Name, tagString) | ||||||
| 		logrus.Infof("new release published: %s", url) | 		logrus.Infof("new release published: %s", url) | ||||||
| 	} else { | 	} else { | ||||||
| 			logrus.Info("dry run: no changes published") | 		logrus.Info("no -p/--publish passed, not publishing") | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
|  | |||||||
| @ -13,20 +13,23 @@ import ( | |||||||
| 	"github.com/go-git/go-git/v5" | 	"github.com/go-git/go-git/v5" | ||||||
| 	"github.com/go-git/go-git/v5/plumbing" | 	"github.com/go-git/go-git/v5/plumbing" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var recipeSyncCommand = &cli.Command{ | var recipeSyncCommand = cli.Command{ | ||||||
| 	Name:      "sync", | 	Name:      "sync", | ||||||
| 	Usage:     "Sync recipe version label", |  | ||||||
| 	Aliases:   []string{"s"}, | 	Aliases:   []string{"s"}, | ||||||
|  | 	Usage:     "Sync recipe version label", | ||||||
| 	ArgsUsage: "<recipe> [<version>]", | 	ArgsUsage: "<recipe> [<version>]", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.DryFlag, | 		internal.DryFlag, | ||||||
| 		internal.MajorFlag, | 		internal.MajorFlag, | ||||||
| 		internal.MinorFlag, | 		internal.MinorFlag, | ||||||
| 		internal.PatchFlag, | 		internal.PatchFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command will generate labels for the main recipe service (i.e. by | This command will generate labels for the main recipe service (i.e. by | ||||||
| convention, the service named 'app') which corresponds to the following format: | convention, the service named 'app') which corresponds to the following format: | ||||||
|  | |||||||
| @ -12,12 +12,13 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| 	"coopcloud.tech/abra/pkg/client" | 	"coopcloud.tech/abra/pkg/client" | ||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
|  | 	"coopcloud.tech/abra/pkg/formatter" | ||||||
| 	recipePkg "coopcloud.tech/abra/pkg/recipe" | 	recipePkg "coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"coopcloud.tech/tagcmp" | 	"coopcloud.tech/tagcmp" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/docker/distribution/reference" | 	"github.com/docker/distribution/reference" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type imgPin struct { | type imgPin struct { | ||||||
| @ -25,10 +26,10 @@ type imgPin struct { | |||||||
| 	version tagcmp.Tag | 	version tagcmp.Tag | ||||||
| } | } | ||||||
|  |  | ||||||
| var recipeUpgradeCommand = &cli.Command{ | var recipeUpgradeCommand = cli.Command{ | ||||||
| 	Name:    "upgrade", | 	Name:    "upgrade", | ||||||
| 	Usage:   "Upgrade recipe image tags", |  | ||||||
| 	Aliases: []string{"u"}, | 	Aliases: []string{"u"}, | ||||||
|  | 	Usage:   "Upgrade recipe image tags", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command reads and attempts to parse all image tags within the given | This command reads and attempts to parse all image tags within the given | ||||||
| <recipe> configuration and prompt with more recent tags to upgrade to. It will | <recipe> configuration and prompt with more recent tags to upgrade to. It will | ||||||
| @ -50,10 +51,14 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 	BashComplete: autocomplete.RecipeNameComplete, | 	BashComplete: autocomplete.RecipeNameComplete, | ||||||
| 	ArgsUsage:    "<recipe>", | 	ArgsUsage:    "<recipe>", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.PatchFlag, | 		internal.PatchFlag, | ||||||
| 		internal.MinorFlag, | 		internal.MinorFlag, | ||||||
| 		internal.MajorFlag, | 		internal.MajorFlag, | ||||||
|  | 		internal.AllTagsFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		recipe := internal.ValidateRecipeWithPrompt(c) | 		recipe := internal.ValidateRecipeWithPrompt(c) | ||||||
|  |  | ||||||
| @ -115,23 +120,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) | 			logrus.Debugf("retrieved %s from remote registry for %s", regVersions, image) | ||||||
|  |  | ||||||
| 			if strings.Contains(image, "library") { | 			image = formatter.StripTagMeta(image) | ||||||
| 				// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>, |  | ||||||
| 				// postgres:<tag>, i.e. images which do not have a username in the | 			switch img.(type) { | ||||||
| 				// first position of the string | 			case reference.NamedTagged: | ||||||
| 				image = strings.Split(image, "/")[1] |  | ||||||
| 			} |  | ||||||
| 			semverLikeTag := true |  | ||||||
| 				if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) { | 				if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) { | ||||||
| 					logrus.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag()) | 					logrus.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag()) | ||||||
| 				semverLikeTag = false | 				} | ||||||
|  | 			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()) | 			tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag()) | ||||||
| 			if err != nil && semverLikeTag { | 			if err != nil { | ||||||
| 				logrus.Fatal(err) | 				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) | 			logrus.Debugf("parsed %s for %s", tag, service.Name) | ||||||
|  |  | ||||||
| 			var compatible []tagcmp.Tag | 			var compatible []tagcmp.Tag | ||||||
| 			for _, regVersion := range regVersions { | 			for _, regVersion := range regVersions { | ||||||
| 				other, err := tagcmp.Parse(regVersion.Name) | 				other, err := tagcmp.Parse(regVersion.Name) | ||||||
| @ -148,8 +156,8 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
|  |  | ||||||
| 			sort.Sort(tagcmp.ByTagDesc(compatible)) | 			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)) | 				logrus.Info(fmt.Sprintf("no new versions available for %s, assuming %s is the latest (use -a/--all-tags to see all anyway)", image, tag)) | ||||||
| 				continue // skip on to the next tag and don't update any compose files | 				continue // skip on to the next tag and don't update any compose files | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @ -188,13 +196,13 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 					if contains { | 					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 { | 					} else { | ||||||
| 						logrus.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString) | 						logrus.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString) | ||||||
| 						continue | 						continue | ||||||
| 					} | 					} | ||||||
| 				} else { | 				} 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 | 					continue | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| @ -211,16 +219,18 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 					if upgradeTag == "" { | 					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 | 						continue | ||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| 					msg := fmt.Sprintf("upgrade to which tag? (service: %s, image: %s, tag: %s)", service.Name, image, tag) | 					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() | 						tag := img.(reference.NamedTagged).Tag() | ||||||
|  | 						if !internal.AllTags { | ||||||
| 							logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", tag)) | 							logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", tag)) | ||||||
|  | 						} | ||||||
| 						msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag) | 						msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag) | ||||||
| 						compatibleStrings = []string{} | 						compatibleStrings = []string{"skip"} | ||||||
| 						for _, regVersion := range regVersions { | 						for _, regVersion := range regVersions { | ||||||
| 							compatibleStrings = append(compatibleStrings, regVersion.Name) | 							compatibleStrings = append(compatibleStrings, regVersion.Name) | ||||||
| 						} | 						} | ||||||
| @ -238,10 +248,13 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			if upgradeTag != "skip" { | 			if upgradeTag != "skip" { | ||||||
| 				if err := recipe.UpdateTag(image, upgradeTag); err != nil { | 				ok, err := recipe.UpdateTag(image, upgradeTag) | ||||||
|  | 				if err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| 				} | 				} | ||||||
|  | 				if ok { | ||||||
| 					logrus.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image) | 					logrus.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image) | ||||||
|  | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				logrus.Warnf("not upgrading %s, skipping as requested", image) | 				logrus.Warnf("not upgrading %s, skipping as requested", image) | ||||||
| 			} | 			} | ||||||
|  | |||||||
| @ -1,34 +1,28 @@ | |||||||
| package recipe | package recipe | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"path" |  | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| 	"coopcloud.tech/abra/pkg/config" |  | ||||||
| 	"coopcloud.tech/abra/pkg/formatter" | 	"coopcloud.tech/abra/pkg/formatter" | ||||||
| 	gitPkg "coopcloud.tech/abra/pkg/git" |  | ||||||
| 	recipePkg "coopcloud.tech/abra/pkg/recipe" | 	recipePkg "coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var recipeVersionCommand = &cli.Command{ | var recipeVersionCommand = cli.Command{ | ||||||
| 	Name:      "versions", | 	Name:      "versions", | ||||||
| 	Usage:        "List recipe versions", |  | ||||||
| 	Aliases:   []string{"v"}, | 	Aliases:   []string{"v"}, | ||||||
|  | 	Usage:     "List recipe versions", | ||||||
| 	ArgsUsage: "<recipe>", | 	ArgsUsage: "<recipe>", | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before:       internal.SubCommandBefore, | ||||||
| 	BashComplete: autocomplete.RecipeNameComplete, | 	BashComplete: autocomplete.RecipeNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		recipe := internal.ValidateRecipe(c) | 		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() | 		catalogue, err := recipePkg.ReadRecipeCatalogue() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package record | package record | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  |  | ||||||
| @ -9,18 +10,21 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/formatter" | 	"coopcloud.tech/abra/pkg/formatter" | ||||||
| 	"github.com/libdns/gandi" | 	"github.com/libdns/gandi" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // RecordListCommand lists domains. | // RecordListCommand lists domains. | ||||||
| var RecordListCommand = &cli.Command{ | var RecordListCommand = cli.Command{ | ||||||
| 	Name:      "list", | 	Name:      "list", | ||||||
| 	Usage:     "List domain name records", | 	Usage:     "List domain name records", | ||||||
| 	Aliases:   []string{"ls"}, | 	Aliases:   []string{"ls"}, | ||||||
| 	ArgsUsage: "<zone>", | 	ArgsUsage: "<zone>", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.DNSProviderFlag, | 		internal.DNSProviderFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command lists all domain name records managed by a 3rd party provider for | This command lists all domain name records managed by a 3rd party provider for | ||||||
| a specific zone. | a specific zone. | ||||||
| @ -49,7 +53,7 @@ are listed. This zone must already be created on your provider account. | |||||||
| 			logrus.Fatalf("%s is not a supported DNS provider", internal.DNSProvider) | 			logrus.Fatalf("%s is not a supported DNS provider", internal.DNSProvider) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		records, err := provider.GetRecords(c.Context, zone) | 		records, err := provider.GetRecords(context.Background(), zone) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package record | package record | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  |  | ||||||
| @ -11,24 +12,26 @@ import ( | |||||||
| 	"github.com/libdns/gandi" | 	"github.com/libdns/gandi" | ||||||
| 	"github.com/libdns/libdns" | 	"github.com/libdns/libdns" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // RecordNewCommand creates a new domain name record. | // RecordNewCommand creates a new domain name record. | ||||||
| var RecordNewCommand = &cli.Command{ | var RecordNewCommand = cli.Command{ | ||||||
| 	Name:      "new", | 	Name:      "new", | ||||||
| 	Usage:     "Create a new domain record", | 	Usage:     "Create a new domain record", | ||||||
| 	Aliases:   []string{"n"}, | 	Aliases:   []string{"n"}, | ||||||
| 	ArgsUsage: "<zone>", | 	ArgsUsage: "<zone>", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.DNSProviderFlag, | 		internal.DNSProviderFlag, | ||||||
| 		internal.DNSTypeFlag, | 		internal.DNSTypeFlag, | ||||||
| 		internal.DNSNameFlag, | 		internal.DNSNameFlag, | ||||||
| 		internal.DNSValueFlag, | 		internal.DNSValueFlag, | ||||||
| 		internal.DNSTTLFlag, | 		internal.DNSTTLFlag, | ||||||
| 		internal.DNSPriorityFlag, | 		internal.DNSPriorityFlag, | ||||||
| 		internal.AutoDNSRecordFlag, |  | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command creates a new domain name record for a specific zone. | This command creates a new domain name record for a specific zone. | ||||||
|  |  | ||||||
| @ -39,13 +42,7 @@ Example: | |||||||
|  |  | ||||||
|     abra record new foo.com -p gandi -t A -n myapp -v 192.168.178.44 |     abra record new foo.com -p gandi -t A -n myapp -v 192.168.178.44 | ||||||
|  |  | ||||||
| Typically, you need two records, an A record which points at the zone (@.) and | You may also invoke this command in "wizard" mode and be prompted for input: | ||||||
| a wildcard record for your apps (*.). Pass "--auto" to have Abra automatically |  | ||||||
| set this up. |  | ||||||
|  |  | ||||||
|     abra record new --auto foo.com -p gandi -v 192.168.178.44 |  | ||||||
|  |  | ||||||
| You may also invoke this command in "wizard" mode and be prompted for input |  | ||||||
|  |  | ||||||
|     abra record new |     abra record new | ||||||
|  |  | ||||||
| @ -71,25 +68,6 @@ You may also invoke this command in "wizard" mode and be prompted for input | |||||||
| 			logrus.Fatalf("%s is not a supported DNS provider", internal.DNSProvider) | 			logrus.Fatalf("%s is not a supported DNS provider", internal.DNSProvider) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if internal.AutoDNSRecord { |  | ||||||
| 			ipv4, err := dns.EnsureIPv4(zone) |  | ||||||
| 			if err != nil { |  | ||||||
| 				logrus.Debugf("no ipv4 associated with %s, prompting for input", zone) |  | ||||||
| 				if err := internal.EnsureDNSValueFlag(c); err != nil { |  | ||||||
| 					logrus.Fatal(err) |  | ||||||
| 				} |  | ||||||
| 				ipv4 = internal.DNSValue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			logrus.Infof("automatically configuring @./*. A records for %s for %s (--auto)", zone, ipv4) |  | ||||||
|  |  | ||||||
| 			if err := autoConfigure(c, &provider, zone, ipv4); err != nil { |  | ||||||
| 				logrus.Fatal(err) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if err := internal.EnsureDNSTypeFlag(c); err != nil { | 		if err := internal.EnsureDNSTypeFlag(c); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -118,7 +96,7 @@ You may also invoke this command in "wizard" mode and be prompted for input | |||||||
| 			record.Priority = internal.DNSPriority | 			record.Priority = internal.DNSPriority | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		records, err := provider.GetRecords(c.Context, zone) | 		records, err := provider.GetRecords(context.Background(), zone) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -132,7 +110,7 @@ You may also invoke this command in "wizard" mode and be prompted for input | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		createdRecords, err := provider.SetRecords( | 		createdRecords, err := provider.SetRecords( | ||||||
| 			c.Context, | 			context.Background(), | ||||||
| 			zone, | 			zone, | ||||||
| 			[]libdns.Record{record}, | 			[]libdns.Record{record}, | ||||||
| 		) | 		) | ||||||
| @ -169,84 +147,3 @@ You may also invoke this command in "wizard" mode and be prompted for input | |||||||
| 		return nil | 		return nil | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| func autoConfigure(c *cli.Context, provider *gandi.Provider, zone, ipv4 string) error { |  | ||||||
| 	ttl, err := dns.GetTTL(internal.DNSTTL) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	atRecord := libdns.Record{ |  | ||||||
| 		Type:  "A", |  | ||||||
| 		Name:  "@", |  | ||||||
| 		Value: ipv4, |  | ||||||
| 		TTL:   ttl, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	wildcardRecord := libdns.Record{ |  | ||||||
| 		Type:  "A", |  | ||||||
| 		Name:  "*", |  | ||||||
| 		Value: ipv4, |  | ||||||
| 		TTL:   ttl, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	records := []libdns.Record{atRecord, wildcardRecord} |  | ||||||
|  |  | ||||||
| 	tableCol := []string{"type", "name", "value", "TTL", "priority"} |  | ||||||
| 	table := formatter.CreateTable(tableCol) |  | ||||||
|  |  | ||||||
| 	for _, record := range records { |  | ||||||
| 		existingRecords, err := provider.GetRecords(c.Context, zone) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		discovered := false |  | ||||||
| 		for _, existingRecord := range existingRecords { |  | ||||||
| 			if existingRecord.Type == record.Type && |  | ||||||
| 				existingRecord.Name == record.Name && |  | ||||||
| 				existingRecord.Value == record.Value { |  | ||||||
| 				logrus.Warnf("%s record: %s %s for %s already exists?", record.Type, record.Name, record.Value, zone) |  | ||||||
| 				discovered = true |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if discovered { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		createdRecords, err := provider.SetRecords( |  | ||||||
| 			c.Context, |  | ||||||
| 			zone, |  | ||||||
| 			[]libdns.Record{record}, |  | ||||||
| 		) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if len(createdRecords) == 0 { |  | ||||||
| 			return fmt.Errorf("provider library reports that no record was created?") |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		createdRecord := createdRecords[0] |  | ||||||
|  |  | ||||||
| 		value := createdRecord.Value |  | ||||||
| 		if len(createdRecord.Value) > 30 { |  | ||||||
| 			value = fmt.Sprintf("%s...", createdRecord.Value[:30]) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		table.Append([]string{ |  | ||||||
| 			createdRecord.Type, |  | ||||||
| 			createdRecord.Name, |  | ||||||
| 			value, |  | ||||||
| 			createdRecord.TTL.String(), |  | ||||||
| 			strconv.Itoa(createdRecord.Priority), |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if table.NumLines() > 0 { |  | ||||||
| 		table.Render() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| package record | package record | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // RecordCommand supports managing DNS entries. | // RecordCommand supports managing DNS entries. | ||||||
| var RecordCommand = &cli.Command{ | var RecordCommand = cli.Command{ | ||||||
| 	Name:      "record", | 	Name:      "record", | ||||||
| 	Usage:     "Manage domain name records", | 	Usage:     "Manage domain name records", | ||||||
| 	Aliases:   []string{"rc"}, | 	Aliases:   []string{"rc"}, | ||||||
| @ -30,7 +30,7 @@ to implement new provider support easily. | |||||||
|     https://pkg.go.dev/github.com/libdns/libdns |     https://pkg.go.dev/github.com/libdns/libdns | ||||||
|  |  | ||||||
| `, | `, | ||||||
| 	Subcommands: []*cli.Command{ | 	Subcommands: []cli.Command{ | ||||||
| 		RecordListCommand, | 		RecordListCommand, | ||||||
| 		RecordNewCommand, | 		RecordNewCommand, | ||||||
| 		RecordRemoveCommand, | 		RecordRemoveCommand, | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package record | package record | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  |  | ||||||
| @ -11,20 +12,23 @@ import ( | |||||||
| 	"github.com/libdns/gandi" | 	"github.com/libdns/gandi" | ||||||
| 	"github.com/libdns/libdns" | 	"github.com/libdns/libdns" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // RecordRemoveCommand lists domains. | // RecordRemoveCommand lists domains. | ||||||
| var RecordRemoveCommand = &cli.Command{ | var RecordRemoveCommand = cli.Command{ | ||||||
| 	Name:      "remove", | 	Name:      "remove", | ||||||
| 	Usage:     "Remove a domain name record", | 	Usage:     "Remove a domain name record", | ||||||
| 	Aliases:   []string{"rm"}, | 	Aliases:   []string{"rm"}, | ||||||
| 	ArgsUsage: "<zone>", | 	ArgsUsage: "<zone>", | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		internal.DNSProviderFlag, | 		internal.DNSProviderFlag, | ||||||
| 		internal.DNSTypeFlag, | 		internal.DNSTypeFlag, | ||||||
| 		internal.DNSNameFlag, | 		internal.DNSNameFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command removes a domain name record for a specific zone. | This command removes a domain name record for a specific zone. | ||||||
|  |  | ||||||
| @ -37,7 +41,7 @@ Example: | |||||||
|  |  | ||||||
|     abra record remove foo.com -p gandi -t A -n myapp |     abra record remove foo.com -p gandi -t A -n myapp | ||||||
|  |  | ||||||
| You may also invoke this command in "wizard" mode and be prompted for input | You may also invoke this command in "wizard" mode and be prompted for input: | ||||||
|  |  | ||||||
|     abra record rm |     abra record rm | ||||||
| `, | `, | ||||||
| @ -70,7 +74,7 @@ You may also invoke this command in "wizard" mode and be prompted for input | |||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		records, err := provider.GetRecords(c.Context, zone) | 		records, err := provider.GetRecords(context.Background(), zone) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| @ -120,7 +124,7 @@ You may also invoke this command in "wizard" mode and be prompted for input | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		_, err = provider.DeleteRecords(c.Context, zone, []libdns.Record{toDelete}) | 		_, err = provider.DeleteRecords(context.Background(), zone, []libdns.Record{toDelete}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -1,12 +1,12 @@ | |||||||
| package server | package server | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"os/user" | 	"os/user" | ||||||
| 	"path" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| @ -22,7 +22,7 @@ import ( | |||||||
| 	"github.com/docker/docker/api/types/swarm" | 	"github.com/docker/docker/api/types/swarm" | ||||||
| 	dockerClient "github.com/docker/docker/client" | 	dockerClient "github.com/docker/docker/client" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| @ -47,26 +47,21 @@ source for this script can be seen here: | |||||||
|  |  | ||||||
| var local bool | var local bool | ||||||
| var localFlag = &cli.BoolFlag{ | var localFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "local", | 	Name:        "local, l", | ||||||
| 	Aliases:     []string{"l"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Use local server", | 	Usage:       "Use local server", | ||||||
| 	Destination: &local, | 	Destination: &local, | ||||||
| } | } | ||||||
|  |  | ||||||
| var provision bool | var provision bool | ||||||
| var provisionFlag = &cli.BoolFlag{ | var provisionFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "provision", | 	Name:        "provision, p", | ||||||
| 	Aliases:     []string{"p"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Provision server so it can deploy apps", | 	Usage:       "Provision server so it can deploy apps", | ||||||
| 	Destination: &provision, | 	Destination: &provision, | ||||||
| } | } | ||||||
|  |  | ||||||
| var sshAuth string | var sshAuth string | ||||||
| var sshAuthFlag = &cli.StringFlag{ | var sshAuthFlag = &cli.StringFlag{ | ||||||
| 	Name:        "ssh-auth", | 	Name:        "ssh-auth, sh", | ||||||
| 	Aliases:     []string{"sh"}, |  | ||||||
| 	Value:       "identity-file", | 	Value:       "identity-file", | ||||||
| 	Usage:       "Select SSH authentication method (identity-file, password)", | 	Usage:       "Select SSH authentication method (identity-file, password)", | ||||||
| 	Destination: &sshAuth, | 	Destination: &sshAuth, | ||||||
| @ -74,22 +69,11 @@ var sshAuthFlag = &cli.StringFlag{ | |||||||
|  |  | ||||||
| var askSudoPass bool | var askSudoPass bool | ||||||
| var askSudoPassFlag = &cli.BoolFlag{ | var askSudoPassFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "ask-sudo-pass", | 	Name:        "ask-sudo-pass, as", | ||||||
| 	Aliases:     []string{"as"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Ask for sudo password", | 	Usage:       "Ask for sudo password", | ||||||
| 	Destination: &askSudoPass, | 	Destination: &askSudoPass, | ||||||
| } | } | ||||||
|  |  | ||||||
| var traefik bool |  | ||||||
| var traefikFlag = &cli.BoolFlag{ |  | ||||||
| 	Name:        "traefik", |  | ||||||
| 	Aliases:     []string{"t"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "Deploy traefik", |  | ||||||
| 	Destination: &traefik, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func cleanUp(domainName string) { | func cleanUp(domainName string) { | ||||||
| 	logrus.Warnf("cleaning up context for %s", domainName) | 	logrus.Warnf("cleaning up context for %s", domainName) | ||||||
| 	if err := client.DeleteContext(domainName); err != nil { | 	if err := client.DeleteContext(domainName); err != nil { | ||||||
| @ -163,12 +147,6 @@ func newLocalServer(c *cli.Context, domainName string) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if traefik { |  | ||||||
| 		if err := deployTraefik(c, cl, domainName); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	logrus.Info("local server has been added") | 	logrus.Info("local server has been added") | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @ -320,7 +298,7 @@ If nothing works, you try running the Docker install script manually on your ser | |||||||
|  |  | ||||||
| func initSwarmLocal(c *cli.Context, cl *dockerClient.Client, domainName string) error { | func initSwarmLocal(c *cli.Context, cl *dockerClient.Client, domainName string) error { | ||||||
| 	initReq := swarm.InitRequest{ListenAddr: "0.0.0.0:2377"} | 	initReq := swarm.InitRequest{ListenAddr: "0.0.0.0:2377"} | ||||||
| 	if _, err := cl.SwarmInit(c.Context, initReq); err != nil { | 	if _, err := cl.SwarmInit(context.Background(), initReq); err != nil { | ||||||
| 		if strings.Contains(err.Error(), "is already part of a swarm") || | 		if strings.Contains(err.Error(), "is already part of a swarm") || | ||||||
| 			strings.Contains(err.Error(), "must specify a listening address") { | 			strings.Contains(err.Error(), "must specify a listening address") { | ||||||
| 			logrus.Infof("swarm mode already initialised on %s", domainName) | 			logrus.Infof("swarm mode already initialised on %s", domainName) | ||||||
| @ -332,7 +310,7 @@ func initSwarmLocal(c *cli.Context, cl *dockerClient.Client, domainName string) | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"} | 	netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"} | ||||||
| 	if _, err := cl.NetworkCreate(c.Context, "proxy", netOpts); err != nil { | 	if _, err := cl.NetworkCreate(context.Background(), "proxy", netOpts); err != nil { | ||||||
| 		if !strings.Contains(err.Error(), "proxy already exists") { | 		if !strings.Contains(err.Error(), "proxy already exists") { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @ -354,7 +332,7 @@ func initSwarm(c *cli.Context, cl *dockerClient.Client, domainName string) error | |||||||
| 		ListenAddr:    "0.0.0.0:2377", | 		ListenAddr:    "0.0.0.0:2377", | ||||||
| 		AdvertiseAddr: ipv4, | 		AdvertiseAddr: ipv4, | ||||||
| 	} | 	} | ||||||
| 	if _, err := cl.SwarmInit(c.Context, initReq); err != nil { | 	if _, err := cl.SwarmInit(context.Background(), initReq); err != nil { | ||||||
| 		if strings.Contains(err.Error(), "is already part of a swarm") || | 		if strings.Contains(err.Error(), "is already part of a swarm") || | ||||||
| 			strings.Contains(err.Error(), "must specify a listening address") { | 			strings.Contains(err.Error(), "must specify a listening address") { | ||||||
| 			logrus.Infof("swarm mode already initialised on %s", domainName) | 			logrus.Infof("swarm mode already initialised on %s", domainName) | ||||||
| @ -366,7 +344,7 @@ func initSwarm(c *cli.Context, cl *dockerClient.Client, domainName string) error | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"} | 	netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"} | ||||||
| 	if _, err := cl.NetworkCreate(c.Context, "proxy", netOpts); err != nil { | 	if _, err := cl.NetworkCreate(context.Background(), "proxy", netOpts); err != nil { | ||||||
| 		if !strings.Contains(err.Error(), "proxy already exists") { | 		if !strings.Contains(err.Error(), "proxy already exists") { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @ -388,34 +366,9 @@ func createServerDir(domainName string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func deployTraefik(c *cli.Context, cl *dockerClient.Client, domainName string) error { | var serverAddCommand = cli.Command{ | ||||||
| 	internal.NoInput = true |  | ||||||
|  |  | ||||||
| 	internal.RecipeName = "traefik" |  | ||||||
| 	internal.NewAppServer = domainName |  | ||||||
| 	internal.Domain = fmt.Sprintf("%s.%s", "traefik", domainName) |  | ||||||
| 	internal.NewAppName = fmt.Sprintf("%s_%s", "traefik", config.SanitiseAppName(domainName)) |  | ||||||
|  |  | ||||||
| 	appEnvPath := path.Join(config.ABRA_DIR, "servers", internal.Domain, fmt.Sprintf("%s.env", internal.NewAppName)) |  | ||||||
| 	if _, err := os.Stat(appEnvPath); os.IsNotExist(err) { |  | ||||||
| 		logrus.Info(fmt.Sprintf("-t/--traefik specified, automatically deploying traefik to %s", internal.NewAppServer)) |  | ||||||
| 		if err := internal.NewAction(c); err != nil { |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		logrus.Infof("%s already exists, not creating again", appEnvPath) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	internal.AppName = internal.NewAppName |  | ||||||
| 	if err := internal.DeployAction(c); err != nil { |  | ||||||
| 		logrus.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var serverAddCommand = &cli.Command{ |  | ||||||
| 	Name:    "add", | 	Name:    "add", | ||||||
|  | 	Aliases: []string{"a"}, | ||||||
| 	Usage:   "Add a server to your configuration", | 	Usage:   "Add a server to your configuration", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command adds a new server to your configuration so that it can be managed | This command adds a new server to your configuration so that it can be managed | ||||||
| @ -451,7 +404,7 @@ system username to make an initial connection. You can use the <user> and | |||||||
|  |  | ||||||
| Example: | Example: | ||||||
|  |  | ||||||
|     abra server add --provision --traefik varia.zone glodemodem 12345 |     abra server add --provision varia.zone glodemodem 12345 | ||||||
|  |  | ||||||
| Abra will construct the following SSH connection and Docker context: | Abra will construct the following SSH connection and Docker context: | ||||||
|  |  | ||||||
| @ -459,25 +412,22 @@ Abra will construct the following SSH connection and Docker context: | |||||||
|  |  | ||||||
| All communication between Abra and the server will use this SSH connection. | All communication between Abra and the server will use this SSH connection. | ||||||
|  |  | ||||||
| In this example, Abra will run the following operations: | In this example, Abra will install Docker and initialise swarm mode. | ||||||
|  |  | ||||||
|     1. Install Docker |  | ||||||
| 		2. Initialise Swarm mode |  | ||||||
| 		3. Deploy Traefik (core web proxy) |  | ||||||
|  |  | ||||||
| You may omit flags to avoid performing this provisioning logic. | You may omit flags to avoid performing this provisioning logic. | ||||||
| `, | `, | ||||||
| 	Aliases: []string{"a"}, |  | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		localFlag, | 		localFlag, | ||||||
| 		provisionFlag, | 		provisionFlag, | ||||||
| 		sshAuthFlag, | 		sshAuthFlag, | ||||||
| 		askSudoPassFlag, | 		askSudoPassFlag, | ||||||
| 		traefikFlag, |  | ||||||
| 	}, | 	}, | ||||||
|  | 	Before:    internal.SubCommandBefore, | ||||||
| 	ArgsUsage: "<domain> [<user>] [<port>]", | 	ArgsUsage: "<domain> [<user>] [<port>]", | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		if c.Args().Len() > 0 && local || !internal.ValidateSubCmdFlags(c) { | 		if len(c.Args()) > 0 && local || !internal.ValidateSubCmdFlags(c) { | ||||||
| 			err := errors.New("cannot use <domain> and --local together") | 			err := errors.New("cannot use <domain> and --local together") | ||||||
| 			internal.ShowSubcommandHelpAndError(c, err) | 			internal.ShowSubcommandHelpAndError(c, err) | ||||||
| 		} | 		} | ||||||
| @ -543,17 +493,11 @@ You may omit flags to avoid performing this provisioning logic. | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if _, err := cl.Info(c.Context); err != nil { | 		if _, err := cl.Info(context.Background()); err != nil { | ||||||
| 			cleanUp(domainName) | 			cleanUp(domainName) | ||||||
| 			logrus.Fatalf("couldn't make a remote docker connection to %s? use --provision/-p to attempt to install", domainName) | 			logrus.Fatalf("couldn't make a remote docker connection to %s? use --provision/-p to attempt to install", domainName) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if traefik { |  | ||||||
| 			if err := deployTraefik(c, cl, domainName); err != nil { |  | ||||||
| 				logrus.Fatal(err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return nil | 		return nil | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,20 +3,24 @@ package server | |||||||
| import ( | import ( | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
| 	"coopcloud.tech/abra/pkg/context" | 	"coopcloud.tech/abra/pkg/context" | ||||||
| 	"coopcloud.tech/abra/pkg/formatter" | 	"coopcloud.tech/abra/pkg/formatter" | ||||||
| 	"github.com/docker/cli/cli/connhelper/ssh" | 	"github.com/docker/cli/cli/connhelper/ssh" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var serverListCommand = &cli.Command{ | var serverListCommand = cli.Command{ | ||||||
| 	Name:    "list", | 	Name:    "list", | ||||||
| 	Aliases: []string{"ls"}, | 	Aliases: []string{"ls"}, | ||||||
| 	Usage:   "List managed servers", | 	Usage:   "List managed servers", | ||||||
| 	ArgsUsage: " ", | 	Flags: []cli.Flag{ | ||||||
| 	HideHelp:  true, | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		dockerContextStore := context.NewDefaultDockerContextStore() | 		dockerContextStore := context.NewDefaultDockerContextStore() | ||||||
| 		contexts, err := dockerContextStore.Store.List() | 		contexts, err := dockerContextStore.Store.List() | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package server | package server | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| @ -10,7 +11,7 @@ import ( | |||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/hetznercloud/hcloud-go/hcloud" | 	"github.com/hetznercloud/hcloud-go/hcloud" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func newHetznerCloudVPS(c *cli.Context) error { | func newHetznerCloudVPS(c *cli.Context) error { | ||||||
| @ -27,7 +28,7 @@ func newHetznerCloudVPS(c *cli.Context) error { | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		sshKey, _, err := client.SSHKey.GetByName(c.Context, sshKey) | 		sshKey, _, err := client.SSHKey.GetByName(context.Background(), sshKey) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @ -72,7 +73,7 @@ func newHetznerCloudVPS(c *cli.Context) error { | |||||||
| 		logrus.Fatal("exiting as requested") | 		logrus.Fatal("exiting as requested") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	res, _, err := client.Server.Create(c.Context, serverOpts) | 	res, _, err := client.Server.Create(context.Background(), serverOpts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @ -110,9 +111,6 @@ bar.example.com). | |||||||
|     @  1800 IN A %s |     @  1800 IN A %s | ||||||
|     *  1800 IN A %s |     *  1800 IN A %s | ||||||
|  |  | ||||||
| "abra record new --auto" can help you do this quickly if you use a supported |  | ||||||
| DNS provider. |  | ||||||
|  |  | ||||||
| 	`, | 	`, | ||||||
| 		internal.HetznerCloudName, ip, rootPassword, | 		internal.HetznerCloudName, ip, rootPassword, | ||||||
| 		ip, ip, ip, | 		ip, ip, ip, | ||||||
| @ -200,7 +198,7 @@ bar.example.com). | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| var serverNewCommand = &cli.Command{ | var serverNewCommand = cli.Command{ | ||||||
| 	Name:    "new", | 	Name:    "new", | ||||||
| 	Aliases: []string{"n"}, | 	Aliases: []string{"n"}, | ||||||
| 	Usage:   "Create a new server using a 3rd party provider", | 	Usage:   "Create a new server using a 3rd party provider", | ||||||
| @ -223,7 +221,12 @@ API tokens are read from the environment if specified, e.g. | |||||||
| Where "$provider_TOKEN" is the expected env var format. | Where "$provider_TOKEN" is the expected env var format. | ||||||
| `, | `, | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  |  | ||||||
| 		internal.ServerProviderFlag, | 		internal.ServerProviderFlag, | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
|  |  | ||||||
| 		// Capsul | 		// Capsul | ||||||
| 		internal.CapsulInstanceURLFlag, | 		internal.CapsulInstanceURLFlag, | ||||||
| @ -240,6 +243,7 @@ Where "$provider_TOKEN" is the expected env var format. | |||||||
| 		internal.HetznerCloudLocationFlag, | 		internal.HetznerCloudLocationFlag, | ||||||
| 		internal.HetznerCloudAPITokenFlag, | 		internal.HetznerCloudAPITokenFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		if err := internal.EnsureServerProvider(); err != nil { | 		if err := internal.EnsureServerProvider(); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package server | package server | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| @ -12,14 +13,12 @@ import ( | |||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/hetznercloud/hcloud-go/hcloud" | 	"github.com/hetznercloud/hcloud-go/hcloud" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var rmServer bool | var rmServer bool | ||||||
| var rmServerFlag = &cli.BoolFlag{ | var rmServerFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "server", | 	Name:        "server, s", | ||||||
| 	Aliases:     []string{"s"}, |  | ||||||
| 	Value:       false, |  | ||||||
| 	Usage:       "remove the actual server also", | 	Usage:       "remove the actual server also", | ||||||
| 	Destination: &rmServer, | 	Destination: &rmServer, | ||||||
| } | } | ||||||
| @ -50,7 +49,7 @@ func rmHetznerCloudVPS(c *cli.Context) error { | |||||||
|  |  | ||||||
| 	client := hcloud.NewClient(hcloud.WithToken(internal.HetznerCloudAPIToken)) | 	client := hcloud.NewClient(hcloud.WithToken(internal.HetznerCloudAPIToken)) | ||||||
|  |  | ||||||
| 	server, _, err := client.Server.Get(c.Context, internal.HetznerCloudName) | 	server, _, err := client.Server.Get(context.Background(), internal.HetznerCloudName) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @ -89,7 +88,7 @@ destroyed. | |||||||
| 		logrus.Fatal("exiting as requested") | 		logrus.Fatal("exiting as requested") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	_, err = client.Server.Delete(c.Context, server) | 	_, err = client.Server.Delete(context.Background(), server) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @ -99,7 +98,7 @@ destroyed. | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| var serverRemoveCommand = &cli.Command{ | var serverRemoveCommand = cli.Command{ | ||||||
| 	Name:      "remove", | 	Name:      "remove", | ||||||
| 	Aliases:   []string{"rm"}, | 	Aliases:   []string{"rm"}, | ||||||
| 	ArgsUsage: "[<server>]", | 	ArgsUsage: "[<server>]", | ||||||
| @ -116,6 +115,8 @@ underlying client connection context. This server will then be lost in time, | |||||||
| like tears in rain. | like tears in rain. | ||||||
| `, | `, | ||||||
| 	Flags: []cli.Flag{ | 	Flags: []cli.Flag{ | ||||||
|  | 		internal.DebugFlag, | ||||||
|  | 		internal.NoInputFlag, | ||||||
| 		rmServerFlag, | 		rmServerFlag, | ||||||
| 		internal.ServerProviderFlag, | 		internal.ServerProviderFlag, | ||||||
|  |  | ||||||
| @ -123,6 +124,7 @@ like tears in rain. | |||||||
| 		internal.HetznerCloudNameFlag, | 		internal.HetznerCloudNameFlag, | ||||||
| 		internal.HetznerCloudAPITokenFlag, | 		internal.HetznerCloudAPITokenFlag, | ||||||
| 	}, | 	}, | ||||||
|  | 	Before: internal.SubCommandBefore, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		serverName := c.Args().Get(1) | 		serverName := c.Args().Get(1) | ||||||
| 		if serverName != "" { | 		if serverName != "" { | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| package server | package server | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ServerCommand defines the `abra server` command and its subcommands | // ServerCommand defines the `abra server` command and its subcommands | ||||||
| var ServerCommand = &cli.Command{ | var ServerCommand = cli.Command{ | ||||||
| 	Name:    "server", | 	Name:    "server", | ||||||
| 	Aliases: []string{"s"}, | 	Aliases: []string{"s"}, | ||||||
| 	Usage:   "Manage servers", | 	Usage:   "Manage servers", | ||||||
| @ -18,7 +18,7 @@ already have a server, you can add it to your configuration using "abra server | |||||||
| add". Abra can provision servers so that they are ready to deploy Co-op Cloud | add". Abra can provision servers so that they are ready to deploy Co-op Cloud | ||||||
| apps, see available flags on "server add" for more. | apps, see available flags on "server add" for more. | ||||||
| `, | `, | ||||||
| 	Subcommands: []*cli.Command{ | 	Subcommands: []cli.Command{ | ||||||
| 		serverNewCommand, | 		serverNewCommand, | ||||||
| 		serverAddCommand, | 		serverAddCommand, | ||||||
| 		serverListCommand, | 		serverListCommand, | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							| @ -5,7 +5,7 @@ go 1.16 | |||||||
| require ( | require ( | ||||||
| 	coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52 | 	coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52 | ||||||
| 	github.com/AlecAivazis/survey/v2 v2.3.2 | 	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/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 | ||||||
| 	github.com/docker/cli v20.10.12+incompatible | 	github.com/docker/cli v20.10.12+incompatible | ||||||
| 	github.com/docker/distribution v2.7.1+incompatible | 	github.com/docker/distribution v2.7.1+incompatible | ||||||
| @ -20,8 +20,7 @@ require ( | |||||||
| 	github.com/schollz/progressbar/v3 v3.8.5 | 	github.com/schollz/progressbar/v3 v3.8.5 | ||||||
| 	github.com/schultz-is/passgen v1.0.1 | 	github.com/schultz-is/passgen v1.0.1 | ||||||
| 	github.com/sirupsen/logrus v1.8.1 | 	github.com/sirupsen/logrus v1.8.1 | ||||||
| 	github.com/urfave/cli/v2 v2.3.0 | 	gotest.tools/v3 v3.1.0 | ||||||
| 	gotest.tools/v3 v3.0.3 |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| @ -43,6 +42,7 @@ require ( | |||||||
| 	github.com/morikuni/aec v1.0.0 // indirect | 	github.com/morikuni/aec v1.0.0 // indirect | ||||||
| 	github.com/opencontainers/runc v1.0.2 // indirect | 	github.com/opencontainers/runc v1.0.2 // indirect | ||||||
| 	github.com/theupdateframework/notary v0.7.0 // indirect | 	github.com/theupdateframework/notary v0.7.0 // indirect | ||||||
|  | 	github.com/urfave/cli v1.22.5 | ||||||
| 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect | 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect | ||||||
| 	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 | 	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 | ||||||
| 	golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e | 	golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								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= | 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 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8= | ||||||
| github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg= | 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.20210731094149-b031ea1211e7 h1:asQtdXYbxEYWcwAQqJTVYC/RltB4eqoWKvqWg/LFPOg= | ||||||
| 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/go.mod h1:oZRCMMRS318l07ei4DTqbZoOawfJlJ4yyo8juk2v4Rk= | ||||||
| github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= | 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-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= | ||||||
| github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= | ||||||
| @ -769,10 +769,9 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr | |||||||
| github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= | github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= | ||||||
| github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= | ||||||
| github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | ||||||
| github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= |  | ||||||
| github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | ||||||
| github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= | github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= | ||||||
| github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= | ||||||
| github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= | github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= | ||||||
| github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= | github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= | ||||||
| github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= | github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= | ||||||
| @ -1045,6 +1044,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-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-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.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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| @ -1148,7 +1148,6 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN | |||||||
| gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= | ||||||
| gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |  | ||||||
| gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| @ -1160,8 +1159,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 h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= | ||||||
| gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= | 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.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.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-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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
| honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ import ( | |||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
| 	"coopcloud.tech/abra/pkg/recipe" | 	"coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // AppNameComplete copletes app names | // AppNameComplete copletes app names | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
|  | 	"coopcloud.tech/abra/pkg/formatter" | ||||||
| 	"coopcloud.tech/abra/pkg/upstream/stack" | 	"coopcloud.tech/abra/pkg/upstream/stack" | ||||||
| 	loader "coopcloud.tech/abra/pkg/upstream/stack" | 	loader "coopcloud.tech/abra/pkg/upstream/stack" | ||||||
| 	composetypes "github.com/docker/cli/cli/compose/types" | 	composetypes "github.com/docker/cli/cli/compose/types" | ||||||
| @ -16,10 +17,10 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| // UpdateTag updates an image tag in-place on file system local compose files. | // 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) | 	composeFiles, err := filepath.Glob(pattern) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return false, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	logrus.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", ")) | 	logrus.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", ")) | ||||||
| @ -30,12 +31,12 @@ func UpdateTag(pattern, image, tag, recipeName string) error { | |||||||
| 		envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample") | 		envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample") | ||||||
| 		sampleEnv, err := config.ReadEnv(envSamplePath) | 		sampleEnv, err := config.ReadEnv(envSamplePath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return false, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		compose, err := loader.LoadComposefile(opts, sampleEnv) | 		compose, err := loader.LoadComposefile(opts, sampleEnv) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return false, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		for _, service := range compose.Services { | 		for _, service := range compose.Services { | ||||||
| @ -45,24 +46,26 @@ func UpdateTag(pattern, image, tag, recipeName string) error { | |||||||
|  |  | ||||||
| 			img, _ := reference.ParseNormalizedNamed(service.Image) | 			img, _ := reference.ParseNormalizedNamed(service.Image) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return false, err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			composeImage := reference.Path(img) | 			var composeTag string | ||||||
| 			if strings.Contains(composeImage, "library") { | 			switch img.(type) { | ||||||
| 				// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>, | 			case reference.NamedTagged: | ||||||
| 				// postgres:<tag>, i.e. images which do not have a username in the | 				composeTag = img.(reference.NamedTagged).Tag() | ||||||
| 				// first position of the string | 			default: | ||||||
| 				composeImage = strings.Split(composeImage, "/")[1] | 				logrus.Debugf("unable to parse %s, skipping", img) | ||||||
|  | 				continue | ||||||
| 			} | 			} | ||||||
| 			composeTag := img.(reference.NamedTagged).Tag() |  | ||||||
|  | 			composeImage := formatter.StripTagMeta(reference.Path(img)) | ||||||
|  |  | ||||||
| 			logrus.Debugf("parsed %s from %s", composeTag, service.Image) | 			logrus.Debugf("parsed %s from %s", composeTag, service.Image) | ||||||
|  |  | ||||||
| 			if image == composeImage { | 			if image == composeImage { | ||||||
| 				bytes, err := ioutil.ReadFile(composeFile) | 				bytes, err := ioutil.ReadFile(composeFile) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return err | 					return false, err | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				old := fmt.Sprintf("%s:%s", composeImage, composeTag) | 				old := fmt.Sprintf("%s:%s", composeImage, composeTag) | ||||||
| @ -72,13 +75,13 @@ func UpdateTag(pattern, image, tag, recipeName string) error { | |||||||
| 				logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename) | 				logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename) | ||||||
|  |  | ||||||
| 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil { | 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil { | ||||||
| 					return err | 					return false, err | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return false, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // UpdateLabel updates a label in-place on file system local compose files. | // 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) | 		serverDir := path.Join(SERVERS_DIR, server) | ||||||
| 		files, err := getAllFilesInDirectory(serverDir) | 		files, err := getAllFilesInDirectory(serverDir) | ||||||
| 		if err != nil { | 		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 { | 		for _, file := range files { | ||||||
| 			appName := strings.TrimSuffix(file.Name(), ".env") | 			appName := strings.TrimSuffix(file.Name(), ".env") | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ import ( | |||||||
| 	"github.com/docker/go-units" | 	"github.com/docker/go-units" | ||||||
| 	"github.com/olekukonko/tablewriter" | 	"github.com/olekukonko/tablewriter" | ||||||
| 	"github.com/schollz/progressbar/v3" | 	"github.com/schollz/progressbar/v3" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func ShortenID(str string) string { | func ShortenID(str string) string { | ||||||
| @ -49,3 +50,22 @@ func CreateProgressbar(length int, title string) *progressbar.ProgressBar { | |||||||
| 		progressbar.OptionSetDescription(title), | 		progressbar.OptionSetDescription(title), | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
|  | } | ||||||
|  | |||||||
| @ -163,12 +163,17 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| // UpdateTag updates a recipe tag | // 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) | 	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 = formatter.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 | // Tags list the recipe tags | ||||||
| @ -693,6 +698,10 @@ func recipeCatalogueFSIsLatest() (bool, error) { | |||||||
| func ReadRecipeCatalogue() (RecipeCatalogue, error) { | func ReadRecipeCatalogue() (RecipeCatalogue, error) { | ||||||
| 	recipes := make(RecipeCatalogue) | 	recipes := make(RecipeCatalogue) | ||||||
|  |  | ||||||
|  | 	if err := EnsureCatalogue(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	recipeFSIsLatest, err := recipeCatalogueFSIsLatest() | 	recipeFSIsLatest, err := recipeCatalogueFSIsLatest() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @ -973,9 +982,8 @@ func GetRecipeVersions(recipeName, registryUsername, registryPassword string) (R | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			path := reference.Path(img) | 			path := reference.Path(img) | ||||||
| 			if strings.Contains(path, "library") { |  | ||||||
| 				path = strings.Split(path, "/")[1] | 			path = formatter.StripTagMeta(path) | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			var tag string | 			var tag string | ||||||
| 			switch img.(type) { | 			switch img.(type) { | ||||||
| @ -1041,3 +1049,18 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri | |||||||
|  |  | ||||||
| 	return versions, nil | 	return versions, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
|  | } | ||||||
|  | |||||||
| @ -325,7 +325,7 @@ func connect(username, host, port string, authMethod ssh.AuthMethod, timeout tim | |||||||
| 	conn, err = net.DialTimeout("tcp", hostnameAndPort, timeout) | 	conn, err = net.DialTimeout("tcp", hostnameAndPort, timeout) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logrus.Debugf("tcp dialing %s failed, trying via ~/.ssh/config", hostnameAndPort) | 		logrus.Debugf("tcp dialing %s failed, trying via ~/.ssh/config", hostnameAndPort) | ||||||
| 		hostConfig, err := GetHostConfig(host, username, port) | 		hostConfig, err := GetHostConfig(host, username, port, true) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @ -452,7 +452,7 @@ func GetContextConnDetails(serverName string) (*dockerSSHPkg.Spec, error) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	hostConfig, err := GetHostConfig(serverName, "", "") | 	hostConfig, err := GetHostConfig(serverName, "", "", false) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return &dockerSSHPkg.Spec{}, err | 		return &dockerSSHPkg.Spec{}, err | ||||||
| 	} | 	} | ||||||
| @ -472,30 +472,36 @@ func GetContextConnDetails(serverName string) (*dockerSSHPkg.Spec, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // GetHostConfig retrieves a ~/.ssh/config config for a host. | // GetHostConfig retrieves a ~/.ssh/config config for a host. | ||||||
| func GetHostConfig(hostname, username, port string) (HostConfig, error) { | func GetHostConfig(hostname, username, port string, override bool) (HostConfig, error) { | ||||||
| 	var hostConfig HostConfig | 	var hostConfig HostConfig | ||||||
|  |  | ||||||
| 	if hostname == "" { | 	if hostname == "" || override { | ||||||
| 		if hostname = ssh_config.Get(hostname, "Hostname"); hostname == "" { | 		if sshHost := ssh_config.Get(hostname, "Hostname"); sshHost != "" { | ||||||
| 			logrus.Debugf("no hostname found in SSH config, assuming %s", hostname) | 			hostname = sshHost | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if username == "" { | 	if username == "" || override { | ||||||
| 		if username = ssh_config.Get(hostname, "User"); username == "" { | 		if sshUser := ssh_config.Get(hostname, "User"); sshUser != "" { | ||||||
|  | 			username = sshUser | ||||||
|  | 		} else { | ||||||
| 			systemUser, err := user.Current() | 			systemUser, err := user.Current() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return hostConfig, err | 				return hostConfig, err | ||||||
| 			} | 			} | ||||||
| 			logrus.Debugf("no username found in SSH config or passed on command-line, assuming %s", username) |  | ||||||
| 			username = systemUser.Username | 			username = systemUser.Username | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if port == "" { | 	if port == "" || override { | ||||||
| 		if port = ssh_config.Get(hostname, "Port"); port == "" { | 		if sshPort := ssh_config.Get(hostname, "Port"); sshPort != "" { | ||||||
| 			logrus.Debugf("no port found in SSH config or passed on command-line, assuming 22") | 			// skip override probably correct port with dummy default value from | ||||||
| 			port = "22" | 			// ssh_config which is 22. only when the original port number is empty | ||||||
|  | 			// should we try this default. this might not cover all cases | ||||||
|  | 			// unfortunately. | ||||||
|  | 			if port != "" && sshPort != "22" { | ||||||
|  | 				port = sshPort | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -507,7 +513,6 @@ func GetHostConfig(hostname, username, port string) (HostConfig, error) { | |||||||
| 		} | 		} | ||||||
| 		hostConfig.IdentityFile = idf | 		hostConfig.IdentityFile = idf | ||||||
| 	} else { | 	} else { | ||||||
| 		logrus.Debugf("no identity file found in SSH config for %s", hostname) |  | ||||||
| 		hostConfig.IdentityFile = "" | 		hostConfig.IdentityFile = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | |||||||
| @ -44,6 +44,7 @@ func getConnectionHelper(daemonURL string, sshFlags []string) (*connhelper.Conne | |||||||
| 			ctxConnDetails.Host, | 			ctxConnDetails.Host, | ||||||
| 			ctxConnDetails.User, | 			ctxConnDetails.User, | ||||||
| 			ctxConnDetails.Port, | 			ctxConnDetails.Port, | ||||||
|  | 			false, | ||||||
| 		) | 		) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
|  | |||||||
| @ -479,24 +479,28 @@ func WaitOnService(ctx context.Context, cl *dockerclient.Client, serviceID, appN | |||||||
|  |  | ||||||
| 	go io.Copy(ioutil.Discard, pipeReader) | 	go io.Copy(ioutil.Discard, pipeReader) | ||||||
|  |  | ||||||
| 	timeout := 30 * time.Second | 	timeout := 50 * time.Second | ||||||
|  |  | ||||||
| 	select { | 	select { | ||||||
| 	case err := <-errChan: | 	case err := <-errChan: | ||||||
| 		return err | 		return err | ||||||
| 	case <-time.After(timeout): | 	case <-time.After(timeout): | ||||||
| 		return fmt.Errorf(fmt.Sprintf(` | 		return fmt.Errorf(fmt.Sprintf(` | ||||||
| %s has still not converged (%s second timeout reached) | %s has not converged (%s second timeout reached). | ||||||
|  |  | ||||||
| This does not necessarily mean your deployment has failed, it may just be that | This does not necessarily mean your deployment has failed, it may just be that | ||||||
| the app is taking longer to deploy based on your server resources or network | the app is taking longer to deploy based on your server resources or network | ||||||
| latency. Please run the following the inspect the logs of your deployed app: | latency. | ||||||
|  |  | ||||||
|  | You can track latest deployment status with: | ||||||
|  |  | ||||||
|  |     abra app ps --watch %s | ||||||
|  |  | ||||||
|  | And inspect the logs with: | ||||||
|  |  | ||||||
|     abra app logs %s |     abra app logs %s | ||||||
|  |  | ||||||
| If a service is failing to even start (run "abra app ps %s" to see what | If a service is failing to even start, try smoke out the error with: | ||||||
| services are running) there could be a few things. The follow command will |  | ||||||
| try to smoke those out for you: |  | ||||||
|  |  | ||||||
|     abra app errors --watch %s |     abra app errors --watch %s | ||||||
|  |  | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| ABRA_VERSION="0.3.0-alpha" | ABRA_VERSION="0.3.0-alpha" | ||||||
| ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION" | ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION" | ||||||
| RC_VERSION="0.4.0-alpha-rc1" | RC_VERSION="0.4.0-alpha-rc6" | ||||||
| RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION" | RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION" | ||||||
|  |  | ||||||
| for arg in "$@"; do | for arg in "$@"; do | ||||||
| @ -58,7 +58,7 @@ function install_abra_release { | |||||||
|   checksum=$(echo "$checksums" | grep "$FILENAME" - | sed -En 's/([0-9a-f]{64})\s+'"$FILENAME"'.*/\1/p') |   checksum=$(echo "$checksums" | grep "$FILENAME" - | sed -En 's/([0-9a-f]{64})\s+'"$FILENAME"'.*/\1/p') | ||||||
|  |  | ||||||
|   echo "downloading $ABRA_VERSION $PLATFORM binary release for abra..." |   echo "downloading $ABRA_VERSION $PLATFORM binary release for abra..." | ||||||
|   wget -q --show-progress "$release_url" -O "$HOME/.local/bin/.abra-download" |   wget -q "$release_url" -O "$HOME/.local/bin/.abra-download" | ||||||
|   localsum=$(sha256sum $HOME/.local/bin/.abra-download | sed -En 's/([0-9a-f]{64})\s+.*/\1/p') |   localsum=$(sha256sum $HOME/.local/bin/.abra-download | sed -En 's/([0-9a-f]{64})\s+.*/\1/p') | ||||||
|   echo "checking if checksums match..." |   echo "checking if checksums match..." | ||||||
|   if [[ "$localsum" != "$checksum" ]]; then |   if [[ "$localsum" != "$checksum" ]]; then | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								tests/integration/.abra/servers/server1/gitea1.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/integration/.abra/servers/server1/gitea1.env
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | TYPE=gitea | ||||||
							
								
								
									
										1
									
								
								tests/integration/.abra/servers/server1/wp1.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/integration/.abra/servers/server1/wp1.env
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | TYPE=wordpress | ||||||
							
								
								
									
										1
									
								
								tests/integration/.abra/servers/server2/wp2.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/integration/.abra/servers/server2/wp2.env
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | TYPE=wordpress | ||||||
							
								
								
									
										4
									
								
								tests/integration/.envrc.sample
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tests/integration/.envrc.sample
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | GANDI_TOKEN=... | ||||||
|  | HCLOUD_TOKEN=... | ||||||
|  | REGISTRY_PASSWORD=... | ||||||
|  | REGISTRY_USERNAME=... | ||||||
							
								
								
									
										7
									
								
								tests/integration/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/integration/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | FROM debian:bullseye-slim | ||||||
|  |  | ||||||
|  | RUN apt update && apt install -y wget curl git; | ||||||
|  |  | ||||||
|  | RUN git config --global user.email "integration-tests@coopcloud.tech"; | ||||||
|  |  | ||||||
|  | RUN git config --global user.name "integration-tests"; | ||||||
							
								
								
									
										4
									
								
								tests/integration/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								tests/integration/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | # integration tests | ||||||
|  |  | ||||||
|  | - `cp .envrc.sample .envrc` (fill out values && `direnv allow`) | ||||||
|  | - `TARGET=install.sh make` (ensure `docker context use default`) | ||||||
							
								
								
									
										15
									
								
								tests/integration/app.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										15
									
								
								tests/integration/app.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | source ./common.sh | ||||||
|  |  | ||||||
|  | echo "all apps, all servers" | ||||||
|  | $ABRA app ls | ||||||
|  | printf "\\n\\n\\n" | ||||||
|  |  | ||||||
|  | echo "all wordpress apps, all servers" | ||||||
|  | $ABRA app ls --type wordpress | ||||||
|  | printf "\\n\\n\\n" | ||||||
|  |  | ||||||
|  | echo "all wordpress apps, only server2" | ||||||
|  | $ABRA app ls --type wordpress --server server2 | ||||||
|  | printf "\\n\\n\\n" | ||||||
							
								
								
									
										9
									
								
								tests/integration/autocomplete.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								tests/integration/autocomplete.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | source ./common.sh | ||||||
|  |  | ||||||
|  | $ABRA autocomplete bash | ||||||
|  |  | ||||||
|  | $ABRA autocomplete fizsh | ||||||
|  |  | ||||||
|  | $ABRA autocomplete zsh | ||||||
							
								
								
									
										7
									
								
								tests/integration/catalogue.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								tests/integration/catalogue.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | source ./common.sh | ||||||
|  |  | ||||||
|  | $ABRA catalogue generate --debug | ||||||
|  |  | ||||||
|  | $ABRA catalogue generate gitea --debug | ||||||
							
								
								
									
										22
									
								
								tests/integration/common.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										22
									
								
								tests/integration/common.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | function init() { | ||||||
|  |   ABRA="$HOME/.local/bin/abra" | ||||||
|  |   INSTALLER_URL="https://install.abra.coopcloud.tech" | ||||||
|  |  | ||||||
|  |   for arg in "$@"; do | ||||||
|  |     if [ "$arg" == "--dev" ]; then | ||||||
|  |       ABRA="/src/abra" | ||||||
|  |       INSTALLER_URL="https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer" | ||||||
|  |     fi | ||||||
|  |   done | ||||||
|  |  | ||||||
|  |   export PATH=$PATH:$HOME/.local/bin | ||||||
|  |  | ||||||
|  |   echo "choosing $ABRA as abra command" | ||||||
|  |   echo "choosing $INSTALLER_URL as abra installer url" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | init "$@" | ||||||
| @ -1,101 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| # the goal of this script is to ensure basic core functionality is working |  | ||||||
| # before we make new releases. we try to make a balance between manual testing |  | ||||||
| # and automated testing, i.e. we don't invest too much time in a fragile |  | ||||||
| # automation that costs us more time to maintain and instead just do the test |  | ||||||
| # manually (see `../manual/manual.md` for more). it is a balance which we |  | ||||||
| # figure out together. |  | ||||||
|  |  | ||||||
| set -ex |  | ||||||
|  |  | ||||||
| ABRA="$HOME/.local/bin/abra -d" |  | ||||||
| INSTALLER_URL="https://install.abra.coopcloud.tech" |  | ||||||
|  |  | ||||||
| for arg in "$@"; do |  | ||||||
|   if [ "$arg" == "--dev" ]; then |  | ||||||
|     ABRA="/src/abra -d" |  | ||||||
|     INSTALLER_URL="https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer" |  | ||||||
|   fi |  | ||||||
| done |  | ||||||
|  |  | ||||||
| export PATH=$PATH:$HOME/.local/bin |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # choosing abra executable for test run |  | ||||||
| # ======================================================================== |  | ||||||
| echo "choosing $ABRA as abra executable" |  | ||||||
| echo "choosing $INSTALLER_URL as abra installer url" |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # latest stable release |  | ||||||
| # ======================================================================== |  | ||||||
| wget -O- https://install.abra.autonomic.zone | bash |  | ||||||
| ~/.local/bin/abra -v |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # latest rc release |  | ||||||
| # ======================================================================== |  | ||||||
| wget -O- https://install.abra.autonomic.zone | bash -s -- --rc |  | ||||||
| ~/.local/bin/abra -v |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # upgrade to stable in-place |  | ||||||
| # ======================================================================== |  | ||||||
| $ABRA upgrade |  | ||||||
| ~/.local/bin/abra -v |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # upgrade to rc in-place |  | ||||||
| # ======================================================================== |  | ||||||
| $ABRA upgrade --rc |  | ||||||
| ~/.local/bin/abra -v |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # autocomplete |  | ||||||
| # ======================================================================== |  | ||||||
| $ABRA autocomplete bash |  | ||||||
| $ABRA autocomplete fizsh |  | ||||||
| $ABRA autocomplete zsh |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # record command |  | ||||||
| # ======================================================================== |  | ||||||
| $ABRA record new -p gandi -t A -n e2e -v 192.157.2.21 coopcloud.tech |  | ||||||
| $ABRA record list -p gandi coopcloud.tech | grep -q e2e |  | ||||||
| $ABRA -n record rm -p gandi -t A -n e2e coopcloud.tech |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # catalogue command |  | ||||||
| # ======================================================================== |  | ||||||
| $ABRA catalogue generate |  | ||||||
| $ABRA catalogue generate -s gitea |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # recipe command |  | ||||||
| # ======================================================================== |  | ||||||
| $ABRA recipe new testrecipe |  | ||||||
|  |  | ||||||
| $ABRA recipe list |  | ||||||
| $ABRA recipe list -p cloud |  | ||||||
|  |  | ||||||
| $ABRA recipe versions peertube |  | ||||||
|  |  | ||||||
| $ABRA recipe lint gitea |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # server command |  | ||||||
| # ======================================================================== |  | ||||||
| $ABRA -n server new -p hetzner-cloud --hn e2e |  | ||||||
|  |  | ||||||
| $ABRA server ls | grep -q e2e |  | ||||||
|  |  | ||||||
| $ABRA -n server rm -s -p hetzner-cloud --hn e2e |  | ||||||
|  |  | ||||||
| # ======================================================================== |  | ||||||
| # app command |  | ||||||
| # ======================================================================== |  | ||||||
|  |  | ||||||
| $ABRA app ls |  | ||||||
|  |  | ||||||
| $ABRA app ls -S |  | ||||||
							
								
								
									
										15
									
								
								tests/integration/install.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										15
									
								
								tests/integration/install.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | source ./common.sh | ||||||
|  |  | ||||||
|  | wget -O- https://install.abra.autonomic.zone | bash | ||||||
|  | ~/.local/bin/abra -v | ||||||
|  |  | ||||||
|  | wget -O- https://install.abra.autonomic.zone | bash -s -- --rc | ||||||
|  | ~/.local/bin/abra -v | ||||||
|  |  | ||||||
|  | $ABRA upgrade | ||||||
|  | ~/.local/bin/abra -v | ||||||
|  |  | ||||||
|  | $ABRA upgrade --rc | ||||||
|  | ~/.local/bin/abra -v | ||||||
							
								
								
									
										11
									
								
								tests/integration/makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tests/integration/makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | default: | ||||||
|  | 	@docker run \ | ||||||
|  | 		-v $$(pwd)/../../:/src \ | ||||||
|  | 		-v $$(pwd)/.abra:/root/.abra \ | ||||||
|  | 		--env-file .envrc \ | ||||||
|  | 		decentral1se/abra-int:latest \ | ||||||
|  | 		sh -c '\ | ||||||
|  | 			echo "Running $(TARGET)..."; \ | ||||||
|  | 			cd /src/tests/integration; \ | ||||||
|  | 			bash "$(TARGET)" -- --dev \ | ||||||
|  | 		' | ||||||
							
								
								
									
										12
									
								
								tests/integration/recipe.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								tests/integration/recipe.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | source ./common.sh | ||||||
|  |  | ||||||
|  | $ABRA recipe new testrecipe | ||||||
|  |  | ||||||
|  | $ABRA recipe list | ||||||
|  | $ABRA recipe list -p cloud | ||||||
|  |  | ||||||
|  | $ABRA recipe versions peertube | ||||||
|  |  | ||||||
|  | $ABRA recipe lint gitea | ||||||
							
								
								
									
										9
									
								
								tests/integration/records.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								tests/integration/records.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | source ./common.sh | ||||||
|  |  | ||||||
|  | $ABRA record new -p gandi -t A -n int-core -v 192.157.2.21 coopcloud.tech | ||||||
|  |  | ||||||
|  | $ABRA record list -p gandi coopcloud.tech | grep -q int-core | ||||||
|  |  | ||||||
|  | $ABRA -n record rm -p gandi -t A -n int-core coopcloud.tech | ||||||
							
								
								
									
										9
									
								
								tests/integration/server.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								tests/integration/server.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | source ./common.sh | ||||||
|  |  | ||||||
|  | $ABRA -n server new -p hetzner-cloud --hn int-core | ||||||
|  |  | ||||||
|  | $ABRA server ls | grep -q int-core | ||||||
|  |  | ||||||
|  | $ABRA -n server rm -s -p hetzner-cloud --hn int-core | ||||||
| @ -1 +0,0 @@ | |||||||
| TYPE=works |  | ||||||
| @ -1,84 +0,0 @@ | |||||||
| --- |  | ||||||
|  |  | ||||||
| # The goal of this compose file is to have a testing ground for understanding |  | ||||||
| # what cases we need to handle to get stable deployments. For that, we need to |  | ||||||
| # work with healthchecks and deploy configurations quite closely. If you run |  | ||||||
| # the `make symlink` target then this will be loaded into a "fake" app on your |  | ||||||
| # local machine which you can deploy with `abra`. |  | ||||||
|  |  | ||||||
| version: "3.8" |  | ||||||
| services: |  | ||||||
|   r1_should_work: |  | ||||||
|     image: redis:alpine |  | ||||||
|     deploy: |  | ||||||
|       update_config: |  | ||||||
|         failure_action: rollback |  | ||||||
|         order: start-first |  | ||||||
|       rollback_config: |  | ||||||
|         order: start-first |  | ||||||
|       restart_policy: |  | ||||||
|         max_attempts: 1 |  | ||||||
|     healthcheck: |  | ||||||
|       test: redis-cli ping |  | ||||||
|       interval: 2s |  | ||||||
|       retries: 3 |  | ||||||
|       start_period: 1s |  | ||||||
|       timeout: 3s |  | ||||||
|  |  | ||||||
|   r2_broken_health_check: |  | ||||||
|     image: redis:alpine |  | ||||||
|     deploy: |  | ||||||
|       update_config: |  | ||||||
|         failure_action: rollback |  | ||||||
|         order: start-first |  | ||||||
|       rollback_config: |  | ||||||
|         order: start-first |  | ||||||
|       restart_policy: |  | ||||||
|         max_attempts: 3 |  | ||||||
|     healthcheck: |  | ||||||
|       test: foobar |  | ||||||
|       interval: 2s |  | ||||||
|       retries: 3 |  | ||||||
|       start_period: 1s |  | ||||||
|       timeout: 3s |  | ||||||
|  |  | ||||||
|   r3_no_health_check: |  | ||||||
|     image: redis:alpine |  | ||||||
|     deploy: |  | ||||||
|       update_config: |  | ||||||
|         failure_action: rollback |  | ||||||
|         order: start-first |  | ||||||
|       rollback_config: |  | ||||||
|         order: start-first |  | ||||||
|       restart_policy: |  | ||||||
|         max_attempts: 3 |  | ||||||
|  |  | ||||||
|   r4_disabled_health_check: |  | ||||||
|     image: redis:alpine |  | ||||||
|     deploy: |  | ||||||
|       update_config: |  | ||||||
|         failure_action: rollback |  | ||||||
|         order: start-first |  | ||||||
|       rollback_config: |  | ||||||
|         order: start-first |  | ||||||
|       restart_policy: |  | ||||||
|         max_attempts: 3 |  | ||||||
|     healthcheck: |  | ||||||
|       disable: true |  | ||||||
|  |  | ||||||
|   r5_should_also_work: |  | ||||||
|     image: redis:alpine |  | ||||||
|     deploy: |  | ||||||
|       update_config: |  | ||||||
|         failure_action: rollback |  | ||||||
|         order: start-first |  | ||||||
|       rollback_config: |  | ||||||
|         order: start-first |  | ||||||
|       restart_policy: |  | ||||||
|         max_attempts: 1 |  | ||||||
|     healthcheck: |  | ||||||
|       test: redis-cli ping |  | ||||||
|       interval: 2s |  | ||||||
|       retries: 3 |  | ||||||
|       start_period: 1s |  | ||||||
|       timeout: 3s |  | ||||||
| @ -1 +0,0 @@ | |||||||
| TYPE=works |  | ||||||
| @ -1,53 +1,40 @@ | |||||||
| # manual test plan | # manual test plan | ||||||
|  |  | ||||||
| Best served after running `make int-core` which assures most core functionality |  | ||||||
| is still working. These manual tests are for testing things that are hard to |  | ||||||
| wire up for testing in an automated way. |  | ||||||
|  |  | ||||||
| ## recipe publish | ## recipe publish | ||||||
|  |  | ||||||
| - `abra recipe upgrade <recipe>` | - `abra recipe upgrade <recipe>` | ||||||
|  |   - `cd ~/.abra/apps/<recipe>/ && git diff` to ensure changes made | ||||||
|  |  | ||||||
| - `abra recipe sync <recipe>` | - `abra recipe sync <recipe>` | ||||||
| - `abra recipe release --publish <recipe>` |   - `cd ~/.abra/apps/<recipe>/ && git diff` to ensure changes made | ||||||
|  |  | ||||||
| ## automagic traefik deploy | - `abra recipe release <recipe> --dry-run` | ||||||
|  |   - prompts should be correct, read what `abra` asks you carefully | ||||||
| - `abra server add -p -t <server>` |  | ||||||
|  |  | ||||||
| ## deploy, upgrade, rollback | ## deploy, upgrade, rollback | ||||||
|  |  | ||||||
|  | - `abra app deploy --chaos <app>` | ||||||
|  | - `abra app deploy --force <app>` | ||||||
| - `abra app deploy <app>` | - `abra app deploy <app>` | ||||||
| - `abra app upgrade <app>` |  | ||||||
| - `abra app rollback <app>` | - `abra app rollback <app>` | ||||||
|  | - `abra app upgrade <app>` | ||||||
| ## backup & restore |  | ||||||
|  |  | ||||||
| - `abra app deploy <app>` |  | ||||||
| - `abra app backup <app>` |  | ||||||
| - `abra app undeploy <app>` |  | ||||||
| - `abra app volume remove --force <app>` |  | ||||||
| - `abra app deploy <app>` |  | ||||||
| - `abra app restore <app>` |  | ||||||
|  |  | ||||||
| ## app day-to-day ops | ## app day-to-day ops | ||||||
|  |  | ||||||
| ### easy mode |  | ||||||
|  |  | ||||||
| - `abra app config <app>` |  | ||||||
| - `abra app check <app>` | - `abra app check <app>` | ||||||
| - `abra app ps <app>` | - `abra app config <app>` | ||||||
| - `abra app logs <app>` |  | ||||||
| - `abra app cp <app>` | - `abra app cp <app>` | ||||||
| - `abra app run <app>` |  | ||||||
| - `abra app secret ls <app>` |  | ||||||
| - `abra app volume ls <app>` |  | ||||||
|  |  | ||||||
| ### hard mode |  | ||||||
|  |  | ||||||
| - `abra app restart <app>` |  | ||||||
| - `abra app remove <app>` |  | ||||||
| - `abra app secret insert <app> foo v1 bar` |  | ||||||
| - `abra app secret remove <app> foo` |  | ||||||
| - `abra app secret generate --all` |  | ||||||
| - `abra app volume remove --force <app>` |  | ||||||
| - `abra app errors -w <app>` | - `abra app errors -w <app>` | ||||||
|  | - `abra app logs <app>` | ||||||
|  | - `abra app ls --status <app>` | ||||||
|  | - `abra app new --secrets <recipe>` | ||||||
|  | - `abra app ps <app>` | ||||||
|  | - `abra app remove <app>` | ||||||
|  | - `abra app restart <app>` | ||||||
|  | - `abra app run <app>` | ||||||
|  | - `abra app secret generate --all` | ||||||
|  | - `abra app secret insert <app> foo v1 bar` | ||||||
|  | - `abra app secret ls <app>` | ||||||
|  | - `abra app secret remove <app> foo` | ||||||
|  | - `abra app volume ls <app>` | ||||||
|  | - `abra app volume remove --force <app>` | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user