feat: git hash arg for deploy #434

Merged
decentral1se merged 6 commits from git-hash-arg into main 2024-07-09 09:42:57 +00:00
33 changed files with 310 additions and 207 deletions

View File

@ -5,11 +5,10 @@ import (
) )
var AppCommand = cli.Command{ var AppCommand = cli.Command{
Name: "app", Name: "app",
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "Manage apps", Usage: "Manage apps",
ArgsUsage: "<domain>", ArgsUsage: "<domain>",
Description: "Functionality for managing the life cycle of your apps",
Subcommands: []cli.Command{ Subcommands: []cli.Command{
appBackupCommand, appBackupCommand,
appCheckCommand, appCheckCommand,

View File

@ -29,10 +29,11 @@ They can be run within the context of a service (e.g. app) or locally on your
work station by passing "--local". Arguments can be passed into these functions work station by passing "--local". Arguments can be passed into these functions
using the "-- <args>" syntax. using the "-- <args>" syntax.
Example: **WARNING**: options must be passed directly after the sub-command "cmd".
abra app cmd example.com app create_user -- me@example.com EXAMPLE:
`,
abra app cmd --local example.com app create_user -- me@example.com`,
ArgsUsage: "<domain> [<service>] <command> [-- <args>]", ArgsUsage: "<domain> [<service>] <command> [-- <args>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,

View File

@ -43,7 +43,7 @@ var appConfigCommand = cli.Command{
ed, ok := os.LookupEnv("EDITOR") ed, ok := os.LookupEnv("EDITOR")
if !ok { if !ok {
edPrompt := &survey.Select{ edPrompt := &survey.Select{
Message: "Which editor do you wish to use?", Message: "which editor do you wish to use?",
Options: []string{"vi", "vim", "nvim", "nano", "pico", "emacs"}, Options: []string{"vi", "vim", "nvim", "nano", "pico", "emacs"},
} }
if err := survey.AskOne(edPrompt, &ed); err != nil { if err := survey.AskOne(edPrompt, &ed); err != nil {

View File

@ -35,17 +35,18 @@ var appDeployCommand = cli.Command{
internal.OfflineFlag, internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Deploy an app.
Deploy an app. It does not support incrementing the version of a deployed app,
for this you need to look at the "abra app upgrade <domain>" command.
You may pass "--force" to re-deploy the same version again. This can be useful This command supports chaos operations. Use "--chaos" to deploy your recipe
if the container runtime has gotten into a weird state. checkout as-is. Recipe commit hashes are also supported values for
"[<version>]". Please note, "upgrade"/"rollback" do not support chaos
operations.
Chaos mode ("--chaos") will deploy your local checkout of a recipe as-is, EXAMPLE:
including unstaged changes and can be useful for live hacking and testing new
recipes. 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, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
@ -76,6 +77,9 @@ recipes.
log.Fatal(err) 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 // NOTE(d1): check out specific version before dealing with secrets. This
// is because we need to deal with GetComposeFiles under the hood and these // is because we need to deal with GetComposeFiles under the hood and these
// files change from version to version which therefore affects which // files change from version to version which therefore affects which
@ -84,9 +88,17 @@ recipes.
if specificVersion != "" { if specificVersion != "" {
version = specificVersion version = specificVersion
log.Debugf("choosing %s as version to deploy", version) log.Debugf("choosing %s as version to deploy", version)
if err := app.Recipe.EnsureVersion(version); err != nil {
var err error
isChaosCommit, err = app.Recipe.EnsureVersion(version)
if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if isChaosCommit {
log.Debugf("assuming '%s' is a chaos commit", version)
internal.Chaos = true
}
} }
secStats, err := secret.PollSecretsStatus(cl, app) secStats, err := secret.PollSecretsStatus(cl, app)
@ -119,10 +131,10 @@ recipes.
} }
if len(versions) == 0 && !internal.Chaos { if len(versions) == 0 && !internal.Chaos {
log.Warn("no published versions in catalogue, trying local recipe repository") log.Debug("no published versions in catalogue, trying local recipe repository")
recipeVersions, err := app.Recipe.GetRecipeVersions(internal.Offline) recipeVersions, err := app.Recipe.GetRecipeVersions(internal.Offline)
if err != nil { if err != nil {
log.Warn(err) log.Fatal(err)
} }
for _, recipeVersion := range recipeVersions { for _, recipeVersion := range recipeVersions {
for version := range recipeVersion { for version := range recipeVersion {
@ -134,7 +146,7 @@ recipes.
if len(versions) > 0 && !internal.Chaos { if len(versions) > 0 && !internal.Chaos {
version = versions[len(versions)-1] version = versions[len(versions)-1]
log.Debugf("choosing %s as version to deploy", version) log.Debugf("choosing %s as version to deploy", version)
if err := app.Recipe.EnsureVersion(version); err != nil { if _, err := app.Recipe.EnsureVersion(version); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} else { } else {
@ -150,10 +162,20 @@ recipes.
chaosVersion := "false" chaosVersion := "false"
if internal.Chaos { if internal.Chaos {
log.Warnf("chaos mode engaged") log.Warnf("chaos mode engaged")
var err error
chaosVersion, err = app.Recipe.ChaosVersion() if isChaosCommit {
if err != nil { chaosVersion = specificVersion
log.Fatal(err) 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)
}
} }
} }

View File

@ -77,8 +77,7 @@ generate a report of all your apps.
By passing the "--status/-S" flag, you can query all your servers for the By passing the "--status/-S" flag, you can query all your servers for the
actual live deployment status. Depending on how many servers you manage, this actual live deployment status. Depending on how many servers you manage, this
can take some time. can take some time.`,
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,

View File

@ -19,8 +19,8 @@ import (
) )
var appNewDescription = ` var appNewDescription = `
Take a recipe and uses it to create a new app. This new app configuration is Creates a new app from a default recipe. This new app configuration is stored
stored in your ~/.abra directory under the appropriate server. in your $ABRA_DIR directory under the appropriate server.
This command does not deploy your app for you. You will need to run "abra app This command does not deploy your app for you. You will need to run "abra app
deploy <domain>" to do so. deploy <domain>" to do so.
@ -35,8 +35,7 @@ store them somewhere safe.
You can use the "--pass/-P" to store these generated passwords locally in a You can use the "--pass/-P" to store these generated passwords locally in a
pass store (see passwordstore.org for more). The pass command must be available pass store (see passwordstore.org for more). The pass command must be available
on your $PATH. on your $PATH.`
`
var appNewCommand = cli.Command{ var appNewCommand = cli.Command{
Name: "new", Name: "new",
@ -92,7 +91,7 @@ var appNewCommand = cli.Command{
version = tag version = tag
} }
if err := recipe.EnsureVersion(version); err != nil { if _, err := recipe.EnsureVersion(version); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} else { } else {
@ -101,7 +100,7 @@ var appNewCommand = cli.Command{
} }
} }
} else { } else {
if err := recipe.EnsureVersion(c.Args().Get(1)); err != nil { if _, err := recipe.EnsureVersion(c.Args().Get(1)); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
@ -174,22 +173,25 @@ var appNewCommand = cli.Command{
table := formatter.CreateTable(tableCol) table := formatter.CreateTable(tableCol)
table.Append([]string{internal.NewAppServer, recipe.Name, internal.Domain}) table.Append([]string{internal.NewAppServer, recipe.Name, internal.Domain})
fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name)) log.Infof("new app '%s' created 🌞", recipe.Name)
fmt.Println("") fmt.Println("")
table.Render() table.Render()
fmt.Println("") fmt.Println("")
fmt.Println("You can configure this app by running the following:")
fmt.Println("Configure this app:")
fmt.Println(fmt.Sprintf("\n abra app config %s", internal.Domain)) fmt.Println(fmt.Sprintf("\n abra app config %s", internal.Domain))
fmt.Println("") fmt.Println("")
fmt.Println("You can deploy this app by running the following:") fmt.Println("Deploy this app:")
fmt.Println(fmt.Sprintf("\n abra app deploy %s", internal.Domain)) fmt.Println(fmt.Sprintf("\n abra app deploy %s", internal.Domain))
if len(secrets) > 0 { if len(secrets) > 0 {
fmt.Println("") fmt.Println("")
fmt.Println("Here are your generated secrets:") fmt.Println("Generated secrets:")
fmt.Println("") fmt.Println("")
secretTable.Render() secretTable.Render()
log.Warn("generated secrets are not shown again, please take note of them NOW") log.Warn("Generated secrets are not shown again, please take note of them NOW")
} }
return nil return nil

View File

@ -55,7 +55,7 @@ var appPsCommand = cli.Command{
if statusMeta, ok := statuses[app.StackName()]; ok { if statusMeta, ok := statuses[app.StackName()]; ok {
isChaos, exists := statusMeta["chaos"] isChaos, exists := statusMeta["chaos"]
if exists && isChaos == "false" { if exists && isChaos == "false" {
if err := app.Recipe.EnsureVersion(deployMeta.Version); err != nil { if _, err := app.Recipe.EnsureVersion(deployMeta.Version); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} else { } else {

View File

@ -36,8 +36,7 @@ Please note, if you delete the local app env file without removing volumes and
secrets first, Abra will *not* be able to help you remove them afterwards. secrets first, Abra will *not* be able to help you remove them afterwards.
To delete everything without prompt, use the "--force/-f" or the "--no-input/n" To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
flag. flag.`,
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.ForceFlag, internal.ForceFlag,
internal.DebugFlag, internal.DebugFlag,

View File

@ -33,10 +33,9 @@ Run "abra app ps <domain>" to see a list of service names.
Pass "--all-services/-a" to restart all services. Pass "--all-services/-a" to restart all services.
Example: EXAMPLE:
abra app restart example.com app abra app restart example.com app`,
`,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)

View File

@ -34,14 +34,18 @@ var appRollbackCommand = cli.Command{
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
This command rolls an app back to a previous version if one exists. This command rolls an app back to a previous version.
You may pass "--force/-f" to downgrade to the same version again. This can be Unlike "deploy", chaos operations are not supported here. Only recipe versions
useful if the container runtime has gotten into a weird state. are supported values for "[<version>]".
This action could be destructive, please ensure you have a copy of your app A rollback can be destructive, please ensure you have a copy of your app data
data beforehand. beforehand.
`,
EXAMPLE:
abra app rollback foo.example.com
abra app rollback foo.example.com 1.2.3+3.2.1`,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
@ -82,10 +86,10 @@ data beforehand.
} }
if len(versions) == 0 { if len(versions) == 0 {
log.Warn("no published versions in catalogue, trying local recipe repository") log.Debug("no published versions in catalogue, trying local recipe repository")
recipeVersions, err := app.Recipe.GetRecipeVersions(internal.Offline) recipeVersions, err := app.Recipe.GetRecipeVersions(internal.Offline)
if err != nil { if err != nil {
log.Warn(err) log.Fatal(err)
} }
for _, recipeVersion := range recipeVersions { for _, recipeVersion := range recipeVersions {
@ -105,15 +109,19 @@ data beforehand.
if specificVersion != "" { if specificVersion != "" {
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version) parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
if err != nil { if err != nil {
log.Fatal(err) log.Fatalf("'%s' is not a known version for %s", deployMeta.Version, app.Recipe.Name)
} }
parsedSpecificVersion, err := tagcmp.Parse(specificVersion) parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
if err != nil { if err != nil {
log.Fatal(err) log.Fatalf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
} }
if parsedSpecificVersion.IsGreaterThan(parsedDeployedVersion) || parsedSpecificVersion.Equals(parsedDeployedVersion) { if parsedSpecificVersion.IsGreaterThan(parsedDeployedVersion) && !parsedSpecificVersion.Equals(parsedDeployedVersion) {
log.Fatalf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
}
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
log.Fatalf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion) log.Fatalf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
} }
@ -121,7 +129,7 @@ data beforehand.
} }
if deployMeta.Version != "unknown" && specificVersion == "" { if deployMeta.Version != "unknown" && specificVersion == "" {
if deployMeta.IsChaos == "true" { if deployMeta.IsChaos {
log.Warn("attempting to rollback a chaos deployment") log.Warn("attempting to rollback a chaos deployment")
} }
@ -154,7 +162,7 @@ data beforehand.
log.Debugf("choosing %s as version to downgrade to (--force/--no-input)", chosenDowngrade) log.Debugf("choosing %s as version to downgrade to (--force/--no-input)", chosenDowngrade)
} else { } else {
msg := fmt.Sprintf("please select a downgrade (version: %s):", deployMeta.Version) msg := fmt.Sprintf("please select a downgrade (version: %s):", deployMeta.Version)
if deployMeta.IsChaos == "true" { if deployMeta.IsChaos {
msg = fmt.Sprintf("please select a downgrade (version: %s, chaosVersion: %s):", deployMeta.Version, deployMeta.ChaosVersion) msg = fmt.Sprintf("please select a downgrade (version: %s, chaosVersion: %s):", deployMeta.Version, deployMeta.ChaosVersion)
} }
@ -170,7 +178,7 @@ data beforehand.
} }
log.Debugf("choosing %s as version to rollback", chosenDowngrade) log.Debugf("choosing %s as version to rollback", chosenDowngrade)
if err := app.Recipe.EnsureVersion(chosenDowngrade); err != nil { if _, err := app.Recipe.EnsureVersion(chosenDowngrade); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -206,8 +214,8 @@ data beforehand.
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade) appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
appPkg.SetUpdateLabel(compose, stackName, app.Env) appPkg.SetUpdateLabel(compose, stackName, app.Env)
chaosVersion := deployMeta.IsChaos chaosVersion := "false"
if deployMeta.IsChaos == "true" { if deployMeta.IsChaos {
chaosVersion = deployMeta.ChaosVersion chaosVersion = deployMeta.ChaosVersion
} }

View File

@ -78,8 +78,7 @@ This does not destroy any of the application data.
However, you should remain vigilant, as your swarm installation will consider However, you should remain vigilant, as your swarm installation will consider
any previously attached volumes as eligible for pruning once undeployed. any previously attached volumes as eligible for pruning once undeployed.
Passing "-p/--prune" does not remove those volumes. Passing "-p/--prune" does not remove those volumes.`,
`,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
stackName := app.StackName() stackName := app.StackName()
@ -100,8 +99,8 @@ Passing "-p/--prune" does not remove those volumes.
log.Fatalf("%s is not deployed?", app.Name) log.Fatalf("%s is not deployed?", app.Name)
} }
chaosVersion := deployMeta.IsChaos chaosVersion := "false"
if deployMeta.IsChaos == "true" { if deployMeta.IsChaos {
chaosVersion = deployMeta.ChaosVersion chaosVersion = deployMeta.ChaosVersion
} }

View File

@ -34,15 +34,18 @@ var appUpgradeCommand = cli.Command{
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
Upgrade an app. You can use it to choose and roll out a new upgrade to a Upgrade an app.
deployed app.
You may pass "--force/-f" to upgrade to the same version again. This can be Unlike "deploy", chaos operations are not supported here. Only recipe versions
useful if the container runtime has gotten into a weird state. are supported values for "[<version>]".
This action could be destructive, please ensure you have a copy of your app An upgrade can be destructive, please ensure you have a copy of your app data
data beforehand. beforehand.
`,
EXAMPLE:
abra app upgrade foo.example.com
abra app upgrade foo.example.com 1.2.3+3.2.1`,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
@ -83,10 +86,10 @@ data beforehand.
} }
if len(versions) == 0 { if len(versions) == 0 {
log.Warn("no published versions in catalogue, trying local recipe repository") log.Debug("no published versions in catalogue, trying local recipe repository")
recipeVersions, err := app.Recipe.GetRecipeVersions(internal.Offline) recipeVersions, err := app.Recipe.GetRecipeVersions(internal.Offline)
if err != nil { if err != nil {
log.Warn(err) log.Fatal(err)
} }
for _, recipeVersion := range recipeVersions { for _, recipeVersion := range recipeVersions {
for version := range recipeVersion { for version := range recipeVersion {
@ -105,15 +108,21 @@ data beforehand.
if specificVersion != "" { if specificVersion != "" {
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version) parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
if err != nil { if err != nil {
log.Fatal(err) log.Fatalf("'%s' is not a known version for %s", deployMeta.Version, app.Recipe.Name)
} }
parsedSpecificVersion, err := tagcmp.Parse(specificVersion) parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
if err != nil { if err != nil {
log.Fatal(err) log.Fatalf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
} }
if parsedSpecificVersion.IsLessThan(parsedDeployedVersion) || parsedSpecificVersion.Equals(parsedDeployedVersion) {
if parsedSpecificVersion.IsLessThan(parsedDeployedVersion) && !parsedSpecificVersion.Equals(parsedDeployedVersion) {
log.Fatalf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion) log.Fatalf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
} }
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
log.Fatalf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
}
availableUpgrades = append(availableUpgrades, specificVersion) availableUpgrades = append(availableUpgrades, specificVersion)
} }
@ -123,7 +132,7 @@ data beforehand.
} }
if deployMeta.Version != "unknown" && specificVersion == "" { if deployMeta.Version != "unknown" && specificVersion == "" {
if deployMeta.IsChaos == "true" { if deployMeta.IsChaos {
log.Warn("attempting to upgrade a chaos deployment") log.Warn("attempting to upgrade a chaos deployment")
} }
@ -150,7 +159,7 @@ data beforehand.
log.Debugf("choosing %s as version to upgrade to", chosenUpgrade) log.Debugf("choosing %s as version to upgrade to", chosenUpgrade)
} else { } else {
msg := fmt.Sprintf("please select an upgrade (version: %s):", deployMeta.Version) msg := fmt.Sprintf("please select an upgrade (version: %s):", deployMeta.Version)
if deployMeta.IsChaos == "true" { if deployMeta.IsChaos {
msg = fmt.Sprintf("please select an upgrade (version: %s, chaosVersion: %s):", deployMeta.Version, deployMeta.ChaosVersion) msg = fmt.Sprintf("please select an upgrade (version: %s, chaosVersion: %s):", deployMeta.Version, deployMeta.ChaosVersion)
} }
@ -197,7 +206,7 @@ data beforehand.
} }
log.Debugf("choosing %s as version to upgrade", chosenUpgrade) log.Debugf("choosing %s as version to upgrade", chosenUpgrade)
if err := app.Recipe.EnsureVersion(chosenUpgrade); err != nil { if _, err := app.Recipe.EnsureVersion(chosenUpgrade); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -250,8 +259,8 @@ data beforehand.
return nil return nil
} }
chaosVersion := deployMeta.IsChaos chaosVersion := "false"
if deployMeta.IsChaos == "true" { if deployMeta.IsChaos {
chaosVersion = deployMeta.ChaosVersion chaosVersion = deployMeta.ChaosVersion
} }

View File

@ -73,8 +73,7 @@ The command is interactive and will show a multiple select input which allows
you to make a seclection. Use the "?" key to see more help on navigating this you to make a seclection. Use the "?" key to see more help on navigating this
interface. interface.
Passing "--force/-f" will select all volumes for removal. Be careful. Passing "--force/-f" will select all volumes for removal. Be careful.`,
`,
ArgsUsage: "<domain>", ArgsUsage: "<domain>",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Flags: []cli.Flag{ Flags: []cli.Flag{

View File

@ -33,14 +33,7 @@ var catalogueGenerateCommand = cli.Command{
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
Generate a new copy of the recipe catalogue which can be found on: Generate a new copy of the recipe catalogue.
https://recipes.coopcloud.tech (website that humans read)
https://recipes.coopcloud.tech/recipes.json (JSON that Abra reads)
It polls the entire git.coopcloud.tech/coop-cloud/... recipe repository
listing, parses README.md and git tags to produce recipe metadata which is
loaded into the catalogue JSON file.
It is possible to generate new metadata for a single recipe by passing It is possible to generate new metadata for a single recipe by passing
<recipe>. The existing local catalogue will be updated, not overwritten. <recipe>. The existing local catalogue will be updated, not overwritten.
@ -51,8 +44,7 @@ If you have a Hub account you can have Abra log you in to avoid this. Pass
Push your new release to git.coopcloud.tech with "-p/--publish". This requires Push your new release to git.coopcloud.tech with "-p/--publish". This requires
that you have permission to git push to these repositories and have your SSH that you have permission to git push to these repositories and have your SSH
keys configured on your account. keys configured on your account.`,
`,
ArgsUsage: "[<recipe>]", ArgsUsage: "[<recipe>]",
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
@ -213,11 +205,10 @@ keys configured on your account.
// CatalogueCommand defines the `abra catalogue` command and sub-commands. // CatalogueCommand defines the `abra catalogue` command and sub-commands.
var CatalogueCommand = cli.Command{ var CatalogueCommand = cli.Command{
Name: "catalogue", Name: "catalogue",
Usage: "Manage the recipe catalogue", Usage: "Manage the recipe catalogue",
Aliases: []string{"c"}, Aliases: []string{"c"},
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Description: "This command helps recipe packagers interact with the recipe catalogue",
Subcommands: []cli.Command{ Subcommands: []cli.Command{
catalogueGenerateCommand, catalogueGenerateCommand,
}, },

View File

@ -25,16 +25,15 @@ import (
var AutoCompleteCommand = cli.Command{ var AutoCompleteCommand = cli.Command{
Name: "autocomplete", Name: "autocomplete",
Aliases: []string{"ac"}, Aliases: []string{"ac"},
Usage: "Configure shell autocompletion (recommended)", Usage: "Configure shell autocompletion",
Description: ` Description: `
Set up auto-completion in your shell by downloading the relevant files and Set up shell auto-completion.
laying out what additional information must be loaded. Supported shells are as
follows: bash, fish, fizsh & zsh.
Example: Supported shells are: bash, fish, fizsh & zsh.
abra autocomplete bash EXAMPLE:
`,
abra autocomplete bash`,
ArgsUsage: "<shell>", ArgsUsage: "<shell>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
@ -81,7 +80,7 @@ Example:
switch shellType { switch shellType {
case "bash": case "bash":
fmt.Println(fmt.Sprintf(` fmt.Println(fmt.Sprintf(`
# Run the following commands to install auto-completion # run the following commands to install auto-completion
sudo mkdir /etc/bash_completion.d/ sudo mkdir /etc/bash_completion.d/
sudo cp %s /etc/bash_completion.d/abra sudo cp %s /etc/bash_completion.d/abra
echo "source /etc/bash_completion.d/abra" >> ~/.bashrc echo "source /etc/bash_completion.d/abra" >> ~/.bashrc
@ -89,19 +88,19 @@ echo "source /etc/bash_completion.d/abra" >> ~/.bashrc
`, autocompletionFile)) `, autocompletionFile))
case "zsh": case "zsh":
fmt.Println(fmt.Sprintf(` fmt.Println(fmt.Sprintf(`
# Run the following commands to install auto-completion # run the following commands to install auto-completion
sudo mkdir /etc/zsh/completion.d/ sudo mkdir /etc/zsh/completion.d/
sudo cp %s /etc/zsh/completion.d/abra sudo cp %s /etc/zsh/completion.d/abra
echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra" >> ~/.zshrc echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra" >> ~/.zshrc
# To test, run the following: "abra app <hit tab key>" - you should see command completion! # to test, run the following: "abra app <hit tab key>" - you should see command completion!
`, autocompletionFile)) `, autocompletionFile))
case "fish": case "fish":
fmt.Println(fmt.Sprintf(` fmt.Println(fmt.Sprintf(`
# Run the following commands to install auto-completion # run the following commands to install auto-completion
sudo mkdir -p /etc/fish/completions sudo mkdir -p /etc/fish/completions
sudo cp %s /etc/fish/completions/abra sudo cp %s /etc/fish/completions/abra
echo "source /etc/fish/completions/abra" >> ~/.config/fish/config.fish echo "source /etc/fish/completions/abra" >> ~/.config/fish/config.fish
# To test, run the following: "abra app <hit tab key>" - you should see command completion! # to test, run the following: "abra app <hit tab key>" - you should see command completion!
`, autocompletionFile)) `, autocompletionFile))
} }
@ -113,14 +112,18 @@ echo "source /etc/fish/completions/abra" >> ~/.config/fish/config.fish
var UpgradeCommand = cli.Command{ var UpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Upgrade Abra itself", Usage: "Upgrade abra",
Description: ` Description: `
Upgrade Abra in-place with the latest stable or release candidate. Upgrade abra in-place with the latest stable or release candidate.
Pass "-r/--rc" to install the latest release candidate. Please bear in mind Use "-r/--rc" to install the latest release candidate. Please bear in mind that
that it may contain catastrophic bugs. Thank you very much for the testing it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
efforts! for the testing efforts 💗
`,
EXAMPLE:
abra upgrade
abra upgrade --rc`,
Flags: []cli.Flag{internal.RCFlag}, Flags: []cli.Flag{internal.RCFlag},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
mainURL := "https://install.abra.coopcloud.tech" mainURL := "https://install.abra.coopcloud.tech"
@ -144,7 +147,7 @@ efforts!
func newAbraApp(version, commit string) *cli.App { func newAbraApp(version, commit string) *cli.App {
app := &cli.App{ app := &cli.App{
Name: "abra", Name: "abra",
Usage: `The Co-op Cloud command-line utility belt 🎩🐇 Usage: `the Co-op Cloud command-line utility belt 🎩🐇
____ ____ _ _ ____ ____ _ _
/ ___|___ ___ _ __ / ___| | ___ _ _ __| | / ___|___ ___ _ __ / ___| | ___ _ _ __| |
| | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' | | | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' |

View File

@ -5,14 +5,13 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
gitPkg "coopcloud.tech/abra/pkg/git" gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
var recipeDiffCommand = cli.Command{ var recipeDiffCommand = cli.Command{
Name: "diff", Name: "diff",
Usage: "Show unstaged changes in recipe config", Usage: "Show unstaged changes in recipe config",
Description: "Due to limitations in our underlying Git dependency, this command requires /usr/bin/git.", Description: "This command requires /usr/bin/git.",
Aliases: []string{"d"}, Aliases: []string{"d"},
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -22,12 +21,7 @@ var recipeDiffCommand = cli.Command{
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipeName := c.Args().First() r := internal.ValidateRecipe(c)
r := recipe.Get(recipeName)
if recipeName != "" {
internal.ValidateRecipe(c)
}
if err := gitPkg.DiffUnstaged(r.Dir); err != nil { if err := gitPkg.DiffUnstaged(r.Dir); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -48,12 +48,7 @@ Create a new recipe.
Abra uses the built-in example repository which is available here: Abra uses the built-in example repository which is available here:
https://git.coopcloud.tech/coop-cloud/example https://git.coopcloud.tech/coop-cloud/example`,
Files within the example repository make use of the Golang templating system
which Abra uses to inject values into the generated recipe folder (e.g. name of
recipe and domain in the sample environment config).
`,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipeName := c.Args().First() recipeName := c.Args().First()
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
@ -100,22 +95,8 @@ recipe and domain in the sample environment config).
log.Fatal(err) log.Fatal(err)
} }
fmt.Print(fmt.Sprintf(` log.Infof("new recipe '%s' created: %s", recipeName, path.Join(r.Dir))
Your new %s recipe has been created in %s. log.Info("happy hacking 🎉")
In order to share your recipe, you can upload it the git repository to:
https://git.coopcloud.tech/coop-cloud/%s
If you're not sure how to do that, come chat with us:
https://docs.coopcloud.tech/intro/contact
See "abra recipe -h" for additional recipe maintainer commands.
Happy Hacking!
`, recipeName, path.Join(r.Dir), recipeName))
return nil return nil
}, },

View File

@ -18,9 +18,7 @@ for you.
Anyone who uses a recipe can become a maintainer. Maintainers typically make Anyone who uses a recipe can become a maintainer. Maintainers typically make
sure the recipe is in good working order and the config upgraded in a timely sure the recipe is in good working order and the config upgraded in a timely
manner. Abra supports convenient automation for recipe maintainenace, see the manner.`,
"abra recipe upgrade", "abra recipe sync" and "abra recipe release" commands.
`,
Subcommands: []cli.Command{ Subcommands: []cli.Command{
recipeFetchCommand, recipeFetchCommand,
recipeLintCommand, recipeLintCommand,

View File

@ -45,8 +45,7 @@ major and therefore require intervention while doing the upgrade work.
Publish your new release to git.coopcloud.tech with "-p/--publish". This Publish your new release to git.coopcloud.tech with "-p/--publish". This
requires that you have permission to git push to these repositories and have requires that you have permission to git push to these repositories and have
your SSH keys configured on your account. your SSH keys configured on your account.`,
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,

View File

@ -12,7 +12,7 @@ import (
var recipeResetCommand = cli.Command{ var recipeResetCommand = cli.Command{
Name: "reset", Name: "reset",
Usage: "Remove all unstaged changes from recipe config", Usage: "Remove all unstaged changes from recipe config",
Description: "WARNING, this will delete your changes. Be Careful.", Description: "WARNING: this will delete your changes. Be Careful.",
Aliases: []string{"rs"}, Aliases: []string{"rs"},
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Flags: []cli.Flag{ Flags: []cli.Flag{

View File

@ -37,8 +37,7 @@ named "app") which corresponds to the following format:
Where <version> can be specifed on the command-line or Abra can attempt to Where <version> can be specifed on the command-line or Abra can attempt to
auto-generate it for you. The <recipe> configuration will be updated on the auto-generate it for you. The <recipe> configuration will be updated on the
local file system. local file system.`,
`,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipe := internal.ValidateRecipe(c)

View File

@ -53,10 +53,11 @@ The command is interactive and will show a select input which allows you to
make a seclection. Use the "?" key to see more help on navigating this make a seclection. Use the "?" key to see more help on navigating this
interface. interface.
You may invoke this command in "wizard" mode and be prompted for input: You may invoke this command in "wizard" mode and be prompted for input.
abra recipe upgrade EXAMPLE:
`,
abra recipe upgrade`,
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,

View File

@ -24,11 +24,10 @@ func sortServiceByName(versions [][]string) func(i, j int) bool {
} }
var recipeVersionCommand = cli.Command{ var recipeVersionCommand = cli.Command{
Name: "versions", Name: "versions",
Aliases: []string{"v"}, Aliases: []string{"v"},
Usage: "List recipe versions", Usage: "List recipe versions",
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Description: "Versions are read from the recipe catalogue.",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag, internal.OfflineFlag,

View File

@ -94,7 +94,7 @@ func createServerDir(name string) (bool, error) {
var serverAddCommand = cli.Command{ var serverAddCommand = cli.Command{
Name: "add", Name: "add",
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "Add a server to your configuration", Usage: "Add a new server to your configuration",
Description: ` Description: `
Add a new server to your configuration so that it can be managed by Abra. Add a new server to your configuration so that it can be managed by Abra.
@ -121,8 +121,7 @@ You can also pass "--no-domain-checks/-D" flag to use any arbitrary name
instead of a real domain. The host will be resolved with the "Hostname" entry instead of a real domain. The host will be resolved with the "Hostname" entry
of your ~/.ssh/config. Checks for a valid online domain will be skipped: of your ~/.ssh/config. Checks for a valid online domain will be skipped:
abra server add -D example abra server add -D example`,
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,

View File

@ -31,13 +31,12 @@ var volumesFilterFlag = &cli.BoolFlag{
var serverPruneCommand = cli.Command{ var serverPruneCommand = cli.Command{
Name: "prune", Name: "prune",
Aliases: []string{"p"}, Aliases: []string{"p"},
Usage: "Prune a managed server; Runs a docker system prune", Usage: "Prune resources on a server",
Description: ` Description: `
Prunes unused containers, networks, and dangling images. Prunes unused containers, networks, and dangling images.
If passing "-v/--volumes" then volumes not connected to a deployed app will Use "-v/--volumes" to remove volumes that are not associated with a deployed
also be removed. This can result in unwanted data loss if not used carefully. app. This can result in unwanted data loss if not used carefully.`,
`,
ArgsUsage: "[<server>]", ArgsUsage: "[<server>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
allFilterFlag, allFilterFlag,

View File

@ -17,12 +17,12 @@ var serverRemoveCommand = cli.Command{
Aliases: []string{"rm"}, Aliases: []string{"rm"},
ArgsUsage: "<server>", ArgsUsage: "<server>",
Usage: "Remove a managed server", Usage: "Remove a managed server",
Description: `Remove a managed server. Description: `
Remove a managed server.
Abra will remove the internal bookkeeping (~/.abra/servers/...) and underlying Abra will remove the internal bookkeeping (~/.abra/servers/...) and underlying
client connection context. This server will then be lost in time, like tears in client connection context. This server will then be lost in time, like tears in
rain. rain.`,
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,

View File

@ -54,10 +54,12 @@ var Notify = cli.Command{
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
It reads the deployed app versions and looks for new versions in the recipe Read the deployed app versions and look for new versions in the recipe
catalogue. If a new patch/minor version is available, a notification is catalogue.
printed. To include major versions use the --major flag.
`, If a new patch/minor version is available, a notification is printed.
Use "--major" to include new major versions.`,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
cl, err := client.New("default") cl, err := client.New("default")
if err != nil { if err != nil {
@ -103,14 +105,17 @@ var UpgradeApp = cli.Command{
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
Upgrade an app by specifying its stack name and recipe. By passing "--all" Upgrade an app by specifying stack name and recipe.
instead, every deployed app is upgraded. For each apps with enabled auto
updates the deployed version is compared with the current recipe catalogue Use "--all" to upgrade every deployed app.
version. If a new patch/minor version is available, the app is upgraded. To
include major versions use the "--major" flag. Don't do that, it will probably For each app with auto updates enabled, the deployed version is compared with
break things. Only apps that are not deployed with "--chaos" are upgraded, to the current recipe catalogue version. If a new patch/minor version is
update chaos deployments use the "--chaos" flag. Use it with care. available, the app is upgraded.
`,
To include major versions use the "--major" flag. You probably don't want that
as it will break things. Only apps that are not deployed with "--chaos" are
upgraded, to update chaos deployments use the "--chaos" flag. Use it with care.`,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
cl, err := client.New("default") cl, err := client.New("default")
if err != nil { if err != nil {
@ -327,7 +332,7 @@ func processRecipeRepoVersion(r recipe.Recipe, version string) error {
return err return err
} }
if err := r.EnsureVersion(version); err != nil { if _, err := r.EnsureVersion(version); err != nil {
return err return err
} }

View File

@ -57,21 +57,23 @@ func (r Recipe) EnsureExists() error {
} }
// EnsureVersion checks whether a specific version exists for a recipe. // EnsureVersion checks whether a specific version exists for a recipe.
func (r Recipe) EnsureVersion(version string) error { func (r Recipe) EnsureVersion(version string) (bool, error) {
isChaosCommit := false
recipeDir := path.Join(config.RECIPES_DIR, r.Name) recipeDir := path.Join(config.RECIPES_DIR, r.Name)
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil { if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
return err return isChaosCommit, err
} }
repo, err := git.PlainOpen(recipeDir) repo, err := git.PlainOpen(recipeDir)
if err != nil { if err != nil {
return err return isChaosCommit, err
} }
tags, err := repo.Tags() tags, err := repo.Tags()
if err != nil { if err != nil {
return nil return isChaosCommit, err
} }
var parsedTags []string var parsedTags []string
@ -83,7 +85,7 @@ func (r Recipe) EnsureVersion(version string) error {
} }
return nil return nil
}); err != nil { }); err != nil {
return err return isChaosCommit, err
} }
joinedTags := strings.Join(parsedTags, ", ") joinedTags := strings.Join(parsedTags, ", ")
@ -91,27 +93,33 @@ func (r Recipe) EnsureVersion(version string) error {
log.Debugf("read %s as tags for recipe %s", joinedTags, r.Name) log.Debugf("read %s as tags for recipe %s", joinedTags, r.Name)
} }
var opts *git.CheckoutOptions
if tagRef.String() == "" { if tagRef.String() == "" {
return fmt.Errorf("the local copy of %s doesn't seem to have version %s available?", r.Name, version) log.Debugf("attempting to checkout '%s' as chaos commit", version)
hash, err := repo.ResolveRevision(plumbing.Revision(version))
if err != nil {
log.Fatalf("unable to resolve '%s': %s", version, err)
}
opts = &git.CheckoutOptions{Hash: *hash, Create: false, Force: true}
isChaosCommit = true
} else {
opts = &git.CheckoutOptions{Branch: tagRef, Create: false, Force: true}
} }
worktree, err := repo.Worktree() worktree, err := repo.Worktree()
if err != nil { if err != nil {
return err return isChaosCommit, nil
} }
opts := &git.CheckoutOptions{
Branch: tagRef,
Create: false,
Force: true,
}
if err := worktree.Checkout(opts); err != nil { if err := worktree.Checkout(opts); err != nil {
return err return isChaosCommit, nil
} }
log.Debugf("successfully checked %s out to %s in %s", r.Name, tagRef.Short(), recipeDir) log.Debugf("successfully checked %s out to %s in %s", r.Name, tagRef.Short(), recipeDir)
return nil return isChaosCommit, nil
} }
// EnsureIsClean makes sure that the recipe repository has no unstaged changes. // EnsureIsClean makes sure that the recipe repository has no unstaged changes.

View File

@ -7,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/signal" "os/signal"
"strconv"
"strings" "strings"
"time" "time"
@ -101,7 +102,7 @@ func GetDeployedServicesByName(ctx context.Context, cl *dockerClient.Client, sta
type DeployMeta struct { type DeployMeta struct {
IsDeployed bool // whether the app is deployed or not IsDeployed bool // whether the app is deployed or not
Version string // the deployed version Version string // the deployed version
IsChaos string // whether or not the deployment is --chaos IsChaos bool // whether or not the deployment is --chaos
ChaosVersion string // the --chaos deployment version ChaosVersion string // the --chaos deployment version
} }
@ -110,7 +111,7 @@ func IsDeployed(ctx context.Context, cl *dockerClient.Client, stackName string)
deployMeta := DeployMeta{ deployMeta := DeployMeta{
IsDeployed: false, IsDeployed: false,
Version: "unknown", Version: "unknown",
IsChaos: "false", // NOTE(d1): match string type used on label IsChaos: false,
ChaosVersion: "false", // NOTE(d1): match string type used on label ChaosVersion: "false", // NOTE(d1): match string type used on label
} }
@ -137,7 +138,11 @@ func IsDeployed(ctx context.Context, cl *dockerClient.Client, stackName string)
labelKey = fmt.Sprintf("coop-cloud.%s.chaos", stackName) labelKey = fmt.Sprintf("coop-cloud.%s.chaos", stackName)
if isChaos, ok := service.Spec.Labels[labelKey]; ok { if isChaos, ok := service.Spec.Labels[labelKey]; ok {
deployMeta.IsChaos = isChaos boolVal, err := strconv.ParseBool(isChaos)
if err != nil {
return deployMeta, fmt.Errorf("unable to parse '%s' value as bool: %s", labelKey, err)
}
deployMeta.IsChaos = boolVal
} }
labelKey = fmt.Sprintf("coop-cloud.%s.chaos-version", stackName) labelKey = fmt.Sprintf("coop-cloud.%s.chaos-version", stackName)

View File

@ -356,3 +356,12 @@ teardown(){
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all --chaos run $ABRA app secret rm "$TEST_APP_DOMAIN" --all --chaos
assert_success assert_success
} }
# bats test_tags=slow
@test "deploy chaos commit" {
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
run $ABRA app deploy "$TEST_APP_DOMAIN" "$tagHash" --no-input --no-converge-checks
assert_success
assert_output --partial 'chaos mode'
}

View File

@ -111,6 +111,26 @@ teardown(){
assert_output --partial "0.1.0+1.20.0" assert_output --partial "0.1.0+1.20.0"
} }
# bats test_tags=slow
@test "force rollback to previous version" {
run $ABRA app deploy "$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'
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"
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
assert_failure
assert_output --partial "not a downgrade"
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \
--no-input --no-converge-checks --force
assert_success
assert_output --partial "0.1.0+1.20.0"
}
# bats test_tags=slow # bats test_tags=slow
@test "rollback to a version 2 tags behind" { @test "rollback to a version 2 tags behind" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input --no-converge-checks run $ABRA app deploy "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input --no-converge-checks
@ -145,3 +165,14 @@ teardown(){
refute_output --partial "${tagHash:0:8}" refute_output --partial "${tagHash:0:8}"
assert_output --partial "false" assert_output --partial "false"
} }
# bats test_tags=slow
@test "chaos commit rollback not possible" {
_deploy_app
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
run $ABRA app rollback "$TEST_APP_DOMAIN" "$tagHash" --no-input --no-converge-checks
assert_failure
assert_output --partial "not a known version"
}

View File

@ -97,6 +97,26 @@ teardown(){
assert_output --partial '0.2.0+1.21.0' assert_output --partial '0.2.0+1.21.0'
} }
# bats test_tags=slow
@test "force upgrade specific version" {
run $ABRA app deploy "$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'
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'
run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input --no-converge-checks
assert_failure
assert_output --partial 'not an upgrade'
run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" \
--no-input --no-converge-checks --force
assert_success
assert_output --partial '0.2.0+1.21.0'
}
# bats test_tags=slow # bats test_tags=slow
@test "upgrade to latest" { @test "upgrade to latest" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
@ -189,3 +209,15 @@ teardown(){
refute_output --partial "${tagHash:0:8}" refute_output --partial "${tagHash:0:8}"
assert_output --partial "false" assert_output --partial "false"
} }
@test "chaos commit upgrade not possible" {
run $ABRA app deploy "$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.2.0+1.21.0")
run $ABRA app upgrade "$TEST_APP_DOMAIN" "$tagHash" --no-input --no-converge-checks
assert_failure
assert_output --partial "not a known version"
}

View File

@ -16,6 +16,14 @@ setup(){
} }
teardown(){ teardown(){
if [[ -f "$ABRA_DIR/servers/$TEST_SERVER/foobar.$TEST_SERVER.env" ]]; then
run $ABRA app undeploy "foobar.$TEST_SERVER" --no-input
assert_success
run $ABRA app rm "foobar.$TEST_SERVER" --no-input
assert_success
fi
if [[ -d "$ABRA_DIR/recipes/foobar" ]]; then if [[ -d "$ABRA_DIR/recipes/foobar" ]]; then
run rm -rf "$ABRA_DIR/recipes/foobar" run rm -rf "$ABRA_DIR/recipes/foobar"
assert_success assert_success
@ -25,26 +33,32 @@ teardown(){
@test "create new recipe" { @test "create new recipe" {
run $ABRA recipe new foobar run $ABRA recipe new foobar
assert_success assert_success
assert_output --partial 'Your new foobar recipe has been created' assert_output --partial "new recipe 'foobar' created"
assert_exists "$ABRA_DIR/recipes/foobar" assert_exists "$ABRA_DIR/recipes/foobar"
} }
# bats test_tags=slow
@test "create new app from new recipe" { @test "create new app from new recipe" {
run $ABRA recipe new foobar run $ABRA recipe new foobar
assert_success assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run $ABRA app new foobar \ run $ABRA app new foobar \
--no-input \ --no-input \
--server "$TEST_SERVER" \ --server "$TEST_SERVER" \
--domain "foobar.$TEST_SERVER" --domain "foobar.$TEST_SERVER"
assert_success assert_success
assert_output --partial 'A new foobar app has been created!' assert_output --partial "new app 'foobar' created"
run $ABRA app deploy "foobar.$TEST_SERVER" --no-input
assert_success
assert_output --partial 'using latest commit'
} }
@test "create new recipe with git credentials" { @test "create new recipe with git credentials" {
run $ABRA recipe new foobar --git-name fooUser --git-email foo@example.com run $ABRA recipe new foobar --git-name fooUser --git-email foo@example.com
assert_success assert_success
assert_output --partial 'Your new foobar recipe has been created' assert_output --partial "new recipe 'foobar' created"
assert_exists "$ABRA_DIR/recipes/foobar" assert_exists "$ABRA_DIR/recipes/foobar"
run bash -c 'git -C "$ABRA_DIR/recipes/foobar" log -n 1' run bash -c 'git -C "$ABRA_DIR/recipes/foobar" log -n 1'