fix!: chaos consistency (deploy/undeploy/rollback/upgrade)

See coop-cloud/organising#559

--chaos for rollback/upgrade goes away.
This commit is contained in:
2024-07-08 12:54:37 +02:00
parent 4580df72cb
commit c33ca1c6bc
17 changed files with 241 additions and 246 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
},

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 <version> 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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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 <domain>" 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 <version> 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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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