abra/cli/app/upgrade.go
decentral1se 9f478dac1d
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
fix: list downgrades/upgrades in correct order
Now that we have correct sorting of versions:

  coop-cloud/organising#427

We don't need to reverse sort. Only for showing prompts when the
latest should be the first. Otherwise, logic can follow the sorted
order, the last item in the list is the latest upgrade.

Related:

  coop-cloud/organising#444

Also fix `upgrade` to actually show the latest version
2023-07-25 15:08:32 +02:00

229 lines
6.4 KiB
Go

package app
import (
"context"
"fmt"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"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"
"github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var appUpgradeCommand = cli.Command{
Name: "upgrade",
Aliases: []string{"up"},
Usage: "Upgrade an app",
ArgsUsage: "<domain>",
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag,
internal.ChaosFlag,
internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag,
},
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
deployed app.
You may pass "--force/-f" to upgrade to the same version again. This can be
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.
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.
`,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
stackName := app.StackName()
conf := runtime.New()
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)
}
if !isDeployed {
logrus.Fatalf("%s is not deployed?", app.Name)
}
catl, err := recipe.ReadRecipeCatalogue()
if err != nil {
logrus.Fatal(err)
}
versions, err := recipe.GetRecipeCatalogueVersions(app.Recipe, catl)
if err != nil {
logrus.Fatal(err)
}
if len(versions) == 0 && !internal.Chaos {
logrus.Fatalf("no published releases for %s in the recipe catalogue?", app.Recipe)
}
var availableUpgrades []string
if deployedVersion == "unknown" {
availableUpgrades = versions
logrus.Warnf("failed to determine version of deployed %s", app.Name)
}
if deployedVersion != "unknown" && !internal.Chaos {
for _, version := range versions {
parsedDeployedVersion, err := tagcmp.Parse(deployedVersion)
if err != nil {
logrus.Fatal(err)
}
parsedVersion, err := tagcmp.Parse(version)
if err != nil {
logrus.Fatal(err)
}
if parsedVersion.IsGreaterThan(parsedDeployedVersion) {
availableUpgrades = append(availableUpgrades, version)
}
}
if len(availableUpgrades) == 0 && !internal.Force {
logrus.Infof("no available upgrades, you're on latest (%s) ✌️", deployedVersion)
return nil
}
}
var chosenUpgrade string
if len(availableUpgrades) > 0 && !internal.Chaos {
if internal.Force || internal.NoInput {
chosenUpgrade = availableUpgrades[len(availableUpgrades)-1]
logrus.Debugf("choosing %s as version to upgrade to", chosenUpgrade)
} else {
prompt := &survey.Select{
Message: fmt.Sprintf("Please select an upgrade (current version: %s):", deployedVersion),
Options: internal.ReverseStringList(availableUpgrades),
}
if err := survey.AskOne(prompt, &chosenUpgrade); err != nil {
return err
}
}
}
if internal.Force && chosenUpgrade == "" {
logrus.Warnf("%s is already upgraded to latest but continuing (--force/--chaos)", app.Name)
chosenUpgrade = deployedVersion
}
// 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 := internal.GetReleaseNotes(app.Recipe, chosenUpgrade)
if err != nil {
return err
}
if !internal.Chaos {
if err := recipe.EnsureVersion(app.Recipe, chosenUpgrade); err != nil {
logrus.Fatal(err)
}
}
if internal.Chaos {
logrus.Warn("chaos mode engaged")
var err error
chosenUpgrade, err = recipe.ChaosVersion(app.Recipe)
if err != nil {
logrus.Fatal(err)
}
}
abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Recipe, "abra.sh")
abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
if err != nil {
logrus.Fatal(err)
}
for k, v := range abraShEnv {
app.Env[k] = v
}
composeFiles, err := config.GetAppComposeFiles(app.Recipe, app.Env)
if err != nil {
logrus.Fatal(err)
}
deployOpts := stack.Deploy{
Composefiles: composeFiles,
Namespace: stackName,
Prune: false,
ResolveImage: stack.ResolveImageAlways,
}
compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env)
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, chosenUpgrade)
config.SetUpdateLabel(compose, stackName, app.Env)
if err := internal.NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil {
logrus.Fatal(err)
}
stack.WaitTimeout, err = config.GetTimeoutFromLabel(compose, stackName)
if err != nil {
logrus.Fatal(err)
}
logrus.Debugf("set waiting timeout to %d s", stack.WaitTimeout)
if err := stack.RunDeploy(cl, deployOpts, compose, stackName, internal.DontWaitConverge); err != nil {
logrus.Fatal(err)
}
postDeployCmds, ok := app.Env["POST_UPGRADE_CMDS"]
if ok && !internal.DontWaitConverge {
logrus.Debugf("run the following post-deploy commands: %s", postDeployCmds)
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,
}