Compare commits

...

25 Commits

Author SHA1 Message Date
c6db9ee355 chore: publish 0.4.0-alpha-rc5
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-18 11:39:02 +01:00
7733637767 fix: ensure catalogue cloned for catalogue reliant commands
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-18 11:19:33 +01:00
88f9796aaf fix: let us know if not pushing changes without dry-run (recipe release)
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-18 10:55:07 +01:00
6cdba0f9de fix: commit changes if dry-run not present (recipe release) 2022-01-18 10:54:54 +01:00
199aa5f4e3 fix: read password length from env files
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-17 22:34:32 +01:00
9b26c24a5f docs: drop that, not happening 2022-01-17 22:27:25 +01:00
ca75654769 fix: read correct app file name for secret generation
Stack name is only an internal docker concept now.
2022-01-17 22:17:59 +01:00
fc2d83d203 fix: better error message for missing server 2022-01-17 22:04:11 +01:00
2f4f288a46 feat: -a/--all-tags for listing all tags on recipe upgrade 2022-01-17 21:59:31 +01:00
e98f00d354 chore: go mod tidy 2022-01-17 21:50:25 +01:00
b4c2773b87 chore(deps): update module gotest.tools/v3 to v3.1.0
Some checks failed
renovate/artifacts Artifact file update failure
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2022-01-17 08:01:18 +00:00
3aec5d1d7e fix: ignore new test repo
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-12 16:11:18 +01:00
e0fa1b6995 fix: let users know what was deleted
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-06 11:47:10 +01:00
b69ab0df65 fix: chaos mode fixed for upgrade/rollback
All checks were successful
continuous-integration/drone/push Build is passing
Follows 4b7ec6384c.
2022-01-06 10:32:24 +01:00
69a7d37fb7 chore: release 0.4.0-alpha-rc4
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-06 10:04:43 +01:00
87649cbbd0 docs: more manual test cases [ci skip] 2022-01-05 19:37:41 +01:00
4b7ec6384c fix: fix chaos mode for deployment
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-05 19:21:41 +01:00
b22b63c2ba fix: only output if volumes selected for removal
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-05 19:00:09 +01:00
d9f3a11265 fix: gracefully handle missing tag for syncing
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-05 18:04:46 +01:00
d7cf11b876 fix: further fixes for gracefully handling missing tag
All checks were successful
continuous-integration/drone/push Build is passing
Follows 1b37d2d5f5.
2022-01-05 17:58:15 +01:00
d7e1b2947a fix: skip failed image parse for upgrade and move on 2022-01-05 17:57:11 +01:00
1b37d2d5f5 fix: handle tags without images gracefully
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-05 17:32:58 +01:00
74dfb12fd6 refactor: centralise tag meta stripping 2022-01-05 17:32:33 +01:00
49ccf2d204 fix: also show skip for non semver tags
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-04 22:49:36 +01:00
76adc45431 docs: match typically log message style 2022-01-04 22:49:23 +01:00
22 changed files with 167 additions and 113 deletions

View File

@ -66,9 +66,7 @@ We maintain a fork of [godotenv](https://github.com/Autonomic-Cooperative/godote
1. multi-line env var support 1. multi-line env var support
2. inline comment parsing 2. inline comment parsing
You can upgrade the version here by running `go get github.com/Autonomic-Cooperative/godotenv@<commit>` where `<commit>` is the 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.
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.
#### `docker/client` #### `docker/client`

View File

@ -141,7 +141,9 @@ var appRemoveCommand = &cli.Command{
logrus.Info("no volumes were removed") logrus.Info("no volumes were removed")
} }
} else { } else {
logrus.Info("no volumes to remove") if Volumes {
logrus.Info("no volumes to remove")
}
} }
err = os.Remove(app.Path) err = os.Remove(app.Path)

View File

@ -45,8 +45,10 @@ recipes.
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
stackName := app.StackName() stackName := app.StackName()
if err := recipe.EnsureUpToDate(app.Type); err != nil { if !internal.Chaos {
logrus.Fatal(err) if err := recipe.EnsureUpToDate(app.Type); err != nil {
logrus.Fatal(err)
}
} }
r, err := recipe.Get(app.Type) r, err := recipe.Get(app.Type)

View File

@ -186,20 +186,29 @@ Example:
if err := cl.SecretRemove(c.Context, secretName); err != nil { if err := cl.SecretRemove(c.Context, secretName); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Infof("deleted %s successfully from server", secretName)
if internal.Pass { if internal.Pass {
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil { if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Infof("deleted %s successfully from local pass store", secretName)
} }
} else { } else {
if parsed == secretToRm { if parsed == secretToRm {
if err := cl.SecretRemove(c.Context, secretName); err != nil { if err := cl.SecretRemove(c.Context, secretName); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Infof("deleted %s successfully from server", secretName)
if internal.Pass { if internal.Pass {
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil { if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Infof("deleted %s successfully from local pass store", secretName)
} }
} }
} }

View File

@ -48,8 +48,10 @@ recipes.
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
stackName := app.StackName() stackName := app.StackName()
if err := recipe.EnsureUpToDate(app.Type); err != nil { if !internal.Chaos {
logrus.Fatal(err) if err := recipe.EnsureUpToDate(app.Type); err != nil {
logrus.Fatal(err)
}
} }
r, err := recipe.Get(app.Type) r, err := recipe.Get(app.Type)

View File

@ -1,8 +1,6 @@
package app package app
import ( import (
"strings"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
@ -22,9 +20,8 @@ func getImagePath(image string) (string, error) {
} }
path := reference.Path(img) 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) logrus.Debugf("parsed %s from %s", path, image)

View File

@ -33,6 +33,7 @@ var CatalogueSkipList = map[string]bool{
"auto-mirror": true, "auto-mirror": true,
"backup-bot": true, "backup-bot": true,
"backup-bot-two": true, "backup-bot-two": true,
"beta.coopcloud.tech": true,
"comrade-renovate-bot": true, "comrade-renovate-bot": true,
"coopcloud.tech": true, "coopcloud.tech": true,
"coturn": true, "coturn": true,
@ -93,12 +94,6 @@ keys configured on your account.
internal.ValidateRecipe(c) 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() repos, err := recipe.ReadReposMetadata()
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)

View File

@ -24,8 +24,10 @@ import (
func DeployAction(c *cli.Context) error { func DeployAction(c *cli.Context) error {
app := ValidateApp(c) app := ValidateApp(c)
if err := recipe.EnsureUpToDate(app.Type); err != nil { if !Chaos {
logrus.Fatal(err) if err := recipe.EnsureUpToDate(app.Type); err != nil {
logrus.Fatal(err)
}
} }
r, err := recipe.Get(app.Type) r, err := recipe.Get(app.Type)

View File

@ -438,6 +438,15 @@ var RegistryPasswordFlag = &cli.StringFlag{
Destination: &RegistryUsername, 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 // SSHFailMsg is a hopefully helpful SSH failure message
var SSHFailMsg = ` var SSHFailMsg = `
Woops, Abra is unable to connect to connect to %s. Woops, Abra is unable to connect to connect to %s.

View File

@ -23,7 +23,7 @@ var RecipeName string
// createSecrets creates all secrets for a new app. // createSecrets creates all secrets for a new app.
func createSecrets(sanitisedAppName string) (AppSecrets, error) { 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) appEnv, err := config.ReadEnv(appEnvPath)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -2,9 +2,9 @@ package internal
import ( import (
"fmt" "fmt"
"strings"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -94,9 +94,7 @@ func GetMainAppImage(recipe recipe.Recipe) (string, error) {
} }
path = reference.Path(img) path = reference.Path(img)
if strings.Contains(path, "library") { path = recipePkg.StripTagMeta(path)
path = strings.Split(path, "/")[1]
}
return path, nil return path, nil
} }

View File

@ -2,14 +2,11 @@ package recipe
import ( import (
"fmt" "fmt"
"path"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -32,12 +29,6 @@ var recipeListCommand = &cli.Command{
patternFlag, patternFlag,
}, },
Action: func(c *cli.Context) error { 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() catl, err := recipe.ReadRecipeCatalogue()
if err != nil { if err != nil {
logrus.Fatal(err.Error()) logrus.Fatal(err.Error())

View File

@ -127,6 +127,7 @@ your SSH keys configured on your account.
func getImageVersions(recipe recipe.Recipe) (map[string]string, error) { func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
var services = make(map[string]string) var services = make(map[string]string)
missingTag := false
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
if service.Image == "" { if service.Image == "" {
continue continue
@ -138,21 +139,27 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
} }
path := reference.Path(img) path := reference.Path(img)
if strings.Contains(path, "library") {
path = strings.Split(path, "/")[1] path = recipePkg.StripTagMeta(path)
}
var tag string var tag string
switch img.(type) { switch img.(type) {
case reference.NamedTagged: case reference.NamedTagged:
tag = img.(reference.NamedTagged).Tag() tag = img.(reference.NamedTagged).Tag()
case reference.Named: case reference.Named:
return services, fmt.Errorf("%s service is missing image tag?", path) if service.Name == "app" {
missingTag = true
}
continue
} }
services[path] = tag services[path] = tag
} }
if missingTag {
return services, fmt.Errorf("app service is missing image tag?")
}
return services, nil 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)
msg := fmt.Sprintf("chore: publish %s release", tag) repoPath := path.Join(config.RECIPES_DIR, recipe.Name)
repoPath := path.Join(config.RECIPES_DIR, recipe.Name) if err := gitPkg.Commit(repoPath, ".", msg, internal.Dry); err != nil {
if err := gitPkg.Commit(repoPath, "compose.**yml", msg, internal.Dry); err != nil { return err
return err
}
} }
return nil return nil
@ -290,13 +295,10 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
if err := recipe.Push(internal.Dry); err != nil { if err := recipe.Push(internal.Dry); err != nil {
return err return err
} }
url := fmt.Sprintf("%s/%s/src/tag/%s", config.REPOS_BASE_URL, recipe.Name, tagString)
if !internal.Dry { logrus.Infof("new release published: %s", url)
url := fmt.Sprintf("%s/%s/src/tag/%s", config.REPOS_BASE_URL, recipe.Name, tagString) } else {
logrus.Infof("new release published: %s", url) logrus.Info("no -p/--publish passed, not publishing")
} else {
logrus.Info("dry run: no changes published")
}
} }
return nil return nil

View File

@ -53,6 +53,7 @@ You may invoke this command in "wizard" mode and be prompted for input:
internal.PatchFlag, internal.PatchFlag,
internal.MinorFlag, internal.MinorFlag,
internal.MajorFlag, internal.MajorFlag,
internal.AllTagsFlag,
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipeWithPrompt(c) 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) logrus.Debugf("retrieved %s from remote registry for %s", regVersions, image)
if strings.Contains(image, "library") { image = recipePkg.StripTagMeta(image)
// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>,
// postgres:<tag>, i.e. images which do not have a username in the switch img.(type) {
// first position of the string case reference.NamedTagged:
image = strings.Split(image, "/")[1] if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
} logrus.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag())
semverLikeTag := true }
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) { default:
logrus.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag()) logrus.Warnf("unable to read tag for image %s, is it missing? skipping upgrade for %s", image, service.Name)
semverLikeTag = false continue
} }
tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag()) tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag())
if err != nil && semverLikeTag { if err != nil {
logrus.Fatal(err) 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) logrus.Debugf("parsed %s for %s", tag, service.Name)
var compatible []tagcmp.Tag var compatible []tagcmp.Tag
for _, regVersion := range regVersions { for _, regVersion := range regVersions {
other, err := tagcmp.Parse(regVersion.Name) 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)) 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)) 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 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 { 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 { } else {
logrus.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString) logrus.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString)
continue continue
} }
} else { } 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 continue
} }
} else { } else {
@ -211,16 +215,18 @@ You may invoke this command in "wizard" mode and be prompted for input:
} }
} }
if upgradeTag == "" { 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 continue
} }
} else { } else {
msg := fmt.Sprintf("upgrade to which tag? (service: %s, image: %s, tag: %s)", service.Name, image, tag) 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() 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) msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
compatibleStrings = []string{} compatibleStrings = []string{"skip"}
for _, regVersion := range regVersions { for _, regVersion := range regVersions {
compatibleStrings = append(compatibleStrings, regVersion.Name) 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 upgradeTag != "skip" {
if err := recipe.UpdateTag(image, upgradeTag); err != nil { ok, err := recipe.UpdateTag(image, upgradeTag)
if err != nil {
logrus.Fatal(err) 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 { } else {
logrus.Warnf("not upgrading %s, skipping as requested", image) logrus.Warnf("not upgrading %s, skipping as requested", image)
} }

View File

@ -1,14 +1,9 @@
package recipe package recipe
import ( import (
"fmt"
"path"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
gitPkg "coopcloud.tech/abra/pkg/git"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -23,12 +18,6 @@ var recipeVersionCommand = &cli.Command{
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) 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() catalogue, err := recipePkg.ReadRecipeCatalogue()
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)

4
go.mod
View File

@ -5,7 +5,7 @@ go 1.16
require ( require (
coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52 coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52
github.com/AlecAivazis/survey/v2 v2.3.2 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/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
github.com/docker/cli v20.10.12+incompatible github.com/docker/cli v20.10.12+incompatible
github.com/docker/distribution v2.7.1+incompatible github.com/docker/distribution v2.7.1+incompatible
@ -21,7 +21,7 @@ require (
github.com/schultz-is/passgen v1.0.1 github.com/schultz-is/passgen v1.0.1
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
gotest.tools/v3 v3.0.3 gotest.tools/v3 v3.1.0
) )
require ( require (

8
go.sum
View File

@ -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= 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 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8=
github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg= 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.20210731094149-b031ea1211e7 h1:asQtdXYbxEYWcwAQqJTVYC/RltB4eqoWKvqWg/LFPOg=
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/go.mod h1:oZRCMMRS318l07ei4DTqbZoOawfJlJ4yyo8juk2v4Rk=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 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-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 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-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-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.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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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 h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 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.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.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-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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -16,10 +16,10 @@ import (
) )
// UpdateTag updates an image tag in-place on file system local compose files. // 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) composeFiles, err := filepath.Glob(pattern)
if err != nil { if err != nil {
return err return false, err
} }
logrus.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", ")) 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") envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
sampleEnv, err := config.ReadEnv(envSamplePath) sampleEnv, err := config.ReadEnv(envSamplePath)
if err != nil { if err != nil {
return err return false, err
} }
compose, err := loader.LoadComposefile(opts, sampleEnv) compose, err := loader.LoadComposefile(opts, sampleEnv)
if err != nil { if err != nil {
return err return false, err
} }
for _, service := range compose.Services { for _, service := range compose.Services {
@ -45,24 +45,26 @@ func UpdateTag(pattern, image, tag, recipeName string) error {
img, _ := reference.ParseNormalizedNamed(service.Image) img, _ := reference.ParseNormalizedNamed(service.Image)
if err != nil { 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) 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) logrus.Debugf("parsed %s from %s", composeTag, service.Image)
if image == composeImage { if image == composeImage {
bytes, err := ioutil.ReadFile(composeFile) bytes, err := ioutil.ReadFile(composeFile)
if err != nil { if err != nil {
return err return false, err
} }
old := fmt.Sprintf("%s:%s", composeImage, composeTag) 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) logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename)
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil { 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. // UpdateLabel updates a label in-place on file system local compose files.

View File

@ -153,7 +153,7 @@ func LoadAppFiles(servers ...string) (AppFiles, error) {
serverDir := path.Join(SERVERS_DIR, server) serverDir := path.Join(SERVERS_DIR, server)
files, err := getAllFilesInDirectory(serverDir) files, err := getAllFilesInDirectory(serverDir)
if err != nil { 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 { for _, file := range files {
appName := strings.TrimSuffix(file.Name(), ".env") appName := strings.TrimSuffix(file.Name(), ".env")

View File

@ -163,12 +163,17 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
} }
// UpdateTag updates a recipe tag // 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) 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 // Tags list the recipe tags
@ -693,6 +698,10 @@ func recipeCatalogueFSIsLatest() (bool, error) {
func ReadRecipeCatalogue() (RecipeCatalogue, error) { func ReadRecipeCatalogue() (RecipeCatalogue, error) {
recipes := make(RecipeCatalogue) recipes := make(RecipeCatalogue)
if err := EnsureCatalogue(); err != nil {
return nil, err
}
recipeFSIsLatest, err := recipeCatalogueFSIsLatest() recipeFSIsLatest, err := recipeCatalogueFSIsLatest()
if err != nil { if err != nil {
return nil, err return nil, err
@ -973,9 +982,8 @@ func GetRecipeVersions(recipeName, registryUsername, registryPassword string) (R
} }
path := reference.Path(img) path := reference.Path(img)
if strings.Contains(path, "library") {
path = strings.Split(path, "/")[1] path = StripTagMeta(path)
}
var tag string var tag string
switch img.(type) { switch img.(type) {
@ -1041,3 +1049,37 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri
return versions, nil 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
}

View File

@ -2,7 +2,7 @@
ABRA_VERSION="0.3.0-alpha" ABRA_VERSION="0.3.0-alpha"
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION" ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
RC_VERSION="0.4.0-alpha-rc3" RC_VERSION="0.4.0-alpha-rc5"
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION" RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
for arg in "$@"; do for arg in "$@"; do

View File

@ -17,6 +17,8 @@ wire up for testing in an automated way.
## deploy, upgrade, rollback ## deploy, upgrade, rollback
- `abra app deploy <app>` - `abra app deploy <app>`
- `abra app deploy --force <app>`
- `abra app deploy --chaos <app>`
- `abra app upgrade <app>` - `abra app upgrade <app>`
- `abra app rollback <app>` - `abra app rollback <app>`
@ -45,6 +47,7 @@ wire up for testing in an automated way.
- `abra app run <app>` - `abra app run <app>`
- `abra app secret ls <app>` - `abra app secret ls <app>`
- `abra app volume ls <app>` - `abra app volume ls <app>`
- `abra app new --secrets <recipe>`
### hard mode ### hard mode