diff --git a/.e2e.env.sample b/.e2e.env.sample deleted file mode 100644 index f16b6c15..00000000 --- a/.e2e.env.sample +++ /dev/null @@ -1,4 +0,0 @@ -GANDI_TOKEN=... -HCLOUD_TOKEN=... -REGISTRY_PASSWORD=... -REGISTRY_USERNAME=... diff --git a/.envrc.sample b/.envrc.sample index fd5b5dea..3f8d827c 100644 --- a/.envrc.sample +++ b/.envrc.sample @@ -1,6 +1,7 @@ go env -w GOPRIVATE=coopcloud.tech # export PASSWORD_STORE_DIR=$(pwd)/../../autonomic/passwords/passwords/ -# export HCLOUD_TOKEN=$(pass show logins/hetzner/cicd/api_key) -# export CAPSUL_TOKEN=... -# export GITEA_TOKEN=... + +# export ABRA_DIR="$HOME/.abra_test" +# export ABRA_TEST_DOMAIN=test.example.com +# export ABRA_SKIP_TEARDOWN=1 # for faster feedback when developing tests diff --git a/.gitignore b/.gitignore index 85da835c..4b36ca73 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ /kadabra abra dist/ -tests/integration/.abra/catalogue +tests/integration/.bats vendor/ diff --git a/AUTHORS.md b/AUTHORS.md index bb499cb0..6698c3cb 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,7 +1,7 @@ # authors > 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 some :heart: +> please do add yourself! This is a community project, let's show some 💞 - 3wordchant - cassowary diff --git a/Makefile b/Makefile index 3349a1b9..ba0759f4 100644 --- a/Makefile +++ b/Makefile @@ -9,12 +9,18 @@ export GOPRIVATE=coopcloud.tech all: format check build test -run: +run-abra: @go run -ldflags=$(LDFLAGS) $(ABRA) -install: +run-kadabra: + @go run -ldflags=$(LDFLAGS) $(KADABRA) + +install-abra: @go install -ldflags=$(LDFLAGS) $(ABRA) +install-kadaabra: + @go install -ldflags=$(LDFLAGS) $(KADABRA) + build-abra: @go build -v -ldflags=$(LDFLAGS) $(ABRA) @@ -36,15 +42,8 @@ check: @test -z $$(gofmt -l .) || \ (echo "gofmt: formatting issue - run 'make format' to resolve" && exit 1) -test: +unit-test: @go test ./... -cover -v loc: @find . -name "*.go" | xargs wc -l - -loc-author: - @git ls-files -z | \ - xargs -0rn 1 -P "$$(nproc)" -I{} sh -c 'git blame -w -M -C -C --line-porcelain -- {} | grep -I --line-buffered "^author "' | \ - sort -f | \ - uniq -ic | \ - sort -n diff --git a/README.md b/README.md index f307c04d..0b50bb2f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,6 @@ The Co-op Cloud utility belt 🎩🐇 -`abra` is the flagship client & command-line tool for Co-op Cloud. It has been developed specifically for the purpose of making the day-to-day operations of [operators](https://docs.coopcloud.tech/operators/) and [maintainers](https://docs.coopcloud.tech/maintainers/) pleasant & convenient. It is libre software, written in [Go](https://go.dev) and maintained and extended by the community :heart: +`abra` is the flagship client & command-line tool for Co-op Cloud. It has been developed specifically for the purpose of making the day-to-day operations of [operators](https://docs.coopcloud.tech/operators/) and [maintainers](https://docs.coopcloud.tech/maintainers/) pleasant & convenient. It is libre software, written in [Go](https://go.dev) and maintained and extended by the community 💖 Please see [docs.coopcloud.tech/abra](https://docs.coopcloud.tech/abra) for help on install, upgrade, hacking, troubleshooting & more! diff --git a/cli/app/backup.go b/cli/app/backup.go index 9b9832e0..18d5d2df 100644 --- a/cli/app/backup.go +++ b/cli/app/backup.go @@ -15,8 +15,7 @@ import ( "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" containerPkg "coopcloud.tech/abra/pkg/container" - "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" + recipePkg "coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/upstream/container" "github.com/docker/cli/cli/command" "github.com/docker/docker/api/types" @@ -72,15 +71,9 @@ This file is a compressed archive which contains all backup paths. To see paths, This single file can be used to restore your app. See "abra app restore" for more. `, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) - cl, err := client.New(app.Server) - if err != nil { - logrus.Fatal(err) - } - - recipe, err := recipe.Get(app.Recipe, conf) + recipe, err := recipePkg.Get(app.Recipe, internal.Offline) if err != nil { logrus.Fatal(err) } @@ -114,6 +107,11 @@ This single file can be used to restore your app. See "abra app restore" for mor } } + cl, err := client.New(app.Server) + if err != nil { + logrus.Fatal(err) + } + serviceName := c.Args().Get(1) if serviceName != "" { backupConfig, ok := backupConfigs[serviceName] diff --git a/cli/app/check.go b/cli/app/check.go index 72c07a65..572b3d6a 100644 --- a/cli/app/check.go +++ b/cli/app/check.go @@ -8,7 +8,7 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/config" - "coopcloud.tech/abra/pkg/runtime" + "coopcloud.tech/abra/pkg/recipe" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -16,16 +16,19 @@ import ( var appCheckCommand = cli.Command{ Name: "check", Aliases: []string{"chk"}, - Usage: "Check if app is configured correctly", + Usage: "Check if an app is configured correctly", ArgsUsage: "", Flags: []cli.Flag{ internal.DebugFlag, - internal.OfflineFlag, }, - Before: internal.SubCommandBefore, + Before: internal.SubCommandBefore, + BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) + + if err := recipe.EnsureExists(app.Recipe); err != nil { + logrus.Fatal(err) + } envSamplePath := path.Join(config.RECIPES_DIR, app.Recipe, ".env.sample") if _, err := os.Stat(envSamplePath); err != nil { @@ -56,5 +59,4 @@ var appCheckCommand = cli.Command{ return nil }, - BashComplete: autocomplete.AppNameComplete, } diff --git a/cli/app/cmd.go b/cli/app/cmd.go index 928fb3d0..23e98494 100644 --- a/cli/app/cmd.go +++ b/cli/app/cmd.go @@ -12,7 +12,7 @@ import ( "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" - "coopcloud.tech/abra/pkg/runtime" + "coopcloud.tech/abra/pkg/recipe" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -39,16 +39,13 @@ Example: internal.LocalCmdFlag, internal.RemoteUserFlag, internal.TtyFlag, - internal.OfflineFlag, }, BashComplete: autocomplete.AppNameComplete, Before: internal.SubCommandBefore, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) - cl, err := client.New(app.Server) - if err != nil { + if err := recipe.EnsureExists(app.Recipe); err != nil { logrus.Fatal(err) } @@ -67,6 +64,10 @@ Example: } if internal.LocalCmd { + if !(len(c.Args()) >= 2) { + internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments")) + } + cmdName := c.Args().Get(1) if err := internal.EnsureCommand(abraSh, app.Recipe, cmdName); err != nil { logrus.Fatal(err) @@ -78,6 +79,7 @@ Example: for k, v := range app.Env { exportEnv = exportEnv + fmt.Sprintf("%s='%s'; ", k, v) } + var sourceAndExec string if hasCmdArgs { logrus.Debugf("parsed following command arguments: %s", parsedCmdArgs) @@ -98,6 +100,10 @@ Example: logrus.Fatal(err) } } else { + if !(len(c.Args()) >= 3) { + internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments")) + } + targetServiceName := c.Args().Get(1) cmdName := c.Args().Get(2) @@ -129,6 +135,11 @@ Example: logrus.Debug("did not detect any command arguments") } + cl, err := client.New(app.Server) + if err != nil { + logrus.Fatal(err) + } + if err := internal.RunCmdRemote(cl, app, abraSh, targetServiceName, cmdName, parsedCmdArgs); err != nil { logrus.Fatal(err) } diff --git a/cli/app/config.go b/cli/app/config.go index 6aebb786..9404fae4 100644 --- a/cli/app/config.go +++ b/cli/app/config.go @@ -20,9 +20,9 @@ var appConfigCommand = cli.Command{ ArgsUsage: "", Flags: []cli.Flag{ internal.DebugFlag, - internal.OfflineFlag, }, - Before: internal.SubCommandBefore, + Before: internal.SubCommandBefore, + BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { appName := c.Args().First() @@ -61,5 +61,4 @@ var appConfigCommand = cli.Command{ return nil }, - BashComplete: autocomplete.AppNameComplete, } diff --git a/cli/app/cp.go b/cli/app/cp.go index 910fbc05..937c7975 100644 --- a/cli/app/cp.go +++ b/cli/app/cp.go @@ -12,7 +12,6 @@ import ( "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/container" "coopcloud.tech/abra/pkg/formatter" - "coopcloud.tech/abra/pkg/runtime" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" dockerClient "github.com/docker/docker/client" @@ -28,10 +27,9 @@ var appCpCommand = cli.Command{ Flags: []cli.Flag{ internal.DebugFlag, internal.NoInputFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, - Usage: "Copy files to/from a running app service", + Usage: "Copy files to/from a deployed app service", Description: ` Copy files to and from any app service file system. @@ -41,16 +39,11 @@ If you want to copy a myfile.txt to the root of the app service: And if you want to copy that file back to your current working directory locally: - abra app cp app:/myfile.txt . + abra app cp app:/myfile.txt . `, + BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) - - cl, err := client.New(app.Server) - if err != nil { - logrus.Fatal(err) - } + app := internal.ValidateApp(c) src := c.Args().Get(1) dst := c.Args().Get(2) @@ -92,19 +85,23 @@ And if you want to copy that file back to your current working directory locally logrus.Debugf("assuming transfer is going TO the container") } - if !isToContainer { - if _, err := os.Stat(dstPath); os.IsNotExist(err) { - logrus.Fatalf("%s does not exist locally?", dstPath) + if isToContainer { + if _, err := os.Stat(srcPath); os.IsNotExist(err) { + logrus.Fatalf("%s does not exist locally?", srcPath) } } + cl, err := client.New(app.Server) + if err != nil { + logrus.Fatal(err) + } + if err := configureAndCp(c, cl, app, srcPath, dstPath, service, isToContainer); err != nil { logrus.Fatal(err) } return nil }, - BashComplete: autocomplete.AppNameComplete, } func configureAndCp( @@ -126,10 +123,6 @@ func configureAndCp( logrus.Debugf("retrieved %s as target container on %s", formatter.ShortenID(container.ID), app.Server) if isToContainer { - if _, err := os.Stat(srcPath); err != nil { - logrus.Fatalf("%s does not exist?", srcPath) - } - toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip} content, err := archive.TarWithOptions(srcPath, toTarOpts) if err != nil { diff --git a/cli/app/deploy.go b/cli/app/deploy.go index 44bbfd0c..81fd780a 100644 --- a/cli/app/deploy.go +++ b/cli/app/deploy.go @@ -3,14 +3,9 @@ package app import ( "context" "fmt" - "io/ioutil" - "os" - "path" - "strings" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" @@ -20,8 +15,6 @@ import ( "coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/upstream/stack" - "github.com/AlecAivazis/survey/v2" - dockerClient "github.com/docker/docker/client" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -48,28 +41,36 @@ for this you need to look at the "abra app upgrade " command. You may pass "--force" to re-deploy the same version again. This can be useful if the container runtime has gotten into a weird state. -Chas mode ("--chaos") will deploy your local checkout of a recipe as-is, +Chaos 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 recipes. `, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) stackName := app.StackName() - cl, err := client.New(app.Server) - if err != nil { + if err := recipe.EnsureExists(app.Recipe); err != nil { logrus.Fatal(err) } if !internal.Chaos { - if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil { + if err := recipe.EnsureIsClean(app.Recipe); err != nil { + logrus.Fatal(err) + } + + if !internal.Offline { + if err := recipe.EnsureUpToDate(app.Recipe); err != nil { + logrus.Fatal(err) + } + } + + if err := recipe.EnsureLatest(app.Recipe); err != nil { logrus.Fatal(err) } } - r, err := recipe.Get(app.Recipe, conf) + r, err := recipe.Get(app.Recipe, internal.Offline) if err != nil { logrus.Fatal(err) } @@ -80,6 +81,11 @@ recipes. logrus.Debugf("checking whether %s is already deployed", stackName) + cl, err := client.New(app.Server) + if err != nil { + logrus.Fatal(err) + } + isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) if err != nil { logrus.Fatal(err) @@ -95,8 +101,8 @@ recipes. isLatestHash := false version := deployedVersion - if version == "unknown" && !internal.Chaos { - catl, err := recipe.ReadRecipeCatalogue(conf) + if !internal.Chaos { + catl, err := recipe.ReadRecipeCatalogue(internal.Offline) if err != nil { logrus.Fatal(err) } @@ -118,20 +124,11 @@ recipes. isLatestHash = true version = formatter.SmallSHA(head.String()) logrus.Warn("no versions detected, using latest commit") - if err := recipe.EnsureLatest(app.Recipe, conf); err != nil { - logrus.Fatal(err) - } - } - } - - if version == "unknown" && !internal.Chaos { - logrus.Debugf("choosing %s as version to deploy", version) - if err := recipe.EnsureVersion(app.Recipe, version); err != nil { - logrus.Fatal(err) } } if version != "unknown" && !internal.Chaos && !isLatestHash { + logrus.Debugf("choosing %s as version to deploy", version) if err := recipe.EnsureVersion(app.Recipe, version); err != nil { logrus.Fatal(err) } @@ -159,6 +156,7 @@ recipes. if err != nil { logrus.Fatal(err) } + deployOpts := stack.Deploy{ Composefiles: composeFiles, Namespace: stackName, @@ -169,13 +167,14 @@ recipes. if err != nil { logrus.Fatal(err) } + config.ExposeAllEnv(stackName, compose, app.Env) config.SetRecipeLabel(compose, stackName, app.Recipe) config.SetChaosLabel(compose, stackName, internal.Chaos) config.SetChaosVersionLabel(compose, stackName, version) config.SetUpdateLabel(compose, stackName, app.Env) - if err := DeployOverview(app, version, "continue with deployment?"); err != nil { + if err := internal.DeployOverview(app, version, "continue with deployment?"); err != nil { logrus.Fatal(err) } @@ -205,175 +204,10 @@ recipes. postDeployCmds, ok := app.Env["POST_DEPLOY_CMDS"] if ok && !internal.DontWaitConverge { logrus.Debugf("run the following post-deploy commands: %s", postDeployCmds) - if err := PostCmds(cl, app, postDeployCmds); err != nil { + if err := internal.PostCmds(cl, app, postDeployCmds); err != nil { logrus.Fatalf("attempting to run post deploy commands, saw: %s", err) } } return nil }, } - -// PostCmds parses a string of commands and executes them inside of the respective services -// the commands string must have the following format: -// " | |... " -func PostCmds(cl *dockerClient.Client, app config.App, commands string) error { - abraSh := path.Join(config.RECIPES_DIR, app.Recipe, "abra.sh") - if _, err := os.Stat(abraSh); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf(fmt.Sprintf("%s does not exist for %s?", abraSh, app.Name)) - } - return err - } - - for _, command := range strings.Split(commands, "|") { - commandParts := strings.Split(command, " ") - if len(commandParts) < 2 { - return fmt.Errorf(fmt.Sprintf("not enough arguments: %s", command)) - } - targetServiceName := commandParts[0] - cmdName := commandParts[1] - parsedCmdArgs := "" - if len(commandParts) > 2 { - parsedCmdArgs = fmt.Sprintf("%s ", strings.Join(commandParts[2:], " ")) - } - logrus.Infof("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName) - - if err := internal.EnsureCommand(abraSh, app.Recipe, cmdName); err != nil { - return err - } - - serviceNames, err := config.GetAppServiceNames(app.Name) - if err != nil { - return err - } - - matchingServiceName := false - for _, serviceName := range serviceNames { - if serviceName == targetServiceName { - matchingServiceName = true - } - } - - if !matchingServiceName { - return fmt.Errorf(fmt.Sprintf("no service %s for %s?", targetServiceName, app.Name)) - } - - logrus.Debugf("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName) - - internal.Tty = true - if err := internal.RunCmdRemote(cl, app, abraSh, targetServiceName, cmdName, parsedCmdArgs); err != nil { - return err - } - } - return nil -} - -// DeployOverview shows a deployment overview -func DeployOverview(app config.App, version, message string) error { - tableCol := []string{"server", "recipe", "config", "domain", "version"} - table := formatter.CreateTable(tableCol) - - deployConfig := "compose.yml" - if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { - deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") - } - - server := app.Server - if app.Server == "default" { - server = "local" - } - - table.Append([]string{server, app.Recipe, deployConfig, app.Domain, version}) - table.Render() - - if internal.NoInput { - return nil - } - - response := false - prompt := &survey.Confirm{ - Message: message, - } - - if err := survey.AskOne(prompt, &response); err != nil { - return err - } - - if !response { - logrus.Fatal("exiting as requested") - } - - return nil -} - -// NewVersionOverview shows an upgrade or downgrade overview -func NewVersionOverview(app config.App, currentVersion, newVersion, releaseNotes string) error { - tableCol := []string{"server", "recipe", "config", "domain", "current version", "to be deployed"} - table := formatter.CreateTable(tableCol) - - deployConfig := "compose.yml" - if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { - deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") - } - - server := app.Server - if app.Server == "default" { - server = "local" - } - - table.Append([]string{server, app.Recipe, deployConfig, app.Domain, currentVersion, newVersion}) - table.Render() - - if releaseNotes == "" { - var err error - releaseNotes, err = GetReleaseNotes(app.Recipe, newVersion) - if err != nil { - return err - } - } - - if releaseNotes != "" && newVersion != "" { - fmt.Println() - fmt.Println(fmt.Sprintf("%s release notes:\n\n%s", newVersion, releaseNotes)) - } else { - logrus.Warnf("no release notes available for %s", newVersion) - } - - if internal.NoInput { - return nil - } - - response := false - prompt := &survey.Confirm{ - Message: "continue with deployment?", - } - - if err := survey.AskOne(prompt, &response); err != nil { - return err - } - - if !response { - logrus.Fatal("exiting as requested") - } - - return nil -} - -// GetReleaseNotes prints release notes for a recipe version -func GetReleaseNotes(recipeName, version string) (string, error) { - if version == "" { - return "", nil - } - - fpath := path.Join(config.RECIPES_DIR, recipeName, "release", version) - - if _, err := os.Stat(fpath); !os.IsNotExist(err) { - releaseNotes, err := ioutil.ReadFile(fpath) - if err != nil { - return "", err - } - return string(releaseNotes), nil - } - - return "", nil -} diff --git a/cli/app/errors.go b/cli/app/errors.go index 4d6d21cb..79425be8 100644 --- a/cli/app/errors.go +++ b/cli/app/errors.go @@ -12,7 +12,6 @@ import ( "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" stack "coopcloud.tech/abra/pkg/upstream/stack" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" @@ -56,8 +55,7 @@ the logs. Before: internal.SubCommandBefore, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) cl, err := client.New(app.Server) if err != nil { @@ -74,25 +72,23 @@ the logs. } if !internal.Watch { - if err := checkErrors(c, cl, app, conf); err != nil { + if err := checkErrors(c, cl, app); err != nil { logrus.Fatal(err) } return nil } for { - if err := checkErrors(c, cl, app, conf); err != nil { + if err := checkErrors(c, cl, app); err != nil { logrus.Fatal(err) } time.Sleep(2 * time.Second) } - - return nil }, } -func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App, conf *runtime.Config) error { - recipe, err := recipe.Get(app.Recipe, conf) +func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error { + recipe, err := recipe.Get(app.Recipe, internal.Offline) if err != nil { return err } diff --git a/cli/app/list.go b/cli/app/list.go index b3e01606..08dff6f2 100644 --- a/cli/app/list.go +++ b/cli/app/list.go @@ -11,7 +11,6 @@ import ( "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/tagcmp" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -84,8 +83,6 @@ can take some time. }, Before: internal.SubCommandBefore, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - appFiles, err := config.LoadAppFiles(listAppServer) if err != nil { logrus.Fatal(err) @@ -113,7 +110,7 @@ can take some time. logrus.Fatal(err) } - catl, err = recipe.ReadRecipeCatalogue(conf) + catl, err = recipe.ReadRecipeCatalogue(internal.Offline) if err != nil { logrus.Fatal(err) } diff --git a/cli/app/logs.go b/cli/app/logs.go index b1cd992c..5c60f324 100644 --- a/cli/app/logs.go +++ b/cli/app/logs.go @@ -11,8 +11,8 @@ import ( "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/service" + "coopcloud.tech/abra/pkg/upstream/stack" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" dockerClient "github.com/docker/docker/client" @@ -79,23 +79,27 @@ var appLogsCommand = cli.Command{ internal.StdErrOnlyFlag, internal.SinceLogsFlag, internal.DebugFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New( - runtime.WithOffline(internal.Offline), - runtime.WithEnsureRecipeExists(false), - ) - - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) + stackName := app.StackName() cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } + isDeployed, _, err := stack.IsDeployed(context.Background(), cl, stackName) + if err != nil { + logrus.Fatal(err) + } + + if !isDeployed { + logrus.Fatalf("%s is not deployed?", app.Name) + } + logOpts.Since = internal.SinceLogs serviceName := c.Args().Get(1) diff --git a/cli/app/new.go b/cli/app/new.go index 9034b3bc..cba41dec 100644 --- a/cli/app/new.go +++ b/cli/app/new.go @@ -13,7 +13,6 @@ import ( "coopcloud.tech/abra/pkg/jsontable" "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/secret" "github.com/AlecAivazis/survey/v2" dockerClient "github.com/docker/docker/client" @@ -55,17 +54,19 @@ var appNewCommand = cli.Command{ internal.SecretsFlag, internal.OfflineFlag, }, - Before: internal.SubCommandBefore, - ArgsUsage: "[]", + Before: internal.SubCommandBefore, + ArgsUsage: "[]", + BashComplete: autocomplete.RecipeNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New( - runtime.WithOffline(internal.Offline), - runtime.WithEnsureRecipeUpToDate(false), - ) + recipe := internal.ValidateRecipe(c) - recipe := internal.ValidateRecipeWithPrompt(c, conf) + if !internal.Offline { + if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil { + logrus.Fatal(err) + } + } - if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil { + if err := recipePkg.EnsureLatest(recipe.Name); err != nil { logrus.Fatal(err) } @@ -144,7 +145,6 @@ var appNewCommand = cli.Command{ return nil }, - BashComplete: autocomplete.RecipeNameComplete, } // AppSecrets represents all app secrest diff --git a/cli/app/ps.go b/cli/app/ps.go index 9f0665c2..b593a64f 100644 --- a/cli/app/ps.go +++ b/cli/app/ps.go @@ -10,7 +10,6 @@ import ( "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/formatter" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/service" stack "coopcloud.tech/abra/pkg/upstream/stack" "github.com/buger/goterm" @@ -30,13 +29,11 @@ var appPsCommand = cli.Command{ Flags: []cli.Flag{ internal.WatchFlag, internal.DebugFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) cl, err := client.New(app.Server) if err != nil { diff --git a/cli/app/remove.go b/cli/app/remove.go index 40b5802a..ea4efedf 100644 --- a/cli/app/remove.go +++ b/cli/app/remove.go @@ -8,7 +8,6 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" - "coopcloud.tech/abra/pkg/runtime" stack "coopcloud.tech/abra/pkg/upstream/stack" "github.com/AlecAivazis/survey/v2" "github.com/docker/docker/api/types" @@ -49,8 +48,7 @@ flag. BashComplete: autocomplete.AppNameComplete, Before: internal.SubCommandBefore, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) if !internal.Force && !internal.NoInput { response := false diff --git a/cli/app/restart.go b/cli/app/restart.go index 0dede1fa..15b4cfd0 100644 --- a/cli/app/restart.go +++ b/cli/app/restart.go @@ -8,7 +8,6 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" - "coopcloud.tech/abra/pkg/runtime" upstream "coopcloud.tech/abra/pkg/upstream/service" stack "coopcloud.tech/abra/pkg/upstream/stack" "github.com/sirupsen/logrus" @@ -28,8 +27,7 @@ var appRestartCommand = cli.Command{ Description: `This command restarts a service within a deployed app.`, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) serviceNameShort := c.Args().Get(1) if serviceNameShort == "" { @@ -42,6 +40,15 @@ var appRestartCommand = cli.Command{ logrus.Fatal(err) } + isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) + if err != nil { + logrus.Fatal(err) + } + + if !isDeployed { + logrus.Fatalf("%s is not deployed?", app.Name) + } + serviceName := fmt.Sprintf("%s_%s", app.StackName(), serviceNameShort) logrus.Debugf("attempting to scale %s to 0 (restart logic)", serviceName) diff --git a/cli/app/restore.go b/cli/app/restore.go index 6303ad28..82e0db01 100644 --- a/cli/app/restore.go +++ b/cli/app/restore.go @@ -12,7 +12,6 @@ import ( "coopcloud.tech/abra/pkg/config" containerPkg "coopcloud.tech/abra/pkg/container" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/upstream/container" "github.com/docker/cli/cli/command" "github.com/docker/docker/api/types" @@ -49,17 +48,17 @@ restoring the backup. Unlike "abra app backup", restore must be run on a per-service basis. You can not restore all services in one go. Backup files produced by Abra are compressed archives which use absolute paths. This allows Abra to restore -according to standard tar command logic. +according to standard tar command logic, i.e. the backup will be restored to +the path it was originally backed up from. Example: abra app restore example.com app ~/.abra/backups/example_com_app_609341138.tar.gz `, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) - cl, err := client.New(app.Server) + recipe, err := recipe.Get(app.Recipe, internal.Offline) if err != nil { logrus.Fatal(err) } @@ -80,11 +79,6 @@ Example: } } - recipe, err := recipe.Get(app.Recipe, conf) - if err != nil { - logrus.Fatal(err) - } - restoreConfigs := make(map[string]restoreConfig) for _, service := range recipe.Config.Services { if restoreEnabled, ok := service.Deploy.Labels["backupbot.restore"]; ok { @@ -114,6 +108,11 @@ Example: rsConfig = restoreConfig{} } + cl, err := client.New(app.Server) + if err != nil { + logrus.Fatal(err) + } + if err := runRestore(cl, app, backupPath, serviceName, rsConfig); err != nil { logrus.Fatal(err) } @@ -170,8 +169,8 @@ func runRestore(cl *dockerClient.Client, app config.App, backupPath, serviceName return err } - // we use absolute paths so tar knows what to do. it will restore files - // according to the paths set in the compresed archive + // NOTE(d1): we use absolute paths so tar knows what to do. it will restore + // files according to the paths set in the compressed archive restorePath := "/" copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false} diff --git a/cli/app/rollback.go b/cli/app/rollback.go index c03f4701..afb70b9e 100644 --- a/cli/app/rollback.go +++ b/cli/app/rollback.go @@ -8,7 +8,6 @@ import ( "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" stack "coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/tagcmp" @@ -49,17 +48,30 @@ recipes. `, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) stackName := app.StackName() + if err := recipe.EnsureExists(app.Recipe); err != nil { + logrus.Fatal(err) + } + if !internal.Chaos { - if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil { + if err := recipe.EnsureIsClean(app.Recipe); err != nil { + logrus.Fatal(err) + } + + if !internal.Offline { + if err := recipe.EnsureUpToDate(app.Recipe); err != nil { + logrus.Fatal(err) + } + } + + if err := recipe.EnsureLatest(app.Recipe); err != nil { logrus.Fatal(err) } } - r, err := recipe.Get(app.Recipe, conf) + r, err := recipe.Get(app.Recipe, internal.Offline) if err != nil { logrus.Fatal(err) } @@ -84,7 +96,7 @@ recipes. logrus.Fatalf("%s is not deployed?", app.Name) } - catl, err := recipe.ReadRecipeCatalogue(conf) + catl, err := recipe.ReadRecipeCatalogue(internal.Offline) if err != nil { logrus.Fatal(err) } @@ -129,7 +141,7 @@ recipes. if len(availableDowngrades) > 0 && !internal.Chaos { if internal.Force || internal.NoInput { chosenDowngrade = availableDowngrades[len(availableDowngrades)-1] - logrus.Debugf("choosing %s as version to downgrade to (--force)", chosenDowngrade) + logrus.Debugf("choosing %s as version to downgrade to (--force/--no-input)", chosenDowngrade) } else { prompt := &survey.Select{ Message: fmt.Sprintf("Please select a downgrade (current version: %s):", deployedVersion), @@ -185,7 +197,7 @@ recipes. config.SetChaosVersionLabel(compose, stackName, chosenDowngrade) config.SetUpdateLabel(compose, stackName, app.Env) - if err := NewVersionOverview(app, deployedVersion, chosenDowngrade, ""); err != nil { + if err := internal.NewVersionOverview(app, deployedVersion, chosenDowngrade, ""); err != nil { logrus.Fatal(err) } diff --git a/cli/app/run.go b/cli/app/run.go index 901edc1e..4ae68c1b 100644 --- a/cli/app/run.go +++ b/cli/app/run.go @@ -9,7 +9,6 @@ import ( "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" containerPkg "coopcloud.tech/abra/pkg/container" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/upstream/container" "github.com/docker/cli/cli/command" "github.com/docker/docker/api/types" @@ -38,15 +37,13 @@ var appRunCommand = cli.Command{ internal.DebugFlag, noTTYFlag, userFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, ArgsUsage: " ...", Usage: "Run a command in a service container", BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) if len(c.Args()) < 2 { internal.ShowSubcommandHelpAndError(c, errors.New("no provided?")) diff --git a/cli/app/secret.go b/cli/app/secret.go index eefbdc90..fa3479ff 100644 --- a/cli/app/secret.go +++ b/cli/app/secret.go @@ -12,7 +12,7 @@ import ( "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/formatter" - "coopcloud.tech/abra/pkg/runtime" + recipePkg "coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/secret" "github.com/docker/docker/api/types" dockerClient "github.com/docker/docker/client" @@ -48,12 +48,12 @@ var appSecretGenerateCommand = cli.Command{ Before: internal.SubCommandBefore, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) - cl, err := client.New(app.Server) - if err != nil { - logrus.Fatal(err) + if !internal.Offline { + if err := recipePkg.EnsureUpToDate(app.Recipe); err != nil { + logrus.Fatal(err) + } } if len(c.Args()) == 1 && !allSecrets { @@ -87,6 +87,11 @@ var appSecretGenerateCommand = cli.Command{ } } + cl, err := client.New(app.Server) + if err != nil { + logrus.Fatal(err) + } + secretVals, err := secret.GenerateSecrets(cl, secretsToCreate, app.StackName(), app.Server) if err != nil { logrus.Fatal(err) @@ -142,18 +147,17 @@ Example: `, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) + + if len(c.Args()) != 4 { + internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?")) + } cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } - if len(c.Args()) != 4 { - internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?")) - } - name := c.Args().Get(1) version := c.Args().Get(2) data := c.Args().Get(3) @@ -203,7 +207,6 @@ var appSecretRmCommand = cli.Command{ internal.NoInputFlag, rmAllSecretsFlag, internal.PassRemoveFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, ArgsUsage: " []", @@ -216,8 +219,7 @@ Example: abra app secret remove myapp db_pass `, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) secrets := secret.ReadSecretEnvVars(app.Env) if c.Args().Get(1) != "" && rmAllSecrets { @@ -295,13 +297,12 @@ var appSecretLsCommand = cli.Command{ Aliases: []string{"ls"}, Flags: []cli.Flag{ internal.DebugFlag, - internal.OfflineFlag, }, - Before: internal.SubCommandBefore, - Usage: "List all secrets", + Before: internal.SubCommandBefore, + Usage: "List all secrets", + BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) secrets := secret.ReadSecretEnvVars(app.Env) tableCol := []string{"Name", "Version", "Generated Name", "Created On Server"} @@ -350,7 +351,6 @@ var appSecretLsCommand = cli.Command{ return nil }, - BashComplete: autocomplete.AppNameComplete, } var appSecretCommand = cli.Command{ diff --git a/cli/app/services.go b/cli/app/services.go index 765c939f..60a7245e 100644 --- a/cli/app/services.go +++ b/cli/app/services.go @@ -9,7 +9,6 @@ import ( "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/formatter" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/service" stack "coopcloud.tech/abra/pkg/upstream/stack" "github.com/docker/docker/api/types" @@ -24,13 +23,11 @@ var appServicesCommand = cli.Command{ ArgsUsage: "", Flags: []cli.Flag{ internal.DebugFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) cl, err := client.New(app.Server) if err != nil { diff --git a/cli/app/undeploy.go b/cli/app/undeploy.go index 6800868f..3a1c2ffa 100644 --- a/cli/app/undeploy.go +++ b/cli/app/undeploy.go @@ -10,7 +10,6 @@ import ( "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/formatter" - "coopcloud.tech/abra/pkg/runtime" stack "coopcloud.tech/abra/pkg/upstream/stack" "github.com/docker/docker/api/types/filters" dockerClient "github.com/docker/docker/client" @@ -87,7 +86,6 @@ var appUndeployCommand = cli.Command{ internal.DebugFlag, internal.NoInputFlag, pruneFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, Usage: "Undeploy an app", @@ -101,8 +99,7 @@ any previously attached volumes as eligible for pruning once undeployed. Passing "-p/--prune" does not remove those volumes. `, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) stackName := app.StackName() cl, err := client.New(app.Server) @@ -121,7 +118,7 @@ Passing "-p/--prune" does not remove those volumes. logrus.Fatalf("%s is not deployed?", app.Name) } - if err := DeployOverview(app, deployedVersion, "continue with undeploy?"); err != nil { + if err := internal.DeployOverview(app, deployedVersion, "continue with undeploy?"); err != nil { logrus.Fatal(err) } diff --git a/cli/app/upgrade.go b/cli/app/upgrade.go index 225e2d7f..7bb85fbf 100644 --- a/cli/app/upgrade.go +++ b/cli/app/upgrade.go @@ -10,7 +10,7 @@ import ( "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" + recipePkg "coopcloud.tech/abra/pkg/recipe" stack "coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/tagcmp" "github.com/AlecAivazis/survey/v2" @@ -51,33 +51,43 @@ 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 recipes. `, + BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) stackName := app.StackName() + if !internal.Chaos { + if err := recipe.EnsureIsClean(app.Recipe); err != nil { + logrus.Fatal(err) + } + + if !internal.Offline { + if err := recipe.EnsureUpToDate(app.Recipe); err != nil { + logrus.Fatal(err) + } + } + + if err := recipe.EnsureLatest(app.Recipe); err != nil { + logrus.Fatal(err) + } + } + + recipe, err := recipePkg.Get(app.Recipe, internal.Offline) + if err != nil { + logrus.Fatal(err) + } + + if err := lint.LintForErrors(recipe); err != nil { + logrus.Fatal(err) + } + + logrus.Debugf("checking whether %s is already deployed", stackName) + cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } - if !internal.Chaos { - if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil { - logrus.Fatal(err) - } - } - - r, err := recipe.Get(app.Recipe, conf) - if err != nil { - logrus.Fatal(err) - } - - if err := lint.LintForErrors(r); err != nil { - logrus.Fatal(err) - } - - logrus.Debugf("checking whether %s is already deployed", stackName) - isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) if err != nil { logrus.Fatal(err) @@ -87,12 +97,12 @@ recipes. logrus.Fatalf("%s is not deployed?", app.Name) } - catl, err := recipe.ReadRecipeCatalogue(conf) + catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline) if err != nil { logrus.Fatal(err) } - versions, err := recipe.GetRecipeCatalogueVersions(app.Recipe, catl) + versions, err := recipePkg.GetRecipeCatalogueVersions(app.Recipe, catl) if err != nil { logrus.Fatal(err) } @@ -152,13 +162,13 @@ recipes. // if release notes written after git tag published, read them before we // check out the tag and then they'll appear to be missing. this covers // when we obviously will forget to write release notes before publishing - releaseNotes, err := GetReleaseNotes(app.Recipe, chosenUpgrade) + releaseNotes, err := internal.GetReleaseNotes(app.Recipe, chosenUpgrade) if err != nil { return err } if !internal.Chaos { - if err := recipe.EnsureVersion(app.Recipe, chosenUpgrade); err != nil { + if err := recipePkg.EnsureVersion(app.Recipe, chosenUpgrade); err != nil { logrus.Fatal(err) } } @@ -166,7 +176,7 @@ recipes. if internal.Chaos { logrus.Warn("chaos mode engaged") var err error - chosenUpgrade, err = recipe.ChaosVersion(app.Recipe) + chosenUpgrade, err = recipePkg.ChaosVersion(app.Recipe) if err != nil { logrus.Fatal(err) } @@ -201,7 +211,7 @@ recipes. config.SetChaosVersionLabel(compose, stackName, chosenUpgrade) config.SetUpdateLabel(compose, stackName, app.Env) - if err := NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil { + if err := internal.NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil { logrus.Fatal(err) } @@ -218,12 +228,11 @@ recipes. postDeployCmds, ok := app.Env["POST_UPGRADE_CMDS"] if ok && !internal.DontWaitConverge { logrus.Debugf("run the following post-deploy commands: %s", postDeployCmds) - if err := PostCmds(cl, app, postDeployCmds); err != nil { + if err := internal.PostCmds(cl, app, postDeployCmds); err != nil { logrus.Fatalf("attempting to run post deploy commands, saw: %s", err) } } return nil }, - BashComplete: autocomplete.AppNameComplete, } diff --git a/cli/app/version.go b/cli/app/version.go index 0777d4aa..f5dff54e 100644 --- a/cli/app/version.go +++ b/cli/app/version.go @@ -8,7 +8,6 @@ import ( "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/upstream/stack" "github.com/docker/distribution/reference" "github.com/sirupsen/logrus" @@ -47,9 +46,9 @@ Show all information about versioning related to a deployed app. This includes the individual image names, tags and digests. But also the Co-op Cloud recipe version. `, + BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) stackName := app.StackName() cl, err := client.New(app.Server) @@ -64,15 +63,19 @@ version. logrus.Fatal(err) } - if deployedVersion == "unknown" { - logrus.Fatalf("failed to determine version of deployed %s", app.Name) - } - if !isDeployed { logrus.Fatalf("%s is not deployed?", app.Name) } - recipeMeta, err := recipe.GetRecipeMeta(app.Recipe, conf) + if deployedVersion == "unknown" { + logrus.Fatalf("failed to determine version of deployed %s", app.Name) + } + + if err := recipe.EnsureExists(app.Recipe); err != nil { + logrus.Fatal(err) + } + + recipeMeta, err := recipe.GetRecipeMeta(app.Recipe, internal.Offline) if err != nil { logrus.Fatal(err) } @@ -100,5 +103,4 @@ version. return nil }, - BashComplete: autocomplete.AppNameComplete, } diff --git a/cli/app/volume.go b/cli/app/volume.go index 2008c1e9..b64ce69c 100644 --- a/cli/app/volume.go +++ b/cli/app/volume.go @@ -7,7 +7,7 @@ import ( "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/formatter" - "coopcloud.tech/abra/pkg/runtime" + "coopcloud.tech/abra/pkg/upstream/stack" "github.com/AlecAivazis/survey/v2" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -20,14 +20,12 @@ var appVolumeListCommand = cli.Command{ Flags: []cli.Flag{ internal.DebugFlag, internal.NoInputFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, Usage: "List volumes associated with an app", BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) cl, err := client.New(app.Server) if err != nil { @@ -83,18 +81,26 @@ Passing "--force/-f" will select all volumes for removal. Be careful. internal.DebugFlag, internal.NoInputFlag, internal.ForceFlag, - internal.OfflineFlag, }, - Before: internal.SubCommandBefore, + Before: internal.SubCommandBefore, + BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - app := internal.ValidateApp(c, conf) + app := internal.ValidateApp(c) cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } + isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) + if err != nil { + logrus.Fatal(err) + } + + if isDeployed { + logrus.Fatalf("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name) + } + filters, err := app.Filters(false, true) if err != nil { logrus.Fatal(err) @@ -124,16 +130,19 @@ Passing "--force/-f" will select all volumes for removal. Be careful. volumesToRemove = volumeNames } - err = client.RemoveVolumes(cl, context.Background(), app.Server, volumesToRemove, internal.Force) - if err != nil { - logrus.Fatal(err) - } + if len(volumesToRemove) > 0 { + err = client.RemoveVolumes(cl, context.Background(), app.Server, volumesToRemove, internal.Force) + if err != nil { + logrus.Fatal(err) + } - logrus.Info("volumes removed successfully") + logrus.Info("volumes removed successfully") + } else { + logrus.Info("no volumes removed") + } return nil }, - BashComplete: autocomplete.AppNameComplete, } var appVolumeCommand = cli.Command{ diff --git a/cli/catalogue/catalogue.go b/cli/catalogue/catalogue.go index a2260acf..7c97372f 100644 --- a/cli/catalogue/catalogue.go +++ b/cli/catalogue/catalogue.go @@ -13,7 +13,6 @@ import ( "coopcloud.tech/abra/pkg/formatter" gitPkg "coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "github.com/go-git/go-git/v5" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -29,6 +28,7 @@ var catalogueGenerateCommand = cli.Command{ internal.PublishFlag, internal.DryFlag, internal.SkipUpdatesFlag, + internal.ChaosFlag, internal.OfflineFlag, }, Before: internal.SubCommandBefore, @@ -53,20 +53,22 @@ Push your new release to git.coopcloud.tech with "-p/--publish". This requires that you have permission to git push to these repositories and have your SSH keys configured on your account. `, - ArgsUsage: "[]", + ArgsUsage: "[]", + BashComplete: autocomplete.RecipeNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) recipeName := c.Args().First() if recipeName != "" { - internal.ValidateRecipe(c, conf) + internal.ValidateRecipe(c) } - if err := catalogue.EnsureUpToDate(conf); err != nil { - logrus.Fatal(err) + if !internal.Chaos { + if err := catalogue.EnsureIsClean(); err != nil { + logrus.Fatal(err) + } } - repos, err := recipe.ReadReposMetadata(conf) + repos, err := recipe.ReadReposMetadata() if err != nil { logrus.Fatal(err) } @@ -83,7 +85,7 @@ keys configured on your account. if !internal.SkipUpdates { logrus.Warn(logMsg) - if err := recipe.UpdateRepositories(repos, recipeName, conf); err != nil { + if err := recipe.UpdateRepositories(repos, recipeName); err != nil { logrus.Fatal(err) } } @@ -101,7 +103,7 @@ keys configured on your account. continue } - versions, err := recipe.GetRecipeVersions(recipeMeta.Name, conf) + versions, err := recipe.GetRecipeVersions(recipeMeta.Name, internal.Offline) if err != nil { logrus.Warn(err) } @@ -137,7 +139,7 @@ keys configured on your account. logrus.Fatal(err) } } else { - catlFS, err := recipe.ReadRecipeCatalogue(conf) + catlFS, err := recipe.ReadRecipeCatalogue(internal.Offline) if err != nil { logrus.Fatal(err) } @@ -211,7 +213,6 @@ keys configured on your account. return nil }, - BashComplete: autocomplete.RecipeNameComplete, } // CatalogueCommand defines the `abra catalogue` command and sub-commands. diff --git a/cli/internal/deploy.go b/cli/internal/deploy.go new file mode 100644 index 00000000..410fa97d --- /dev/null +++ b/cli/internal/deploy.go @@ -0,0 +1,180 @@ +package internal + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "strings" + + "coopcloud.tech/abra/pkg/config" + "coopcloud.tech/abra/pkg/formatter" + "github.com/AlecAivazis/survey/v2" + dockerClient "github.com/docker/docker/client" + "github.com/sirupsen/logrus" +) + +// NewVersionOverview shows an upgrade or downgrade overview +func NewVersionOverview(app config.App, currentVersion, newVersion, releaseNotes string) error { + tableCol := []string{"server", "recipe", "config", "domain", "current version", "to be deployed"} + table := formatter.CreateTable(tableCol) + + deployConfig := "compose.yml" + if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { + deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") + } + + server := app.Server + if app.Server == "default" { + server = "local" + } + + table.Append([]string{server, app.Recipe, deployConfig, app.Domain, currentVersion, newVersion}) + table.Render() + + if releaseNotes == "" { + var err error + releaseNotes, err = GetReleaseNotes(app.Recipe, newVersion) + if err != nil { + return err + } + } + + if releaseNotes != "" && newVersion != "" { + fmt.Println() + fmt.Println(fmt.Sprintf("%s release notes:\n\n%s", newVersion, releaseNotes)) + } else { + logrus.Warnf("no release notes available for %s", newVersion) + } + + if NoInput { + return nil + } + + response := false + prompt := &survey.Confirm{ + Message: "continue with deployment?", + } + + if err := survey.AskOne(prompt, &response); err != nil { + return err + } + + if !response { + logrus.Fatal("exiting as requested") + } + + return nil +} + +// GetReleaseNotes prints release notes for a recipe version +func GetReleaseNotes(recipeName, version string) (string, error) { + if version == "" { + return "", nil + } + + fpath := path.Join(config.RECIPES_DIR, recipeName, "release", version) + + if _, err := os.Stat(fpath); !os.IsNotExist(err) { + releaseNotes, err := ioutil.ReadFile(fpath) + if err != nil { + return "", err + } + return string(releaseNotes), nil + } + + return "", nil +} + +// PostCmds parses a string of commands and executes them inside of the respective services +// the commands string must have the following format: +// " | |... " +func PostCmds(cl *dockerClient.Client, app config.App, commands string) error { + abraSh := path.Join(config.RECIPES_DIR, app.Recipe, "abra.sh") + if _, err := os.Stat(abraSh); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf(fmt.Sprintf("%s does not exist for %s?", abraSh, app.Name)) + } + return err + } + + for _, command := range strings.Split(commands, "|") { + commandParts := strings.Split(command, " ") + if len(commandParts) < 2 { + return fmt.Errorf(fmt.Sprintf("not enough arguments: %s", command)) + } + targetServiceName := commandParts[0] + cmdName := commandParts[1] + parsedCmdArgs := "" + if len(commandParts) > 2 { + parsedCmdArgs = fmt.Sprintf("%s ", strings.Join(commandParts[2:], " ")) + } + logrus.Infof("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName) + + if err := EnsureCommand(abraSh, app.Recipe, cmdName); err != nil { + return err + } + + serviceNames, err := config.GetAppServiceNames(app.Name) + if err != nil { + return err + } + + matchingServiceName := false + for _, serviceName := range serviceNames { + if serviceName == targetServiceName { + matchingServiceName = true + } + } + + if !matchingServiceName { + return fmt.Errorf(fmt.Sprintf("no service %s for %s?", targetServiceName, app.Name)) + } + + logrus.Debugf("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName) + + Tty = true + if err := RunCmdRemote(cl, app, abraSh, targetServiceName, cmdName, parsedCmdArgs); err != nil { + return err + } + } + return nil +} + +// DeployOverview shows a deployment overview +func DeployOverview(app config.App, version, message string) error { + tableCol := []string{"server", "recipe", "config", "domain", "version"} + table := formatter.CreateTable(tableCol) + + deployConfig := "compose.yml" + if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { + deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") + } + + server := app.Server + if app.Server == "default" { + server = "local" + } + + table.Append([]string{server, app.Recipe, deployConfig, app.Domain, version}) + table.Render() + + if NoInput { + return nil + } + + response := false + prompt := &survey.Confirm{ + Message: message, + } + + if err := survey.AskOne(prompt, &response); err != nil { + return err + } + + if !response { + logrus.Fatal("exiting as requested") + } + + return nil +} diff --git a/cli/internal/validate.go b/cli/internal/validate.go index 05bc7fdb..46a442d6 100644 --- a/cli/internal/validate.go +++ b/cli/internal/validate.go @@ -7,53 +7,19 @@ import ( "coopcloud.tech/abra/pkg/app" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "github.com/AlecAivazis/survey/v2" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) // ValidateRecipe ensures the recipe arg is valid. -func ValidateRecipe(c *cli.Context, conf *runtime.Config) recipe.Recipe { - recipeName := c.Args().First() - - if recipeName == "" { - ShowSubcommandHelpAndError(c, errors.New("no recipe name provided")) - } - - chosenRecipe, err := recipe.Get(recipeName, conf) - if err != nil { - if c.Command.Name == "generate" { - if strings.Contains(err.Error(), "missing a compose") { - logrus.Fatal(err) - } - logrus.Warn(err) - } else { - if strings.Contains(err.Error(), "template_driver is not allowed") { - logrus.Warnf("ensure %s recipe compose.* files include \"version: '3.8'\"", recipeName) - } - logrus.Fatalf("unable to validate recipe: %s", err) - } - } - - if err := recipe.EnsureLatest(recipeName, conf); err != nil { - logrus.Fatal(err) - } - - logrus.Debugf("validated %s as recipe argument", recipeName) - - return chosenRecipe -} - -// ValidateRecipeWithPrompt ensures a recipe argument is present before -// validating, asking for input if required. -func ValidateRecipeWithPrompt(c *cli.Context, conf *runtime.Config) recipe.Recipe { +func ValidateRecipe(c *cli.Context) recipe.Recipe { recipeName := c.Args().First() if recipeName == "" && !NoInput { var recipes []string - catl, err := recipe.ReadRecipeCatalogue(conf) + catl, err := recipe.ReadRecipeCatalogue(Offline) if err != nil { logrus.Fatal(err) } @@ -91,13 +57,19 @@ func ValidateRecipeWithPrompt(c *cli.Context, conf *runtime.Config) recipe.Recip ShowSubcommandHelpAndError(c, errors.New("no recipe name provided")) } - chosenRecipe, err := recipe.Get(recipeName, conf) + chosenRecipe, err := recipe.Get(recipeName, Offline) if err != nil { - logrus.Fatal(err) - } - - if err := recipe.EnsureLatest(recipeName, conf); err != nil { - logrus.Fatal(err) + if c.Command.Name == "generate" { + if strings.Contains(err.Error(), "missing a compose") { + logrus.Fatal(err) + } + logrus.Warn(err) + } else { + if strings.Contains(err.Error(), "template_driver is not allowed") { + logrus.Warnf("ensure %s recipe compose.* files include \"version: '3.8'\"", recipeName) + } + logrus.Fatalf("unable to validate recipe: %s", err) + } } logrus.Debugf("validated %s as recipe argument", recipeName) @@ -106,7 +78,7 @@ func ValidateRecipeWithPrompt(c *cli.Context, conf *runtime.Config) recipe.Recip } // ValidateApp ensures the app name arg is valid. -func ValidateApp(c *cli.Context, conf *runtime.Config) config.App { +func ValidateApp(c *cli.Context) config.App { appName := c.Args().First() if appName == "" { @@ -118,10 +90,6 @@ func ValidateApp(c *cli.Context, conf *runtime.Config) config.App { logrus.Fatal(err) } - if err := recipe.EnsureExists(app.Recipe, conf); err != nil { - logrus.Fatal(err) - } - logrus.Debugf("validated %s as app argument", appName) return app @@ -190,14 +158,14 @@ func ValidateServer(c *cli.Context) string { } } - if !matched { - ShowSubcommandHelpAndError(c, errors.New("server doesn't exist?")) - } - if serverName == "" { ShowSubcommandHelpAndError(c, errors.New("no server provided")) } + if !matched { + ShowSubcommandHelpAndError(c, errors.New("server doesn't exist?")) + } + logrus.Debugf("validated %s as server argument", serverName) return serverName diff --git a/cli/recipe/fetch.go b/cli/recipe/fetch.go index 17d6b59c..4996ba62 100644 --- a/cli/recipe/fetch.go +++ b/cli/recipe/fetch.go @@ -4,7 +4,6 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -17,25 +16,26 @@ var recipeFetchCommand = cli.Command{ Description: "Fetchs all recipes without arguments.", Flags: []cli.Flag{ internal.DebugFlag, - internal.OfflineFlag, + internal.NoInputFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.RecipeNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) recipeName := c.Args().First() if recipeName != "" { - internal.ValidateRecipe(c, conf) - return nil // ValidateRecipe ensures latest checkout + internal.ValidateRecipe(c) } - repos, err := recipe.ReadReposMetadata(conf) - if err != nil { + if err := recipe.EnsureExists(recipeName); err != nil { logrus.Fatal(err) } - if err := recipe.UpdateRepositories(repos, recipeName, conf); err != nil { + if err := recipe.EnsureUpToDate(recipeName); err != nil { + logrus.Fatal(err) + } + + if err := recipe.EnsureLatest(recipeName); err != nil { logrus.Fatal(err) } diff --git a/cli/recipe/lint.go b/cli/recipe/lint.go index 623872e6..dfeba1a2 100644 --- a/cli/recipe/lint.go +++ b/cli/recipe/lint.go @@ -7,8 +7,6 @@ import ( "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/lint" - recipePkg "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -22,16 +20,12 @@ var recipeLintCommand = cli.Command{ internal.DebugFlag, internal.OnlyErrorFlag, internal.OfflineFlag, + internal.NoInputFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.RecipeNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - recipe := internal.ValidateRecipe(c, conf) - - if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil { - logrus.Fatal(err) - } + recipe := internal.ValidateRecipe(c) tableCol := []string{"ref", "rule", "severity", "satisfied", "skipped", "resolve"} table := formatter.CreateTable(tableCol) diff --git a/cli/recipe/list.go b/cli/recipe/list.go index 846a5d11..4ad35476 100644 --- a/cli/recipe/list.go +++ b/cli/recipe/list.go @@ -9,7 +9,6 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -34,9 +33,7 @@ var recipeListCommand = cli.Command{ }, Before: internal.SubCommandBefore, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - - catl, err := recipe.ReadRecipeCatalogue(conf) + catl, err := recipe.ReadRecipeCatalogue(internal.Offline) if err != nil { logrus.Fatal(err.Error()) } diff --git a/cli/recipe/release.go b/cli/recipe/release.go index 82d490f4..7d25d93c 100644 --- a/cli/recipe/release.go +++ b/cli/recipe/release.go @@ -13,7 +13,6 @@ import ( gitPkg "coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/tagcmp" "github.com/AlecAivazis/survey/v2" "github.com/docker/distribution/reference" @@ -60,12 +59,15 @@ your SSH keys configured on your account. Before: internal.SubCommandBefore, BashComplete: autocomplete.RecipeNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New( - runtime.WithOffline(internal.Offline), - runtime.WithEnsureRecipeUpToDate(false), - ) + recipe := internal.ValidateRecipe(c) - recipe := internal.ValidateRecipeWithPrompt(c, conf) + if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil { + logrus.Fatal(err) + } + + if err := recipePkg.EnsureLatest(recipe.Name); err != nil { + logrus.Fatal(err) + } imagesTmp, err := getImageVersions(recipe) if err != nil { diff --git a/cli/recipe/sync.go b/cli/recipe/sync.go index 39f4189d..0bd52fc9 100644 --- a/cli/recipe/sync.go +++ b/cli/recipe/sync.go @@ -8,7 +8,6 @@ import ( "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/config" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/tagcmp" "github.com/AlecAivazis/survey/v2" "github.com/go-git/go-git/v5" @@ -29,7 +28,6 @@ var recipeSyncCommand = cli.Command{ internal.MajorFlag, internal.MinorFlag, internal.PatchFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, Description: ` @@ -42,13 +40,9 @@ Where can be specifed on the command-line or Abra can attempt to auto-generate it for you. The configuration will be updated on the local file system. `, + BashComplete: autocomplete.RecipeNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New( - runtime.WithOffline(internal.Offline), - runtime.WithEnsureRecipeUpToDate(false), - ) - - recipe := internal.ValidateRecipeWithPrompt(c, conf) + recipe := internal.ValidateRecipe(c) mainApp, err := internal.GetMainAppImage(recipe) if err != nil { @@ -203,5 +197,4 @@ likely to change. return nil }, - BashComplete: autocomplete.RecipeNameComplete, } diff --git a/cli/recipe/upgrade.go b/cli/recipe/upgrade.go index b152568f..fe7bbf83 100644 --- a/cli/recipe/upgrade.go +++ b/cli/recipe/upgrade.go @@ -15,7 +15,6 @@ import ( "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/formatter" recipePkg "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/tagcmp" "github.com/AlecAivazis/survey/v2" "github.com/docker/distribution/reference" @@ -58,8 +57,7 @@ You may invoke this command in "wizard" mode and be prompted for input: abra recipe upgrade `, - BashComplete: autocomplete.RecipeNameComplete, - ArgsUsage: "", + ArgsUsage: "", Flags: []cli.Flag{ internal.DebugFlag, internal.NoInputFlag, @@ -70,12 +68,16 @@ You may invoke this command in "wizard" mode and be prompted for input: internal.AllTagsFlag, internal.OfflineFlag, }, - Before: internal.SubCommandBefore, + Before: internal.SubCommandBefore, + BashComplete: autocomplete.RecipeNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - recipe := internal.ValidateRecipeWithPrompt(c, conf) + recipe := internal.ValidateRecipe(c) - if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil { + if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil { + logrus.Fatal(err) + } + + if err := recipePkg.EnsureLatest(recipe.Name); err != nil { logrus.Fatal(err) } @@ -185,7 +187,7 @@ You may invoke this command in "wizard" mode and be prompted for input: continue // skip on to the next tag and don't update any compose files } - catlVersions, err := recipePkg.VersionsOfService(recipe.Name, service.Name, conf) + catlVersions, err := recipePkg.VersionsOfService(recipe.Name, service.Name, internal.Offline) if err != nil { logrus.Fatal(err) } diff --git a/cli/recipe/version.go b/cli/recipe/version.go index 4852a93f..5e1e7ca6 100644 --- a/cli/recipe/version.go +++ b/cli/recipe/version.go @@ -5,7 +5,6 @@ import ( "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/formatter" recipePkg "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -18,23 +17,19 @@ var recipeVersionCommand = cli.Command{ Flags: []cli.Flag{ internal.DebugFlag, internal.OfflineFlag, + internal.NoInputFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.RecipeNameComplete, Action: func(c *cli.Context) error { - conf := runtime.New( - runtime.WithOffline(internal.Offline), - runtime.WithEnsureRecipeUpToDate(false), - ) + recipe := internal.ValidateRecipe(c) - recipe := internal.ValidateRecipe(c, conf) - - catalogue, err := recipePkg.ReadRecipeCatalogue(conf) + catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline) if err != nil { logrus.Fatal(err) } - recipeMeta, ok := catalogue[recipe.Name] + recipeMeta, ok := catl[recipe.Name] if !ok { logrus.Fatalf("%s recipe doesn't exist?", recipe.Name) } diff --git a/cli/server/add.go b/cli/server/add.go index 098bcdae..c4bf6f13 100644 --- a/cli/server/add.go +++ b/cli/server/add.go @@ -118,7 +118,6 @@ developer machine. internal.DebugFlag, internal.NoInputFlag, localFlag, - internal.OfflineFlag, }, Before: internal.SubCommandBefore, ArgsUsage: "", diff --git a/cli/server/list.go b/cli/server/list.go index 3da5a816..6eaad343 100644 --- a/cli/server/list.go +++ b/cli/server/list.go @@ -60,6 +60,17 @@ var serverListCommand = cli.Command{ if err != nil { logrus.Fatal(err) } + + if sp.Host == "" { + sp.Host = "unknown" + } + if sp.User == "" { + sp.User = "unknown" + } + if sp.Port == "" { + sp.Port = "unknown" + } + row = []string{serverName, sp.Host, sp.User, sp.Port} } } @@ -73,8 +84,11 @@ var serverListCommand = cli.Command{ } if problemsFilter { - if row[1] == "unknown" { - table.Append(row) + for _, val := range row { + if val == "unknown" { + table.Append(row) + break + } } } else { table.Append(row) diff --git a/cli/server/prune.go b/cli/server/prune.go index a64734d8..8945aadf 100644 --- a/cli/server/prune.go +++ b/cli/server/prune.go @@ -20,12 +20,12 @@ var allFilterFlag = &cli.BoolFlag{ Destination: &allFilter, } -var volunesFilter bool +var volumesFilter bool var volumesFilterFlag = &cli.BoolFlag{ Name: "volumes, v", Usage: "Prune volumes. This will remove app data, Be Careful!", - Destination: &volunesFilter, + Destination: &volumesFilter, } var serverPruneCommand = cli.Command{ @@ -35,7 +35,7 @@ var serverPruneCommand = cli.Command{ Description: ` Prunes unused containers, networks, and dangling images. -If passing "-v/--volumes" then volumes not connected with a deployed app will +If passing "-v/--volumes" then volumes not connected to a deployed app will also be removed. This can result in unwanted data loss if not used carefully. `, ArgsUsage: "[]", @@ -44,12 +44,11 @@ also be removed. This can result in unwanted data loss if not used carefully. volumesFilterFlag, internal.DebugFlag, internal.OfflineFlag, + internal.NoInputFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.ServerNameComplete, Action: func(c *cli.Context) error { - var args filters.Args - serverName := internal.ValidateServer(c) cl, err := client.New(serverName) @@ -57,6 +56,8 @@ also be removed. This can result in unwanted data loss if not used carefully. logrus.Fatal(err) } + var args filters.Args + ctx := context.Background() cr, err := cl.ContainersPrune(ctx, args) if err != nil { @@ -75,6 +76,7 @@ also be removed. This can result in unwanted data loss if not used carefully. pruneFilters := filters.NewArgs() if allFilter { + logrus.Debugf("removing all images, not only dangling ones") pruneFilters.Add("dangling", "false") } @@ -86,7 +88,7 @@ also be removed. This can result in unwanted data loss if not used carefully. imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed) logrus.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed) - if volunesFilter { + if volumesFilter { vr, err := cl.VolumesPrune(ctx, args) if err != nil { logrus.Fatal(err) diff --git a/cli/server/remove.go b/cli/server/remove.go index 196dc421..83817c5a 100644 --- a/cli/server/remove.go +++ b/cli/server/remove.go @@ -15,7 +15,7 @@ import ( var serverRemoveCommand = cli.Command{ Name: "remove", Aliases: []string{"rm"}, - ArgsUsage: "[]", + ArgsUsage: "", Usage: "Remove a managed server", Description: `Remove a managed server. diff --git a/cli/updater/updater.go b/cli/updater/updater.go index 3ed7d787..5e6d14ad 100644 --- a/cli/updater/updater.go +++ b/cli/updater/updater.go @@ -12,7 +12,6 @@ import ( "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/upstream/convert" "coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/tagcmp" @@ -58,8 +57,6 @@ catalogue. If a new patch/minor version is available, a notification is printed. To include major versions use the --major flag. `, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - cl, err := client.New("default") if err != nil { logrus.Fatal(err) @@ -78,7 +75,7 @@ printed. To include major versions use the --major flag. } if recipeName != "" { - _, err = getLatestUpgrade(cl, stackName, recipeName, conf) + _, err = getLatestUpgrade(cl, stackName, recipeName) if err != nil { logrus.Fatal(err) } @@ -113,8 +110,6 @@ break things. Only apps that are not deployed with "--chaos" are upgraded, to update chaos deployments use the "--chaos" flag. Use it with care. `, Action: func(c *cli.Context) error { - conf := runtime.New(runtime.WithOffline(internal.Offline)) - cl, err := client.New("default") if err != nil { logrus.Fatal(err) @@ -123,7 +118,7 @@ update chaos deployments use the "--chaos" flag. Use it with care. if !updateAll { stackName := c.Args().Get(0) recipeName := c.Args().Get(1) - err = tryUpgrade(cl, stackName, recipeName, conf) + err = tryUpgrade(cl, stackName, recipeName) if err != nil { logrus.Fatal(err) } @@ -143,7 +138,7 @@ update chaos deployments use the "--chaos" flag. Use it with care. logrus.Fatal(err) } - err = tryUpgrade(cl, stackName, recipeName, conf) + err = tryUpgrade(cl, stackName, recipeName) if err != nil { logrus.Fatal(err) } @@ -227,14 +222,13 @@ func getEnv(cl *dockerclient.Client, stackName string) (config.AppEnv, error) { // getLatestUpgrade returns the latest available version for an app respecting // the "--major" flag if it is newer than the currently deployed version. -func getLatestUpgrade(cl *dockerclient.Client, stackName string, - recipeName string, conf *runtime.Config) (string, error) { +func getLatestUpgrade(cl *dockerclient.Client, stackName string, recipeName string) (string, error) { deployedVersion, err := getDeployedVersion(cl, stackName, recipeName) if err != nil { return "", err } - availableUpgrades, err := getAvailableUpgrades(cl, stackName, recipeName, deployedVersion, conf) + availableUpgrades, err := getAvailableUpgrades(cl, stackName, recipeName, deployedVersion) if err != nil { return "", err } @@ -277,8 +271,8 @@ func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName st // than the deployed version. It only includes major upgrades if the "--major" // flag is set. func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName string, - deployedVersion string, conf *runtime.Config) ([]string, error) { - catl, err := recipe.ReadRecipeCatalogue(conf) + deployedVersion string) ([]string, error) { + catl, err := recipe.ReadRecipeCatalogue(internal.Offline) if err != nil { return nil, err } @@ -322,12 +316,12 @@ func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName // processRecipeRepoVersion clones, pulls, checks out the version and lints the // recipe repository. -func processRecipeRepoVersion(recipeName, version string, conf *runtime.Config) error { - if err := recipe.EnsureExists(recipeName, conf); err != nil { +func processRecipeRepoVersion(recipeName, version string) error { + if err := recipe.EnsureExists(recipeName); err != nil { return err } - if err := recipe.EnsureUpToDate(recipeName, conf); err != nil { + if err := recipe.EnsureUpToDate(recipeName); err != nil { return err } @@ -335,7 +329,7 @@ func processRecipeRepoVersion(recipeName, version string, conf *runtime.Config) return err } - if r, err := recipe.Get(recipeName, conf); err != nil { + if r, err := recipe.Get(recipeName, internal.Offline); err != nil { return err } else if err := lint.LintForErrors(r); err != nil { return err @@ -392,7 +386,7 @@ func createDeployConfig(recipeName string, stackName string, env config.AppEnv) } // tryUpgrade performs the upgrade if all the requirements are fulfilled. -func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *runtime.Config) error { +func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error { if recipeName == "" { logrus.Debugf("don't update %s due to missing recipe name", stackName) return nil @@ -418,7 +412,7 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *run return nil } - upgradeVersion, err := getLatestUpgrade(cl, stackName, recipeName, conf) + upgradeVersion, err := getLatestUpgrade(cl, stackName, recipeName) if err != nil { return err } @@ -428,14 +422,14 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *run return nil } - err = upgrade(cl, stackName, recipeName, upgradeVersion, conf) + err = upgrade(cl, stackName, recipeName, upgradeVersion) return err } // upgrade performs all necessary steps to upgrade an app. func upgrade(cl *dockerclient.Client, stackName, recipeName, - upgradeVersion string, conf *runtime.Config) error { + upgradeVersion string) error { env, err := getEnv(cl, stackName) if err != nil { return err @@ -448,7 +442,7 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName, Env: env, } - if err = processRecipeRepoVersion(recipeName, upgradeVersion, conf); err != nil { + if err = processRecipeRepoVersion(recipeName, upgradeVersion); err != nil { return err } diff --git a/pkg/autocomplete/autocomplete.go b/pkg/autocomplete/autocomplete.go index c952ba57..807422fb 100644 --- a/pkg/autocomplete/autocomplete.go +++ b/pkg/autocomplete/autocomplete.go @@ -5,7 +5,6 @@ import ( "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -28,12 +27,7 @@ func AppNameComplete(c *cli.Context) { // RecipeNameComplete completes recipe names. func RecipeNameComplete(c *cli.Context) { - // defaults since we can't take arguments here... this means auto-completion - // of recipe names always access the network if e.g. the catalogue needs - // cloning / updating - conf := runtime.New() - - catl, err := recipe.ReadRecipeCatalogue(conf) + catl, err := recipe.ReadRecipeCatalogue(false) if err != nil { logrus.Warn(err) } diff --git a/pkg/catalogue/catalogue.go b/pkg/catalogue/catalogue.go index 5a603f5c..413aec61 100644 --- a/pkg/catalogue/catalogue.go +++ b/pkg/catalogue/catalogue.go @@ -8,7 +8,6 @@ import ( "coopcloud.tech/abra/pkg/config" gitPkg "coopcloud.tech/abra/pkg/git" - "coopcloud.tech/abra/pkg/runtime" "github.com/go-git/go-git/v5" "github.com/sirupsen/logrus" ) @@ -54,13 +53,9 @@ var CatalogueSkipList = map[string]bool{ } // EnsureCatalogue ensures that the catalogue is cloned locally & present. -func EnsureCatalogue(conf *runtime.Config) error { +func EnsureCatalogue() error { catalogueDir := path.Join(config.ABRA_DIR, "catalogue") if _, err := os.Stat(catalogueDir); err != nil && os.IsNotExist(err) { - if conf.Offline { - return fmt.Errorf("no local copy of the catalogue available, network access required") - } - url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME) if err := gitPkg.Clone(catalogueDir, url); err != nil { return err @@ -72,9 +67,8 @@ func EnsureCatalogue(conf *runtime.Config) error { return nil } -// EnsureUpToDate ensures that the local catalogue has no unstaged changes as -// is up to date. This is useful to run before doing catalogue generation. -func EnsureUpToDate(conf *runtime.Config) error { +// EnsureIsClean makes sure that the catalogue has no unstaged changes. +func EnsureIsClean() error { isClean, err := gitPkg.IsClean(config.CATALOGUE_DIR) if err != nil { return err @@ -85,11 +79,11 @@ func EnsureUpToDate(conf *runtime.Config) error { return fmt.Errorf(msg, config.CATALOGUE_DIR) } - if conf.Offline { - logrus.Debug("attempting to use local catalogue without access network (\"--offline\")") - return nil - } + return nil +} +// EnsureUpToDate ensures that the local catalogue is up to date. +func EnsureUpToDate() error { repo, err := git.PlainOpen(config.CATALOGUE_DIR) if err != nil { return err diff --git a/pkg/config/app.go b/pkg/config/app.go index d86ba4f1..0ad683d2 100644 --- a/pkg/config/app.go +++ b/pkg/config/app.go @@ -326,7 +326,7 @@ func TemplateAppEnvSample(recipeName, appName, server, domain string) error { } appEnvPath := path.Join(ABRA_DIR, "servers", server, fmt.Sprintf("%s.env", appName)) - if _, err := os.Stat(appEnvPath); os.IsExist(err) { + if _, err := os.Stat(appEnvPath); !os.IsNotExist(err) { return fmt.Errorf("%s already exists?", appEnvPath) } diff --git a/pkg/lint/recipe.go b/pkg/lint/recipe.go index 10233f92..cae797f3 100644 --- a/pkg/lint/recipe.go +++ b/pkg/lint/recipe.go @@ -9,7 +9,6 @@ import ( "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/tagcmp" "github.com/docker/distribution/reference" "github.com/go-git/go-git/v5" @@ -334,11 +333,7 @@ func LintImagePresent(recipe recipe.Recipe) (bool, error) { } func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) { - // defaults since we can't take arguments here... this means this lint rule - // always access the network if e.g. the catalogue needs cloning / updating - conf := runtime.New() - - catl, err := recipePkg.ReadRecipeCatalogue(conf) + catl, err := recipePkg.ReadRecipeCatalogue(false) if err != nil { logrus.Fatal(err) } diff --git a/pkg/recipe/recipe.go b/pkg/recipe/recipe.go index ca5aabfc..92023379 100644 --- a/pkg/recipe/recipe.go +++ b/pkg/recipe/recipe.go @@ -17,7 +17,6 @@ import ( "coopcloud.tech/abra/pkg/formatter" gitPkg "coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/limit" - "coopcloud.tech/abra/pkg/runtime" "coopcloud.tech/abra/pkg/upstream/stack" loader "coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/web" @@ -206,8 +205,8 @@ func (r Recipe) Tags() ([]string, error) { } // Get retrieves a recipe. -func Get(recipeName string, conf *runtime.Config) (Recipe, error) { - if err := EnsureExists(recipeName, conf); err != nil { +func Get(recipeName string, offline bool) (Recipe, error) { + if err := EnsureExists(recipeName); err != nil { return Recipe{}, err } @@ -233,7 +232,7 @@ func Get(recipeName string, conf *runtime.Config) (Recipe, error) { return Recipe{}, err } - meta, err := GetRecipeMeta(recipeName, conf) + meta, err := GetRecipeMeta(recipeName, offline) if err != nil { switch err.(type) { case RecipeMissingFromCatalogue: @@ -251,19 +250,10 @@ func Get(recipeName string, conf *runtime.Config) (Recipe, error) { } // EnsureExists ensures that a recipe is locally cloned -func EnsureExists(recipeName string, conf *runtime.Config) error { - if !conf.RecipeExists { - logrus.Debug("skipping ensuring recipe locally exists") - return nil - } - +func EnsureExists(recipeName string) error { recipeDir := path.Join(config.RECIPES_DIR, recipeName) if _, err := os.Stat(recipeDir); os.IsNotExist(err) { - if conf.Offline { - return fmt.Errorf("no local copy of %s available, network access required", recipeName) - } - logrus.Debugf("%s does not exist, attemmpting to clone", recipeDir) url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipeName) if err := gitPkg.Clone(recipeDir, url); err != nil { @@ -282,15 +272,6 @@ func EnsureExists(recipeName string, conf *runtime.Config) error { func EnsureVersion(recipeName, version string) error { recipeDir := path.Join(config.RECIPES_DIR, recipeName) - isClean, err := gitPkg.IsClean(recipeDir) - if err != nil { - return err - } - - if !isClean { - return fmt.Errorf("%s has locally unstaged changes", recipeName) - } - if err := gitPkg.EnsureGitRepo(recipeDir); err != nil { return err } @@ -342,30 +323,31 @@ func EnsureVersion(recipeName, version string) error { return nil } -// EnsureLatest makes sure the latest commit is checked out for a local recipe repository -func EnsureLatest(recipeName string, conf *runtime.Config) error { - if !conf.RecipeLatest { - logrus.Debug("skipping ensuring recipe is synced with remote") - return nil - } - +// EnsureIsClean makes sure that the recipe repository has no unstaged changes. +func EnsureIsClean(recipeName string) error { recipeDir := path.Join(config.RECIPES_DIR, recipeName) isClean, err := gitPkg.IsClean(recipeDir) if err != nil { - return err + return fmt.Errorf("unable to check git clean status in %s: %s", recipeDir, err) } if !isClean { - return fmt.Errorf("%s has locally unstaged changes", recipeName) + msg := "%s (%s) has locally unstaged changes? please commit/remove your changes before proceeding" + return fmt.Errorf(msg, recipeName, recipeDir) } + return nil +} + +// EnsureLatest makes sure the latest commit is checked out for a local recipe repository +func EnsureLatest(recipeName string) error { + recipeDir := path.Join(config.RECIPES_DIR, recipeName) + if err := gitPkg.EnsureGitRepo(recipeDir); err != nil { return err } - logrus.Debugf("attempting to open git repository in %s", recipeDir) - repo, err := git.PlainOpen(recipeDir) if err != nil { return err @@ -376,24 +358,9 @@ func EnsureLatest(recipeName string, conf *runtime.Config) error { return err } - meta, err := GetRecipeMeta(recipeName, conf) + branch, err := gitPkg.GetDefaultBranch(repo, recipeDir) if err != nil { - switch err.(type) { - case RecipeMissingFromCatalogue: - meta = RecipeMeta{} - default: - return err - } - } - - var branch plumbing.ReferenceName - if meta.DefaultBranch != "" { - branch = plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", meta.DefaultBranch)) - } else { - branch, err = gitPkg.GetDefaultBranch(repo, recipeDir) - if err != nil { - return err - } + return err } checkOutOpts := &git.CheckoutOptions{ @@ -598,29 +565,9 @@ func GetStringInBetween(recipeName, str, start, end string) (result string, err } // EnsureUpToDate ensures that the local repo is synced to the remote -func EnsureUpToDate(recipeName string, conf *runtime.Config) error { - if !conf.RecipeLatest { - logrus.Debug("skipping ensuring recipe is synced with remote") - return nil - } - +func EnsureUpToDate(recipeName string) error { recipeDir := path.Join(config.RECIPES_DIR, recipeName) - isClean, err := gitPkg.IsClean(recipeDir) - if err != nil { - return fmt.Errorf("unable to check git clean status in %s: %s", recipeDir, err) - } - - if !isClean { - msg := "%s (%s) has locally unstaged changes? please commit/remove your changes before proceeding" - return fmt.Errorf(msg, recipeName, recipeDir) - } - - if conf.Offline { - logrus.Debug("attempting to use local recipe without access network (\"--offline\")") - return nil - } - repo, err := git.PlainOpen(recipeDir) if err != nil { return fmt.Errorf("unable to open %s: %s", recipeDir, err) @@ -671,15 +618,17 @@ func EnsureUpToDate(recipeName string, conf *runtime.Config) error { } // ReadRecipeCatalogue reads the recipe catalogue. -func ReadRecipeCatalogue(conf *runtime.Config) (RecipeCatalogue, error) { +func ReadRecipeCatalogue(offline bool) (RecipeCatalogue, error) { recipes := make(RecipeCatalogue) - if err := catalogue.EnsureCatalogue(conf); err != nil { + if err := catalogue.EnsureCatalogue(); err != nil { return nil, err } - if err := catalogue.EnsureUpToDate(conf); err != nil { - return nil, err + if !offline { + if err := catalogue.EnsureUpToDate(); err != nil { + return nil, err + } } if err := readRecipeCatalogueFS(&recipes); err != nil { @@ -706,10 +655,10 @@ func readRecipeCatalogueFS(target interface{}) error { } // VersionsOfService lists the version of a service. -func VersionsOfService(recipe, serviceName string, conf *runtime.Config) ([]string, error) { +func VersionsOfService(recipe, serviceName string, offline bool) ([]string, error) { var versions []string - catalogue, err := ReadRecipeCatalogue(conf) + catalogue, err := ReadRecipeCatalogue(offline) if err != nil { return nil, err } @@ -743,8 +692,8 @@ func (r RecipeMissingFromCatalogue) Error() string { } // GetRecipeMeta retrieves the recipe metadata from the recipe catalogue. -func GetRecipeMeta(recipeName string, conf *runtime.Config) (RecipeMeta, error) { - catl, err := ReadRecipeCatalogue(conf) +func GetRecipeMeta(recipeName string, offline bool) (RecipeMeta, error) { + catl, err := ReadRecipeCatalogue(offline) if err != nil { return RecipeMeta{}, err } @@ -756,10 +705,6 @@ func GetRecipeMeta(recipeName string, conf *runtime.Config) (RecipeMeta, error) } } - if err := EnsureExists(recipeName, conf); err != nil { - return RecipeMeta{}, err - } - logrus.Debugf("recipe metadata retrieved for %s", recipeName) return recipeMeta, nil @@ -843,11 +788,7 @@ type InternalTracker struct { type RepoCatalogue map[string]RepoMeta // ReadReposMetadata retrieves coop-cloud/... repo metadata from Gitea. -func ReadReposMetadata(conf *runtime.Config) (RepoCatalogue, error) { - if conf.Offline { - return nil, fmt.Errorf("network access required to query recipes metadata") - } - +func ReadReposMetadata() (RepoCatalogue, error) { reposMeta := make(RepoCatalogue) pageIdx := 1 @@ -882,7 +823,7 @@ func ReadReposMetadata(conf *runtime.Config) (RepoCatalogue, error) { } // GetRecipeVersions retrieves all recipe versions. -func GetRecipeVersions(recipeName string, conf *runtime.Config) (RecipeVersions, error) { +func GetRecipeVersions(recipeName string, offline bool) (RecipeVersions, error) { versions := RecipeVersions{} recipeDir := path.Join(config.RECIPES_DIR, recipeName) @@ -920,7 +861,7 @@ func GetRecipeVersions(recipeName string, conf *runtime.Config) (RecipeVersions, logrus.Debugf("successfully checked out %s in %s", ref.Name(), recipeDir) - recipe, err := Get(recipeName, conf) + recipe, err := Get(recipeName, offline) if err != nil { return err } @@ -1027,11 +968,7 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri } // UpdateRepositories clones and updates all recipe repositories locally. -func UpdateRepositories(repos RepoCatalogue, recipeName string, conf *runtime.Config) error { - if conf.Offline { - return fmt.Errorf("network access required to update recipes") - } - +func UpdateRepositories(repos RepoCatalogue, recipeName string) error { var barLength int if recipeName != "" { barLength = 1 @@ -1065,10 +1002,6 @@ func UpdateRepositories(repos RepoCatalogue, recipeName string, conf *runtime.Co logrus.Fatal(err) } - if err := EnsureUpToDate(rm.Name, conf); err != nil { - logrus.Fatal(err) - } - ch <- rm.Name retrieveBar.Add(1) }(repoMeta) diff --git a/pkg/runtime/config.go b/pkg/runtime/config.go deleted file mode 100644 index cd0fa2a9..00000000 --- a/pkg/runtime/config.go +++ /dev/null @@ -1,59 +0,0 @@ -package runtime - -import "github.com/sirupsen/logrus" - -// Config is a runtime behaviour modifier. -type Config struct { - Offline bool // Whether or not Abra should prefer local / offline access - RecipeExists bool // Whether or not Abra should ensure the recipe is locally cloned - RecipeLatest bool // Whether or not Abra should ensure the recipe has the latest commit -} - -// Option is a runtime configuration option. -type Option func(c *Config) - -// New creates a new runtime configuration. -func New(opts ...Option) *Config { - conf := &Config{ - Offline: false, - RecipeExists: true, - RecipeLatest: true, - } - - for _, optFunc := range opts { - optFunc(conf) - } - - return conf -} - -// WithOffline ensures Abra attempts to prefer local file system and offline -// access. -func WithOffline(offline bool) Option { - return func(c *Config) { - if offline { - logrus.Debugf("runtime config: attempting to run in offline mode") - } - c.Offline = offline - } -} - -// WithEnsureRecipeExists ensures recipe exists locally. -func WithEnsureRecipeExists(exists bool) Option { - return func(c *Config) { - if exists { - logrus.Debugf("runtime config: ensuring recipe exists") - } - c.RecipeExists = exists - } -} - -// WithEnsureRecipeUpToDate ensures recipe is synced with the remote. -func WithEnsureRecipeUpToDate(upToDate bool) Option { - return func(c *Config) { - if upToDate { - logrus.Debugf("runtime config: ensuring recipe is synced with remote") - } - c.RecipeLatest = upToDate - } -} diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..73858fbc --- /dev/null +++ b/tests/README.md @@ -0,0 +1,10 @@ +# Test suite + +* Unit testing is done in the packages themselves, run `find . -name + "*_test.go"` to find those. Use `make test` to run the entire unit test + suite. Some unit tests require mocked files which are located in + `./tests/resources.` + +* Integration tests are in `./tests/integration`. Please see [these + docs](https://docs.coopcloud.tech/abra/hack/#integration-tests) for + instructions and tips on how to run them. diff --git a/tests/integration/app_backup.bats b/tests/integration/app_backup.bats new file mode 100644 index 00000000..bccb4e7a --- /dev/null +++ b/tests/integration/app_backup.bats @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +setup_file() { + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file() { + _rm_app + _rm_server +} + +setup() { + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "retrieve recipe if missing" { + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE" + + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE" + assert_success + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE" + + run $ABRA app backup "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'no containers matching' + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE" +} + +@test "detect backup labels" { + run $ABRA app backup "$TEST_APP_DOMAIN" --debug + assert_failure + assert_output --partial 'no containers matching' + + assert_output --partial 'detected backup paths' + assert_output --partial 'detected pre-hook command' + assert_output --partial 'detected post-hook command' +} + +@test "error if backups not enabled" { + run sed -i '/backupbot.backup=true/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" + assert_success + + run $ABRA app backup "$TEST_APP_DOMAIN" app + assert_failure + assert_output --partial 'no backup config for app' + + _checkout_recipe "$TEST_RECIPE" +} + +@test "error if backup paths not configured" { + run sed -i '/backupbot.backup.path=.*/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" + assert_success + + run $ABRA app backup "$TEST_APP_DOMAIN" app + assert_failure + assert_output --partial 'backup paths are empty for app?' + + _checkout_recipe "$TEST_RECIPE" +} + +# bats test_tags=slow +@test "backup single service" { + _deploy_app + + run $ABRA app backup "$TEST_APP_DOMAIN" app + assert_success + assert_output --partial 'running backup for the app service' + + sanitisedDomainName="${TEST_APP_DOMAIN//./_}" + assert_output --partial "_$sanitisedDomainName_app" + + assert_exists "$ABRA_DIR/backups" + assert bash -c "ls $ABRA_DIR/backups | grep -q $1_$sanitisedDomainName_app" + + _undeploy_app +} diff --git a/tests/integration/app_check.bats b/tests/integration/app_check.bats new file mode 100644 index 00000000..c315ff14 --- /dev/null +++ b/tests/integration/app_check.bats @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app check + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app check DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "retrieve recipe if missing" { + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE" + assert_success + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE" + + run $ABRA app check "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'all necessary environment variables defined' + + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE" +} + +@test "error if missing .env.sample" { + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/.env.sample" + assert_success + + run $ABRA app check "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial '.env.sample does not exist?' + + _checkout_recipe "$TEST_RECIPE" +} + +@test "error if missing env var" { + run bash -c 'echo "NEW_VAR=foo" >> "$ABRA_DIR/recipes/$TEST_RECIPE/.env.sample"' + assert_success + + run $ABRA app check "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial \ + "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env is missing NEW_VAR" + + _checkout_recipe "$TEST_RECIPE" +} diff --git a/tests/integration/app_cmd.bats b/tests/integration/app_cmd.bats new file mode 100644 index 00000000..6ab74d0f --- /dev/null +++ b/tests/integration/app_cmd.bats @@ -0,0 +1,115 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app cmd + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app cmd DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "retrieve recipe if missing" { + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE" + assert_success + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE" + + run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local + assert_success + assert_output --partial 'baz' + + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE" +} + +@test "error if missing arguments without passing --local" { + run $ABRA app cmd "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'missing arguments' +} + +@test "error if missing arguments when passing --local" { + run $ABRA app cmd "$TEST_APP_DOMAIN" --local + assert_failure + assert_output --partial 'missing arguments' +} + +@test "cannot use --local and --user at same time" { + run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local --user root + assert_failure + assert_output --partial 'cannot use --local & --user together' +} + +@test "error if missing abra.sh" { + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/abra.sh" + assert_success + + run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local + assert_failure + assert_output --partial "$ABRA_DIR/recipes/$TEST_RECIPE/abra.sh does not exist" + + _checkout_recipe "$TEST_RECIPE" +} + +@test "error if missing command" { + run $ABRA app cmd "$TEST_APP_DOMAIN" doesnt_exist --local + assert_failure + assert_output --partial "doesn't have a doesnt_exist function" +} + +@test "run --local command" { + run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd --local + assert_success + assert_output --partial 'baz' +} + +@test "run command with single arg" { + run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd_arg --local -- bing + assert_success + assert_output --partial 'bing' +} + +@test "run command with several args" { + run $ABRA app cmd "$TEST_APP_DOMAIN" test_cmd_args --local -- bong bang + assert_success + assert_output --partial 'bong bang' +} + +# bats test_tags=slow +@test "run command on service" { + _deploy_app + + run $ABRA app cmd "$TEST_APP_DOMAIN" app test_cmd + assert_success + assert_output --partial 'baz' + + _undeploy_app +} + +# bats test_tags=slow +@test "error if missing service" { + _deploy_app + + run $ABRA app cmd "$TEST_APP_DOMAIN" doesnt_exist test_cmd + assert_failure + assert_output --partial 'no service doesnt_exist' + + _undeploy_app +} diff --git a/tests/integration/app_config.bats b/tests/integration/app_config.bats new file mode 100644 index 00000000..6b400248 --- /dev/null +++ b/tests/integration/app_config.bats @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app config + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app config DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} diff --git a/tests/integration/app_cp.bats b/tests/integration/app_cp.bats new file mode 100644 index 00000000..4123b530 --- /dev/null +++ b/tests/integration/app_cp.bats @@ -0,0 +1,123 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app cp + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app cp DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if missing src/dest arguments" { + run $ABRA app cp "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'missing argument' + + run $ABRA app cp "$TEST_APP_DOMAIN" myfile.txt + assert_failure + assert_output --partial 'missing argument' +} + +@test "either src/dest has correct syntax" { + run $ABRA app cp "$TEST_APP_DOMAIN" myfile.txt app + assert_failure + assert_output --partial 'arguments must take $SERVICE:$PATH form' + + run $ABRA app cp "$TEST_APP_DOMAIN" app . + assert_failure + assert_output --partial 'arguments must take $SERVICE:$PATH form' +} + +@test "detect 'coming FROM' syntax" { + run $ABRA app cp "$TEST_APP_DOMAIN" app:/myfile.txt . --debug + assert_failure + assert_output --partial 'coming FROM the container' +} + +@test "detect 'going TO' syntax" { + run $ABRA app cp "$TEST_APP_DOMAIN" myfile.txt app:/somewhere --debug + assert_failure + assert_output --partial 'going TO the container' +} + +@test "error if local file missing" { + run $ABRA app cp "$TEST_APP_DOMAIN" myfile.txt app:/somewhere + assert_failure + assert_output --partial 'myfile.txt does not exist locally?' +} + +# bats test_tags=slow +@test "error if service doesn't exist" { + _deploy_app + + run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt" + assert_success + + run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" doesnt_exist:/ + assert_failure + assert_output --partial 'no containers matching' + + run rm -rf "$BATS_TMPDIR/myfile.txt" + assert_success + + _undeploy_app +} + +# bats test_tags=slow +@test "copy to container" { + _deploy_app + + run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt" + assert_success + + run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc + assert_success + + run rm -rf "$BATS_TMPDIR/myfile.txt" + assert_success + + _undeploy_app +} + +# bats test_tags=slow +@test "copy from container" { + _deploy_app + + run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt" + assert_success + + run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc + assert_success + + run rm -rf "$BATS_TMPDIR/myfile.txt" + assert_success + + run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR" + assert_success + assert_exists "$BATS_TMPDIR/myfile.txt" + assert bash -c "cat $BATS_TMPDIR/myfile.txt | grep -q foo" + + run rm -rf "$BATS_TMPDIR/myfile.txt" + assert_success + + _undeploy_app +} diff --git a/tests/integration/app_deploy.bats b/tests/integration/app_deploy.bats new file mode 100644 index 00000000..5c04f49a --- /dev/null +++ b/tests/integration/app_deploy.bats @@ -0,0 +1,276 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app deploy + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app deploy DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "bail if unstaged changes and no --chaos" { + run bash -c "echo foo >> $ABRA_DIR/recipes/$TEST_RECIPE/foo" + assert_success + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_success + assert_output --partial 'foo' + + run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input + assert_failure + assert_output --partial 'locally unstaged changes' + refute_output --partial 'chaos' + + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo" + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" +} + +# bats test_tags=slow +@test "do not bail if unstaged changes and --chaos" { + run bash -c 'echo "unstaged changes" >> "$ABRA_DIR/recipes/$TEST_RECIPE/foo"' + assert_success + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_success + assert_output --partial 'foo' + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --chaos --no-input --no-converge-checks + assert_success + assert_output --partial 'chaos' + + _undeploy_app + + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo" + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" +} + +# bats test_tags=slow +@test "ensure recipe up to date if no --offline" { + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + refute_output --partial 'behind 3' + + _undeploy_app +} + +# bats test_tags=slow +@test "ensure recipe not up to date if --offline" { + latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" + refute [ -z "$latestCommit" ]; + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --offline + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + _undeploy_app + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit" + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + refute_output --partial 'behind 3' +} + +# bats test_tags=slow +@test "deploy latest commit if no published versions and no --chaos" { + latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" + + run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks + assert_success + assert_output --partial "$latestCommit" + refute_output --partial 'chaos' + + _undeploy_app +} + +# bats test_tags=slow +@test "ensure same commit if --chaos" { + latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + threeCommitsBack="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --chaos + assert_success + refute_output --partial "$latestCommit" + assert_output --partial "$threeCommitsBack" + assert_output --partial 'chaos' + + _undeploy_app + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit" + assert_success + refute_output --partial 'behind 3' +} + +# bats test_tags=slow +@test "retrieve recipe if missing" { + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE" + assert_success + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE" + + run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks + assert_success + + _undeploy_app + + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE" +} + +@test "no deploy if lint error" { + run sed -i '/traefik.enable=.*/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" + assert_success + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --chaos + assert_failure + assert_output --partial 'failed lint checks' + + _checkout_recipe "$TEST_RECIPE" +} + +# bats test_tags=slow +@test "error if already deployed and no --force/--chaos" { + _deploy_app + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks + assert_failure + assert_output --partial 'already deployed' + + _undeploy_app +} + +# bats test_tags=slow +@test "re-deploy deployed app if --force/--chaos" { + _deploy_app + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --force + assert_success + assert_output --partial 'already deployed but continuing' + assert_output --partial '--force' + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --chaos + assert_success + assert_output --partial 'already deployed but continuing' + assert_output --partial '--chaos' + + _undeploy_app +} + +# bats test_tags=slow +@test "deploy latest version from catalogue if no --chaos" { + latestVersion=$(jq -r '.gitea.versions[-1] | keys[0]' < "$ABRA_DIR/catalogue/recipes.json") + refute [ -z "$latestVersion" ]; + + run $ABRA app new gitea \ + --no-input \ + --server "$TEST_SERVER" \ + --domain "gitea.$TEST_SERVER" \ + --secrets + assert_success + assert_exists "$ABRA_DIR/servers/$TEST_SERVER/gitea.$TEST_SERVER.env" + + run $ABRA app deploy "gitea.$TEST_SERVER" --no-input --no-converge-checks + assert_success + assert_output --partial "$latestVersion" + + run $ABRA app undeploy "gitea.$TEST_SERVER" --no-input + assert_success + + run $ABRA app secret remove "gitea.$TEST_SERVER" --all --no-input + assert_success + + # NOTE(d1): to let the stack come down before nuking volumes + sleep 5 + + run $ABRA app volume remove "gitea.$TEST_SERVER" --no-input + assert_success + + run $ABRA app remove "gitea.$TEST_SERVER" --no-input + assert_success + assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/gitea.$TEST_SERVER.env" +} + +# bats test_tags=slow +@test "skip domain check if missing DOMAIN=" { + run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$BATS_TMPDIR/$TEST_APP_DOMAIN.env" + assert_success + assert_exists "$BATS_TMPDIR/$TEST_APP_DOMAIN.env" + + run grep -q "DOMAIN=" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" + assert_success + + run sed -i '/DOMAIN=.*/d' "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" + assert_success + + run grep -q "DOMAIN=" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" + assert_failure + + run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks + assert_success + assert_output --partial 'no DOMAIN=... configured for app' + + run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input + assert_success + + run mv "$BATS_TMPDIR/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" + assert_success +} + +# bats test_tags=slow +@test "skip domain check when requested" { + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --no-domain-checks + assert_success + assert_output --partial 'skipping domain checks as requested' + + _undeploy_app +} diff --git a/tests/integration/app_errors.bats b/tests/integration/app_errors.bats new file mode 100644 index 00000000..813547fd --- /dev/null +++ b/tests/integration/app_errors.bats @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app errors + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app errors DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if not deployed" { + run $ABRA app errors "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'is not deployed' +} + +# bats test_tags=slow +@test "report errors" { + _deploy_app + + run $ABRA app errors "$TEST_APP_DOMAIN" + assert_success + + _undeploy_app +} diff --git a/tests/integration/app_list.bats b/tests/integration/app_list.bats new file mode 100644 index 00000000..0f62e1ee --- /dev/null +++ b/tests/integration/app_list.bats @@ -0,0 +1,130 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "list without status" { + run $ABRA app ls + assert_success + assert_output --partial "$TEST_SERVER" + assert_output --partial "$TEST_APP_DOMAIN" +} + +# bats test_tags=slow +@test "list with status" { + run $ABRA app ls --status + assert_success + assert_output --partial "$TEST_SERVER" + assert_output --partial "$TEST_APP_DOMAIN" + assert_output --partial "unknown" + + _deploy_app + + run $ABRA app ls --status + assert_success + assert_output --partial "$TEST_SERVER" + assert_output --partial "$TEST_APP_DOMAIN" + assert_output --partial "deployed" + + _undeploy_app +} + +@test "filter by server" { + run mkdir -p "$ABRA_DIR/servers/foo.com" + assert_success + assert_exists "$ABRA_DIR/servers/foo.com" + + run cp \ + "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" \ + "$ABRA_DIR/servers/foo.com/app.foo.com.env" + assert_exists "$ABRA_DIR/servers/foo.com/app.foo.com.env" + + run $ABRA app ls + assert_success + assert_output --partial "$TEST_SERVER" + assert_output --partial "foo.com" + + run $ABRA app ls --server foo.com + assert_success + refute_output --partial "server: $TEST_SERVER |" + assert_output --partial "server: foo.com |" + + run rm -rf "$ABRA_DIR/servers/foo.com" + assert_success + assert_not_exists "$ABRA_DIR/servers/foo.com" +} + +@test "filter by recipe" { + run mkdir -p "$ABRA_DIR/servers/foo.com" + assert_success + assert_exists "$ABRA_DIR/servers/foo.com" + + run cp \ + "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" \ + "$ABRA_DIR/servers/foo.com/app.foo.com.env" + assert_exists "$ABRA_DIR/servers/foo.com/app.foo.com.env" + + run sed -i "s/TYPE=$TEST_RECIPE/TYPE=foo-recipe/g" "$ABRA_DIR/servers/foo.com/app.foo.com.env" + assert grep -q "TYPE=foo-recipe" "$ABRA_DIR/servers/foo.com/app.foo.com.env" + + run $ABRA app ls + assert_success + assert_output --partial "$TEST_RECIPE" + assert_output --partial "foo-recipe" + + run $ABRA app ls --recipe foo-recipe + assert_success + refute_output --partial "$TEST_RECIPE" + assert_output --partial "foo-recipe" +} + +@test "server stats are correct" { + run $ABRA app ls + assert_success + assert_output --partial "server: $TEST_SERVER" + assert_output --partial "total apps: 1" + + run mkdir -p "$ABRA_DIR/servers/foo.com" + assert_success + assert_exists "$ABRA_DIR/servers/foo.com" + + run cp \ + "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" \ + "$ABRA_DIR/servers/foo.com/app.foo.com.env" + assert_exists "$ABRA_DIR/servers/foo.com/app.foo.com.env" + + run $ABRA app ls + assert_success + assert_output --partial "$TEST_SERVER" + assert_output --partial "foo.com" + assert_output --partial "total servers: 2" + assert_output --partial "total apps: 2" + + run rm -rf "$ABRA_DIR/servers/foo.com" + assert_success + assert_not_exists "$ABRA_DIR/servers/foo.com" +} + +@test "output is machine readable" { + run $ABRA app ls --machine + + expectedOutput='{"' + expectedOutput+="$TEST_SERVER" + expectedOutput+='":{"apps":' + + assert_output --partial "$expectedOutput" +} diff --git a/tests/integration/app_logs.bats b/tests/integration/app_logs.bats new file mode 100644 index 00000000..07c68a2a --- /dev/null +++ b/tests/integration/app_logs.bats @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app logs + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app logs DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if not deployed" { + run $ABRA app logs "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'is not deployed' +} diff --git a/tests/integration/app_new.bats b/tests/integration/app_new.bats new file mode 100644 index 00000000..282b24cc --- /dev/null +++ b/tests/integration/app_new.bats @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "create new app" { + run $ABRA app new "$TEST_RECIPE" \ + --no-input \ + --server "$TEST_SERVER" \ + --domain "$TEST_APP_DOMAIN" + assert_success + assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" + + _rm_app +} + +@test "does not overwrite existing env files" { + _new_app + + run $ABRA app new "$TEST_RECIPE" \ + --no-input \ + --server "$TEST_SERVER" \ + --domain "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'already exists' + + _rm_app +} + +@test "generate secrets" { + run $ABRA app new "$TEST_RECIPE" \ + --no-input \ + --server "$TEST_SERVER" \ + --domain "$TEST_APP_DOMAIN" \ + --secrets + assert_success + assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" + + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'test_password' +} diff --git a/tests/integration/app_ps.bats b/tests/integration/app_ps.bats new file mode 100644 index 00000000..edc60cb5 --- /dev/null +++ b/tests/integration/app_ps.bats @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app ps + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app ps DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if not deployed" { + run $ABRA app ps "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'is not deployed' +} + +# bats test_tags=slow +@test "show ps report" { + _deploy_app + + run $ABRA app ps "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'app' + assert_output --partial 'healthy' + + _undeploy_app +} diff --git a/tests/integration/app_remove.bats b/tests/integration/app_remove.bats new file mode 100644 index 00000000..63626fd0 --- /dev/null +++ b/tests/integration/app_remove.bats @@ -0,0 +1,148 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app deploy + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app deploy DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "do not show ALERTA warning if --force / --no-input" { + run $ABRA app rm "$TEST_APP_DOMAIN" --force + refute_output --partial 'ALERTA' + + run $ABRA app rm "$TEST_APP_DOMAIN" --no-input + refute_output --partial 'ALERTA' + + _new_app +} + +# bats test_tags=slow +@test "error if still deployed" { + _deploy_app + + run $ABRA app rm "$TEST_APP_DOMAIN" --no-input + assert_failure + assert_output --partial 'is still deployed' + + _undeploy_app +} + +@test "detect no secrets to remove" { + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'test_password' + + run $ABRA app secret rm "$TEST_APP_DOMAIN" --all --no-input + assert_success + + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'test_password' + assert_output --partial 'false' + + run $ABRA app rm "$TEST_APP_DOMAIN" --no-input + assert_success + assert_output --partial 'no secrets to remove' + + _new_app +} + +@test "remove secrets" { + run $ABRA app secret generate "$TEST_APP_DOMAIN" --all + assert_failure + assert_output --partial 'already exists' + + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'test_password' + + run $ABRA app rm "$TEST_APP_DOMAIN" --no-input + assert_success + refute_output --partial 'no secrets to remove' + + sanitisedDomainName="${TEST_APP_DOMAIN//./_}" + assert_output --partial "$sanitisedDomainName_test_password_v1 removed" + + _new_app +} + +# bats test_tags=slow +@test "detect no volumes to remove" { + _deploy_app + + run $ABRA app volume ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'test-volume' + + _undeploy_app + + # NOTE(d1): to let the stack come down before nuking volumes + sleep 5 + + run $ABRA app volume rm "$TEST_APP_DOMAIN" --force + assert_success + + run $ABRA app volume ls "$TEST_APP_DOMAIN" + assert_success + refute_output --partial 'test-volume' + assert_output --partial 'no volumes created' + + run $ABRA app rm "$TEST_APP_DOMAIN" --no-input + assert_success + assert_output --partial 'no volumes to remove' + + _new_app +} + +# bats test_tags=slow +@test "remove volumes" { + _deploy_app + + run $ABRA app volume ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'test-volume' + + _undeploy_app + + # NOTE(d1): to let the stack come down before nuking volumes + sleep 5 + + run $ABRA app rm "$TEST_APP_DOMAIN" --no-input + assert_success + assert_output --partial 'test-volume' + assert_output --partial 'removed' + + _new_app +} + +@test "remove .env file" { + assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" + + run $ABRA app rm "$TEST_APP_DOMAIN" --no-input + assert_success + + assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" + + _new_app +} diff --git a/tests/integration/app_restart.bats b/tests/integration/app_restart.bats new file mode 100644 index 00000000..18ee570f --- /dev/null +++ b/tests/integration/app_restart.bats @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app restart + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app restart DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if service missing" { + run $ABRA app restart "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'missing service' +} + +@test "error if not deployed" { + run $ABRA app restart "$TEST_APP_DOMAIN" app + assert_failure + assert_output --partial 'is not deployed' +} + +# bats test_tags=slow +@test "app is restarted" { + _deploy_app + + run $ABRA app restart "$TEST_APP_DOMAIN" app --debug + assert_success + assert_output --regexp 'attempting to scale .* to 0' + assert_output --regexp 'attempting to scale .* to 1' + assert_output --partial 'service successfully restarted' + + _undeploy_app +} diff --git a/tests/integration/app_restore.bats b/tests/integration/app_restore.bats new file mode 100644 index 00000000..4aed8db9 --- /dev/null +++ b/tests/integration/app_restore.bats @@ -0,0 +1,146 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app restore + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app restore DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if missing service" { + run $ABRA app restore "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'missing ' + + run $ABRA app restore "$TEST_APP_DOMAIN" app + assert_failure + assert_output --partial 'missing ' +} + +@test "error if file doesn't exist" { + run $ABRA app restore "$TEST_APP_DOMAIN" app DOESNTEXIST + assert_failure + assert_output --partial "doesn't exist" +} + +@test "retrieve recipe if missing" { + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE" + assert_success + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE" + + run $ABRA app restore "$TEST_APP_DOMAIN" app DOESNTEXIST + assert_failure + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE" +} + +# bats test_tags=slow +@test "detect labels if restore enabled" { + run touch "$BATS_TMPDIR/foo.txt" + assert_success + assert_exists "$BATS_TMPDIR/foo.txt" + + run tar -cvf "$BATS_TMPDIR/foo.tar.gz" "$BATS_TMPDIR/foo.txt" + assert_success + assert_exists "$BATS_TMPDIR/foo.tar.gz" + + _deploy_app + + run $ABRA app restore "$TEST_APP_DOMAIN" app "$BATS_TMPDIR/foo.tar.gz" --debug + assert_success + assert_output --partial 'restore config detected' + assert_output --partial 'detected pre-hook command' + assert_output --partial 'detected post-hook command' + + _undeploy_app +} + +# bats test_tags=slow +@test "no error if restore not enabled" { + run sed -i '/backupbot.restore=.*/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" + assert_success + + run touch "$BATS_TMPDIR/foo.txt" + assert_success + assert_exists "$BATS_TMPDIR/foo.txt" + + run tar -cvf "$BATS_TMPDIR/foo.tar.gz" "$BATS_TMPDIR/foo.txt" + assert_success + assert_exists "$BATS_TMPDIR/foo.tar.gz" + + run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --chaos + assert_success + + run $ABRA app restore "$TEST_APP_DOMAIN" app "$BATS_TMPDIR/foo.tar.gz" --debug + assert_success + refute_output --partial 'restore config detected' + refute_output --partial 'detected pre-hook command' + refute_output --partial 'detected post-hook command' + + _undeploy_app + + _checkout_recipe "$TEST_RECIPE" +} + +# bats test_tags=slow +@test "error if service doesn't exist" { + run touch "$BATS_TMPDIR/foo.txt" + assert_success + assert_exists "$BATS_TMPDIR/foo.txt" + + run tar -cvf "$BATS_TMPDIR/foo.tar.gz" "$BATS_TMPDIR/foo.txt" + assert_success + assert_exists "$BATS_TMPDIR/foo.tar.gz" + + _deploy_app + + run $ABRA app restore "$TEST_APP_DOMAIN" DOESNTEXIST "$BATS_TMPDIR/foo.tar.gz" --debug + assert_failure + assert_output --partial 'no containers matching' + + _undeploy_app +} + +# bats test_tags=slow +@test "restore backup" { + run touch "$BATS_TMPDIR/foo.txt" + assert_success + assert_exists "$BATS_TMPDIR/foo.txt" + + run tar -cvf "$BATS_TMPDIR/foo.tar.gz" "$BATS_TMPDIR/foo.txt" + assert_success + assert_exists "$BATS_TMPDIR/foo.tar.gz" + + _deploy_app + + run $ABRA app restore "$TEST_APP_DOMAIN" app "$BATS_TMPDIR/foo.tar.gz" --debug + assert_success + assert_output --partial 'restore config detected' + assert_output --partial 'detected pre-hook command' + assert_output --partial 'detected post-hook command' + + run $ABRA app run "$TEST_APP_DOMAIN" app ls "$BATS_TMPDIR" + assert_success + assert_output --partial 'foo.txt' + + _undeploy_app +} diff --git a/tests/integration/app_rollback.bats b/tests/integration/app_rollback.bats new file mode 100644 index 00000000..2f2c246e --- /dev/null +++ b/tests/integration/app_rollback.bats @@ -0,0 +1,232 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +# TODO(d1): test "no available downgrades" when this is implemented +# https://git.coopcloud.tech/coop-cloud/organising/issues/204 + +@test "validate app argument" { + run $ABRA app rollback + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app rollback DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "retrieve recipe if missing" { + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE" + assert_success + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE" + + run $ABRA app rollback "$TEST_APP_DOMAIN" --no-input --no-converge-checks + assert_failure + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE" +} + +@test "ensure recipe up to date if no --offline" { + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run $ABRA app rollback "$TEST_APP_DOMAIN" --no-input --no-converge-checks + assert_failure + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + refute_output --partial 'behind 3' +} + +@test "ensure recipe not up to date if --offline" { + latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" + refute [ -z "$latestCommit" ]; + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run $ABRA app rollback "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --offline + assert_failure + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit" + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + refute_output --partial 'behind 3' +} + +@test "bail if unstaged changes and no --chaos" { + run bash -c "echo foo >> $ABRA_DIR/recipes/$TEST_RECIPE/foo" + assert_success + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_success + assert_output --partial 'foo' + + run $ABRA app rollback "$TEST_APP_DOMAIN" --no-input --no-converge-checks + assert_failure + assert_output --partial 'locally unstaged changes' + refute_output --partial 'chaos' + + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo" + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" +} + +# bats test_tags=slow +@test "do not bail if unstaged changes and --chaos" { + run bash -c 'echo "unstaged changes" >> "$ABRA_DIR/recipes/$TEST_RECIPE/foo"' + assert_success + assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_success + assert_output --partial 'foo' + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --chaos --no-input --no-converge-checks + assert_success + assert_output --partial 'chaos' + + run $ABRA app rollback "$TEST_APP_DOMAIN" \ + --chaos --no-input --no-converge-checks + assert_success + assert_output --partial 'chaos' + + _undeploy_app + + run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo" + assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" +} + +# bats test_tags=slow +@test "ensure same commit if --chaos" { + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout main + assert_success + + latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + threeCommitsBack="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --chaos + assert_success + refute_output --partial "$latestCommit" + assert_output --partial "$threeCommitsBack" + assert_output --partial 'chaos' + + run $ABRA app rollback "$TEST_APP_DOMAIN" \ + --chaos --no-input --no-converge-checks + assert_success + refute_output --partial "$latestCommit" + assert_output --partial "$threeCommitsBack" + assert_output --partial 'chaos' + + _undeploy_app + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit" + assert_success + refute_output --partial 'behind 3' +} + +# bats test_tags=slow +@test "no rollback if lint error" { + _deploy_app + + run sed -i '/traefik.enable=.*/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" + assert_success + + run $ABRA app rollback "$TEST_APP_DOMAIN" --no-input --chaos + assert_failure + assert_output --partial 'failed lint checks' + + _undeploy_app + + _checkout_recipe "$TEST_RECIPE" +} + +@test "error if not already deployed" { + run $ABRA app rollback "$TEST_APP_DOMAIN" --no-input --chaos + assert_failure + assert_output --partial 'not deployed' +} + +# bats test_tags=slow +@test "error if no published release and no --chaos" { + _deploy_app + + run $ABRA app rollback "$TEST_APP_DOMAIN" --no-input + assert_failure + assert_output --partial 'no published releases' + + _undeploy_app +} + +# bats test_tags=slow +@test "rollback to previous version" { + latestVersion=$(jq -r '.gitea.versions[-1] | keys[0]' < "$ABRA_DIR/catalogue/recipes.json") + refute [ -z "$latestVersion" ]; + + rollbackVersion=$(jq -r '.gitea.versions[-2] | keys[0]' < "$ABRA_DIR/catalogue/recipes.json") + refute [ -z "$rollbackVersion" ]; + + run $ABRA app new gitea \ + --no-input \ + --server "$TEST_SERVER" \ + --domain "gitea.$TEST_SERVER" \ + --secrets + assert_success + assert_exists "$ABRA_DIR/servers/$TEST_SERVER/gitea.$TEST_SERVER.env" + + run $ABRA app deploy "gitea.$TEST_SERVER" --no-input --no-converge-checks + assert_success + assert_output --partial "$latestVersion" + + run $ABRA app rollback "gitea.$TEST_SERVER" --no-input --no-converge-checks + assert_success + assert_output --partial "$rollbackVersion" + + run $ABRA app undeploy "gitea.$TEST_SERVER" --no-input + assert_success + + run $ABRA app secret remove "gitea.$TEST_SERVER" --all --no-input + assert_success + + # NOTE(d1): to let the stack come down before nuking volumes + sleep 5 + + run $ABRA app volume remove "gitea.$TEST_SERVER" --no-input + assert_success + + run $ABRA app remove "gitea.$TEST_SERVER" --no-input + assert_success + assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/gitea.$TEST_SERVER.env" +} diff --git a/tests/integration/app_run.bats b/tests/integration/app_run.bats new file mode 100644 index 00000000..8980abf4 --- /dev/null +++ b/tests/integration/app_run.bats @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app run + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app run DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if missing service" { + run $ABRA app run "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'no provided' + + run $ABRA app run "$TEST_APP_DOMAIN" app + assert_failure + assert_output --partial 'no provided' +} + +# bats test_tags=slow +@test "error if service doesn't exist" { + _deploy_app + + run $ABRA app run "$TEST_APP_DOMAIN" DOESNTEXIST ls + assert_failure + assert_output --partial 'no containers matching' + + _undeploy_app +} + +# bats test_tags=slow +@test "run command" { + _deploy_app + + run $ABRA app run "$TEST_APP_DOMAIN" app ls / + assert_success + assert_output --partial 'root' + + _undeploy_app +} diff --git a/tests/integration/app_secret.bats b/tests/integration/app_secret.bats new file mode 100644 index 00000000..171b7f44 --- /dev/null +++ b/tests/integration/app_secret.bats @@ -0,0 +1,216 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app + + run $ABRA app secret rm "$TEST_APP_DOMAIN" --all + assert_success +} + +teardown_file(){ + run $ABRA app secret rm "$TEST_APP_DOMAIN" --all + assert_success + + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "generate: validate arguments" { + run $ABRA app secret generate + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app secret generate DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' + + run $ABRA app secret generate "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'missing arguments' + + run $ABRA app secret generate "$TEST_APP_DOMAIN" testSecret testVersion --all + assert_failure + assert_output --partial 'cannot use' + assert_output --partial "'--all' together" +} + +@test "generate: single secret no match" { + run $ABRA app secret generate "$TEST_APP_DOMAIN" DOESNTEXIST v1 + assert_failure + assert_output --partial "doesn't exist in the env config" +} + +@test "generate: recipe up to date if no --offline" { + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run $ABRA app secret generate "$TEST_APP_DOMAIN" --all + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + refute_output --partial 'behind 3' + + run $ABRA app secret rm "$TEST_APP_DOMAIN" --all + assert_success +} + +@test "generate: recipe not up to date if --offline" { + latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" + refute [ -z "$latestCommit" ]; + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run $ABRA app secret generate "$TEST_APP_DOMAIN" --all --offline + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run $ABRA app secret rm "$TEST_APP_DOMAIN" --all + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit" + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + refute_output --partial 'behind 3' +} + +@test "generate: create secrets" { + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'false' + + run $ABRA app secret generate "$TEST_APP_DOMAIN" --all + assert_success + + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'true' + + run $ABRA app secret rm "$TEST_APP_DOMAIN" --all + assert_success +} + +@test "insert: validate arguments" { + run $ABRA app secret insert + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app secret insert "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'missing arguments' + + run $ABRA app secret insert "$TEST_APP_DOMAIN" bar + assert_failure + assert_output --partial 'missing arguments' + + run $ABRA app secret insert "$TEST_APP_DOMAIN" bar baz + assert_failure + assert_output --partial 'missing arguments' +} + +@test "insert: create secret" { + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'false' + + run $ABRA app secret insert "$TEST_APP_DOMAIN" test_password v1 foo + assert_success + assert_output --partial 'successfully stored on server' + + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'true' + + run $ABRA app secret rm "$TEST_APP_DOMAIN" test_password + assert_success +} + +@test "rm: validate arguments" { + run $ABRA app secret rm + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app secret rm DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' + + run $ABRA app secret rm "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'no secret(s) specified' + + run $ABRA app secret rm "$TEST_APP_DOMAIN" test_password --all + assert_failure + assert_output --partial 'cannot use' + assert_output --partial "'--all' together" +} + +@test "rm: single secret no match" { + run $ABRA app secret rm "$TEST_APP_DOMAIN" foo_password + assert_failure + assert_output --partial "doesn't exist on server" +} + +@test "rm: no secret match" { + run $ABRA app secret rm "$TEST_APP_DOMAIN" --all + assert_failure + assert_output --partial 'no secrets to remove' +} + +@test "rm: remove secret" { + run $ABRA app secret generate "$TEST_APP_DOMAIN" --all + assert_success + + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'true' + + run $ABRA app secret rm "$TEST_APP_DOMAIN" --all + assert_success + + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'false' +} + +@test "ls: validate arguments" { + run $ABRA app secret ls + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app secret ls DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "ls: show secrets" { + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'false' + + run $ABRA app secret generate "$TEST_APP_DOMAIN" --all + assert_success + + run $ABRA app secret ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'true' + + run $ABRA app secret rm "$TEST_APP_DOMAIN" --all + assert_success +} diff --git a/tests/integration/app_services.bats b/tests/integration/app_services.bats new file mode 100644 index 00000000..9fee6b1d --- /dev/null +++ b/tests/integration/app_services.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app services + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app services DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if not deployed" { + run $ABRA app services "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'is not deployed' +} + +# bats test_tags=slow +@test "list services" { + _deploy_app + + run $ABRA app services "$TEST_APP_DOMAIN" + assert_success + + sanitisedDomainName="${TEST_APP_DOMAIN//./_}" + assert_output --partial "$sanitisedDomainName_app" + assert_output --partial "nginx" + + _undeploy_app +} diff --git a/tests/integration/app_undeploy.bats b/tests/integration/app_undeploy.bats new file mode 100644 index 00000000..051eb458 --- /dev/null +++ b/tests/integration/app_undeploy.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app undeploy + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app undeploy DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if not deployed" { + run $ABRA app undeploy "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'is not deployed' +} + +# bats test_tags=slow +@test "undeploy app" { + _deploy_app + _undeploy_app +} + +# bats test_tags=slow +@test "undeploy and prune" { + _deploy_app + + run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input --prune + assert_success +} diff --git a/tests/integration/app_upgrade.bats b/tests/integration/app_upgrade.bats new file mode 100644 index 00000000..8d0b5133 --- /dev/null +++ b/tests/integration/app_upgrade.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app upgrade + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app upgrade DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} diff --git a/tests/integration/app_version.bats b/tests/integration/app_version.bats new file mode 100644 index 00000000..b4cd3910 --- /dev/null +++ b/tests/integration/app_version.bats @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate app argument" { + run $ABRA app version + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app version DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "error if not deployed" { + run $ABRA app version "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'is not deployed' +} + +# bats test_tags=slow +@test "error if version unknown" { + run sed -i '/coop-cloud.${STACK_NAME}.version=.*/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" + assert_success + + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --chaos + assert_success + + run $ABRA app version "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'failed to determine' + + _checkout_recipe "$TEST_RECIPE" + _undeploy_app +} + +# bats test_tags=slow +@test "error if no version in catalogue" { + _deploy_app + + run $ABRA app version "$TEST_APP_DOMAIN" + assert_failure + assert_output --partial 'could not retrieve deployed version' + + _undeploy_app +} + +@test "list version" { + latestVersion=$(jq -r '.gitea.versions[-1] | keys[0]' < "$ABRA_DIR/catalogue/recipes.json") + refute [ -z "$latestVersion" ]; + + run $ABRA app new gitea \ + --no-input \ + --server "$TEST_SERVER" \ + --domain "gitea.$TEST_SERVER" \ + --secrets + assert_success + + run $ABRA app deploy "gitea.$TEST_SERVER" \ + --no-input --no-converge-checks + assert_success + + run $ABRA app version "gitea.$TEST_SERVER" + assert_success + assert_output --partial "$latestVersion" + + run $ABRA app undeploy "gitea.$TEST_SERVER" --no-input + assert_success + + run $ABRA app secret remove "gitea.$TEST_SERVER" --all --no-input + assert_success + + # NOTE(d1): to let the stack come down before nuking volumes + sleep 5 + + run $ABRA app volume remove "gitea.$TEST_SERVER" --no-input + assert_success + + run $ABRA app remove "gitea.$TEST_SERVER" --no-input + assert_success + assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/gitea.$TEST_SERVER.env" +} diff --git a/tests/integration/app_volume.bats b/tests/integration/app_volume.bats new file mode 100644 index 00000000..9c0c9d5b --- /dev/null +++ b/tests/integration/app_volume.bats @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "ls validate app argument" { + run $ABRA app volume ls + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app volume ls DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +@test "list no volumes" { + run $ABRA app volume ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'no volumes created' +} + +# bats test_tags=slow +@test "list volumes" { + _deploy_app + + run $ABRA app volume ls "$TEST_APP_DOMAIN" + assert_success + assert_output --partial 'test-volume' + + _undeploy_app +} + +@test "rm validate app argument" { + run $ABRA app volume rm + assert_failure + assert_output --partial 'no app provided' + + run $ABRA app volume rm DOESNTEXIST + assert_failure + assert_output --partial 'cannot find app' +} + +# bats test_tags=slow +@test "rm error if deployed" { + _deploy_app + + run $ABRA app volume rm "$TEST_APP_DOMAIN" --force + assert_failure + assert_output --partial 'is still deployed' + + _undeploy_app +} + +# bats test_tags=slow +@test "remove volumes" { + _deploy_app + + _undeploy_app + + # NOTE(d1): to let the stack come down before nuking volumes + sleep 5 + + run $ABRA app volume rm "$TEST_APP_DOMAIN" --force + assert_success + assert_output --partial 'volumes removed successfully' +} + +# bats test_tags=slow +@test "remove no volumes" { + _deploy_app + + _undeploy_app + + # NOTE(d1): to let the stack come down before nuking volumes + sleep 5 + + run $ABRA app volume rm "$TEST_APP_DOMAIN" --force + assert_success + assert_output --partial 'volumes removed successfully' + + run $ABRA app volume rm "$TEST_APP_DOMAIN" --force + assert_success + assert_output --partial 'no volumes removed' +} diff --git a/tests/integration/autocomplete.bats b/tests/integration/autocomplete.bats index 6d33bf4f..3f3e7d7a 100644 --- a/tests/integration/autocomplete.bats +++ b/tests/integration/autocomplete.bats @@ -1,35 +1,30 @@ #!/usr/bin/env bash -setup() { - DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )" - source "$DIR/helpers.sh" - _setup_env +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup } @test "bash autocompletion" { run $ABRA autocomplete bash assert_success - assert [ -f "$ABRA_DIR/autocompletion/bash" ] + assert_exists "$ABRA_DIR/autocompletion/bash" } @test "zsh autocompletion" { run $ABRA autocomplete zsh assert_success - assert [ -f "$ABRA_DIR/autocompletion/zsh" ] + assert_exists "$ABRA_DIR/autocompletion/zsh" } @test "fish autocompletion" { run $ABRA autocomplete fish assert_success - assert [ -f "$ABRA_DIR/autocompletion/fish" ] + assert_exists "$ABRA_DIR/autocompletion/fish" } @test "fizsh autocompletion" { run $ABRA autocomplete fizsh assert_success - assert [ -f "$ABRA_DIR/autocompletion/zsh" ] -} - -teardown(){ - _default_teardown + assert_exists "$ABRA_DIR/autocompletion/zsh" } diff --git a/tests/integration/catalogue.bats b/tests/integration/catalogue.bats index c98ab365..be5e8d57 100644 --- a/tests/integration/catalogue.bats +++ b/tests/integration/catalogue.bats @@ -1,21 +1,18 @@ #!/usr/bin/env bash -setup() { - DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )" - source "$DIR/helpers.sh" - _setup_env +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup } -@test "catalogue generate" { +# bats test_tags=slow +@test "generate entire catalogue" { run $ABRA catalogue generate assert_success } -@test "catalogue generate specific recipe" { +# bats test_tags=slow +@test "generate only specific recipe" { run $ABRA catalogue generate gitea assert_success } - -teardown(){ - _default_teardown -} diff --git a/tests/integration/dirs.bats b/tests/integration/dirs.bats index 0ba86678..dfe0a71d 100644 --- a/tests/integration/dirs.bats +++ b/tests/integration/dirs.bats @@ -1,12 +1,15 @@ #!/usr/bin/env bash -setup() { - DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )" - source "$DIR/helpers.sh" - _setup_env +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + + if [[ -d "$ABRA_DIR" ]]; then + rm -rf "$ABRA_DIR" + fi } -@test "ABRA_DIR can be overriden" { +@test "ABRA_DIR is overriden" { ABRA_DIR="$HOME/.abra_foo" run $ABRA app ls @@ -15,22 +18,25 @@ setup() { # checks if it should create these base directories and that is what we want assert_failure - assert [ -d "$HOME/.abra_foo" ] + assert_exists "$HOME/.abra_foo" + + run rm -rf "$ABRA_DIR" + assert_success } -@test "abra directories created" { +@test "abra directory is created" { run $ABRA app ls # no servers yet, so will fail. however, it will run the required code which # checks if it should create these base directories and that is what we want assert_failure - assert [ -d "$ABRA_DIR" ] - assert [ -d "$ABRA_DIR/servers" ] - assert [ -d "$ABRA_DIR/recipes" ] - assert [ -d "$ABRA_DIR/backups" ] - assert [ -d "$ABRA_DIR/vendor" ] - assert [ -d "$ABRA_DIR/catalogue" ] + assert_exists "$ABRA_DIR" + assert_exists "$ABRA_DIR/servers" + assert_exists "$ABRA_DIR/recipes" + assert_exists "$ABRA_DIR/backups" + assert_exists "$ABRA_DIR/vendor" + assert_exists "$ABRA_DIR/catalogue" } @test "catalogue recipe is a git repository" { @@ -42,10 +48,6 @@ setup() { assert_output --partial 'local recipe catalogue is missing' - assert [ -d "$ABRA_DIR/catalogue" ] - assert [ -d "$ABRA_DIR/catalogue/.git" ] -} - -teardown(){ - _default_teardown + assert_exists "$ABRA_DIR/catalogue" + assert_exists "$ABRA_DIR/catalogue/.git" } diff --git a/tests/integration/helpers.sh b/tests/integration/helpers.sh deleted file mode 100644 index f3aa6ead..00000000 --- a/tests/integration/helpers.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -_build_abra() { - if [[ ! -e "$ROOT/abra" ]]; then - cd "$ROOT" && make build-abra - return 0 - fi - return 0 -} - -_build_kadabra() { - if [[ ! -e "$ROOT/kadabra" ]]; then - cd "$ROOT" && make build-kadabra - return 0 - fi - return 0 -} - -_setup_env(){ - load '/usr/lib/bats/bats-support/load' - load '/usr/lib/bats/bats-assert/load' - - ROOT="$DIR/../.." - ABRA="$ROOT/abra" - KADABRA="$ROOT/kadabra" - - ABRA_DIR="$HOME/.abra_test" - - _build_abra - _build_kadabra -} - -_default_teardown(){ - rm -rf "$ABRA_DIR" -} diff --git a/tests/integration/helpers/app.bash b/tests/integration/helpers/app.bash new file mode 100644 index 00000000..17dbbc2f --- /dev/null +++ b/tests/integration/helpers/app.bash @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +_new_app() { + run $ABRA app new "$TEST_RECIPE" \ + --no-input \ + --server "$TEST_SERVER" \ + --domain "$TEST_APP_DOMAIN" \ + --secrets + assert_success + assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" +} + +_deploy_app() { + run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input + assert_success + + run $ABRA app ls --server "$TEST_SERVER" --status + assert_success + assert_output --partial "$TEST_APP_DOMAIN" + assert_output --partial 'deployed' +} + +_undeploy_app() { + run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input + assert_success + + run $ABRA app ls --server "$TEST_SERVER" --status + assert_success + assert_output --partial "$TEST_APP_DOMAIN" + assert_output --partial 'unknown' +} + +_rm_app() { + # NOTE(d1): not asserting outcomes on teardown here since some might fail + # depending on what the test created. all commands run through anyway + if [[ -f "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" ]]; then + run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input + run $ABRA app secret remove "$TEST_APP_DOMAIN" --all --no-input + run $ABRA app volume remove "$TEST_APP_DOMAIN" --no-input + run $ABRA app remove "$TEST_APP_DOMAIN" --no-input + fi +} diff --git a/tests/integration/helpers/common.bash b/tests/integration/helpers/common.bash new file mode 100644 index 00000000..379569ea --- /dev/null +++ b/tests/integration/helpers/common.bash @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +_common_setup() { + load '/usr/lib/bats/bats-support/load' + load '/usr/lib/bats/bats-assert/load' + load '/usr/lib/bats/bats-file/load' + + load "$PWD/tests/integration/helpers/app" + load "$PWD/tests/integration/helpers/git" + load "$PWD/tests/integration/helpers/recipe" + load "$PWD/tests/integration/helpers/server" + + export ABRA="$PWD/abra" + export KADABRA="$PWD/kadabra" + + export TEST_APP_NAME="$(basename "${BATS_TEST_FILENAME//./_}")" + export TEST_APP_DOMAIN="$TEST_APP_NAME.$TEST_SERVER" + export TEST_RECIPE="abra-integration-test-recipe" +} diff --git a/tests/integration/helpers/git.bash b/tests/integration/helpers/git.bash new file mode 100644 index 00000000..a8f41f4d --- /dev/null +++ b/tests/integration/helpers/git.bash @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +_checkout_recipe() { + if [[ -z "$1" ]]; then + echo 'forgot to pass argument to function?' + exit 1 + fi + + run git -C "$ABRA_DIR/recipes/$1" checkout . + assert_success +} diff --git a/tests/integration/helpers/recipe.bash b/tests/integration/helpers/recipe.bash new file mode 100644 index 00000000..4341f65c --- /dev/null +++ b/tests/integration/helpers/recipe.bash @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +_fetch_recipe() { + if [[ -z "$1" ]]; then + echo 'forgot to pass argument to function?' + exit 1 + fi + + if [[ ! -d "$ABRA_DIR/recipes/$1" ]]; then + run mkdir -p "$ABRA_DIR/recipes" + assert_success + + run git clone "https://git.coopcloud.tech/coop-cloud/$1" "$ABRA_DIR/recipes/$1" + assert_success + fi +} diff --git a/tests/integration/helpers/server.bash b/tests/integration/helpers/server.bash new file mode 100644 index 00000000..edeb9115 --- /dev/null +++ b/tests/integration/helpers/server.bash @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +_add_server() { + run $ABRA server add "$TEST_SERVER" + assert_success + assert_exists "$ABRA_DIR/servers/$TEST_SERVER" +} + +_rm_server() { + run $ABRA server remove --no-input "$TEST_SERVER" + assert_success + assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER" +} + +_rm_default_server(){ + run rm -rf "$ABRA_DIR/servers/default" + assert_success + assert_not_exists "$ABRA_DIR/servers/default" +} diff --git a/tests/integration/install.bats b/tests/integration/install.bats index 6385e11f..a92ce234 100644 --- a/tests/integration/install.bats +++ b/tests/integration/install.bats @@ -1,38 +1,37 @@ #!/usr/bin/env bash setup() { - DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )" - source "$DIR/helpers.sh" - _setup_env + load "$PWD/tests/integration/helpers/common" + _common_setup if [[ -f "$HOME/.local/bin/abra" ]]; then mv "$HOME/.local/bin/abra" "$HOME/.local/bin/abra_before_test" fi } -@test "install from script" { - run bash -c 'curl https://install.abra.coopcloud.tech | bash' - assert_success - - assert [ -f "$HOME/.local/bin/abra" ] - run $HOME/.local/bin/abra -v - assert_output --partial 'beta' -} - -@test "install release candidate from script" { - run bash -c 'curl https://install.abra.coopcloud.tech | bash -s -- --rc' - assert_success - - assert [ -f "$HOME/.local/bin/abra" ] - run $HOME/.local/bin/abra -v - assert_output --partial '-rc' -} - teardown(){ - _default_teardown - if [[ -f "$HOME/.local/bin/abra_before_test" ]]; then rm -rf "$HOME/.local/bin/abra" mv "$HOME/.local/bin/abra_before_test" "$HOME/.local/bin/abra" fi } + +# bats test_tags=slow +@test "install from script" { + run bash -c 'curl https://install.abra.coopcloud.tech | bash' + assert_success + + assert_exists "$HOME/.local/bin/abra" + run "$HOME/.local/bin/abra" -v + assert_output --partial 'beta' +} + +# bats test_tags=slow +@test "install release candidate from script" { + run bash -c 'curl https://install.abra.coopcloud.tech | bash -s -- --rc' + assert_success + + assert_exists "$HOME/.local/bin/abra" + run "$HOME/.local/bin/abra" -v + assert_output --partial '-rc' +} diff --git a/tests/integration/recipe.bats b/tests/integration/recipe.bats deleted file mode 100644 index 833ace07..00000000 --- a/tests/integration/recipe.bats +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash - -setup() { - DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )" - source "$DIR/helpers.sh" - _setup_env - - mkdir -p "$HOME/.abra_test/servers/example.com" - if ! grep -R -q "example.com" "$HOME/.docker"; then - docker context create --docker host=ssh://foo@example.com:222 example.com - fi -} - -@test "create new recipe" { - run $ABRA recipe new foobar - assert_success - assert_output --partial 'Your new foobar recipe has been created' - - run $ABRA app new foobar \ - --no-input \ - --server example.com \ - --domain foobar.example.com - assert_success - assert_output --partial 'A new foobar app has been created!' -} - -@test "recipe fetch" { - run $ABRA recipe fetch matrix-synapse - assert_success - assert [ -d "$ABRA_DIR/recipes/matrix-synapse" ] -} - -@test "recipe sync allows unstaged changes" { - run $ABRA recipe fetch matrix-synapse - assert_success - - run echo "unstaged changes" >> "$ABRA_DIR/recipes/matrix-synapse/foo" - assert_success - - run $ABRA recipe sync matrix-synapse --patch - assert_success -} - -@test "recipe sync detects unstaged label changes" { - run $ABRA recipe fetch matrix-synapse - assert_success - - run $ABRA recipe sync matrix-synapse --patch - assert_success - - run $ABRA recipe sync matrix-synapse --patch - assert_success - assert_output --partial 'is already set, nothing to do?' -} - -@test "recipe list" { - run $ABRA recipe list - assert_success - NUM_RECIPES=$(jq length "$ABRA_DIR/catalogue/recipes.json") - assert_output --partial "total recipes: $NUM_RECIPES" -} - -@test "recipe list with pattern" { - run $ABRA recipe list --pattern cloud - assert_success - assert_output --partial 'nextcloud' - refute_output --partial 'matrix-synapse' -} - -@test "recipe lint" { - run $ABRA recipe lint gitea - assert_success -} - -@test "recipe versions" { - run $ABRA recipe versions gitea - assert_success - assert_output --partial '2.3.2+1.20.3-rootless' -} - -teardown() { - _default_teardown - - if grep -R -q "example.com" "$HOME/.docker"; then - docker context rm example.com - fi -} diff --git a/tests/integration/recipe_fetch.bats b/tests/integration/recipe_fetch.bats new file mode 100644 index 00000000..a64306ab --- /dev/null +++ b/tests/integration/recipe_fetch.bats @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +setup() { + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "recipe fetch" { + run rm -rf "$ABRA_DIR/recipes/matrix-synapse" + assert_success + assert_not_exists "$ABRA_DIR/recipes/matrix-synapse" + + run $ABRA recipe fetch matrix-synapse + assert_success + assert_exists "$ABRA_DIR/recipes/matrix-synapse" +} diff --git a/tests/integration/recipe_lint.bats b/tests/integration/recipe_lint.bats new file mode 100644 index 00000000..63a3e824 --- /dev/null +++ b/tests/integration/recipe_lint.bats @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +setup() { + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "recipe lint" { + run $ABRA recipe lint gitea + assert_success + assert_output --partial 'compose config has expected version' +} + +@test "recipe lint warns on error" { + run $ABRA recipe lint "$TEST_RECIPE" + assert_success + refute_output --partial 'watch out, some critical errors are present' + + run sed -i '/traefik.enable=.*/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" + assert_success + + run $ABRA recipe lint "$TEST_RECIPE" + assert_success --partial 'watch out, some critical errors are present' + + _checkout_recipe "$TEST_RECIPE" +} + +@test "recipe lint uses latest commit" { + _fetch_recipe "$TEST_RECIPE" + + latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run $ABRA recipe lint "$TEST_RECIPE" + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + assert_output --partial 'behind 3' + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit" + assert_success + + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status + refute_output --partial 'behind 3' +} diff --git a/tests/integration/recipe_list.bats b/tests/integration/recipe_list.bats new file mode 100644 index 00000000..ed2b7eb1 --- /dev/null +++ b/tests/integration/recipe_list.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +setup() { + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "recipe list" { + run $ABRA recipe list + assert_success + NUM_RECIPES=$(jq length "$ABRA_DIR/catalogue/recipes.json") + assert_output --partial "total recipes: $NUM_RECIPES" +} + +@test "recipe list with pattern" { + run $ABRA recipe list --pattern cloud + assert_success + assert_output --partial 'nextcloud' + refute_output --partial 'matrix-synapse' +} diff --git a/tests/integration/recipe_new.bats b/tests/integration/recipe_new.bats new file mode 100644 index 00000000..6149f053 --- /dev/null +++ b/tests/integration/recipe_new.bats @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +teardown(){ + if [[ -d "$ABRA_DIR/recipes/foobar" ]]; then + run rm -rf "$ABRA_DIR/recipes/foobar" + assert_success + fi +} + +@test "create new recipe" { + run $ABRA recipe new foobar + assert_success + assert_output --partial 'Your new foobar recipe has been created' + assert_exists "$ABRA_DIR/recipes/foobar" +} + +@test "create new app from new recipe" { + run $ABRA recipe new foobar + assert_success + + run $ABRA app new foobar \ + --no-input \ + --server "$TEST_SERVER" \ + --domain "foobar.$TEST_SERVER" + assert_success + assert_output --partial 'A new foobar app has been created!' +} diff --git a/tests/integration/recipe_release.bats b/tests/integration/recipe_release.bats new file mode 100644 index 00000000..0b70ef53 --- /dev/null +++ b/tests/integration/recipe_release.bats @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +setup() { + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +# bats test_tags=slow +@test "pull in latest changes" { + run $ABRA recipe fetch matrix-synapse + assert_success + + run git -C "$ABRA_DIR/recipes/matrix-synapse" reset --hard HEAD~3 + assert_success + + run $ABRA recipe release matrix-synapse --no-input + assert_failure + assert_output --regexp 'latest git tag .* are the same' + + run git -C "$ABRA_DIR/recipes/matrix-synapse" status + refute_output --partial 'behind 3' +} diff --git a/tests/integration/recipe_sync.bats b/tests/integration/recipe_sync.bats new file mode 100644 index 00000000..241a2fbe --- /dev/null +++ b/tests/integration/recipe_sync.bats @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +setup() { + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "allow unstaged changes" { + run $ABRA recipe fetch matrix-synapse + assert_success + + run echo "unstaged changes" >> "$ABRA_DIR/recipes/matrix-synapse/foo" + assert_success + + run git -C "$ABRA_DIR/recipes/matrix-synapse" status + assert_success + assert_output --partial 'foo' + + run $ABRA recipe sync matrix-synapse --patch + assert_success + + run rm -rf "$ABRA_DIR/recipes/matrix-synapse/foo" + assert_success + assert_not_exists "$ABRA_DIR/recipes/matrix-synapse/foo" +} + +@test "detect unstaged label changes" { + run $ABRA recipe fetch matrix-synapse + assert_success + + run $ABRA recipe sync matrix-synapse --patch + assert_success + + run $ABRA recipe sync matrix-synapse --patch + assert_success + assert_output --partial 'is already set, nothing to do?' +} + +# TODO(d1): implement diff --git a/tests/integration/recipe_upgrade.bats b/tests/integration/recipe_upgrade.bats new file mode 100644 index 00000000..627b24e1 --- /dev/null +++ b/tests/integration/recipe_upgrade.bats @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +setup() { + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +# TODO(d1): implement diff --git a/tests/integration/recipe_version.bats b/tests/integration/recipe_version.bats new file mode 100644 index 00000000..5d5bfca7 --- /dev/null +++ b/tests/integration/recipe_version.bats @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +setup() { + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "recipe versions" { + run $ABRA recipe versions gitea + assert_success + assert_output --partial '2.3.2+1.20.3-rootless' +} diff --git a/tests/integration/server_add.bats b/tests/integration/server_add.bats new file mode 100644 index 00000000..72d9a1cc --- /dev/null +++ b/tests/integration/server_add.bats @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "add new server" { + run $ABRA server add "$TEST_SERVER" + assert_success + assert_exists "$ABRA_DIR/servers/$TEST_SERVER" + + run $ABRA server ls + assert_output --partial "$TEST_SERVER" + + assert bash -c "docker context ls | grep -q $TEST_SERVER" +} + +@test "error if using domain and --local together" { + run $ABRA server add "$TEST_SERVER" --local + assert_failure + assert_output --partial 'cannot use and --local together' +} + +@test "create local server" { + run docker swarm init + assert_success + + run $ABRA server add --local + assert_success + assert_exists "$ABRA_DIR/servers/default" + assert bash -c "docker context ls | grep -q default" + assert_output --partial 'local server added' + + docker swarm leave --force + assert_success + + _rm_default_server +} + +@test "create local server fails when no docker swarm" { + run $ABRA server add --local + assert_failure + assert_not_exists "$ABRA_DIR/servers/default" + assert_output --partial 'swarm mode not enabled on local server' +} + +@test "cleanup when cannot add server" { + run $ABRA server add example.com + assert_failure + assert_not_exists "$ABRA_DIR/servers/example.com" + refute bash -c "docker context ls | grep -q example.com" +} diff --git a/tests/integration/server_list.bats b/tests/integration/server_list.bats new file mode 100644 index 00000000..8396c215 --- /dev/null +++ b/tests/integration/server_list.bats @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "list server" { + run "$ABRA" server ls + assert_success + assert_output --partial "$TEST_SERVER" +} + +@test "show 'local' when --local server created" { + run docker swarm init + assert_success + + run $ABRA server add --local + assert_success + assert_exists "$ABRA_DIR/servers/default" + + run "$ABRA" server ls + assert_success + assert_output --partial 'default' + assert_output --partial 'local' + assert_output --partial 'n/a' + + run docker swarm leave --force + assert_success + + _rm_default_server +} + +@test "filter by problem" { + run "$ABRA" server ls --problems + assert_success + assert_output --partial 'all servers wired up correctly' + + run docker context create --docker host=ssh://incorrect nowhere.com + assert_success + + run mkdir -p "$ABRA_DIR/servers/nowhere.com" + assert_success + + run "$ABRA" server ls --problems + assert_success + assert_output --partial 'unknown' + + run rm -rf "$ABRA_DIR/servers/nowhere.com" + assert_success + + run docker context rm nowhere.com + assert_success +} + +@test "machine readable output" { + run "$ABRA" server ls --machine + assert_success + + expectedOutput='[{"name":"' + expectedOutput+="$TEST_SERVER" + expectedOutput+='"' + + assert_output --partial "$expectedOutput" +} diff --git a/tests/integration/server_prune.bats b/tests/integration/server_prune.bats new file mode 100644 index 00000000..0b908df0 --- /dev/null +++ b/tests/integration/server_prune.bats @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate server" { + run $ABRA server prune --no-input + assert_failure + assert_output --partial 'no server provided' + + run $ABRA server prune foo + assert_failure + assert_output --partial "server doesn't exist" +} + +@test "prune containers, networks and images" { + run $ABRA server prune "$TEST_SERVER" + assert_success + assert_output --partial 'containers pruned' + assert_output --partial 'networks pruned' + assert_output --partial 'images pruned' +} + +@test "prune all images" { + run $ABRA server prune "$TEST_SERVER" --all --debug + assert_success + assert_output --partial 'removing all images, not only dangling ones' +} + +@test "prune volumes" { + run $ABRA server prune "$TEST_SERVER" --volumes + assert_success + assert_output --partial 'volumes pruned' +} diff --git a/tests/integration/server_remove.bats b/tests/integration/server_remove.bats new file mode 100644 index 00000000..2d3b7f8a --- /dev/null +++ b/tests/integration/server_remove.bats @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server +} + +teardown_file(){ + _rm_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup +} + +@test "validate server" { + run $ABRA server remove --no-input + assert_failure + assert_output --partial 'no server provided' + + run $ABRA server remove foo + assert_failure + assert_output --partial "server doesn't exist" +} + +@test "remove server" { + assert_exists "$ABRA_DIR/servers/$TEST_SERVER" + assert bash -c "docker context ls | grep -q $TEST_SERVER" + + run $ABRA server remove --no-input "$TEST_SERVER" + assert_success + assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER" + refute bash -c "docker context ls | grep -q $TEST_SERVER" +} diff --git a/tests/integration/setup_suite.bash b/tests/integration/setup_suite.bash new file mode 100644 index 00000000..f0438864 --- /dev/null +++ b/tests/integration/setup_suite.bash @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +setup_suite(){ + if [[ -z "${TEST_SERVER}" ]]; then + echo 'set $TEST_SERVER before running the test suite' >&3 + exit 1 + fi + + if [[ -z "${ABRA_DIR}" ]]; then + echo 'set $ABRA_DIR before running the test suite' >&3 + exit 1 + fi + + if [[ ! -f "$PWD/abra" ]]; then + make build-abra + fi + + if [[ ! -f "$PWD/kadabra" ]]; then + make build-kadabra + fi + + if [[ -d "$ABRA_DIR" ]]; then + rm -rf "$ABRA_DIR" + fi + + # NOTE(d1): hack to copy over a local copy of the catalogue from the typical + # $HOME/.abra directory if it exists. This avoids a costly git clone over the + # network for every test invocation + if [[ ! -d "$ABRA_DIR/catalogue" ]]; then + if [[ -d "$HOME/.abra/catalogue" ]]; then + mkdir -p "$ABRA_DIR" + cp -r "$HOME/.abra/catalogue" "$ABRA_DIR" + git -C "$ABRA_DIR/catalogue" checkout . + fi + fi +} + +teardown_suite(){ + if [[ -d "$ABRA_DIR" ]]; then + rm -rf "$ABRA_DIR" + fi +} diff --git a/tests/integration/upgrade.bats b/tests/integration/upgrade.bats index 6022cfde..5dbe65f2 100644 --- a/tests/integration/upgrade.bats +++ b/tests/integration/upgrade.bats @@ -1,40 +1,39 @@ #!/usr/bin/env bash setup() { - DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )" - source "$DIR/helpers.sh" - _setup_env + load "$PWD/tests/integration/helpers/common" + _common_setup if [[ -f "$HOME/.local/bin/abra" ]]; then mv "$HOME/.local/bin/abra" "$HOME/.local/bin/abra_before_test" fi } -@test "abra upgrade" { - run $ABRA upgrade - assert_success - assert_output --partial 'Public interest infrastructure' - - assert [ -f "$HOME/.local/bin/abra" ] - run $HOME/.local/bin/abra -v - assert_output --partial 'beta' -} - -@test "abra upgrade release candidate" { - run $ABRA upgrade --rc - assert_success - assert_output --partial 'Public interest infrastructure' - - assert [ -f "$HOME/.local/bin/abra" ] - run $HOME/.local/bin/abra -v - assert_output --partial '-rc' -} - teardown(){ - _default_teardown - if [[ -f "$HOME/.local/bin/abra_before_test" ]]; then rm -rf "$HOME/.local/bin/abra" mv "$HOME/.local/bin/abra_before_test" "$HOME/.local/bin/abra" fi } + +# bats test_tags=slow +@test "abra upgrade" { + run $ABRA upgrade + assert_success + assert_output --partial 'Public interest infrastructure' + + assert_exists "$HOME/.local/bin/abra" + run "$HOME/.local/bin/abra" -v + assert_output --partial 'beta' +} + +# bats test_tags=slow +@test "abra upgrade release candidate" { + run $ABRA upgrade --rc + assert_success + assert_output --partial 'Public interest infrastructure' + + assert_exists "$HOME/.local/bin/abra" + run "$HOME/.local/bin/abra" -v + assert_output --partial '-rc' +} diff --git a/tests/integration/version.bats b/tests/integration/version.bats index dcc77a08..71da7632 100644 --- a/tests/integration/version.bats +++ b/tests/integration/version.bats @@ -1,9 +1,8 @@ #!/usr/bin/env bash setup() { - DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )" - source "$DIR/helpers.sh" - _setup_env + load "$PWD/tests/integration/helpers/common" + _common_setup } @test "abra version" { @@ -11,7 +10,3 @@ setup() { assert_success assert_output --partial 'dev' } - -teardown(){ - _default_teardown -} diff --git a/tests/manual/.envrc.sample b/tests/manual/.envrc.sample deleted file mode 100644 index f16b6c15..00000000 --- a/tests/manual/.envrc.sample +++ /dev/null @@ -1,4 +0,0 @@ -GANDI_TOKEN=... -HCLOUD_TOKEN=... -REGISTRY_PASSWORD=... -REGISTRY_USERNAME=... diff --git a/tests/manual/.gitignore b/tests/manual/.gitignore deleted file mode 100644 index 98d8a5a6..00000000 --- a/tests/manual/.gitignore +++ /dev/null @@ -1 +0,0 @@ -logs diff --git a/tests/manual/README.md b/tests/manual/README.md deleted file mode 100644 index f51e2573..00000000 --- a/tests/manual/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# integration tests - -> You need to be a member of Autonomic Co-op to run these tests, sorry! - -`testfunctions.sh` contains the functions necessary to save and manipulate -logs. Run `test_all.sh logdir` to run tests specified in that file and save the -logs to `logdir`. - -When creating new tests, make sure the test command is a one-liner (you can use -`;` to separate commands). Include `testfunctions.sh` and then write your tests -like this: - -``` -run_test '$ABRA other stuff here' -``` - -By default, the testing script will ask after every command if the execution -succeeded. If you reply `n`, it will log the test in the `logdir`. If you want -all tests to run without questions, run `export logall=yes` before executing -the test script. - -To run tests, you'll need to prepare your environment: - -``` -cp .envrc.sample .envrc # fill out values... -direnv allow -./test_all.sh logs -``` diff --git a/tests/manual/app.sh b/tests/manual/app.sh deleted file mode 100755 index 93f43c98..00000000 --- a/tests/manual/app.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -source ./testfunctions.sh -source ./common.sh - -run_test '$ABRA app ls' - -run_test '$ABRA app ls --status' - -run_test '$ABRA app ls --type wordpress' - -run_test '$ABRA app ls --type wordpress --server swarm.autonomic.zone' - -run_test '$ABRA app ls --type wordpress --server swarm.autonomic.zone --status' diff --git a/tests/manual/cmd.sh b/tests/manual/cmd.sh deleted file mode 100755 index 03280e52..00000000 --- a/tests/manual/cmd.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -source ./testfunctions.sh -source ./common.sh - -create_server_app_recipe - -run_test '$ABRA app cmd foo.com test --local' - -run_test '$ABRA app cmd foo.com test --local -- foo' - -run_test '$ABRA app cmd foo.com test --local -- foo bar baz' - -clean_server_app_recipe diff --git a/tests/manual/common.sh b/tests/manual/common.sh deleted file mode 100755 index a53a2c63..00000000 --- a/tests/manual/common.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -set -e - -create_server_app_recipe() { - ln -srf ../resources/testapp ~/.abra/servers/foo.com - ln -srf ../resources/testrecipe ~/.abra/recipes -} - -clean_server_app_recipe() { - unlink ~/.abra/servers/foo.com - unlink ~/.abra/recipes/testrecipe -} - -function init() { - ABRA="$(pwd)/../../abra" - INSTALLER_URL="https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer" - - export PATH=$PATH:$HOME/.local/bin - - echo "choosing $ABRA as abra command" - echo "choosing $INSTALLER_URL as abra installer url" -} - -init "$@" diff --git a/tests/manual/records.sh b/tests/manual/records.sh deleted file mode 100755 index a40856c5..00000000 --- a/tests/manual/records.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -source ./testfunctions.sh -source ./common.sh - -run_test "$ABRA record new \ - --provider gandi \ - --record-type A \ - --record-name integration-tests \ - --record-value 192.157.2.21 \ - --no-input coopcloud.tech \ - " - -run_test '$ABRA record list --provider gandi coopcloud.tech' - -run_test "$ABRA record rm \ - --provider gandi \ - --record-type A \ - --record-name integration-tests \ - --no-input coopcloud.tech - " diff --git a/tests/manual/server.sh b/tests/manual/server.sh deleted file mode 100755 index 6a1d0381..00000000 --- a/tests/manual/server.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -source ./testfunctions.sh -source ./common.sh - -run_test '$ABRA server new --provider hetzner-cloud --hetzner-name integration-tests --no-input' - -run_test '$ABRA server ls' - -run_test '$ABRA server rm --provider hetzner-cloud --hetzner-name int-core --server --no-input' diff --git a/tests/manual/test_all.sh b/tests/manual/test_all.sh deleted file mode 100755 index 23187234..00000000 --- a/tests/manual/test_all.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -if [ -z $1 ]; then - echo "usage: ./test_all.sh logdir" - exit -fi - -res_dir=$1/ -if [[ ! -d "$res_dir" ]]; then - mkdir "$res_dir" -fi - -# Usage: run_test [number] [name] [command] -run_test () { - logfile="$res_dir/$1-$2.log" - echo $logfile -} - -testScripts=("app.sh" "autocomplete.sh" "catalogue.sh" "install.sh" "recipe.sh" "records.sh" "server.sh", "cmd.sh") - -for i in "${testScripts[@]}"; do - cmd="./$i $res_dir${i/sh/log}" - eval $cmd -done diff --git a/tests/manual/testfunctions.sh b/tests/manual/testfunctions.sh deleted file mode 100644 index d23d1b5c..00000000 --- a/tests/manual/testfunctions.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -if [ -z $1 ]; then - logfile=/dev/null -else - logfile=$1 -fi - -if [ -z $logall ]; then - logall=no -fi - -run_test () { - if [ -z "$@" ]; then - echo "run_test needs a command to run" - else - tempLogfile=$(mktemp) - cmd=$(eval echo "$@") - echo -e "\\n------------ INPUT -------------------" | tee -a $tempLogfile - echo "$" "$cmd" | tee -a $tempLogfile - echo "------------ OUTPUT ------------------" | tee -a $tempLogfile - eval $cmd 2>&1 | tee -a $tempLogfile - if [ $logall = "yes" ]; then - cat $tempLogfile >> $logfile - echo -e "\\n\\n" >> $logfile - else - read -N 1 -p "Did the test pass? [y/n]: " pass - if [ $pass = 'n' ]; then - cat $tempLogfile >> $logfile - echo -e "\\n\\n" >> $logfile - fi - fi - rm $tempLogfile - fi -} diff --git a/tests/resources/testapp/foo.com.env b/tests/resources/testapp/foo.com.env deleted file mode 100644 index 66deff11..00000000 --- a/tests/resources/testapp/foo.com.env +++ /dev/null @@ -1 +0,0 @@ -TYPE=test diff --git a/tests/resources/testapp/testapp b/tests/resources/testapp/testapp deleted file mode 120000 index 945c9b46..00000000 --- a/tests/resources/testapp/testapp +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/tests/resources/testrecipe/abra.sh b/tests/resources/testrecipe/abra.sh deleted file mode 100644 index 70c2cb5e..00000000 --- a/tests/resources/testrecipe/abra.sh +++ /dev/null @@ -1,5 +0,0 @@ -test(){ - echo "1: $1" - echo "2: $2" - echo "all: $@" -} diff --git a/tests/resources/testrecipe/compose.yml b/tests/resources/testrecipe/compose.yml deleted file mode 100644 index f240b68c..00000000 --- a/tests/resources/testrecipe/compose.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -version: "3.8" - -services: - app: []