From c33ca1c6bcac617e1ccd0b2efe5eebde9389a36a Mon Sep 17 00:00:00 2001 From: decentral1se Date: Mon, 8 Jul 2024 12:54:37 +0200 Subject: [PATCH] fix!: chaos consistency (deploy/undeploy/rollback/upgrade) See https://git.coopcloud.tech/coop-cloud/organising/issues/559 --chaos for rollback/upgrade goes away. --- cli/app/deploy.go | 13 ++-- cli/app/logs.go | 4 +- cli/app/ps.go | 8 +-- cli/app/remove.go | 4 +- cli/app/restart.go | 4 +- cli/app/rollback.go | 75 ++++++++++---------- cli/app/services.go | 4 +- cli/app/undeploy.go | 33 +++------ cli/app/upgrade.go | 79 ++++++++++----------- cli/app/volume.go | 4 +- cli/internal/deploy.go | 27 +++++-- cli/updater/updater.go | 8 +-- pkg/app/app.go | 4 +- pkg/upstream/stack/stack.go | 56 +++++++++++---- tests/integration/app_rollback.bats | 105 +++++++--------------------- tests/integration/app_undeploy.bats | 15 ++++ tests/integration/app_upgrade.bats | 44 ++++++------ 17 files changed, 241 insertions(+), 246 deletions(-) diff --git a/cli/app/deploy.go b/cli/app/deploy.go index cdbca1af..ce011ce0 100644 --- a/cli/app/deploy.go +++ b/cli/app/deploy.go @@ -71,7 +71,7 @@ recipes. log.Fatal(err) } - isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) + deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName) if err != nil { log.Fatal(err) } @@ -80,7 +80,7 @@ recipes. // is because we need to deal with GetComposeFiles under the hood and these // files change from version to version which therefore affects which // secrets might be generated - version := deployedVersion + version := deployMeta.Version if specificVersion != "" { version = specificVersion log.Debugf("choosing %s as version to deploy", version) @@ -100,7 +100,7 @@ recipes. } } - if isDeployed { + if deployMeta.IsDeployed { if internal.Force || internal.Chaos { log.Warnf("%s is already deployed but continuing (--force/--chaos)", app.Name) } else { @@ -147,10 +147,11 @@ recipes. } } + chaosVersion := "false" if internal.Chaos { log.Warnf("chaos mode engaged") var err error - version, err = app.Recipe.ChaosVersion() + chaosVersion, err = app.Recipe.ChaosVersion() if err != nil { log.Fatal(err) } @@ -184,7 +185,7 @@ recipes. appPkg.ExposeAllEnv(stackName, compose, app.Env) appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name) appPkg.SetChaosLabel(compose, stackName, internal.Chaos) - appPkg.SetChaosVersionLabel(compose, stackName, version) + appPkg.SetChaosVersionLabel(compose, stackName, chaosVersion) appPkg.SetUpdateLabel(compose, stackName, app.Env) envVars, err := appPkg.CheckEnv(app) @@ -198,7 +199,7 @@ recipes. } } - if err := internal.DeployOverview(app, version, "continue with deployment?"); err != nil { + if err := internal.DeployOverview(app, version, chaosVersion, "continue with deployment?"); err != nil { log.Fatal(err) } diff --git a/cli/app/logs.go b/cli/app/logs.go index c0682033..319c642c 100644 --- a/cli/app/logs.go +++ b/cli/app/logs.go @@ -47,12 +47,12 @@ var appLogsCommand = cli.Command{ log.Fatal(err) } - isDeployed, _, err := stack.IsDeployed(context.Background(), cl, stackName) + deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName) if err != nil { log.Fatal(err) } - if !isDeployed { + if !deployMeta.IsDeployed { log.Fatalf("%s is not deployed?", app.Name) } diff --git a/cli/app/ps.go b/cli/app/ps.go index 01d687d5..0af9b69c 100644 --- a/cli/app/ps.go +++ b/cli/app/ps.go @@ -41,12 +41,12 @@ var appPsCommand = cli.Command{ log.Fatal(err) } - isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, app.StackName()) + deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName()) if err != nil { log.Fatal(err) } - if !isDeployed { + if !deployMeta.IsDeployed { log.Fatalf("%s is not deployed?", app.Name) } @@ -55,7 +55,7 @@ var appPsCommand = cli.Command{ if statusMeta, ok := statuses[app.StackName()]; ok { isChaos, exists := statusMeta["chaos"] if exists && isChaos == "false" { - if err := app.Recipe.EnsureVersion(deployedVersion); err != nil { + if err := app.Recipe.EnsureVersion(deployMeta.Version); err != nil { log.Fatal(err) } } else { @@ -66,7 +66,7 @@ var appPsCommand = cli.Command{ } } - showPSOutput(app, cl, deployedVersion, chaosVersion) + showPSOutput(app, cl, deployMeta.Version, chaosVersion) return nil }, diff --git a/cli/app/remove.go b/cli/app/remove.go index fb15c861..9bf4164c 100644 --- a/cli/app/remove.go +++ b/cli/app/remove.go @@ -66,11 +66,11 @@ flag. log.Fatal(err) } - isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) + deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName()) if err != nil { log.Fatal(err) } - if isDeployed { + if deployMeta.IsDeployed { log.Fatalf("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name) } diff --git a/cli/app/restart.go b/cli/app/restart.go index 27c77abf..91a192c9 100644 --- a/cli/app/restart.go +++ b/cli/app/restart.go @@ -67,12 +67,12 @@ Example: log.Fatal(err) } - isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) + deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName()) if err != nil { log.Fatal(err) } - if !isDeployed { + if !deployMeta.IsDeployed { log.Fatalf("%s is not deployed?", app.Name) } diff --git a/cli/app/rollback.go b/cli/app/rollback.go index b552d2b1..ebb8c169 100644 --- a/cli/app/rollback.go +++ b/cli/app/rollback.go @@ -28,7 +28,6 @@ var appRollbackCommand = cli.Command{ internal.DebugFlag, internal.NoInputFlag, internal.ForceFlag, - internal.ChaosFlag, internal.NoDomainChecksFlag, internal.DontWaitConvergeFlag, internal.OfflineFlag, @@ -42,21 +41,12 @@ useful if the container runtime has gotten into a weird state. This action could be destructive, please ensure you have a copy of your app data beforehand. - -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 { app := internal.ValidateApp(c) stackName := app.StackName() - specificVersion := c.Args().Get(1) - if specificVersion != "" && internal.Chaos { - log.Fatal("cannot use and --chaos together") - } - if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { log.Fatal(err) } @@ -72,12 +62,12 @@ recipes. log.Debugf("checking whether %s is already deployed", stackName) - isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) + deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName) if err != nil { log.Fatal(err) } - if !isDeployed { + if !deployMeta.IsDeployed { log.Fatalf("%s is not deployed?", app.Name) } @@ -91,12 +81,13 @@ recipes. log.Fatal(err) } - if len(versions) == 0 && !internal.Chaos { + if len(versions) == 0 { log.Warn("no published versions in catalogue, trying local recipe repository") recipeVersions, err := app.Recipe.GetRecipeVersions(internal.Offline) if err != nil { log.Warn(err) } + for _, recipeVersion := range recipeVersions { for version := range recipeVersion { versions = append(versions, version) @@ -105,76 +96,82 @@ recipes. } var availableDowngrades []string - if deployedVersion == "unknown" { + if deployMeta.Version == "unknown" { availableDowngrades = versions log.Warnf("failed to determine deployed version of %s", app.Name) } + specificVersion := c.Args().Get(1) if specificVersion != "" { - parsedDeployedVersion, err := tagcmp.Parse(deployedVersion) + parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version) if err != nil { log.Fatal(err) } + parsedSpecificVersion, err := tagcmp.Parse(specificVersion) if err != nil { log.Fatal(err) } + if parsedSpecificVersion.IsGreaterThan(parsedDeployedVersion) || parsedSpecificVersion.Equals(parsedDeployedVersion) { - log.Fatalf("%s is not a downgrade for %s?", deployedVersion, specificVersion) + log.Fatalf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion) } + availableDowngrades = append(availableDowngrades, specificVersion) } - if deployedVersion != "unknown" && !internal.Chaos && specificVersion == "" { + if deployMeta.Version != "unknown" && specificVersion == "" { + if deployMeta.IsChaos == "true" { + log.Warn("attempting to rollback a chaos deployment") + } + for _, version := range versions { - parsedDeployedVersion, err := tagcmp.Parse(deployedVersion) + parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version) if err != nil { log.Fatal(err) } + parsedVersion, err := tagcmp.Parse(version) if err != nil { log.Fatal(err) } + if parsedVersion.IsLessThan(parsedDeployedVersion) && !(parsedVersion.Equals(parsedDeployedVersion)) { availableDowngrades = append(availableDowngrades, version) } } if len(availableDowngrades) == 0 && !internal.Force { - log.Info("no available downgrades, you're on oldest ✌️") + log.Info("no available downgrades") return nil } } var chosenDowngrade string - if len(availableDowngrades) > 0 && !internal.Chaos { + if len(availableDowngrades) > 0 { if internal.Force || internal.NoInput || specificVersion != "" { chosenDowngrade = availableDowngrades[len(availableDowngrades)-1] log.Debugf("choosing %s as version to downgrade to (--force/--no-input)", chosenDowngrade) } else { + msg := fmt.Sprintf("please select a downgrade (version: %s):", deployMeta.Version) + if deployMeta.IsChaos == "true" { + msg = fmt.Sprintf("please select a downgrade (version: %s, chaosVersion: %s):", deployMeta.Version, deployMeta.ChaosVersion) + } + prompt := &survey.Select{ - Message: fmt.Sprintf("Please select a downgrade (current version: %s):", deployedVersion), + Message: msg, Options: internal.ReverseStringList(availableDowngrades), } + if err := survey.AskOne(prompt, &chosenDowngrade); err != nil { return err } } } - if !internal.Chaos { - if err := app.Recipe.EnsureVersion(chosenDowngrade); err != nil { - log.Fatal(err) - } - } - - if internal.Chaos { - log.Warn("chaos mode engaged") - var err error - chosenDowngrade, err = app.Recipe.ChaosVersion() - if err != nil { - log.Fatal(err) - } + log.Debugf("choosing %s as version to rollback", chosenDowngrade) + if err := app.Recipe.EnsureVersion(chosenDowngrade); err != nil { + log.Fatal(err) } abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath) @@ -189,6 +186,7 @@ recipes. if err != nil { log.Fatal(err) } + deployOpts := stack.Deploy{ Composefiles: composeFiles, Namespace: stackName, @@ -196,18 +194,25 @@ recipes. ResolveImage: stack.ResolveImageAlways, Detach: false, } + compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env) if err != nil { log.Fatal(err) } + appPkg.ExposeAllEnv(stackName, compose, app.Env) appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name) appPkg.SetChaosLabel(compose, stackName, internal.Chaos) appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade) appPkg.SetUpdateLabel(compose, stackName, app.Env) + chaosVersion := deployMeta.IsChaos + if deployMeta.IsChaos == "true" { + chaosVersion = deployMeta.ChaosVersion + } + // NOTE(d1): no release notes implemeneted for rolling back - if err := internal.NewVersionOverview(app, deployedVersion, chosenDowngrade, ""); err != nil { + if err := internal.NewVersionOverview(app, deployMeta.Version, chaosVersion, chosenDowngrade, ""); err != nil { log.Fatal(err) } diff --git a/cli/app/services.go b/cli/app/services.go index 60843d81..1986f6e7 100644 --- a/cli/app/services.go +++ b/cli/app/services.go @@ -34,12 +34,12 @@ var appServicesCommand = cli.Command{ log.Fatal(err) } - isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) + deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName()) if err != nil { log.Fatal(err) } - if !isDeployed { + if !deployMeta.IsDeployed { log.Fatalf("%s is not deployed?", app.Name) } diff --git a/cli/app/undeploy.go b/cli/app/undeploy.go index c0844b2e..1563ca60 100644 --- a/cli/app/undeploy.go +++ b/cli/app/undeploy.go @@ -3,7 +3,6 @@ package app import ( "context" "fmt" - "time" "coopcloud.tech/abra/cli/internal" appPkg "coopcloud.tech/abra/pkg/app" @@ -28,27 +27,10 @@ var pruneFlag = &cli.BoolFlag{ // pruneApp runs the equivalent of a "docker system prune" but only filtering // against resources connected with the app deployment. It is not a system wide // prune. Volumes are not pruned to avoid unwated data loss. -func pruneApp(c *cli.Context, cl *dockerClient.Client, app appPkg.App) error { +func pruneApp(cl *dockerClient.Client, app appPkg.App) error { stackName := app.StackName() ctx := context.Background() - for { - log.Debugf("polling for %s stack, waiting to be undeployed...", stackName) - - services, err := stack.GetStackServices(ctx, cl, stackName) - if err != nil { - return err - } - - if len(services) == 0 { - log.Debugf("%s undeployed, moving on with pruning logic", stackName) - time.Sleep(time.Second) // give runtime more time to tear down related state - break - } - - time.Sleep(time.Second) - } - pruneFilters := filters.NewArgs() stackSearch := fmt.Sprintf("%s*", stackName) pruneFilters.Add("label", stackSearch) @@ -109,16 +91,21 @@ Passing "-p/--prune" does not remove those volumes. log.Debugf("checking whether %s is already deployed", stackName) - isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) + deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName) if err != nil { log.Fatal(err) } - if !isDeployed { + if !deployMeta.IsDeployed { log.Fatalf("%s is not deployed?", app.Name) } - if err := internal.DeployOverview(app, deployedVersion, "continue with undeploy?"); err != nil { + chaosVersion := deployMeta.IsChaos + if deployMeta.IsChaos == "true" { + chaosVersion = deployMeta.ChaosVersion + } + + if err := internal.DeployOverview(app, deployMeta.Version, chaosVersion, "continue with undeploy?"); err != nil { log.Fatal(err) } @@ -131,7 +118,7 @@ Passing "-p/--prune" does not remove those volumes. } if prune { - if err := pruneApp(c, cl, app); err != nil { + if err := pruneApp(cl, app); err != nil { log.Fatal(err) } } diff --git a/cli/app/upgrade.go b/cli/app/upgrade.go index b663526b..bb7a7775 100644 --- a/cli/app/upgrade.go +++ b/cli/app/upgrade.go @@ -27,7 +27,6 @@ var appUpgradeCommand = cli.Command{ internal.DebugFlag, internal.NoInputFlag, internal.ForceFlag, - internal.ChaosFlag, internal.NoDomainChecksFlag, internal.DontWaitConvergeFlag, internal.OfflineFlag, @@ -35,11 +34,7 @@ var appUpgradeCommand = cli.Command{ }, Before: internal.SubCommandBefore, Description: ` -Upgrade an app. You can use it to choose and roll out a new upgrade to an -existing app. - -This command specifically supports incrementing the version of running apps, as -opposed to "abra app deploy " which will not change the version of a +Upgrade an app. You can use it to choose and roll out a new upgrade to a deployed app. You may pass "--force/-f" to upgrade to the same version again. This can be @@ -47,21 +42,12 @@ useful if the container runtime has gotten into a weird state. This action could be destructive, please ensure you have a copy of your app data beforehand. - -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 { app := internal.ValidateApp(c) stackName := app.StackName() - specificVersion := c.Args().Get(1) - if specificVersion != "" && internal.Chaos { - log.Fatal("cannot use and --chaos together") - } - if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { log.Fatal(err) } @@ -77,12 +63,12 @@ recipes. log.Fatal(err) } - isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) + deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName) if err != nil { log.Fatal(err) } - if !isDeployed { + if !deployMeta.IsDeployed { log.Fatalf("%s is not deployed?", app.Name) } @@ -96,7 +82,7 @@ recipes. log.Fatal(err) } - if len(versions) == 0 && !internal.Chaos { + if len(versions) == 0 { log.Warn("no published versions in catalogue, trying local recipe repository") recipeVersions, err := app.Recipe.GetRecipeVersions(internal.Offline) if err != nil { @@ -110,13 +96,14 @@ recipes. } var availableUpgrades []string - if deployedVersion == "unknown" { + if deployMeta.Version == "unknown" { availableUpgrades = versions log.Warnf("failed to determine deployed version of %s", app.Name) } + specificVersion := c.Args().Get(1) if specificVersion != "" { - parsedDeployedVersion, err := tagcmp.Parse(deployedVersion) + parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version) if err != nil { log.Fatal(err) } @@ -125,17 +112,21 @@ recipes. log.Fatal(err) } if parsedSpecificVersion.IsLessThan(parsedDeployedVersion) || parsedSpecificVersion.Equals(parsedDeployedVersion) { - log.Fatalf("%s is not an upgrade for %s?", deployedVersion, specificVersion) + log.Fatalf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion) } availableUpgrades = append(availableUpgrades, specificVersion) } - parsedDeployedVersion, err := tagcmp.Parse(deployedVersion) + parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version) if err != nil { log.Fatal(err) } - if deployedVersion != "unknown" && !internal.Chaos && specificVersion == "" { + if deployMeta.Version != "unknown" && specificVersion == "" { + if deployMeta.IsChaos == "true" { + log.Warn("attempting to upgrade a chaos deployment") + } + for _, version := range versions { parsedVersion, err := tagcmp.Parse(version) if err != nil { @@ -147,21 +138,27 @@ recipes. } if len(availableUpgrades) == 0 && !internal.Force { - log.Infof("no available upgrades, you're on latest (%s) ✌️", deployedVersion) + log.Info("no available upgrades") return nil } } var chosenUpgrade string - if len(availableUpgrades) > 0 && !internal.Chaos { + if len(availableUpgrades) > 0 { if internal.Force || internal.NoInput || specificVersion != "" { chosenUpgrade = availableUpgrades[len(availableUpgrades)-1] log.Debugf("choosing %s as version to upgrade to", chosenUpgrade) } else { + msg := fmt.Sprintf("please select an upgrade (version: %s):", deployMeta.Version) + if deployMeta.IsChaos == "true" { + msg = fmt.Sprintf("please select an upgrade (version: %s, chaosVersion: %s):", deployMeta.Version, deployMeta.ChaosVersion) + } + prompt := &survey.Select{ - Message: fmt.Sprintf("Please select an upgrade (current version: %s):", deployedVersion), + Message: msg, Options: internal.ReverseStringList(availableUpgrades), } + if err := survey.AskOne(prompt, &chosenUpgrade); err != nil { return err } @@ -169,8 +166,8 @@ recipes. } if internal.Force && chosenUpgrade == "" { - log.Warnf("%s is already upgraded to latest but continuing (--force/--chaos)", app.Name) - chosenUpgrade = deployedVersion + log.Warnf("%s is already upgraded to latest but continuing (--force)", app.Name) + chosenUpgrade = deployMeta.Version } // if release notes written after git tag published, read them before we @@ -199,19 +196,9 @@ recipes. } } - if !internal.Chaos { - if err := app.Recipe.EnsureVersion(chosenUpgrade); err != nil { - log.Fatal(err) - } - } - - if internal.Chaos { - log.Warn("chaos mode engaged") - var err error - chosenUpgrade, err = app.Recipe.ChaosVersion() - if err != nil { - log.Fatal(err) - } + log.Debugf("choosing %s as version to upgrade", chosenUpgrade) + if err := app.Recipe.EnsureVersion(chosenUpgrade); err != nil { + log.Fatal(err) } abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath) @@ -226,6 +213,7 @@ recipes. if err != nil { log.Fatal(err) } + deployOpts := stack.Deploy{ Composefiles: composeFiles, Namespace: stackName, @@ -233,10 +221,12 @@ recipes. ResolveImage: stack.ResolveImageAlways, Detach: false, } + compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env) if err != nil { log.Fatal(err) } + appPkg.ExposeAllEnv(stackName, compose, app.Env) appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name) appPkg.SetChaosLabel(compose, stackName, internal.Chaos) @@ -260,7 +250,12 @@ recipes. return nil } - if err := internal.NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil { + chaosVersion := deployMeta.IsChaos + if deployMeta.IsChaos == "true" { + chaosVersion = deployMeta.ChaosVersion + } + + if err := internal.NewVersionOverview(app, deployMeta.Version, chaosVersion, chosenUpgrade, releaseNotes); err != nil { log.Fatal(err) } diff --git a/cli/app/volume.go b/cli/app/volume.go index cf21d6ab..d9d2f68a 100644 --- a/cli/app/volume.go +++ b/cli/app/volume.go @@ -92,12 +92,12 @@ Passing "--force/-f" will select all volumes for removal. Be careful. log.Fatal(err) } - isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName()) + deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName()) if err != nil { log.Fatal(err) } - if isDeployed { + if deployMeta.IsDeployed { log.Fatalf("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name) } diff --git a/cli/internal/deploy.go b/cli/internal/deploy.go index 698aab05..3147d66c 100644 --- a/cli/internal/deploy.go +++ b/cli/internal/deploy.go @@ -13,8 +13,8 @@ import ( ) // NewVersionOverview shows an upgrade or downgrade overview -func NewVersionOverview(app appPkg.App, currentVersion, newVersion, releaseNotes string) error { - tableCol := []string{"server", "recipe", "config", "domain", "current version", "to be deployed"} +func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion, releaseNotes string) error { + tableCol := []string{"server", "recipe", "config", "domain", "version", "chaos", "to deploy"} table := formatter.CreateTable(tableCol) deployConfig := "compose.yml" @@ -27,7 +27,15 @@ func NewVersionOverview(app appPkg.App, currentVersion, newVersion, releaseNotes server = "local" } - table.Append([]string{server, app.Recipe.Name, deployConfig, app.Domain, currentVersion, newVersion}) + table.Append([]string{ + server, + app.Recipe.Name, + deployConfig, + app.Domain, + currentVersion, + chaosVersion, + newVersion, + }) table.Render() if releaseNotes != "" && newVersion != "" { @@ -112,8 +120,8 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error { } // DeployOverview shows a deployment overview -func DeployOverview(app appPkg.App, version, message string) error { - tableCol := []string{"server", "recipe", "config", "domain", "version"} +func DeployOverview(app appPkg.App, version, chaosVersion, message string) error { + tableCol := []string{"server", "recipe", "config", "domain", "version", "chaos"} table := formatter.CreateTable(tableCol) deployConfig := "compose.yml" @@ -126,7 +134,14 @@ func DeployOverview(app appPkg.App, version, message string) error { server = "local" } - table.Append([]string{server, app.Recipe.Name, deployConfig, app.Domain, version}) + table.Append([]string{ + server, + app.Recipe.Name, + deployConfig, + app.Domain, + version, + chaosVersion, + }) table.Render() if NoInput { diff --git a/cli/updater/updater.go b/cli/updater/updater.go index 336596b0..bd5faf40 100644 --- a/cli/updater/updater.go +++ b/cli/updater/updater.go @@ -253,20 +253,20 @@ func getLatestUpgrade(cl *dockerclient.Client, stackName string, recipeName stri func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName string) (string, error) { log.Debugf("retrieve deployed version whether %s is already deployed", stackName) - isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) + deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName) if err != nil { return "", err } - if !isDeployed { + if !deployMeta.IsDeployed { return "", fmt.Errorf("%s is not deployed?", stackName) } - if deployedVersion == "unknown" { + if deployMeta.Version == "unknown" { return "", fmt.Errorf("failed to determine deployed version of %s", stackName) } - return deployedVersion, nil + return deployMeta.Version, nil } // getAvailableUpgrades returns all available versions of an app that are newer diff --git a/pkg/app/app.go b/pkg/app/app.go index 41bb2259..9b09dcd9 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -494,13 +494,13 @@ func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv envfile.AppEnv func ExposeAllEnv(stackName string, compose *composetypes.Config, appEnv envfile.AppEnv) { for _, service := range compose.Services { if service.Name == "app" { - log.Debugf("Add the following environment to the app service config of %s:", stackName) + log.Debugf("add the following environment to the app service config of %s:", stackName) for k, v := range appEnv { _, exists := service.Environment[k] if !exists { value := v service.Environment[k] = &value - log.Debugf("Add Key: %s Value: %s to %s", k, value, stackName) + log.Debugf("add env var: %s value: %s to %s", k, value, stackName) } } } diff --git a/pkg/upstream/stack/stack.go b/pkg/upstream/stack/stack.go index 98a953a7..a7c0fb5f 100644 --- a/pkg/upstream/stack/stack.go +++ b/pkg/upstream/stack/stack.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "os/signal" + "strings" "time" stdlibErr "errors" @@ -35,7 +36,7 @@ const ( ) // Timeout to wait until docker services converge, default is 50s (random choice) -var WaitTimeout int = 50 +var WaitTimeout = 50 type StackStatus struct { Services []swarm.Service @@ -96,35 +97,64 @@ func GetDeployedServicesByName(ctx context.Context, cl *dockerClient.Client, sta return StackStatus{services, nil} } -// IsDeployed chekcks whether an appp is deployed or not. -func IsDeployed(ctx context.Context, cl *dockerClient.Client, stackName string) (bool, string, error) { - version := "unknown" - isDeployed := false +// DeployMeta is runtime metadata about an app deployment. +type DeployMeta struct { + IsDeployed bool // whether the app is deployed or not + Version string // the deployed version + IsChaos string // whether or not the deployment is --chaos + ChaosVersion string // the --chaos deployment version +} + +// IsDeployed gathers metadata about an app deployment. +func IsDeployed(ctx context.Context, cl *dockerClient.Client, stackName string) (DeployMeta, error) { + deployMeta := DeployMeta{ + IsDeployed: false, + Version: "unknown", + IsChaos: "false", // NOTE(d1): match string type used on label + ChaosVersion: "false", // NOTE(d1): match string type used on label + } filter := filters.NewArgs() filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName)) services, err := cl.ServiceList(ctx, types.ServiceListOptions{Filters: filter}) if err != nil { - return false, version, err + return deployMeta, err } if len(services) > 0 { + deployMeta.IsDeployed = true + for _, service := range services { - labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName) - if deployedVersion, ok := service.Spec.Labels[labelKey]; ok { - version = deployedVersion - break + splitter := fmt.Sprintf("%s_", stackName) + serviceName := strings.Split(service.Spec.Name, splitter)[1] + + if serviceName == "app" { + labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName) + if deployedVersion, ok := service.Spec.Labels[labelKey]; ok { + deployMeta.Version = deployedVersion + } + + labelKey = fmt.Sprintf("coop-cloud.%s.chaos", stackName) + if isChaos, ok := service.Spec.Labels[labelKey]; ok { + deployMeta.IsChaos = isChaos + } + + labelKey = fmt.Sprintf("coop-cloud.%s.chaos-version", stackName) + if chaosVersion, ok := service.Spec.Labels[labelKey]; ok { + deployMeta.ChaosVersion = chaosVersion + } } } - log.Debugf("%s has been detected as deployed with version %s", stackName, version) + log.Debugf("%s has been detected as deployed: %v", stackName, deployMeta) - return true, version, nil + return deployMeta, nil } log.Debugf("%s has been detected as not deployed", stackName) - return isDeployed, version, nil + + return deployMeta, nil } // pruneServices removes services that are no longer referenced in the source diff --git a/tests/integration/app_rollback.bats b/tests/integration/app_rollback.bats index 3b23c57b..1ab2f030 100644 --- a/tests/integration/app_rollback.bats +++ b/tests/integration/app_rollback.bats @@ -72,80 +72,6 @@ teardown(){ assert_equal $(_get_current_hash) $wantHash } -@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" { - latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)" - - run $ABRA app deploy "$TEST_APP_DOMAIN" \ - --no-input --chaos - assert_success - assert_output --partial "${latestCommit}" - assert_output --partial 'chaos' - - run $ABRA app rollback "$TEST_APP_DOMAIN" \ - --chaos --no-input --no-converge-checks - assert_success - assert_output --partial "$latestCommit" - assert_output --partial 'chaos' -} - -# 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 --chaos --no-converge-checks - assert_failure - assert_output --partial 'failed lint checks' -} - @test "error if not already deployed" { run $ABRA app rollback "$TEST_APP_DOMAIN" --no-input assert_failure @@ -174,13 +100,6 @@ teardown(){ assert_output --partial 'is not a downgrade' } -@test "bail out if specific version and chaos" { - run $ABRA app rollback "$TEST_APP_DOMAIN" "0.2.0+1.21.0" \ - --chaos --no-input --no-converge-checks - assert_failure - assert_output --partial 'cannot use' -} - # bats test_tags=slow @test "rollback to previous version" { run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks @@ -202,3 +121,27 @@ teardown(){ assert_success assert_output --partial "0.1.0+1.20.0" } + +# bats test_tags=slow +@test "rollback chaos deployment" { + tagHash=$(_get_tag_hash "0.2.0+1.21.0") + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash" + assert_success + + run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --chaos + assert_success + assert_output --partial "${tagHash:0:8}" + + run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks + assert_success + assert_output --partial "0.1.1+1.20.2" + assert_output --partial "${tagHash:0:8}" + + run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks + assert_success + assert_output --partial "0.1.0+1.20.0" + + tagHash=$(_get_tag_hash "0.1.1+1.20.2") + refute_output --partial "${tagHash:0:8}" + assert_output --partial "false" +} diff --git a/tests/integration/app_undeploy.bats b/tests/integration/app_undeploy.bats index 385b705b..3f9314a2 100644 --- a/tests/integration/app_undeploy.bats +++ b/tests/integration/app_undeploy.bats @@ -41,6 +41,9 @@ teardown(){ @test "undeploy app" { _deploy_app _undeploy_app + + # NOTE(d1): ensure not chaos undeploy + assert_output --partial 'false' } # bats test_tags=slow @@ -50,3 +53,15 @@ teardown(){ run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input --prune assert_success } + +# bats test_tags=slow +@test "undeploy chaos deployment" { + run $ABRA app deploy "$TEST_APP_DOMAIN" \ + --no-input --no-converge-checks --chaos + + _undeploy_app + + # NOTE(d1): ensure chaos undeploy + assert_output --partial ${_get_current_hash:0:8} + refute_output --partial 'false' +} diff --git a/tests/integration/app_upgrade.bats b/tests/integration/app_upgrade.bats index 03625311..af4e9b64 100644 --- a/tests/integration/app_upgrade.bats +++ b/tests/integration/app_upgrade.bats @@ -43,26 +43,6 @@ teardown(){ assert_output --partial 'is not an upgrade' } -@test "bail out if specific version and chaos" { - run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" \ - --chaos --no-input --no-converge-checks - assert_failure - assert_output --partial 'cannot use' -} - -# bats test_tags=slow -@test "no upgrade if lint error" { - _deploy_app - - run sed -i '/traefik.enable=.*/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" - assert_success - - run $ABRA app upgrade "$TEST_APP_DOMAIN" \ - --no-input --no-converge-checks --chaos - assert_failure - assert_output --partial 'failed lint checks' -} - # bats test_tags=slow @test "no upgrade if on latest version" { _deploy_app @@ -185,3 +165,27 @@ teardown(){ assert_output --partial 'release notes bar' # 0.1.1+1.20.2 assert_output --partial 'release notes baz' # 0.2.0+1.21.0 } + +# bats test_tags=slow +@test "upgrade chaos deployment" { + tagHash=$(_get_tag_hash "0.1.0+1.20.0") + run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash" + assert_success + + run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --chaos + assert_success + assert_output --partial "${tagHash:0:8}" + + run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks + assert_success + assert_output --partial "0.1.1+1.20.2" + assert_output --partial "${tagHash:0:8}" + + run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input --no-converge-checks + assert_success + assert_output --partial "0.2.0+1.21.0" + + tagHash=$(_get_tag_hash "0.1.1+1.20.2") + refute_output --partial "${tagHash:0:8}" + assert_output --partial "false" +}