Compare commits
45 Commits
0.4.0-alph
...
0.4.0-alph
Author | SHA1 | Date | |
---|---|---|---|
c6db9ee355
|
|||
7733637767
|
|||
88f9796aaf
|
|||
6cdba0f9de
|
|||
199aa5f4e3
|
|||
9b26c24a5f
|
|||
ca75654769
|
|||
fc2d83d203
|
|||
2f4f288a46
|
|||
e98f00d354
|
|||
b4c2773b87 | |||
3aec5d1d7e
|
|||
e0fa1b6995
|
|||
b69ab0df65
|
|||
69a7d37fb7
|
|||
87649cbbd0
|
|||
4b7ec6384c
|
|||
b22b63c2ba
|
|||
d9f3a11265
|
|||
d7cf11b876
|
|||
d7e1b2947a
|
|||
1b37d2d5f5
|
|||
74dfb12fd6
|
|||
49ccf2d204
|
|||
76adc45431
|
|||
e38a0078f3
|
|||
25b44dc54e
|
|||
0c2f6fb676
|
|||
10e4a8b97f
|
|||
eed2756784
|
|||
b61b8f0d2a
|
|||
763e7b5bff
|
|||
d5ab9aedbf
|
|||
2ebb00c9d4
|
|||
6d76b3646a
|
|||
636dc82258
|
|||
66d5453248
|
|||
ba9abcb0d7
|
|||
a1cbf21f61
|
|||
bd1da39374
|
|||
8b90519bc9
|
|||
65feda7f1d
|
|||
64e223a810
|
|||
379e01d855
|
|||
a421c0dca5
|
@ -66,9 +66,7 @@ We maintain a fork of [godotenv](https://github.com/Autonomic-Cooperative/godote
|
||||
1. multi-line env var support
|
||||
2. inline comment parsing
|
||||
|
||||
You can upgrade the version here by running `go get github.com/Autonomic-Cooperative/godotenv@<commit>` where `<commit>` is the
|
||||
latest commit you want to pin to. We are aiming to migrate to YAML format for the environment configuration, so this should only
|
||||
be a temporary thing.
|
||||
You can upgrade the version here by running `go get github.com/Autonomic-Cooperative/godotenv@<commit>` where `<commit>` is the latest commit you want to pin to. At time of writing, `go get github.com/Autonomic-Cooperative/godotenv@b031ea1211e7fd297af4c7747ffb562ebe00cd33` is the command you want to run to maintain the above functionality.
|
||||
|
||||
#### `docker/client`
|
||||
|
||||
|
@ -30,10 +30,10 @@ is failing to deploy or having issues, it could be a lot of things.
|
||||
|
||||
This command currently takes into account:
|
||||
|
||||
Is the service deployed?
|
||||
Is the service deployed?
|
||||
Is the service killed by an OOM error?
|
||||
Is the service reporting an error (like in "ps --no-trunc" output)
|
||||
Is the service healthcheck failing? what are the healthcheck logs?
|
||||
Is the service reporting an error (like in "ps --no-trunc" output)
|
||||
Is the service healthcheck failing? what are the healthcheck logs?
|
||||
|
||||
Got any more ideas? Please let us know:
|
||||
|
||||
|
@ -39,13 +39,13 @@ var appRemoveCommand = &cli.Command{
|
||||
if !internal.Force {
|
||||
response := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("about to delete %s, are you sure?", app.Name),
|
||||
Message: fmt.Sprintf("about to remove %s, are you sure?", app.Name),
|
||||
}
|
||||
if err := survey.AskOne(prompt, &response); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if !response {
|
||||
logrus.Fatal("user aborted app removal")
|
||||
logrus.Fatal("aborting as requested")
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,18 +54,16 @@ var appRemoveCommand = &cli.Command{
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Force {
|
||||
isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName())
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if isDeployed {
|
||||
logrus.Fatalf("%s is still deployed. Run \"abra app undeploy %s \" or pass --force", app.Name, app.Name)
|
||||
}
|
||||
isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName())
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if isDeployed {
|
||||
logrus.Fatalf("%s is still deployed. Run \"abra app undeploy %s \" or pass --force", app.Name, app.Name)
|
||||
}
|
||||
|
||||
fs := filters.NewArgs()
|
||||
fs.Add("name", app.Name)
|
||||
fs.Add("name", app.StackName())
|
||||
secretList, err := cl.SecretList(c.Context, types.SecretListOptions{Filters: fs})
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
@ -81,6 +79,7 @@ var appRemoveCommand = &cli.Command{
|
||||
|
||||
if len(secrets) > 0 {
|
||||
var secretNamesToRemove []string
|
||||
|
||||
if !internal.Force {
|
||||
secretsPrompt := &survey.MultiSelect{
|
||||
Message: "which secrets do you want to remove?",
|
||||
@ -142,7 +141,9 @@ var appRemoveCommand = &cli.Command{
|
||||
logrus.Info("no volumes were removed")
|
||||
}
|
||||
} else {
|
||||
logrus.Info("no volumes to remove")
|
||||
if Volumes {
|
||||
logrus.Info("no volumes to remove")
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Remove(app.Path)
|
||||
|
@ -45,8 +45,10 @@ recipes.
|
||||
app := internal.ValidateApp(c)
|
||||
stackName := app.StackName()
|
||||
|
||||
if err := recipe.EnsureUpToDate(app.Type); err != nil {
|
||||
logrus.Fatal(err)
|
||||
if !internal.Chaos {
|
||||
if err := recipe.EnsureUpToDate(app.Type); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
r, err := recipe.Get(app.Type)
|
||||
|
@ -20,18 +20,19 @@ import (
|
||||
var allSecrets bool
|
||||
var allSecretsFlag = &cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"A"},
|
||||
Aliases: []string{"a"},
|
||||
Value: false,
|
||||
Destination: &allSecrets,
|
||||
Usage: "Generate all secrets",
|
||||
}
|
||||
|
||||
var appSecretGenerateCommand = &cli.Command{
|
||||
Name: "generate",
|
||||
Aliases: []string{"g"},
|
||||
Usage: "Generate secrets",
|
||||
ArgsUsage: "<secret> <version>",
|
||||
Flags: []cli.Flag{allSecretsFlag, internal.PassFlag},
|
||||
Name: "generate",
|
||||
Aliases: []string{"g"},
|
||||
Usage: "Generate secrets",
|
||||
ArgsUsage: "<secret> <version>",
|
||||
Flags: []cli.Flag{allSecretsFlag, internal.PassFlag},
|
||||
BashComplete: autocomplete.AppNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
@ -95,11 +96,12 @@ var appSecretGenerateCommand = &cli.Command{
|
||||
}
|
||||
|
||||
var appSecretInsertCommand = &cli.Command{
|
||||
Name: "insert",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "Insert secret",
|
||||
Flags: []cli.Flag{internal.PassFlag},
|
||||
ArgsUsage: "<app> <secret-name> <version> <data>",
|
||||
Name: "insert",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "Insert secret",
|
||||
Flags: []cli.Flag{internal.PassFlag},
|
||||
ArgsUsage: "<app> <secret-name> <version> <data>",
|
||||
BashComplete: autocomplete.AppNameComplete,
|
||||
Description: `
|
||||
This command inserts a secret into an app environment.
|
||||
|
||||
@ -139,11 +141,12 @@ Example:
|
||||
}
|
||||
|
||||
var appSecretRmCommand = &cli.Command{
|
||||
Name: "remove",
|
||||
Usage: "Remove a secret",
|
||||
Aliases: []string{"rm"},
|
||||
Flags: []cli.Flag{allSecretsFlag, internal.PassFlag},
|
||||
ArgsUsage: "<app> <secret-name>",
|
||||
Name: "remove",
|
||||
Usage: "Remove a secret",
|
||||
Aliases: []string{"rm"},
|
||||
Flags: []cli.Flag{allSecretsFlag, internal.PassFlag},
|
||||
ArgsUsage: "<app> <secret-name>",
|
||||
BashComplete: autocomplete.AppNameComplete,
|
||||
Description: `
|
||||
This command removes a secret from an app environment.
|
||||
|
||||
@ -183,20 +186,29 @@ Example:
|
||||
if err := cl.SecretRemove(c.Context, secretName); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
logrus.Infof("deleted %s successfully from server", secretName)
|
||||
|
||||
if internal.Pass {
|
||||
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
logrus.Infof("deleted %s successfully from local pass store", secretName)
|
||||
}
|
||||
} else {
|
||||
if parsed == secretToRm {
|
||||
if err := cl.SecretRemove(c.Context, secretName); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
logrus.Infof("deleted %s successfully from server", secretName)
|
||||
|
||||
if internal.Pass {
|
||||
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
logrus.Infof("deleted %s successfully from local pass store", secretName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,8 +48,10 @@ recipes.
|
||||
app := internal.ValidateApp(c)
|
||||
stackName := app.StackName()
|
||||
|
||||
if err := recipe.EnsureUpToDate(app.Type); err != nil {
|
||||
logrus.Fatal(err)
|
||||
if !internal.Chaos {
|
||||
if err := recipe.EnsureUpToDate(app.Type); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
r, err := recipe.Get(app.Type)
|
||||
@ -113,7 +115,7 @@ recipes.
|
||||
}
|
||||
|
||||
if len(availableUpgrades) == 0 && !internal.Force {
|
||||
logrus.Info("no available upgrades, you're on latest ✌️")
|
||||
logrus.Infof("no available upgrades, you're on latest (%s) ✌️", deployedVersion)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
@ -22,9 +20,8 @@ func getImagePath(image string) (string, error) {
|
||||
}
|
||||
|
||||
path := reference.Path(img)
|
||||
if strings.Contains(path, "library") {
|
||||
path = strings.Split(path, "/")[1]
|
||||
}
|
||||
|
||||
path = recipe.StripTagMeta(path)
|
||||
|
||||
logrus.Debugf("parsed %s from %s", path, image)
|
||||
|
||||
|
@ -33,6 +33,7 @@ var CatalogueSkipList = map[string]bool{
|
||||
"auto-mirror": true,
|
||||
"backup-bot": true,
|
||||
"backup-bot-two": true,
|
||||
"beta.coopcloud.tech": true,
|
||||
"comrade-renovate-bot": true,
|
||||
"coopcloud.tech": true,
|
||||
"coturn": true,
|
||||
@ -93,12 +94,6 @@ keys configured on your account.
|
||||
internal.ValidateRecipe(c)
|
||||
}
|
||||
|
||||
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
|
||||
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
|
||||
if err := gitPkg.Clone(catalogueDir, url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repos, err := recipe.ReadReposMetadata()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
|
@ -24,8 +24,10 @@ import (
|
||||
func DeployAction(c *cli.Context) error {
|
||||
app := ValidateApp(c)
|
||||
|
||||
if err := recipe.EnsureUpToDate(app.Type); err != nil {
|
||||
logrus.Fatal(err)
|
||||
if !Chaos {
|
||||
if err := recipe.EnsureUpToDate(app.Type); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
r, err := recipe.Get(app.Type)
|
||||
@ -58,7 +60,7 @@ func DeployAction(c *cli.Context) error {
|
||||
}
|
||||
|
||||
version := deployedVersion
|
||||
if version == "" && !Chaos {
|
||||
if version == "unknown" && !Chaos {
|
||||
catl, err := recipe.ReadRecipeCatalogue()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
@ -86,14 +88,14 @@ func DeployAction(c *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if version == "" && !Chaos {
|
||||
if version == "unknown" && !Chaos {
|
||||
logrus.Debugf("choosing %s as version to deploy", version)
|
||||
if err := recipe.EnsureVersion(app.Type, version); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if version != "" && !Chaos {
|
||||
if version != "unknown" && !Chaos {
|
||||
if err := recipe.EnsureVersion(app.Type, version); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
@ -221,7 +223,7 @@ func NewVersionOverview(app config.App, currentVersion, newVersion, releaseNotes
|
||||
}
|
||||
}
|
||||
|
||||
if releaseNotes != "" {
|
||||
if releaseNotes != "" && newVersion != "" {
|
||||
fmt.Println()
|
||||
fmt.Println(fmt.Sprintf("%s release notes:\n\n%s", newVersion, releaseNotes))
|
||||
} else {
|
||||
@ -250,6 +252,10 @@ func NewVersionOverview(app config.App, currentVersion, newVersion, releaseNotes
|
||||
|
||||
// GetReleaseNotes prints release notes for a recipe version
|
||||
func GetReleaseNotes(recipeName, version string) (string, error) {
|
||||
if version == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
fpath := path.Join(config.RECIPES_DIR, recipeName, "release", version)
|
||||
|
||||
if _, err := os.Stat(fpath); !os.IsNotExist(err) {
|
||||
|
@ -438,6 +438,15 @@ var RegistryPasswordFlag = &cli.StringFlag{
|
||||
Destination: &RegistryUsername,
|
||||
}
|
||||
|
||||
var AllTags bool
|
||||
var AllTagsFlag = &cli.BoolFlag{
|
||||
Name: "all-tags",
|
||||
Aliases: []string{"a"},
|
||||
Value: false,
|
||||
Usage: "List all tags, not just upgrades",
|
||||
Destination: &AllTags,
|
||||
}
|
||||
|
||||
// SSHFailMsg is a hopefully helpful SSH failure message
|
||||
var SSHFailMsg = `
|
||||
Woops, Abra is unable to connect to connect to %s.
|
||||
|
@ -23,7 +23,7 @@ var RecipeName string
|
||||
|
||||
// createSecrets creates all secrets for a new app.
|
||||
func createSecrets(sanitisedAppName string) (AppSecrets, error) {
|
||||
appEnvPath := path.Join(config.ABRA_DIR, "servers", NewAppServer, fmt.Sprintf("%s.env", sanitisedAppName))
|
||||
appEnvPath := path.Join(config.ABRA_DIR, "servers", NewAppServer, fmt.Sprintf("%s.env", NewAppName))
|
||||
appEnv, err := config.ReadEnv(appEnvPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -163,9 +163,9 @@ func NewAction(c *cli.Context) error {
|
||||
NewAppServer = "local"
|
||||
}
|
||||
|
||||
tableCol := []string{"Name", "Domain", "Type", "Server"}
|
||||
tableCol := []string{"server", "type", "domain", "app name"}
|
||||
table := formatter.CreateTable(tableCol)
|
||||
table.Append([]string{sanitisedAppName, Domain, recipe.Name, NewAppServer})
|
||||
table.Append([]string{NewAppServer, recipe.Name, Domain, NewAppName})
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name))
|
||||
@ -173,10 +173,10 @@ func NewAction(c *cli.Context) error {
|
||||
table.Render()
|
||||
fmt.Println("")
|
||||
fmt.Println("You can configure this app by running the following:")
|
||||
fmt.Println(fmt.Sprintf("\n abra app config %s", sanitisedAppName))
|
||||
fmt.Println(fmt.Sprintf("\n abra app config %s", NewAppName))
|
||||
fmt.Println("")
|
||||
fmt.Println("You can deploy this app by running the following:")
|
||||
fmt.Println(fmt.Sprintf("\n abra app deploy %s", sanitisedAppName))
|
||||
fmt.Println(fmt.Sprintf("\n abra app deploy %s", NewAppName))
|
||||
fmt.Println("")
|
||||
|
||||
return nil
|
||||
|
@ -2,9 +2,9 @@ package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -94,9 +94,7 @@ func GetMainAppImage(recipe recipe.Recipe) (string, error) {
|
||||
}
|
||||
|
||||
path = reference.Path(img)
|
||||
if strings.Contains(path, "library") {
|
||||
path = strings.Split(path, "/")[1]
|
||||
}
|
||||
path = recipePkg.StripTagMeta(path)
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
@ -2,14 +2,11 @@ package recipe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -32,12 +29,6 @@ var recipeListCommand = &cli.Command{
|
||||
patternFlag,
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
|
||||
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
|
||||
if err := gitPkg.Clone(catalogueDir, url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
catl, err := recipe.ReadRecipeCatalogue()
|
||||
if err != nil {
|
||||
logrus.Fatal(err.Error())
|
||||
|
@ -127,6 +127,7 @@ your SSH keys configured on your account.
|
||||
func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
|
||||
var services = make(map[string]string)
|
||||
|
||||
missingTag := false
|
||||
for _, service := range recipe.Config.Services {
|
||||
if service.Image == "" {
|
||||
continue
|
||||
@ -138,21 +139,27 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
|
||||
}
|
||||
|
||||
path := reference.Path(img)
|
||||
if strings.Contains(path, "library") {
|
||||
path = strings.Split(path, "/")[1]
|
||||
}
|
||||
|
||||
path = recipePkg.StripTagMeta(path)
|
||||
|
||||
var tag string
|
||||
switch img.(type) {
|
||||
case reference.NamedTagged:
|
||||
tag = img.(reference.NamedTagged).Tag()
|
||||
case reference.Named:
|
||||
return services, fmt.Errorf("%s service is missing image tag?", path)
|
||||
if service.Name == "app" {
|
||||
missingTag = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
services[path] = tag
|
||||
}
|
||||
|
||||
if missingTag {
|
||||
return services, fmt.Errorf("app service is missing image tag?")
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
@ -232,12 +239,10 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if internal.Publish {
|
||||
msg := fmt.Sprintf("chore: publish %s release", tag)
|
||||
repoPath := path.Join(config.RECIPES_DIR, recipe.Name)
|
||||
if err := gitPkg.Commit(repoPath, "compose.**yml", msg, internal.Dry); err != nil {
|
||||
return err
|
||||
}
|
||||
msg := fmt.Sprintf("chore: publish %s release", tag)
|
||||
repoPath := path.Join(config.RECIPES_DIR, recipe.Name)
|
||||
if err := gitPkg.Commit(repoPath, ".", msg, internal.Dry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -290,13 +295,10 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
|
||||
if err := recipe.Push(internal.Dry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !internal.Dry {
|
||||
url := fmt.Sprintf("%s/%s/src/tag/%s", config.REPOS_BASE_URL, recipe.Name, tagString)
|
||||
logrus.Infof("new release published: %s", url)
|
||||
} else {
|
||||
logrus.Info("dry run: no changes published")
|
||||
}
|
||||
url := fmt.Sprintf("%s/%s/src/tag/%s", config.REPOS_BASE_URL, recipe.Name, tagString)
|
||||
logrus.Infof("new release published: %s", url)
|
||||
} else {
|
||||
logrus.Info("no -p/--publish passed, not publishing")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -53,6 +53,7 @@ You may invoke this command in "wizard" mode and be prompted for input:
|
||||
internal.PatchFlag,
|
||||
internal.MinorFlag,
|
||||
internal.MajorFlag,
|
||||
internal.AllTagsFlag,
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
recipe := internal.ValidateRecipeWithPrompt(c)
|
||||
@ -115,23 +116,26 @@ You may invoke this command in "wizard" mode and be prompted for input:
|
||||
}
|
||||
logrus.Debugf("retrieved %s from remote registry for %s", regVersions, image)
|
||||
|
||||
if strings.Contains(image, "library") {
|
||||
// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>,
|
||||
// postgres:<tag>, i.e. images which do not have a username in the
|
||||
// first position of the string
|
||||
image = strings.Split(image, "/")[1]
|
||||
}
|
||||
semverLikeTag := true
|
||||
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
|
||||
logrus.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag())
|
||||
semverLikeTag = false
|
||||
image = recipePkg.StripTagMeta(image)
|
||||
|
||||
switch img.(type) {
|
||||
case reference.NamedTagged:
|
||||
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
|
||||
logrus.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag())
|
||||
}
|
||||
default:
|
||||
logrus.Warnf("unable to read tag for image %s, is it missing? skipping upgrade for %s", image, service.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag())
|
||||
if err != nil && semverLikeTag {
|
||||
logrus.Fatal(err)
|
||||
if err != nil {
|
||||
logrus.Warnf("unable to parse %s, error was: %s, skipping upgrade for %s", image, err.Error(), service.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Debugf("parsed %s for %s", tag, service.Name)
|
||||
|
||||
var compatible []tagcmp.Tag
|
||||
for _, regVersion := range regVersions {
|
||||
other, err := tagcmp.Parse(regVersion.Name)
|
||||
@ -148,7 +152,7 @@ You may invoke this command in "wizard" mode and be prompted for input:
|
||||
|
||||
sort.Sort(tagcmp.ByTagDesc(compatible))
|
||||
|
||||
if len(compatible) == 0 && semverLikeTag {
|
||||
if len(compatible) == 0 && !internal.AllTags {
|
||||
logrus.Info(fmt.Sprintf("no new versions available for %s, %s is the latest", image, tag))
|
||||
continue // skip on to the next tag and don't update any compose files
|
||||
}
|
||||
@ -188,13 +192,13 @@ You may invoke this command in "wizard" mode and be prompted for input:
|
||||
}
|
||||
}
|
||||
if contains {
|
||||
logrus.Infof("Upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString)
|
||||
logrus.Infof("upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString)
|
||||
} else {
|
||||
logrus.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
logrus.Fatalf("Service %s is at version %s, but pinned to %s, please correct your compose.yml file manually!", service.Name, tag.String(), pinnedTag.String())
|
||||
logrus.Fatalf("service %s is at version %s, but pinned to %s, please correct your compose.yml file manually!", service.Name, tag.String(), pinnedTag.String())
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
@ -211,16 +215,18 @@ You may invoke this command in "wizard" mode and be prompted for input:
|
||||
}
|
||||
}
|
||||
if upgradeTag == "" {
|
||||
logrus.Warnf("not upgrading from %s to %s for %s, because the upgrade type is more serious than what user wants.", tag.String(), compatible[0].String(), image)
|
||||
logrus.Warnf("not upgrading from %s to %s for %s, because the upgrade type is more serious than what user wants", tag.String(), compatible[0].String(), image)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
msg := fmt.Sprintf("upgrade to which tag? (service: %s, image: %s, tag: %s)", service.Name, image, tag)
|
||||
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
|
||||
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) || internal.AllTags {
|
||||
tag := img.(reference.NamedTagged).Tag()
|
||||
logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", tag))
|
||||
if !internal.AllTags {
|
||||
logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", tag))
|
||||
}
|
||||
msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
|
||||
compatibleStrings = []string{}
|
||||
compatibleStrings = []string{"skip"}
|
||||
for _, regVersion := range regVersions {
|
||||
compatibleStrings = append(compatibleStrings, regVersion.Name)
|
||||
}
|
||||
@ -238,10 +244,13 @@ You may invoke this command in "wizard" mode and be prompted for input:
|
||||
}
|
||||
}
|
||||
if upgradeTag != "skip" {
|
||||
if err := recipe.UpdateTag(image, upgradeTag); err != nil {
|
||||
ok, err := recipe.UpdateTag(image, upgradeTag)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
logrus.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image)
|
||||
if ok {
|
||||
logrus.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image)
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("not upgrading %s, skipping as requested", image)
|
||||
}
|
||||
|
@ -1,14 +1,9 @@
|
||||
package recipe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -23,12 +18,6 @@ var recipeVersionCommand = &cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
recipe := internal.ValidateRecipe(c)
|
||||
|
||||
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
|
||||
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
|
||||
if err := gitPkg.Clone(catalogueDir, url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
catalogue, err := recipePkg.ReadRecipeCatalogue()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
|
4
go.mod
4
go.mod
@ -5,7 +5,7 @@ go 1.16
|
||||
require (
|
||||
coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2
|
||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4
|
||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731094149-b031ea1211e7
|
||||
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
|
||||
github.com/docker/cli v20.10.12+incompatible
|
||||
github.com/docker/distribution v2.7.1+incompatible
|
||||
@ -21,7 +21,7 @@ require (
|
||||
github.com/schultz-is/passgen v1.0.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
gotest.tools/v3 v3.0.3
|
||||
gotest.tools/v3 v3.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
8
go.sum
8
go.sum
@ -28,8 +28,8 @@ coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52/go.mod h1:ESVm0wQKcbcFi
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
|
||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4 h1:aYUdiI42a4fWfPoUr25XlaJrFEICv24+o/gWhqYS/jk=
|
||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4/go.mod h1:oZRCMMRS318l07ei4DTqbZoOawfJlJ4yyo8juk2v4Rk=
|
||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731094149-b031ea1211e7 h1:asQtdXYbxEYWcwAQqJTVYC/RltB4eqoWKvqWg/LFPOg=
|
||||
github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731094149-b031ea1211e7/go.mod h1:oZRCMMRS318l07ei4DTqbZoOawfJlJ4yyo8juk2v4Rk=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
@ -1045,6 +1045,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -1160,8 +1161,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
|
||||
gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -16,10 +16,10 @@ import (
|
||||
)
|
||||
|
||||
// UpdateTag updates an image tag in-place on file system local compose files.
|
||||
func UpdateTag(pattern, image, tag, recipeName string) error {
|
||||
func UpdateTag(pattern, image, tag, recipeName string) (bool, error) {
|
||||
composeFiles, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
logrus.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", "))
|
||||
@ -30,12 +30,12 @@ func UpdateTag(pattern, image, tag, recipeName string) error {
|
||||
envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
|
||||
sampleEnv, err := config.ReadEnv(envSamplePath)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
compose, err := loader.LoadComposefile(opts, sampleEnv)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, service := range compose.Services {
|
||||
@ -45,24 +45,26 @@ func UpdateTag(pattern, image, tag, recipeName string) error {
|
||||
|
||||
img, _ := reference.ParseNormalizedNamed(service.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
var composeTag string
|
||||
switch img.(type) {
|
||||
case reference.NamedTagged:
|
||||
composeTag = img.(reference.NamedTagged).Tag()
|
||||
default:
|
||||
// unable to parse, typically image missing tag
|
||||
return false, nil
|
||||
}
|
||||
|
||||
composeImage := reference.Path(img)
|
||||
if strings.Contains(composeImage, "library") {
|
||||
// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>,
|
||||
// postgres:<tag>, i.e. images which do not have a username in the
|
||||
// first position of the string
|
||||
composeImage = strings.Split(composeImage, "/")[1]
|
||||
}
|
||||
composeTag := img.(reference.NamedTagged).Tag()
|
||||
|
||||
logrus.Debugf("parsed %s from %s", composeTag, service.Image)
|
||||
|
||||
if image == composeImage {
|
||||
bytes, err := ioutil.ReadFile(composeFile)
|
||||
if err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
old := fmt.Sprintf("%s:%s", composeImage, composeTag)
|
||||
@ -72,13 +74,13 @@ func UpdateTag(pattern, image, tag, recipeName string) error {
|
||||
logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename)
|
||||
|
||||
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil {
|
||||
return err
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// UpdateLabel updates a label in-place on file system local compose files.
|
||||
|
@ -153,7 +153,7 @@ func LoadAppFiles(servers ...string) (AppFiles, error) {
|
||||
serverDir := path.Join(SERVERS_DIR, server)
|
||||
files, err := getAllFilesInDirectory(serverDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("server %s doesn't exist? Run \"abra server ls\" to check", server)
|
||||
}
|
||||
for _, file := range files {
|
||||
appName := strings.TrimSuffix(file.Name(), ".env")
|
||||
|
@ -163,12 +163,17 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
|
||||
}
|
||||
|
||||
// UpdateTag updates a recipe tag
|
||||
func (r Recipe) UpdateTag(image, tag string) error {
|
||||
func (r Recipe) UpdateTag(image, tag string) (bool, error) {
|
||||
pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, r.Name)
|
||||
if err := compose.UpdateTag(pattern, image, tag, r.Name); err != nil {
|
||||
return err
|
||||
|
||||
image = StripTagMeta(image)
|
||||
|
||||
ok, err := compose.UpdateTag(pattern, image, tag, r.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return nil
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// Tags list the recipe tags
|
||||
@ -693,6 +698,10 @@ func recipeCatalogueFSIsLatest() (bool, error) {
|
||||
func ReadRecipeCatalogue() (RecipeCatalogue, error) {
|
||||
recipes := make(RecipeCatalogue)
|
||||
|
||||
if err := EnsureCatalogue(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recipeFSIsLatest, err := recipeCatalogueFSIsLatest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -973,9 +982,8 @@ func GetRecipeVersions(recipeName, registryUsername, registryPassword string) (R
|
||||
}
|
||||
|
||||
path := reference.Path(img)
|
||||
if strings.Contains(path, "library") {
|
||||
path = strings.Split(path, "/")[1]
|
||||
}
|
||||
|
||||
path = StripTagMeta(path)
|
||||
|
||||
var tag string
|
||||
switch img.(type) {
|
||||
@ -1041,3 +1049,37 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri
|
||||
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
// StripTagMeta strips front-matter image tag data that we don't need for parsing.
|
||||
func StripTagMeta(image string) string {
|
||||
originalImage := image
|
||||
|
||||
if strings.Contains(image, "docker.io") {
|
||||
image = strings.Split(image, "/")[1]
|
||||
}
|
||||
|
||||
if strings.Contains(image, "library") {
|
||||
image = strings.Split(image, "/")[1]
|
||||
}
|
||||
|
||||
if originalImage != image {
|
||||
logrus.Debugf("stripped %s to %s for parsing", originalImage, image)
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
// EnsureCatalogue ensures that the catalogue is cloned locally & present.
|
||||
func EnsureCatalogue() error {
|
||||
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
|
||||
if _, err := os.Stat(catalogueDir); err != nil && os.IsNotExist(err) {
|
||||
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "recipes")
|
||||
if err := gitPkg.Clone(catalogueDir, url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("cloned catalogue repository to %s", catalogueDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -479,24 +479,28 @@ func WaitOnService(ctx context.Context, cl *dockerclient.Client, serviceID, appN
|
||||
|
||||
go io.Copy(ioutil.Discard, pipeReader)
|
||||
|
||||
timeout := 30 * time.Second
|
||||
timeout := 50 * time.Second
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
return err
|
||||
case <-time.After(timeout):
|
||||
return fmt.Errorf(fmt.Sprintf(`
|
||||
%s has still not converged (%s second timeout reached)
|
||||
%s has not converged (%s second timeout reached).
|
||||
|
||||
This does not necessarily mean your deployment has failed, it may just be that
|
||||
the app is taking longer to deploy based on your server resources or network
|
||||
latency. Please run the following the inspect the logs of your deployed app:
|
||||
latency.
|
||||
|
||||
You can track latest deployment status with:
|
||||
|
||||
abra app ps --watch %s
|
||||
|
||||
And inspect the logs with:
|
||||
|
||||
abra app logs %s
|
||||
|
||||
If a service is failing to even start (run "abra app ps %s" to see what
|
||||
services are running) there could be a few things. The follow command will
|
||||
try to smoke those out for you:
|
||||
If a service is failing to even start, try smoke out the error with:
|
||||
|
||||
abra app errors --watch %s
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
ABRA_VERSION="0.3.0-alpha"
|
||||
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
|
||||
RC_VERSION="0.4.0-alpha-rc1"
|
||||
RC_VERSION="0.4.0-alpha-rc5"
|
||||
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
|
||||
|
||||
for arg in "$@"; do
|
||||
@ -58,7 +58,7 @@ function install_abra_release {
|
||||
checksum=$(echo "$checksums" | grep "$FILENAME" - | sed -En 's/([0-9a-f]{64})\s+'"$FILENAME"'.*/\1/p')
|
||||
|
||||
echo "downloading $ABRA_VERSION $PLATFORM binary release for abra..."
|
||||
wget -q --show-progress "$release_url" -O "$HOME/.local/bin/.abra-download"
|
||||
wget -q "$release_url" -O "$HOME/.local/bin/.abra-download"
|
||||
localsum=$(sha256sum $HOME/.local/bin/.abra-download | sed -En 's/([0-9a-f]{64})\s+.*/\1/p')
|
||||
echo "checking if checksums match..."
|
||||
if [[ "$localsum" != "$checksum" ]]; then
|
||||
|
@ -61,9 +61,9 @@ $ABRA autocomplete zsh
|
||||
# ========================================================================
|
||||
# record command
|
||||
# ========================================================================
|
||||
$ABRA record new -p gandi -t A -n e2e -v 192.157.2.21 coopcloud.tech
|
||||
$ABRA record list -p gandi coopcloud.tech | grep -q e2e
|
||||
$ABRA -n record rm -p gandi -t A -n e2e coopcloud.tech
|
||||
$ABRA record new -p gandi -t A -n int-core -v 192.157.2.21 coopcloud.tech
|
||||
$ABRA record list -p gandi coopcloud.tech | grep -q int-core
|
||||
$ABRA -n record rm -p gandi -t A -n int-core coopcloud.tech
|
||||
|
||||
# ========================================================================
|
||||
# catalogue command
|
||||
@ -86,11 +86,11 @@ $ABRA recipe lint gitea
|
||||
# ========================================================================
|
||||
# server command
|
||||
# ========================================================================
|
||||
$ABRA -n server new -p hetzner-cloud --hn e2e
|
||||
$ABRA -n server new -p hetzner-cloud --hn int-core
|
||||
|
||||
$ABRA server ls | grep -q e2e
|
||||
$ABRA server ls | grep -q int-core
|
||||
|
||||
$ABRA -n server rm -s -p hetzner-cloud --hn e2e
|
||||
$ABRA -n server rm -s -p hetzner-cloud --hn int-core
|
||||
|
||||
# ========================================================================
|
||||
# app command
|
||||
|
@ -17,6 +17,8 @@ wire up for testing in an automated way.
|
||||
## deploy, upgrade, rollback
|
||||
|
||||
- `abra app deploy <app>`
|
||||
- `abra app deploy --force <app>`
|
||||
- `abra app deploy --chaos <app>`
|
||||
- `abra app upgrade <app>`
|
||||
- `abra app rollback <app>`
|
||||
|
||||
@ -33,6 +35,10 @@ wire up for testing in an automated way.
|
||||
|
||||
### easy mode
|
||||
|
||||
- `abra app ls -t <recipe>`
|
||||
- `abra app ls -s <server>`
|
||||
- `abra app ls -s <server> -t <recipe>`
|
||||
- `abra app ls -s <server> -t <recipe> -S`
|
||||
- `abra app config <app>`
|
||||
- `abra app check <app>`
|
||||
- `abra app ps <app>`
|
||||
@ -41,6 +47,7 @@ wire up for testing in an automated way.
|
||||
- `abra app run <app>`
|
||||
- `abra app secret ls <app>`
|
||||
- `abra app volume ls <app>`
|
||||
- `abra app new --secrets <recipe>`
|
||||
|
||||
### hard mode
|
||||
|
||||
|
Reference in New Issue
Block a user