0
0
forked from toolshed/abra

Compare commits

..

16 Commits

Author SHA1 Message Date
8cbf118f66 feat: introduce local recipes 2025-11-04 09:18:29 +01:00
cc8703310c chore: update translation files
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-11-04 07:35:14 +00:00
fcd5bd863d chore: make i18n 2025-11-04 08:35:04 +01:00
e6af2da9dd refactor: named note, merge if clause 2025-11-04 08:34:49 +01:00
4b688825e0 feat: create docker context when server folder does exist 2025-11-03 17:29:04 +01:00
b0cf2a1f8e chore: make i18n 2025-11-02 10:44:34 +00:00
6b7020d457 test: env version to .config.env 2025-11-02 10:44:34 +00:00
efdac610bd fix: skip local server on it's own 2025-11-02 10:44:34 +00:00
cd6021f116 fix: expose new version
See toolshed/abra#713
2025-11-02 10:44:34 +00:00
ee8de8ef5c chore: update translation files
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-31 20:38:18 +00:00
e5a653c002 chore: make i18n 2025-10-31 21:38:01 +01:00
2cca04de90 fix(move): does not error when secret already exists on new server
See toolshed/abra#709
2025-10-31 21:37:55 +01:00
f2f79e2df8 chore: update translation files
Updated by "Update PO files to match POT (msgmerge)" add-on in Weblate.

Translation: Co-op Cloud/abra
Translate-URL: https://translate.coopcloud.tech/projects/co-op-cloud/abra/
2025-10-31 20:33:36 +00:00
dd83741a9f chore: i18n 2025-10-31 21:31:49 +01:00
dc2cd85d91 feat!: abra app env pull
`abra app env` -> `abra app env list`.

See toolshed/abra#497
2025-10-31 21:31:43 +01:00
96e59cf196 test: adjust to match new reality [ci skip] 2025-10-31 14:35:57 +01:00
23 changed files with 1161 additions and 347 deletions

View File

@ -152,7 +152,6 @@ checkout as-is. Recipe commit hashes are also supported as values for
log.Fatal(err)
}
appPkg.ExposeAllEnv(stackName, compose, app.Env)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
if internal.Chaos {
@ -171,6 +170,9 @@ checkout as-is. Recipe commit hashes are also supported as values for
}
appPkg.SetVersionLabel(compose, stackName, versionLabel)
newRecipeWithDeployVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, toDeployVersion)
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithDeployVersion)
envVars, err := appPkg.CheckEnv(app)
if err != nil {
log.Fatal(err)

View File

@ -1,28 +1,50 @@
package app
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/app"
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
containerPkg "coopcloud.tech/abra/pkg/container"
contextPkg "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/docker/api/types/filters"
"github.com/spf13/cobra"
)
// translators: `abra app env` aliases. use a comma separated list of aliases with
// no spaces in between
// translators: `abra app env` aliases. use a comma separated list of aliases
// with no spaces in between
var appEnvAliases = i18n.G("e")
var AppEnvCommand = &cobra.Command{
// translators: `app env` command
Use: i18n.G("env <domain> [flags]"),
Aliases: strings.Split(appEnvAliases, ","),
// translators: Short description for `app env` command
Short: i18n.G("Show app .env values"),
Example: i18n.G(" abra app env 1312.net"),
// translators: `abra app env list` aliases. use a comma separated list of
// aliases with no spaces in between
var appEnvListAliases = i18n.G("l,ls")
// translators: `abra app env pull` aliases. use a comma separated list of
// aliases with no spaces in between
var appEnvPullAliases = i18n.G("pl,p")
var AppEnvListCommand = &cobra.Command{
// translators: `app env list` command
Use: i18n.G("list <domain> [flags]"),
Aliases: strings.Split(appEnvListAliases, ","),
// translators: Short description for `app env list` command
Short: i18n.G("List all app environment values"),
Example: i18n.G(" abra app env list 1312.net"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -49,3 +71,274 @@ var AppEnvCommand = &cobra.Command{
fmt.Println(overview)
},
}
var AppEnvPullCommand = &cobra.Command{
// translators: `app pull` command
Use: i18n.G("pull <domain> [flags]"),
Aliases: strings.Split(appEnvPullAliases, ","),
// translators: Short description for `app env pull` command
Short: i18n.G("Pull app environment values from a deployed app"),
Long: i18n.G(`Pull app environment values from a deploymed app.
A convenient command for when you've lost your app environment file or want to
synchronize your local app environment values with what is deployed live.`),
Example: i18n.G(` # pull existing .env file and overwrite local values
abra app env pull 1312.net --force
# pull lost app .env file
abra app env pull my.gitea.net --server 1312.net`),
Args: cobra.MaximumNArgs(2),
ValidArgsFunction: func(
cmd *cobra.Command,
args []string,
toComplete string) ([]string, cobra.ShellCompDirective) {
return autocomplete.AppNameComplete()
},
Run: func(cmd *cobra.Command, args []string) {
appName := args[0]
appEnvPath := path.Join(config.ABRA_DIR, "servers", server, fmt.Sprintf("%s.env", appName))
if _, err := os.Stat(appEnvPath); !os.IsNotExist(err) {
log.Fatal(i18n.G("%s already exists?", appEnvPath))
}
if server == "" {
log.Fatal(i18n.G("unable to determine server of app %s, please pass --server/-s", appName))
}
serverDir := filepath.Join(config.SERVERS_DIR, server)
if _, err := os.Stat(serverDir); os.IsNotExist(err) {
log.Fatal(i18n.G("unknown server %s, run \"abra server add %s\"?", server, server))
}
store := contextPkg.NewDefaultDockerContextStore()
contexts, err := store.Store.List()
if err != nil {
log.Fatal(i18n.G("unable to look up server context for %s: %s", server, err))
}
var contextCreated bool
if server == "default" {
contextCreated = true
}
for _, context := range contexts {
if context.Name == server {
contextCreated = true
}
}
if !contextCreated {
log.Fatal(i18n.G("%s missing context, run \"abra server add %s\"?", server, server))
}
cl, err := client.New(server)
if err != nil {
log.Fatal(err)
}
deployMeta, err := stack.IsDeployed(context.Background(), cl, appPkg.StackName(appName))
if err != nil {
log.Fatal(err)
}
if !deployMeta.IsDeployed {
log.Fatal(i18n.G("%s is not deployed?", appName))
}
filters := filters.NewArgs()
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(appName), "app"))
targetContainer, err := containerPkg.GetContainer(context.Background(), cl, filters, internal.NoInput)
if err != nil {
log.Fatal(i18n.G("unable to retrieve container for %s: %s", appName, err))
}
inspectResult, err := cl.ContainerInspect(context.Background(), targetContainer.ID)
if err != nil {
log.Fatal(i18n.G("unable to inspect container for %s: %s", appName, err))
}
deploymentEnv := make(map[string]string)
for _, envVar := range inspectResult.Config.Env {
split := strings.SplitN(envVar, "=", 2)
if len(split) != 2 {
log.Debug(i18n.G("no value attached to %s", envVar))
continue
}
key, val := split[0], split[1]
deploymentEnv[key] = val
}
log.Debug(i18n.G("pulled env values from %s deployment: %s", appName, deploymentEnv))
var (
recipeEnvVar string
recipeKey string
)
if r, ok := deploymentEnv["TYPE"]; ok {
recipeKey = "TYPE"
recipeEnvVar = r
}
if r, ok := deploymentEnv["RECIPE"]; ok {
recipeKey = "RECIPE"
recipeEnvVar = r
}
if recipeEnvVar == "" {
log.Fatal(i18n.G("unable to determine recipe type from %s, env: %v", appName, inspectResult.Config.Env))
}
var recipeName = recipeEnvVar
if strings.Contains(recipeEnvVar, ":") {
split := strings.Split(recipeEnvVar, ":")
recipeName = split[0]
}
recipe := internal.ValidateRecipe(
[]string{recipeName},
cmd.Name(),
)
version := deployMeta.Version
if deployMeta.IsChaos {
version = deployMeta.ChaosVersion
}
if _, err := recipe.EnsureVersion(version); err != nil {
log.Fatal(err)
}
mergedEnv, err := recipe.SampleEnv()
if err != nil {
log.Fatal(err)
}
log.Debug(i18n.G("retrieved env values from .env.sample of %s: %s", recipe.Name, mergedEnv))
for k, v := range deploymentEnv {
mergedEnv[k] = v
}
if !strings.Contains(recipeEnvVar, ":") {
mergedEnv[recipeKey] = fmt.Sprintf("%s:%s", mergedEnv[recipeKey], version)
}
log.Debug(i18n.G("final merged env values for %s are: %s", appName, mergedEnv))
envSample, err := os.ReadFile(recipe.SampleEnvPath)
if err != nil {
log.Fatal(err)
}
err = os.WriteFile(appEnvPath, envSample, 0o664)
if err != nil {
log.Fatal(i18n.G("unable to write new env %s: %s", appEnvPath, err))
}
read, err := os.ReadFile(appEnvPath)
if err != nil {
log.Fatal(i18n.G("unable to read new env %s: %s", appEnvPath, err))
}
sampleEnv, err := recipe.SampleEnv()
if err != nil {
log.Fatal(err)
}
var composeFileUpdated bool
newContents := string(read)
for key, val := range mergedEnv {
if sampleEnv[key] == val {
continue
}
if key == "COMPOSE_FILE" {
composeFileUpdated = true
continue
}
if m, _ := regexp.MatchString(fmt.Sprintf(`#%s=`, key), newContents); m {
log.Debug(i18n.G("uncommenting %s", key))
re := regexp.MustCompile(fmt.Sprintf(`#%s=`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf("%s=", key))
}
if m, _ := regexp.MatchString(fmt.Sprintf(`# %s=`, key), newContents); m {
log.Debug(i18n.G("uncommenting %s", key))
re := regexp.MustCompile(fmt.Sprintf(`# %s=`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf("%s=", key))
}
if m, _ := regexp.MatchString(fmt.Sprintf(`%s=".*"`, key), newContents); m {
log.Debug(i18n.G(`inserting %s="%s" (double quotes)`, key, val))
re := regexp.MustCompile(fmt.Sprintf(`%s=".*"`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf(`%s="%s"`, key, val))
continue
}
if m, _ := regexp.MatchString(fmt.Sprintf(`%s='.*'`, key), newContents); m {
log.Debug(i18n.G(`inserting %s='%s' (single quotes)`, key, val))
re := regexp.MustCompile(fmt.Sprintf(`%s='.*'`, key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf(`%s='%s'`, key, val))
continue
}
if m, _ := regexp.MatchString(fmt.Sprintf("%s=.*", key), newContents); m {
log.Debug(i18n.G("inserting %s=%s (no quotes)", key, val))
re := regexp.MustCompile(fmt.Sprintf("%s=.*", key))
newContents = re.ReplaceAllString(newContents, fmt.Sprintf("%s=%s", key, val))
}
}
err = os.WriteFile(appEnvPath, []byte(newContents), 0)
if err != nil {
log.Fatal(i18n.G("unable to write new env %s: %s", appEnvPath, err))
}
log.Info(i18n.G("%s successfully created", appEnvPath))
if composeFileUpdated {
log.Warn(i18n.G("manual update required: COMPOSE_FILE=\"%s\"", mergedEnv["COMPOSE_FILE"]))
}
},
}
var AppEnvCommand = &cobra.Command{
// translators: `app env` command group
Use: i18n.G("env [cmd] [args] [flags]"),
Aliases: strings.Split(appEnvAliases, ","),
// translators: Short description for `app env` command group
Short: i18n.G("Manage app environment values"),
}
var (
server string
)
func init() {
AppEnvPullCommand.Flags().BoolVarP(
&internal.Force,
i18n.G("force"),
i18n.G("f"),
false,
i18n.G("perform action without further prompt"),
)
AppEnvPullCommand.Flags().StringVarP(
&server,
i18n.G("server"),
i18n.G("s"),
"",
i18n.G("server associated with deployed app"),
)
AppEnvPullCommand.RegisterFlagCompletionFunc(
i18n.G("server"),
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return autocomplete.ServerNameComplete()
},
)
}

View File

@ -128,6 +128,10 @@ Use "--dry-run/-r" to see which secrets and volumes will be moved.`),
secretName := strings.Join(sname[:len(sname)-1], "_")
data := resources.Secrets[secretName]
if err := client.StoreSecret(newServerClient, s.Spec.Name, data); err != nil {
if strings.Contains(err.Error(), "already exists") {
log.Info(i18n.G("skipping secret (because it already exists) on %s: %s", s.Spec.Name, newServer))
continue
}
log.Fatal(i18n.G("failed to store secret on %s: %s", err, newServer))
}
log.Info(i18n.G("created secret on %s: %s", s.Spec.Name, newServer))

View File

@ -2,6 +2,7 @@ package app
import (
"errors"
"fmt"
"strings"
appPkg "coopcloud.tech/abra/pkg/app"
@ -177,7 +178,9 @@ beforehand. See "abra app backup" for more.`),
log.Fatal(err)
}
appPkg.ExposeAllEnv(stackName, compose, app.Env)
newRecipeWithDowngradeVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, chosenDowngrade)
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithDowngradeVersion)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
if internal.Chaos {

View File

@ -28,6 +28,7 @@ var AppUndeployCommand = &cobra.Command{
Use: i18n.G("undeploy <domain> [flags]"),
// translators: Short description for `app undeploy` command
Aliases: strings.Split(appUndeployAliases, ","),
Short: i18n.G("Undeploy a deployed app"),
Long: i18n.G(`This does not destroy any application data.
However, you should remain vigilant, as your swarm installation will consider

View File

@ -190,7 +190,9 @@ beforehand. See "abra app backup" for more.`),
log.Fatal(err)
}
appPkg.ExposeAllEnv(stackName, compose, app.Env)
newRecipeWithUpgradeVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, chosenUpgrade)
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithUpgradeVersion)
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
if internal.Chaos {

View File

@ -42,8 +42,7 @@ local file system.`),
ValidArgsFunction: func(
cmd *cobra.Command,
args []string,
toComplete string,
) ([]string, cobra.ShellCompDirective) {
toComplete string) ([]string, cobra.ShellCompDirective) {
switch l := len(args); l {
case 0:
return autocomplete.RecipeNameComplete()
@ -132,16 +131,10 @@ likely to change.
log.Fatal(err)
}
latestRelease := "0.0.0+0.0.0"
if len(tags) > 0 {
latestRelease = tags[len(tags)-1]
}
latestRelease := tags[len(tags)-1]
changesTable.Headers(i18n.G("SERVICE"), latestRelease, i18n.G("PROPOSED CHANGES"))
latestRecipeVersion := latestRelease
if len(versions) > 0 {
latestRecipeVersion = versions[len(versions)-1]
}
latestRecipeVersion := versions[len(versions)-1]
allRecipeVersions := catl[recipe.Name].Versions
for _, recipeVersion := range allRecipeVersions {
if serviceVersions, ok := recipeVersion[latestRecipeVersion]; ok {

View File

@ -283,6 +283,11 @@ Config:
app.AppBackupSnapshotsCommand,
)
app.AppEnvCommand.AddCommand(
app.AppEnvListCommand,
app.AppEnvPullCommand,
)
app.AppCommand.AddCommand(
app.AppBackupCommand,
app.AppCheckCommand,

View File

@ -502,7 +502,12 @@ func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv envfile.AppEnv
}
// ExposeAllEnv exposes all env variables to the app container
func ExposeAllEnv(stackName string, compose *composetypes.Config, appEnv envfile.AppEnv) {
func ExposeAllEnv(
stackName string,
compose *composetypes.Config,
appEnv envfile.AppEnv,
toDeployVersion string,
) {
for _, service := range compose.Services {
if service.Name == "app" {
log.Debug(i18n.G("adding env vars to %s service config", stackName))
@ -510,6 +515,11 @@ func ExposeAllEnv(stackName string, compose *composetypes.Config, appEnv envfile
_, exists := service.Environment[k]
if !exists {
value := v
if k == "TYPE" || k == "RECIPE" {
// NOTE(d1): don't use the wrong version from the app env
// since we are deploying a new version
value = toDeployVersion
}
service.Environment[k] = &value
log.Debug(i18n.G("%s: %s: %s", stackName, k, value))
}
@ -624,6 +634,10 @@ func (a App) WipeRecipeVersion() error {
// WriteRecipeVersion writes the recipe version to the app .env file.
func (a App) WriteRecipeVersion(version string, dryRun bool) error {
if a.Recipe.Local {
return nil
}
file, err := os.Open(a.Path)
if err != nil {
return err

View File

@ -44,11 +44,20 @@ func New(serverName string, opts ...Opt) (*client.Client, error) {
ctx, err := GetContext(serverName)
if err != nil {
serverDir := path.Join(config.SERVERS_DIR, serverName)
if _, err := os.Stat(serverDir); err == nil {
return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName))
if _, err := os.Stat(serverDir); err != nil {
return nil, errors.New(i18n.G("server missing, run \"abra server add %s\"?", serverName))
}
return nil, errors.New(i18n.G("unknown server, run \"abra server add %s\"?", serverName))
// NOTE(p4u1): when the docker context does not exist but the server folder
// is there, let's create a new docker context.
if err = CreateContext(serverName); err != nil {
return nil, errors.New(i18n.G("server missing context, context creation failed: %s", err))
}
ctx, err = GetContext(serverName)
if err != nil {
return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName))
}
}
ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx)

View File

@ -282,11 +282,7 @@ func GatherImagesForDeploy(cl *dockerClient.Client, app appPkg.App, compose *com
}
imageBaseName := reference.Path(imageParsed)
namedTag, ok := imageParsed.(reference.NamedTagged)
if !ok {
continue
}
imageTag := namedTag.Tag()
imageTag := imageParsed.(reference.NamedTagged).Tag()
existingImageVersion, ok := newImages[imageBaseName]
if !ok {

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -486,6 +486,10 @@ func LintSecretLengths(recipe recipe.Recipe) (bool, error) {
}
func LintValidTags(recipe recipe.Recipe) (bool, error) {
if recipe.Local {
return true, nil
}
repo, err := git.PlainOpen(recipe.Dir)
if err != nil {
return false, errors.New(i18n.G("unable to open %s: %s", recipe.Dir, err))

View File

@ -28,6 +28,10 @@ type EnsureContext struct {
// Ensure makes sure the recipe exists, is up to date and has the specific
// version checked out.
func (r Recipe) Ensure(ctx EnsureContext) error {
if r.Local {
return nil
}
if err := r.EnsureExists(); err != nil {
return err
}
@ -68,6 +72,10 @@ func (r Recipe) Ensure(ctx EnsureContext) error {
// EnsureExists ensures that the recipe is locally cloned
func (r Recipe) EnsureExists() error {
if r.Local {
return nil
}
if _, err := os.Stat(r.Dir); os.IsNotExist(err) {
if err := gitPkg.Clone(r.Dir, r.GitURL); err != nil {
return err
@ -83,6 +91,10 @@ func (r Recipe) EnsureExists() error {
// IsChaosCommit determines if a version sttring is a chaos commit or not.
func (r Recipe) IsChaosCommit(version string) (bool, error) {
if r.Local {
return false, nil
}
isChaosCommit := false
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
@ -118,6 +130,10 @@ func (r Recipe) IsChaosCommit(version string) (bool, error) {
// EnsureVersion checks whether a specific version exists for a recipe.
func (r Recipe) EnsureVersion(version string) (bool, error) {
if r.Local {
return false, nil
}
isChaosCommit := false
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
@ -182,6 +198,10 @@ func (r Recipe) EnsureVersion(version string) (bool, error) {
// EnsureIsClean makes sure that the recipe repository has no unstaged changes.
func (r Recipe) EnsureIsClean() error {
if r.Local {
return nil
}
isClean, err := gitPkg.IsClean(r.Dir)
if err != nil {
return errors.New(i18n.G("unable to check git clean status in %s: %s", r.Dir, err))
@ -196,6 +216,10 @@ func (r Recipe) EnsureIsClean() error {
// EnsureLatest makes sure the latest commit is checked out for the local recipe repository
func (r Recipe) EnsureLatest() error {
if r.Local {
return nil
}
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
return err
}
@ -231,6 +255,10 @@ func (r Recipe) EnsureLatest() error {
// EnsureUpToDate ensures that the local repo is synced to the remote
func (r Recipe) EnsureUpToDate() error {
if r.Local {
return nil
}
repo, err := git.PlainOpen(r.Dir)
if err != nil {
return errors.New(i18n.G("unable to open %s: %s", r.Dir, err))
@ -282,6 +310,10 @@ func (r Recipe) EnsureUpToDate() error {
// IsDirty checks whether a recipe is dirty or not.
func (r *Recipe) IsDirty() (bool, error) {
if r.Local {
return false, nil
}
isClean, err := gitPkg.IsClean(r.Dir)
if err != nil {
return false, err
@ -292,6 +324,10 @@ func (r *Recipe) IsDirty() (bool, error) {
// ChaosVersion constructs a chaos mode recipe version.
func (r *Recipe) ChaosVersion() (string, error) {
if r.Local {
return "", nil
}
var version string
head, err := r.Head()
@ -315,6 +351,10 @@ func (r *Recipe) ChaosVersion() (string, error) {
// Push pushes the latest changes to a SSH URL remote. You need to have your
// local SSH configuration for git.coopcloud.tech working for this to work
func (r Recipe) Push(dryRun bool) error {
if r.Local {
return nil
}
repo, err := git.PlainOpen(r.Dir)
if err != nil {
return err
@ -333,6 +373,10 @@ func (r Recipe) Push(dryRun bool) error {
// Tags list the recipe tags
func (r Recipe) Tags() ([]string, error) {
if r.Local {
return nil, nil
}
var tags []string
repo, err := git.PlainOpen(r.Dir)
@ -371,6 +415,10 @@ func (r Recipe) Tags() ([]string, error) {
// GetRecipeVersions retrieves all recipe versions.
func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
if r.Local {
return nil, nil, nil
}
var warnMsg []string
versions := RecipeVersions{}

View File

@ -124,38 +124,47 @@ type Features struct {
func Get(name string) Recipe {
version := ""
versionRaw := ""
if strings.Contains(name, ":") {
split := strings.Split(name, ":")
if len(split) > 2 {
log.Fatal(i18n.G("version seems invalid: %s", name))
}
name = split[0]
gitURL := ""
sshURL := ""
dir := ""
local := false
if strings.HasPrefix(name, "./") {
dir = path.Join(config.ABRA_DIR, name)
local = true
} else {
if strings.Contains(name, ":") {
split := strings.Split(name, ":")
if len(split) > 2 {
log.Fatal(i18n.G("version seems invalid: %s", name))
}
name = split[0]
version = split[1]
versionRaw = version
if strings.HasSuffix(version, config.DIRTY_DEFAULT) {
version = strings.Replace(split[1], config.DIRTY_DEFAULT, "", 1)
log.Debug(i18n.G("removed dirty suffix from .env version: %s -> %s", split[1], version))
version = split[1]
versionRaw = version
if strings.HasSuffix(version, config.DIRTY_DEFAULT) {
version = strings.Replace(split[1], config.DIRTY_DEFAULT, "", 1)
log.Debug(i18n.G("removed dirty suffix from .env version: %s -> %s", split[1], version))
}
}
gitURL = fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, name)
sshURL = fmt.Sprintf(config.RECIPES_SSH_URL_TEMPLATE, name)
if strings.Contains(name, "/") {
u, err := url.Parse(name)
if err != nil {
log.Fatal(i18n.G("invalid recipe: %s", err))
}
u.Scheme = "https"
gitURL = u.String() + ".git"
u.Scheme = "ssh"
u.User = url.User("git")
sshURL = u.String() + ".git"
}
dir = path.Join(config.RECIPES_DIR, escapeRecipeName(name))
}
gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, name)
sshURL := fmt.Sprintf(config.RECIPES_SSH_URL_TEMPLATE, name)
if strings.Contains(name, "/") {
u, err := url.Parse(name)
if err != nil {
log.Fatal(i18n.G("invalid recipe: %s", err))
}
u.Scheme = "https"
gitURL = u.String() + ".git"
u.Scheme = "ssh"
u.User = url.User("git")
sshURL = u.String() + ".git"
}
dir := path.Join(config.RECIPES_DIR, escapeRecipeName(name))
r := Recipe{
Name: name,
EnvVersion: version,
@ -163,6 +172,7 @@ func Get(name string) Recipe {
Dir: dir,
GitURL: gitURL,
SSHURL: sshURL,
Local: local,
ComposePath: path.Join(dir, "compose.yml"),
ReadmePath: path.Join(dir, "README.md"),
@ -187,6 +197,7 @@ type Recipe struct {
Dir string
GitURL string
SSHURL string
Local bool
ComposePath string
ReadmePath string

View File

@ -127,3 +127,14 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
}
# bats test_tags=slow
@test "new env version written to container env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "$TEST_RECIIPE:0.1.0+1.20.0"
}

View File

@ -28,17 +28,17 @@ teardown(){
}
@test "validate app argument" {
run $ABRA app env
run $ABRA app env list
assert_failure
run $ABRA app env DOESNTEXIST
run $ABRA app env list DOESNTEXIST
assert_failure
}
@test "show env version" {
latestRelease=$(_latest_release)
run $ABRA app env "$TEST_APP_DOMAIN"
run $ABRA app env list "$TEST_APP_DOMAIN"
assert_success
assert_output --partial "$latestRelease"
}
@ -48,7 +48,7 @@ teardown(){
assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
run $ABRA app env "$TEST_APP_DOMAIN"
run $ABRA app env list "$TEST_APP_DOMAIN"
assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
@ -57,3 +57,44 @@ teardown(){
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
}
@test "app env pull explodes when no deployed app" {
run $ABRA app env pull "$TEST_APP_DOMAIN" -s "$TEST_SERVER"
assert_failure
}
# bats test_tags=slow
@test "app env pull recreates app env when missing" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
assert_success
run rm -rf "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
run $ABRA app env pull "$TEST_APP_DOMAIN" -s "$TEST_SERVER"
assert_success
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
}
# bats test_tags=slow
@test "app env pull recreates correct version" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
assert_success
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run rm -rf "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
run $ABRA app env pull "$TEST_APP_DOMAIN" -s "$TEST_SERVER"
assert_success
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
}

View File

@ -33,10 +33,29 @@ teardown(){
assert_success
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \
--no-input --no-converge-checks --debug
--no-input --no-converge-checks
assert_success
run grep -q "TYPE=abra-test-recipe:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
}
# bats test_tags=slow
@test "new env version written to container env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input
assert_success
run grep -q "TYPE=abra-test-recipe:0.2.0+1.21.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \
--no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "$TEST_RECIIPE:0.1.0+1.20.0"
}

View File

@ -256,7 +256,7 @@ teardown(){
}
# bats test_tags=slow
@test "commit deploy upgrade is possible" {
@test "specific version upgrade after chaos deploy" {
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success
@ -269,17 +269,26 @@ teardown(){
assert_success
assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}"
assert_output --regexp "ENV VERSION.*${tagHash:0:8}"
assert_output --regexp "NEW DEPLOYMENT.*0\.1\.1\+1\.20\.2"
}
@test "chaos commit upgrade is 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'
# bats test_tags=slow
@test "upgrade to latest after chaos deploy" {
latestRelease=$(_latest_release)
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
run $ABRA app upgrade "$TEST_APP_DOMAIN" "$tagHash" --no-input --no-converge-checks
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash"
assert_success
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --chaos
assert_success
assert_output --partial "${tagHash:0:8}"
run $ABRA app upgrade "$TEST_APP_DOMAIN" --no-input --no-converge-checks
assert_success
assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}"
assert_output --regexp "ENV VERSION.*${tagHash:0:8}"
assert_output --partial "${latestRelease}"
}
# bats test_tags=slow

View File

@ -40,3 +40,21 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
}
# bats test_tags=slow
@test "new env version written to container env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
assert_success
run grep -q "TYPE=abra-test-recipe:0.1.0+1.20.0" \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
assert_output --partial "$TEST_RECIIPE:0.2.0+1.21.0"
}

View File

@ -101,6 +101,9 @@ teardown() {
assert_success
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
assert_success
run $ABRA recipe release "$TEST_RECIPE" --no-input --patch
assert_success
assert_output --partial 'no -p/--publish passed, not publishing'
@ -119,6 +122,9 @@ teardown() {
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" commit -m "added some release notes"
assert_success
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
assert_success
run $ABRA recipe release "$TEST_RECIPE" --no-input --minor
assert_success
assert_output --partial 'no -p/--publish passed, not publishing'