refactor/fix: deploy/upgrade/rollback
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing

See coop-cloud/abra#461
This commit is contained in:
2025-01-01 19:15:22 +01:00
parent 5975be6870
commit b0cd8ccbb9
85 changed files with 783 additions and 7118 deletions

View File

@ -2,10 +2,10 @@ package app
import (
"context"
"errors"
"fmt"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/envfile"
@ -18,6 +18,7 @@ import (
"coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack"
dockerClient "github.com/docker/docker/client"
"github.com/spf13/cobra"
)
@ -27,9 +28,9 @@ var AppDeployCommand = &cobra.Command{
Short: "Deploy an app",
Long: `Deploy an app.
This command supports chaos operations. Use "--chaos/-c" to deploy your recipe
checkout as-is. Recipe commit hashes are also supported values for "[version]".
Please note, "upgrade"/"rollback" do not support chaos operations.`,
This command supports chaos operations. Use "--chaos/-C" to deploy your recipe
checkout as-is. Recipe commit hashes are also supported as values for
"[version]". Please note, "upgrade"/"rollback" do not support chaos operations.`,
Example: ` # standard deployment
abra app deploy 1312.net
@ -61,29 +62,20 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
}
},
Run: func(cmd *cobra.Command, args []string) {
var warnMessages []string
var (
deployWarnMessages []string
toDeployVersion string
isChaosCommit bool
toDeployChaosVersion = config.CHAOS_DEFAULT
)
app := internal.ValidateApp(args)
stackName := app.StackName()
ok, err := validateChaosXORVersion(args)
if !ok {
log.Fatalf(err.Error())
if err := validateArgsAndFlags(args); err != nil {
log.Fatal(err)
}
specificVersion := getSpecifiedVersion(args)
if specificVersion != "" {
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
app.Recipe.Version = specificVersion
}
if specificVersion == "" && app.Recipe.Version != "" && !internal.Chaos {
log.Debugf("retrieved %s as version from env file", app.Recipe.Version)
specificVersion = app.Recipe.Version
}
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
log.Fatal(err)
}
@ -91,96 +83,66 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
log.Fatal(err)
}
log.Debugf("checking whether %s is already deployed", stackName)
cl, err := client.New(app.Server)
if err != nil {
log.Fatal(err)
}
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
log.Debugf("checking whether %s is already deployed", app.StackName())
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
if err != nil {
log.Fatal(err)
}
// NOTE(d1): handles "[version] as git hash" use case
var isChaosCommit bool
// NOTE(d1): check out specific version before dealing with secrets. This
// 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
toDeployVersion := deployMeta.Version
if specificVersion != "" {
toDeployVersion = specificVersion
log.Debugf("choosing %s as version to deploy", toDeployVersion)
var err error
isChaosCommit, err = app.Recipe.EnsureVersion(toDeployVersion)
if err != nil {
log.Fatal(err)
}
if isChaosCommit {
log.Debugf("assuming '%s' is a chaos commit", toDeployVersion)
internal.Chaos = true
}
}
secStats, err := secret.PollSecretsStatus(cl, app)
if err != nil {
log.Fatal(err)
}
for _, secStat := range secStats {
if !secStat.CreatedOnRemote {
log.Fatalf("unable to deploy, secrets not generated (%s)?", secStat.LocalName)
}
}
if deployMeta.IsDeployed && !(internal.Force || internal.Chaos) {
log.Fatalf("%s is already deployed", app.Name)
}
if !internal.Chaos && specificVersion == "" {
versions, err := app.Recipe.Tags()
if err != nil {
log.Fatal(err)
}
if len(args) == 2 && args[1] != "" {
toDeployVersion = args[1]
}
if len(versions) > 0 && !internal.Chaos {
toDeployVersion = versions[len(versions)-1]
log.Debugf("choosing %s as version to deploy", toDeployVersion)
if _, err := app.Recipe.EnsureVersion(toDeployVersion); err != nil {
log.Fatal(err)
}
} else {
head, err := app.Recipe.Head()
if err != nil {
log.Fatal(err)
}
toDeployVersion = formatter.SmallSHA(head.String())
if !deployMeta.IsDeployed &&
toDeployVersion == "" &&
app.Recipe.EnvVersion != "" && !internal.IgnoreEnvVersion {
log.Debugf("new deployment, choosing .env version: %s", app.Recipe.EnvVersion)
toDeployVersion = app.Recipe.EnvVersion
}
if !internal.Chaos && toDeployVersion == "" {
if err := getLatestVersionOrCommit(app, &toDeployVersion); err != nil {
log.Fatal(err)
}
}
toDeployChaosVersion := config.CHAOS_DEFAULT
if internal.Chaos {
if isChaosCommit {
toDeployChaosVersion = specificVersion
versionLabelLocal, err := app.Recipe.GetVersionLabelLocal()
if err != nil {
log.Fatal(err)
}
toDeployVersion = versionLabelLocal
} else {
var err error
toDeployChaosVersion, err = app.Recipe.ChaosVersion()
if err != nil {
log.Fatal(err)
}
if err := getChaosVersion(app, &toDeployVersion, &toDeployChaosVersion); err != nil {
log.Fatal(err)
}
}
isChaosCommit, err = app.Recipe.EnsureVersion(toDeployVersion)
if err != nil {
log.Fatal(err)
}
if isChaosCommit {
log.Debugf("assuming chaos commit: %s", toDeployVersion)
internal.Chaos = true
toDeployChaosVersion = toDeployVersion
toDeployVersion, err = app.Recipe.GetVersionLabelLocal()
if err != nil {
log.Fatal(err)
}
}
if err := validateSecrets(cl, app); err != nil {
log.Fatal(err)
}
abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath)
if err != nil {
log.Fatal(err)
@ -194,6 +156,7 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
log.Fatal(err)
}
stackName := app.StackName()
deployOpts := stack.Deploy{
Composefiles: composeFiles,
Namespace: stackName,
@ -224,23 +187,22 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
for _, envVar := range envVars {
if !envVar.Present {
warnMessages = append(warnMessages,
fmt.Sprintf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain),
deployWarnMessages = append(deployWarnMessages,
fmt.Sprintf("%s missing from %s.env", envVar.Name, app.Domain),
)
}
}
if !internal.NoDomainChecks {
domainName, ok := app.Env["DOMAIN"]
if ok {
if domainName, ok := app.Env["DOMAIN"]; ok {
if _, err = dns.EnsureDomainsResolveSameIPv4(domainName, app.Server); err != nil {
log.Fatal(err)
}
} else {
log.Debug("skipping domain checks as no DOMAIN=... configured for app")
log.Debug("skipping domain checks, no DOMAIN=... configured")
}
} else {
log.Debug("skipping domain checks as requested")
log.Debug("skipping domain checks")
}
deployedVersion := config.NO_VERSION_DEFAULT
@ -248,13 +210,20 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
deployedVersion = deployMeta.Version
}
toWriteVersion := toDeployVersion
if internal.Chaos || isChaosCommit {
toWriteVersion = toDeployChaosVersion
}
if err := internal.DeployOverview(
app,
warnMessages,
deployWarnMessages,
deployedVersion,
deployMeta.ChaosVersion,
toDeployVersion,
toDeployChaosVersion); err != nil {
toDeployChaosVersion,
toWriteVersion,
); err != nil {
log.Fatal(err)
}
@ -262,7 +231,8 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
if err != nil {
log.Fatal(err)
}
log.Debugf("set waiting timeout to %d s", stack.WaitTimeout)
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
if err := stack.RunDeploy(cl, deployOpts, compose, app.Name, internal.DontWaitConverge); err != nil {
log.Fatal(err)
@ -276,30 +246,77 @@ Please note, "upgrade"/"rollback" do not support chaos operations.`,
}
}
app.Recipe.Version = toDeployVersion
if toDeployChaosVersion != config.CHAOS_DEFAULT {
app.Recipe.Version = toDeployChaosVersion
}
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
log.Fatalf("writing new recipe version in env file: %s", err)
if err := app.WriteRecipeVersion(toWriteVersion, false); err != nil {
log.Fatalf("writing recipe version failed: %s", err)
}
},
}
// validateChaosXORVersion xor checks version/chaos mode
func validateChaosXORVersion(args []string) (bool, error) {
if getSpecifiedVersion(args) != "" && internal.Chaos {
return false, errors.New("cannot use [version] and --chaos together")
func getChaosVersion(app app.App, toDeployVersion, toDeployChaosVersion *string) error {
var err error
*toDeployChaosVersion, err = app.Recipe.ChaosVersion()
if err != nil {
return err
}
return true, nil
*toDeployVersion, err = app.Recipe.GetVersionLabelLocal()
if err != nil {
return err
}
return nil
}
// getSpecifiedVersion retrieves the specific version if available
func getSpecifiedVersion(args []string) string {
if len(args) >= 2 {
return args[1]
func getLatestVersionOrCommit(app app.App, toDeployVersion *string) error {
versions, err := app.Recipe.Tags()
if err != nil {
return err
}
return ""
if len(versions) > 0 && !internal.Chaos {
*toDeployVersion = versions[len(versions)-1]
log.Debugf("choosing %s as version to deploy", *toDeployVersion)
if _, err := app.Recipe.EnsureVersion(*toDeployVersion); err != nil {
return err
}
return nil
}
head, err := app.Recipe.Head()
if err != nil {
return err
}
*toDeployVersion = formatter.SmallSHA(head.String())
return nil
}
// validateArgsAndFlags ensures compatible args/flags.
func validateArgsAndFlags(args []string) error {
if len(args) == 2 && args[1] != "" && internal.Chaos {
return fmt.Errorf("cannot use [version] and --chaos together")
}
return nil
}
func validateSecrets(cl *dockerClient.Client, app app.App) error {
secStats, err := secret.PollSecretsStatus(cl, app)
if err != nil {
return err
}
for _, secStat := range secStats {
if !secStat.CreatedOnRemote {
return fmt.Errorf("secret not generated: %s", secStat.LocalName)
}
}
return nil
}
func init() {
@ -332,6 +349,6 @@ func init() {
"no-converge-checks",
"c",
false,
"do not wait for converge logic checks",
"disable converge logic checks",
)
}