243 lines
6.3 KiB
Go
243 lines
6.3 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
|
|
"coopcloud.tech/abra/cli/internal"
|
|
"coopcloud.tech/abra/pkg/autocomplete"
|
|
"coopcloud.tech/abra/pkg/envfile"
|
|
"coopcloud.tech/abra/pkg/secret"
|
|
|
|
appPkg "coopcloud.tech/abra/pkg/app"
|
|
"coopcloud.tech/abra/pkg/client"
|
|
"coopcloud.tech/abra/pkg/dns"
|
|
"coopcloud.tech/abra/pkg/formatter"
|
|
"coopcloud.tech/abra/pkg/git"
|
|
"coopcloud.tech/abra/pkg/lint"
|
|
"coopcloud.tech/abra/pkg/log"
|
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
var appDeployCommand = cli.Command{
|
|
Name: "deploy",
|
|
Aliases: []string{"d"},
|
|
Usage: "Deploy an app",
|
|
ArgsUsage: "<domain> [<version>]",
|
|
Flags: []cli.Flag{
|
|
internal.DebugFlag,
|
|
internal.NoInputFlag,
|
|
internal.ForceFlag,
|
|
internal.ChaosFlag,
|
|
internal.NoDomainChecksFlag,
|
|
internal.DontWaitConvergeFlag,
|
|
internal.OfflineFlag,
|
|
},
|
|
Before: internal.SubCommandBefore,
|
|
Description: `Deploy an app.
|
|
|
|
This command supports chaos operations. Use "--chaos" 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.
|
|
|
|
EXAMPLE:
|
|
|
|
abra app deploy foo.example.com
|
|
abra app deploy foo.example.com 1.2.3+3.2.1
|
|
abra app deploy foo.example.com 1e83340e`,
|
|
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)
|
|
}
|
|
|
|
if err := lint.LintForErrors(app.Recipe); err != nil {
|
|
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)
|
|
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
|
|
version := deployMeta.Version
|
|
if specificVersion != "" {
|
|
version = specificVersion
|
|
log.Debugf("choosing %s as version to deploy", version)
|
|
|
|
var err error
|
|
isChaosCommit, err = app.Recipe.EnsureVersion(version)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if isChaosCommit {
|
|
log.Debugf("assuming '%s' is a chaos commit", version)
|
|
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 {
|
|
if internal.Force || internal.Chaos {
|
|
log.Warnf("%s is already deployed but continuing (--force/--chaos)", app.Name)
|
|
} else {
|
|
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(versions) > 0 && !internal.Chaos {
|
|
version = versions[len(versions)-1]
|
|
log.Debugf("choosing %s as version to deploy", version)
|
|
if _, err := app.Recipe.EnsureVersion(version); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
head, err := git.GetRecipeHead(app.Recipe.Name)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
version = formatter.SmallSHA(head.String())
|
|
log.Warn("no versions detected, using latest commit")
|
|
}
|
|
}
|
|
|
|
chaosVersion := "false"
|
|
if internal.Chaos {
|
|
log.Warnf("chaos mode engaged")
|
|
|
|
if isChaosCommit {
|
|
chaosVersion = specificVersion
|
|
versionLabelLocal, err := app.Recipe.GetVersionLabelLocal()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
version = versionLabelLocal
|
|
} else {
|
|
var err error
|
|
chaosVersion, err = app.Recipe.ChaosVersion()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
for k, v := range abraShEnv {
|
|
app.Env[k] = v
|
|
}
|
|
|
|
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
deployOpts := stack.Deploy{
|
|
Composefiles: composeFiles,
|
|
Namespace: stackName,
|
|
Prune: false,
|
|
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, chaosVersion)
|
|
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
|
|
|
envVars, err := appPkg.CheckEnv(app)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
for _, envVar := range envVars {
|
|
if !envVar.Present {
|
|
log.Warnf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain)
|
|
}
|
|
}
|
|
|
|
if err := internal.DeployOverview(app, version, chaosVersion, "continue with deployment?"); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if !internal.NoDomainChecks {
|
|
domainName, ok := app.Env["DOMAIN"]
|
|
if ok {
|
|
if _, err = dns.EnsureDomainsResolveSameIPv4(domainName, app.Server); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
log.Warn("skipping domain checks as no DOMAIN=... configured for app")
|
|
}
|
|
} else {
|
|
log.Warn("skipping domain checks as requested")
|
|
}
|
|
|
|
stack.WaitTimeout, err = appPkg.GetTimeoutFromLabel(compose, stackName)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
log.Debugf("set waiting timeout to %d s", stack.WaitTimeout)
|
|
|
|
if err := stack.RunDeploy(cl, deployOpts, compose, app.Name, internal.DontWaitConverge); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
postDeployCmds, ok := app.Env["POST_DEPLOY_CMDS"]
|
|
if ok && !internal.DontWaitConverge {
|
|
log.Debugf("run the following post-deploy commands: %s", postDeployCmds)
|
|
if err := internal.PostCmds(cl, app, postDeployCmds); err != nil {
|
|
log.Fatalf("attempting to run post deploy commands, saw: %s", err)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
}
|