WIP: feat: translation support
Some checks failed
continuous-integration/drone/push Build is failing

See #483
This commit is contained in:
2025-08-19 11:22:52 +02:00
parent 5cf6048ecb
commit 6436b17839
105 changed files with 14626 additions and 1601 deletions

View File

@ -1,12 +1,12 @@
package app
import (
"github.com/leonelquinteros/gotext"
"coopcloud.tech/abra/pkg/i18n"
"github.com/spf13/cobra"
)
var AppCommand = &cobra.Command{
Use: "app [cmd] [args] [flags]",
Aliases: []string{"a"},
Short: gotext.Get("Manage apps"),
Use: i18n.G("app [cmd] [args] [flags]"),
Aliases: []string{i18n.G("a")},
Short: i18n.G("Manage apps"),
}

View File

@ -6,14 +6,15 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/spf13/cobra"
)
var AppBackupListCommand = &cobra.Command{
Use: "list <domain> [flags]",
Aliases: []string{"ls"},
Short: "List the contents of a snapshot",
Use: i18n.G("list <domain> [flags]"),
Aliases: []string{i18n.G("ls")},
Short: i18n.G("List the contents of a snapshot"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -40,17 +41,17 @@ var AppBackupListCommand = &cobra.Command{
}
if snapshot != "" {
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
log.Debug(i18n.G("including SNAPSHOT=%s in backupbot exec invocation", snapshot))
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
}
if showAllPaths {
log.Debugf("including SHOW_ALL=%v in backupbot exec invocation", showAllPaths)
log.Debug(i18n.G("including SHOW_ALL=%v in backupbot exec invocation", showAllPaths))
execEnv = append(execEnv, fmt.Sprintf("SHOW_ALL=%v", showAllPaths))
}
if timestamps {
log.Debugf("including TIMESTAMPS=%v in backupbot exec invocation", timestamps)
log.Debug(i18n.G("including TIMESTAMPS=%v in backupbot exec invocation", timestamps))
execEnv = append(execEnv, fmt.Sprintf("TIMESTAMPS=%v", timestamps))
}
@ -61,13 +62,13 @@ var AppBackupListCommand = &cobra.Command{
}
var AppBackupDownloadCommand = &cobra.Command{
Use: "download <domain> [flags]",
Aliases: []string{"d"},
Short: "Download a snapshot",
Long: `Downloads a backup.tar.gz to the current working directory.
Use: i18n.G("download <domain> [flags]"),
Aliases: []string{i18n.G("d")},
Short: i18n.G("Download a snapshot"),
Long: i18n.G(`Downloads a backup.tar.gz to the current working directory.
"--volumes/-v" includes data contained in volumes alongide paths specified in
"backupbot.backup.path" labels.`,
"backupbot.backup.path" labels.`),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -98,22 +99,22 @@ var AppBackupDownloadCommand = &cobra.Command{
}
if snapshot != "" {
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
log.Debug(i18n.G("including SNAPSHOT=%s in backupbot exec invocation", snapshot))
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
}
if includePath != "" {
log.Debugf("including INCLUDE_PATH=%s in backupbot exec invocation", includePath)
log.Debug(i18n.G("including INCLUDE_PATH=%s in backupbot exec invocation", includePath))
execEnv = append(execEnv, fmt.Sprintf("INCLUDE_PATH=%s", includePath))
}
if includeSecrets {
log.Debugf("including SECRETS=%v in backupbot exec invocation", includeSecrets)
log.Debug(i18n.G("including SECRETS=%v in backupbot exec invocation", includeSecrets))
execEnv = append(execEnv, fmt.Sprintf("SECRETS=%v", includeSecrets))
}
if includeVolumes {
log.Debugf("including VOLUMES=%v in backupbot exec invocation", includeVolumes)
log.Debug(i18n.G("including VOLUMES=%v in backupbot exec invocation", includeVolumes))
execEnv = append(execEnv, fmt.Sprintf("VOLUMES=%v", includeVolumes))
}
@ -130,9 +131,9 @@ var AppBackupDownloadCommand = &cobra.Command{
}
var AppBackupCreateCommand = &cobra.Command{
Use: "create <domain> [flags]",
Aliases: []string{"c"},
Short: "Create a new snapshot",
Use: i18n.G("create <domain> [flags]"),
Aliases: []string{i18n.G("c")},
Short: i18n.G("Create a new snapshot"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -163,7 +164,7 @@ var AppBackupCreateCommand = &cobra.Command{
}
if retries != "" {
log.Debugf("including RETRIES=%s in backupbot exec invocation", retries)
log.Debug(i18n.G("including RETRIES=%s in backupbot exec invocation", retries))
execEnv = append(execEnv, fmt.Sprintf("RETRIES=%s", retries))
}
@ -174,9 +175,9 @@ var AppBackupCreateCommand = &cobra.Command{
}
var AppBackupSnapshotsCommand = &cobra.Command{
Use: "snapshots <domain> [flags]",
Aliases: []string{"s"},
Short: "List all snapshots",
Use: i18n.G("snapshots <domain> [flags]"),
Aliases: []string{i18n.G("s")},
Short: i18n.G("List all snapshots"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -209,9 +210,9 @@ var AppBackupSnapshotsCommand = &cobra.Command{
}
var AppBackupCommand = &cobra.Command{
Use: "backup [cmd] [args] [flags]",
Aliases: []string{"b"},
Short: "Manage app backups",
Use: i18n.G("backup [cmd] [args] [flags]"),
Aliases: []string{i18n.G("b")},
Short: i18n.G("Manage app backups"),
}
var (
@ -227,81 +228,81 @@ var (
func init() {
AppBackupListCommand.Flags().StringVarP(
&snapshot,
"snapshot",
"s",
i18n.G("snapshot"),
i18n.G("s"),
"",
"list specific snapshot",
i18n.G("list specific snapshot"),
)
AppBackupListCommand.Flags().BoolVarP(
&showAllPaths,
"all",
"a",
i18n.G("all"),
i18n.G("a"),
false,
"show all paths",
i18n.G("show all paths"),
)
AppBackupListCommand.Flags().BoolVarP(
&timestamps,
"timestamps",
"t",
i18n.G("timestamps"),
i18n.G("t"),
false,
"include timestamps",
i18n.G("include timestamps"),
)
AppBackupDownloadCommand.Flags().StringVarP(
&snapshot,
"snapshot",
"s",
i18n.G("snapshot"),
i18n.G("s"),
"",
"list specific snapshot",
i18n.G("list specific snapshot"),
)
AppBackupDownloadCommand.Flags().StringVarP(
&includePath,
"path",
"p",
i18n.G("path"),
i18n.G("p"),
"",
"volumes path",
i18n.G("volumes path"),
)
AppBackupDownloadCommand.Flags().BoolVarP(
&includeSecrets,
"secrets",
"S",
i18n.G("secrets"),
i18n.G("S"),
false,
"include secrets",
i18n.G("include secrets"),
)
AppBackupDownloadCommand.Flags().BoolVarP(
&includeVolumes,
"volumes",
"v",
i18n.G("volumes"),
i18n.G("v"),
false,
"include volumes",
i18n.G("include volumes"),
)
AppBackupDownloadCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
AppBackupCreateCommand.Flags().StringVarP(
&retries,
"retries",
"r",
i18n.G("retries"),
i18n.G("r"),
"1",
"number of retry attempts",
i18n.G("number of retry attempts"),
)
AppBackupCreateCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
}

View File

@ -7,16 +7,17 @@ import (
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
)
var AppCheckCommand = &cobra.Command{
Use: "check <domain> [flags]",
Aliases: []string{"chk"},
Short: "Ensure an app is well configured",
Long: `Compare env vars in both the app ".env" and recipe ".env.sample" file.
Use: i18n.G("check <domain> [flags]"),
Aliases: []string{i18n.G("chk")},
Short: i18n.G("Ensure an app is well configured"),
Long: i18n.G(`Compare env vars in both the app ".env" and recipe ".env.sample" file.
The goal is to ensure that recipe ".env.sample" env vars are defined in your
app ".env" file. Only env var definitions in the ".env.sample" which are
@ -25,7 +26,7 @@ these env vars, then "check" will complain.
Recipe maintainers may or may not provide defaults for env vars within their
recipes regardless of commenting or not (e.g. through the use of
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -83,9 +84,9 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
func init() {
AppCheckCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
}

View File

@ -13,15 +13,16 @@ import (
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/spf13/cobra"
)
var AppCmdCommand = &cobra.Command{
Use: "command <domain> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]",
Aliases: []string{"cmd"},
Short: "Run app commands",
Long: `Run an app specific command.
Use: i18n.G("command <domain> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]"),
Aliases: []string{i18n.G("cmd")},
Short: i18n.G("Run app commands"),
Long: i18n.G(`Run an app specific command.
These commands are bash functions, defined in the abra.sh of the recipe itself.
They can be run within the context of a service (e.g. app) or locally on your
@ -30,24 +31,24 @@ work station by passing "--local/-l".
N.B. If using the "--" style to pass arguments, flags (e.g. "--local/-l") must
be passed *before* the "--". It is possible to pass arguments without the "--"
as long as no dashes are present (i.e. "foo" works without "--", "-foo"
does not).`,
Example: ` # pass <cmd> args/flags without "--"
does not).`),
Example: i18n.G(` # pass <cmd> args/flags without "--"
abra app cmd 1312.net app my_cmd_arg foo --user bar
# pass <cmd> args/flags with "--"
abra app cmd 1312.net app my_cmd_args --user bar -- foo -vvv
# drop the [service] arg if using "--local/-l"
abra app cmd 1312.net my_cmd --local`,
abra app cmd 1312.net my_cmd --local`),
Args: func(cmd *cobra.Command, args []string) error {
if local {
if !(len(args) >= 2) {
return errors.New("requires at least 2 arguments with --local/-l")
return errors.New(i18n.G("requires at least 2 arguments with --local/-l"))
}
if slices.Contains(os.Args, "--") {
if cmd.ArgsLenAtDash() > 2 {
return errors.New("accepts at most 2 args with --local/-l")
return errors.New(i18n.G("accepts at most 2 args with --local/-l"))
}
}
@ -63,7 +64,7 @@ does not).`,
}
if !(len(args) >= 3) {
return errors.New("requires at least 3 arguments")
return errors.New(i18n.G("requires at least 3 arguments"))
}
return nil
@ -97,14 +98,14 @@ does not).`,
}
if local && remoteUser != "" {
log.Fatal("cannot use --local & --user together")
log.Fatal(i18n.G("cannot use --local & --user together"))
}
hasCmdArgs, parsedCmdArgs := parseCmdArgs(args, local)
if _, err := os.Stat(app.Recipe.AbraShPath); err != nil {
if os.IsNotExist(err) {
log.Fatalf("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name)
log.Fatal(i18n.G("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name))
}
log.Fatal(err)
}
@ -115,7 +116,7 @@ does not).`,
log.Fatal(err)
}
log.Debugf("--local detected, running %s on local work station", cmdName)
log.Debug(i18n.G("--local detected, running %s on local work station", cmdName))
var exportEnv string
for k, v := range app.Env {
@ -124,16 +125,16 @@ does not).`,
var sourceAndExec string
if hasCmdArgs {
log.Debugf("parsed following command arguments: %s", parsedCmdArgs)
log.Debug(i18n.G("parsed following command arguments: %s", parsedCmdArgs))
sourceAndExec = fmt.Sprintf("TARGET=local; APP_NAME=%s; STACK_NAME=%s; %s . %s; %s %s", app.Name, app.StackName(), exportEnv, app.Recipe.AbraShPath, cmdName, parsedCmdArgs)
} else {
log.Debug("did not detect any command arguments")
log.Debug(i18n.G("did not detect any command arguments"))
sourceAndExec = fmt.Sprintf("TARGET=local; APP_NAME=%s; STACK_NAME=%s; %s . %s; %s", app.Name, app.StackName(), exportEnv, app.Recipe.AbraShPath, cmdName)
}
shell := "/bin/bash"
if _, err := os.Stat(shell); errors.Is(err, os.ErrNotExist) {
log.Debugf("%s does not exist locally, use /bin/sh as fallback", shell)
log.Debug(i18n.G("%s does not exist locally, use /bin/sh as fallback", shell))
shell = "/bin/sh"
}
cmd := exec.Command(shell, "-c", sourceAndExec)
@ -164,15 +165,15 @@ does not).`,
}
if !matchingServiceName {
log.Fatalf("no service %s for %s?", targetServiceName, app.Name)
log.Fatal(i18n.G("no service %s for %s?", targetServiceName, app.Name))
}
log.Debugf("running command %s within the context of %s_%s", cmdName, app.StackName(), targetServiceName)
log.Debug(i18n.G("running command %s within the context of %s_%s", cmdName, app.StackName(), targetServiceName))
if hasCmdArgs {
log.Debugf("parsed following command arguments: %s", parsedCmdArgs)
log.Debug(i18n.G("parsed following command arguments: %s", parsedCmdArgs))
} else {
log.Debug("did not detect any command arguments")
log.Debug(i18n.G("did not detect any command arguments"))
}
cl, err := client.New(app.Server)
@ -192,9 +193,9 @@ does not).`,
}
var AppCmdListCommand = &cobra.Command{
Use: "list <domain> [flags]",
Aliases: []string{"ls"},
Short: "List all available commands",
Use: i18n.G("list <domain> [flags]"),
Aliases: []string{i18n.G("ls")},
Short: i18n.G("List all available commands"),
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
app := internal.ValidateApp(args)
@ -244,33 +245,33 @@ var (
func init() {
AppCmdCommand.Flags().BoolVarP(
&local,
"local",
"l",
i18n.G("local"),
i18n.G("l"),
false,
"run command locally",
i18n.G("run command locally"),
)
AppCmdCommand.Flags().StringVarP(
&remoteUser,
"user",
"u",
i18n.G("user"),
i18n.G("u"),
"",
"request remote user",
i18n.G("request remote user"),
)
AppCmdCommand.Flags().BoolVarP(
&disableTTY,
"tty",
"T",
i18n.G("tty"),
i18n.G("T"),
false,
"disable remote TTY",
i18n.G("disable remote TTY"),
)
AppCmdCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
}

View File

@ -6,16 +6,17 @@ import (
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
)
var AppConfigCommand = &cobra.Command{
Use: "config <domain> [flags]",
Aliases: []string{"cfg"},
Short: "Edit app config",
Example: " abra config 1312.net",
Use: i18n.G("config <domain> [flags]"),
Aliases: []string{i18n.G("cfg")},
Short: i18n.G("Edit app config"),
Example: i18n.G(" abra config 1312.net"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -32,13 +33,13 @@ var AppConfigCommand = &cobra.Command{
appName := args[0]
appFile, exists := files[appName]
if !exists {
log.Fatalf("cannot find app with name %s", appName)
log.Fatal(i18n.G("cannot find app with name %s", appName))
}
ed, ok := os.LookupEnv("EDITOR")
if !ok {
edPrompt := &survey.Select{
Message: "which editor do you wish to use?",
Message: i18n.G("which editor do you wish to use?"),
Options: []string{"vi", "vim", "nvim", "nano", "pico", "emacs"},
}
if err := survey.AskOne(edPrompt, &ed); err != nil {

View File

@ -3,7 +3,6 @@ package app
import (
"context"
"errors"
"fmt"
"io"
"os"
"path"
@ -15,6 +14,7 @@ import (
"coopcloud.tech/abra/pkg/client"
containerPkg "coopcloud.tech/abra/pkg/container"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/container"
"github.com/docker/cli/cli/command"
@ -26,14 +26,14 @@ import (
)
var AppCpCommand = &cobra.Command{
Use: "cp <domain> <src> <dst> [flags]",
Aliases: []string{"c"},
Short: "Copy files to/from a deployed app service",
Example: ` # copy myfile.txt to the root of the app service
Use: i18n.G("cp <domain> <src> <dst> [flags]"),
Aliases: []string{i18n.G("c")},
Short: i18n.G("Copy files to/from a deployed app service"),
Example: i18n.G(` # copy myfile.txt to the root of the app service
abra app cp 1312.net myfile.txt app:/
# copy that file back to your current working directory locally
abra app cp 1312.net app:/myfile.txt ./`,
abra app cp 1312.net app:/myfile.txt ./`),
Args: cobra.ExactArgs(3),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -69,7 +69,7 @@ var AppCpCommand = &cobra.Command{
if err != nil {
log.Fatal(err)
}
log.Debugf("retrieved %s as target container on %s", formatter.ShortenID(container.ID), app.Server)
log.Debug(i18n.G("retrieved %s as target container on %s", formatter.ShortenID(container.ID), app.Server))
if toContainer {
err = CopyToContainer(cl, container.ID, srcPath, dstPath)
@ -82,7 +82,7 @@ var AppCpCommand = &cobra.Command{
},
}
var errServiceMissing = errors.New("one of <src>/<dest> arguments must take $SERVICE:$PATH form")
var errServiceMissing = errors.New(i18n.G("one of <src>/<dest> arguments must take $SERVICE:$PATH form"))
// parseSrcAndDst parses src and dest string. One of src or dst must be of the form $SERVICE:$PATH
func parseSrcAndDst(src, dst string) (srcPath string, dstPath string, service string, toContainer bool, err error) {
@ -105,7 +105,7 @@ func parseSrcAndDst(src, dst string) (srcPath string, dstPath string, service st
func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath string) error {
srcStat, err := os.Stat(srcPath)
if err != nil {
return fmt.Errorf("local %s ", err)
return errors.New(i18n.G("local %s ", err))
}
dstStat, err := cl.ContainerStatPath(context.Background(), containerID, dstPath)
@ -114,7 +114,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
if errdefs.IsNotFound(err) {
dstExists = false
} else {
return fmt.Errorf("remote path: %s", err)
return errors.New(i18n.G("remote path: %s", err))
}
}
@ -142,7 +142,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
Detach: false,
Tty: true,
}); err != nil {
return fmt.Errorf("create remote directory: %s", err)
return errors.New(i18n.G("create remote directory: %s", err))
}
case CopyModeFileToFile:
// Remove the file component from the path, since docker can only copy
@ -161,7 +161,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
return err
}
log.Debugf("copy %s from local to %s on container", srcPath, dstPath)
log.Debug(i18n.G("copy %s from local to %s on container", srcPath, dstPath))
copyOpts := containertypes.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false}
if err := cl.CopyToContainer(context.Background(), containerID, dstPath, content, copyOpts); err != nil {
return err
@ -181,7 +181,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
Detach: false,
Tty: true,
}); err != nil {
return fmt.Errorf("create remote directory: %s", err)
return errors.New(i18n.G("create remote directory: %s", err))
}
}
@ -194,9 +194,9 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
srcStat, err := cl.ContainerStatPath(context.Background(), containerID, srcPath)
if err != nil {
if errdefs.IsNotFound(err) {
return fmt.Errorf("remote: %s does not exist", srcPath)
return errors.New(i18n.G("remote: %s does not exist", srcPath))
} else {
return fmt.Errorf("remote path: %s", err)
return errors.New(i18n.G("remote path: %s", err))
}
}
@ -207,7 +207,7 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
if os.IsNotExist(err) {
dstExists = false
} else {
return fmt.Errorf("remote path: %s", err)
return errors.New(i18n.G("remote path: %s", err))
}
} else {
dstMode = dstStat.Mode()
@ -242,7 +242,7 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
content, _, err := cl.CopyFromContainer(context.Background(), containerID, srcPath)
if err != nil {
return fmt.Errorf("copy: %s", err)
return errors.New(i18n.G("copy: %s", err))
}
defer content.Close()
if err := archive.Untar(content, dstPath, &archive.TarOptions{
@ -250,7 +250,7 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
Compression: archive.Gzip,
NoLchown: true,
}); err != nil {
return fmt.Errorf("untar: %s", err)
return errors.New(i18n.G("untar: %s", err))
}
if moveDstFile != "" {
@ -269,8 +269,8 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
}
var (
ErrCopyDirToFile = fmt.Errorf("can't copy dir to file")
ErrDstDirNotExist = fmt.Errorf("destination directory does not exist")
ErrCopyDirToFile = errors.New(i18n.G("can't copy dir to file"))
ErrDstDirNotExist = errors.New(i18n.G("destination directory does not exist"))
)
type CopyMode int
@ -375,9 +375,9 @@ func moveFile(sourcePath, destPath string) error {
func init() {
AppCpCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
}

View File

@ -2,7 +2,7 @@ package app
import (
"context"
"fmt"
"errors"
"strings"
"coopcloud.tech/abra/cli/internal"
@ -16,6 +16,7 @@ import (
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/dns"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack"
@ -24,15 +25,15 @@ import (
)
var AppDeployCommand = &cobra.Command{
Use: "deploy <domain> [version] [flags]",
Aliases: []string{"d"},
Short: "Deploy an app",
Long: `Deploy an app.
Use: i18n.G("deploy <domain> [version] [flags]"),
Aliases: []string{i18n.G("d")},
Short: i18n.G("Deploy an app"),
Long: i18n.G(`Deploy an app.
This command supports chaos operations. Use "--chaos/-C" to deploy your recipe
checkout as-is. Recipe commit hashes are also supported as values for
"[version]". Please note, "upgrade"/"rollback" do not support chaos operations.`,
Example: ` # standard deployment
"[version]". Please note, "upgrade"/"rollback" do not support chaos operations.`),
Example: i18n.G(` # standard deployment
abra app deploy 1312.net
# chaos deployment
@ -42,7 +43,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
abra app deploy 1312.net 2.0.0+1.2.3
# deploy a specific git hash
abra app deploy 1312.net 886db76d`,
abra app deploy 1312.net 886db76d`),
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -55,7 +56,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
case 1:
app, err := appPkg.Get(args[0])
if err != nil {
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
errMsg := i18n.G("autocomplete failed: %s", err)
return []string{errMsg}, cobra.ShellCompDirectiveError
}
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
@ -84,7 +85,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
log.Fatal(err)
}
log.Debugf("checking whether %s is already deployed", app.StackName())
log.Debug(i18n.G("checking whether %s is already deployed", app.StackName()))
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
if err != nil {
@ -92,18 +93,18 @@ checkout as-is. Recipe commit hashes are also supported as values for
}
if deployMeta.IsDeployed && !(internal.Force || internal.Chaos) {
log.Fatalf("%s is already deployed", app.Name)
log.Fatal(i18n.G("%s is already deployed", app.Name))
}
toDeployVersion, err = getDeployVersion(args, deployMeta, app)
if err != nil {
log.Fatal(fmt.Errorf("get deploy version: %s", err))
log.Fatal(i18n.G("get deploy version: %s", err))
}
if !internal.Chaos {
_, err = app.Recipe.EnsureVersion(toDeployVersion)
if err != nil {
log.Fatalf("ensure recipe: %s", err)
log.Fatal(i18n.G("ensure recipe: %s", err))
}
}
@ -162,7 +163,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
for _, envVar := range envVars {
if !envVar.Present {
deployWarnMessages = append(deployWarnMessages,
fmt.Sprintf("%s missing from %s.env", envVar.Name, app.Domain),
i18n.G("%s missing from %s.env", envVar.Name, app.Domain),
)
}
}
@ -173,10 +174,10 @@ checkout as-is. Recipe commit hashes are also supported as values for
log.Fatal(err)
}
} else {
log.Debug("skipping domain checks, no DOMAIN=... configured")
log.Debug(i18n.G("skipping domain checks, no DOMAIN=... configured"))
}
} else {
log.Debug("skipping domain checks")
log.Debug(i18n.G("skipping domain checks"))
}
deployedVersion := config.NO_VERSION_DEFAULT
@ -199,7 +200,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
log.Fatal(err)
}
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
log.Debug(i18n.G("set waiting timeout to %d second(s)", stack.WaitTimeout))
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
if err != nil {
@ -225,14 +226,14 @@ checkout as-is. Recipe commit hashes are also supported as values for
postDeployCmds, ok := app.Env["POST_DEPLOY_CMDS"]
if ok && !internal.DontWaitConverge {
log.Debugf("run the following post-deploy commands: %s", postDeployCmds)
log.Debug(i18n.G("run the following post-deploy commands: %s", postDeployCmds))
if err := internal.PostCmds(cl, app, postDeployCmds); err != nil {
log.Fatalf("attempting to run post deploy commands, saw: %s", err)
log.Fatal(i18n.G("attempting to run post deploy commands, saw: %s", err))
}
}
if err := app.WriteRecipeVersion(toDeployVersion, false); err != nil {
log.Fatalf("writing recipe version failed: %s", err)
log.Fatal(i18n.G("writing recipe version failed: %s", err))
}
},
}
@ -258,7 +259,7 @@ func getLatestVersionOrCommit(app app.App) (string, error) {
// validateArgsAndFlags ensures compatible args/flags.
func validateArgsAndFlags(args []string) error {
if len(args) == 2 && args[1] != "" && internal.Chaos {
return fmt.Errorf("cannot use [version] and --chaos together")
return errors.New(i18n.G("cannot use [version] and --chaos together"))
}
return nil
@ -272,7 +273,7 @@ func validateSecrets(cl *dockerClient.Client, app app.App) error {
for _, secStat := range secStats {
if !secStat.CreatedOnRemote {
return fmt.Errorf("secret not generated: %s", secStat.LocalName)
return errors.New(i18n.G("secret not generated: %s", secStat.LocalName))
}
}
@ -286,33 +287,33 @@ func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app app.App
if err != nil {
return "", err
}
log.Debugf("version: taking chaos version: %s", v)
log.Debug(i18n.G("version: taking chaos version: %s", v))
return v, nil
}
// Check if the deploy version is set with a cli argument
if len(cliArgs) == 2 && cliArgs[1] != "" {
log.Debugf("version: taking version from cli arg: %s", cliArgs[1])
log.Debug(i18n.G("version: taking version from cli arg: %s", cliArgs[1]))
return cliArgs[1], nil
}
// Check if the recipe has a version in the .env file
if app.Recipe.EnvVersion != "" && !internal.IgnoreEnvVersion {
if strings.HasSuffix(app.Recipe.EnvVersionRaw, "+U") {
return "", fmt.Errorf("version: can not redeploy chaos version %s", app.Recipe.EnvVersionRaw)
return "", errors.New(i18n.G("version: can not redeploy chaos version %s", app.Recipe.EnvVersionRaw))
}
log.Debugf("version: taking version from .env file: %s", app.Recipe.EnvVersion)
log.Debug(i18n.G("version: taking version from .env file: %s", app.Recipe.EnvVersion))
return app.Recipe.EnvVersion, nil
}
// Take deployed version
if deployMeta.IsDeployed {
log.Debugf("version: taking deployed version: %s", deployMeta.Version)
log.Debug(i18n.G("version: taking deployed version: %s", deployMeta.Version))
return deployMeta.Version, nil
}
v, err := getLatestVersionOrCommit(app)
log.Debugf("version: taking new recipe version: %s", v)
log.Debug(i18n.G("version: taking new recipe version: %s", v))
if err != nil {
return "", err
}
@ -322,33 +323,33 @@ func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app app.App
func init() {
AppDeployCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
AppDeployCommand.Flags().BoolVarP(
&internal.Force,
"force",
"f",
i18n.G("force"),
i18n.G("f"),
false,
"perform action without further prompt",
i18n.G("perform action without further prompt"),
)
AppDeployCommand.Flags().BoolVarP(
&internal.NoDomainChecks,
"no-domain-checks",
"D",
i18n.G("no-domain-checks"),
i18n.G("D"),
false,
"disable public DNS checks",
i18n.G("disable public DNS checks"),
)
AppDeployCommand.Flags().BoolVarP(
&internal.DontWaitConverge,
"no-converge-checks",
"c",
i18n.G("no-converge-checks"),
i18n.G("c"),
false,
"disable converge logic checks",
i18n.G("disable converge logic checks"),
)
}

View File

@ -7,14 +7,15 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"github.com/spf13/cobra"
)
var AppEnvCommand = &cobra.Command{
Use: "env <domain> [flags]",
Aliases: []string{"e"},
Short: "Show app .env values",
Example: " abra app env 1312.net",
Use: i18n.G("env <domain> [flags]"),
Aliases: []string{i18n.G("e")},
Short: i18n.G("Show app .env values"),
Example: i18n.G(" abra app env 1312.net"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -37,7 +38,7 @@ var AppEnvCommand = &cobra.Command{
rows = append(rows, []string{k, app.Env[k]})
}
overview := formatter.CreateOverview("ENV OVERVIEW", rows)
overview := formatter.CreateOverview(i18n.G("ENV OVERVIEW"), rows)
fmt.Println(overview)
},
}

View File

@ -9,6 +9,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/convert"
composetypes "github.com/docker/cli/cli/compose/types"
@ -19,11 +20,11 @@ import (
)
var AppLabelsCommand = &cobra.Command{
Use: "labels <domain> [flags]",
Aliases: []string{"lb"},
Short: "Show deployment labels",
Long: "Both local recipe and live deployment labels are shown.",
Example: " abra app labels 1312.net",
Use: i18n.G("labels <domain> [flags]"),
Aliases: []string{i18n.G("lb")},
Short: i18n.G("Show deployment labels"),
Long: i18n.G("Both local recipe and live deployment labels are shown."),
Example: i18n.G(" abra app labels 1312.net"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -49,7 +50,7 @@ var AppLabelsCommand = &cobra.Command{
}
rows := [][]string{
{"DEPLOYED LABELS", "---"},
{i18n.G("DEPLOYED LABELS"), "---"},
}
remoteLabelKeys := make([]string, 0, len(remoteLabels))
@ -67,10 +68,10 @@ var AppLabelsCommand = &cobra.Command{
}
if len(remoteLabelKeys) == 0 {
rows = append(rows, []string{"unknown"})
rows = append(rows, []string{i18n.G("unknown")})
}
rows = append(rows, []string{"RECIPE LABELS", "---"})
rows = append(rows, []string{i18n.G("RECIPE LABELS"), "---"})
config, err := app.Recipe.GetComposeConfig(app.Env)
if err != nil {
@ -98,7 +99,7 @@ var AppLabelsCommand = &cobra.Command{
})
}
overview := formatter.CreateOverview("LABELS OVERVIEW", rows)
overview := formatter.CreateOverview(i18n.G("LABELS OVERVIEW"), rows)
fmt.Println(overview)
},
}
@ -131,9 +132,9 @@ func getLabels(cl *dockerClient.Client, stackName string) (map[string]string, er
func init() {
AppLabelsCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
}

View File

@ -11,6 +11,7 @@ import (
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/tagcmp"
"github.com/spf13/cobra"
@ -39,20 +40,20 @@ type serverStatus struct {
}
var AppListCommand = &cobra.Command{
Use: "list [flags]",
Aliases: []string{"ls"},
Short: "List all managed apps",
Long: `Generate a report of all managed apps.
Use: i18n.G("list [flags]"),
Aliases: []string{i18n.G("ls")},
Short: i18n.G("List all managed apps"),
Long: i18n.G(`Generate a report of all managed apps.
Use "--status/-S" flag to query all servers for the live deployment status.`,
Example: ` # list apps of all servers without live status
Use "--status/-S" flag to query all servers for the live deployment status.`),
Example: i18n.G(` # list apps of all servers without live status
abra app ls
# list apps of a specific server with live status
abra app ls -s 1312.net -S
# list apps of all servers which match a specific recipe
abra app ls -r gitea`,
abra app ls -r gitea`),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
appFiles, err := appPkg.LoadAppFiles(listAppServer)
@ -107,11 +108,11 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
totalAppsCount++
if status {
status := "unknown"
version := "unknown"
chaos := "unknown"
chaosVersion := "unknown"
autoUpdate := "unknown"
status := i18n.G("unknown")
version := i18n.G("unknown")
chaos := i18n.G("unknown")
chaosVersion := i18n.G("unknown")
autoUpdate := i18n.G("unknown")
if statusMeta, ok := statuses[app.StackName()]; ok {
if currentVersion, exists := statusMeta["version"]; exists {
if currentVersion != "" {
@ -144,12 +145,12 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
var newUpdates []string
if version != "unknown" && chaos == "false" {
if err := app.Recipe.EnsureExists(); err != nil {
log.Fatalf("unable to clone %s: %s", app.Name, err)
log.Fatal(i18n.G("unable to clone %s: %s", app.Name, err))
}
updates, err := app.Recipe.Tags()
if err != nil {
log.Fatalf("unable to retrieve tags for %s: %s", app.Name, err)
log.Fatal(i18n.G("unable to retrieve tags for %s: %s", app.Name, err))
}
parsedVersion, err := tagcmp.Parse(version)
@ -171,9 +172,9 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
if len(newUpdates) == 0 {
if version == "unknown" {
appStats.Upgrade = "unknown"
appStats.Upgrade = i18n.G("unknown")
} else {
appStats.Upgrade = "latest"
appStats.Upgrade = i18n.G("latest")
stats.LatestCount++
}
} else {
@ -212,14 +213,15 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
serverStat := allStats[app.Server]
headers := []string{"RECIPE", "DOMAIN", "SERVER"}
headers := []string{i18n.G("RECIPE"), i18n.G("DOMAIN"), i18n.G("SERVER")}
if status {
headers = append(headers, []string{
"STATUS",
"CHAOS",
"VERSION",
"UPGRADE",
"AUTOUPDATE"}...,
i18n.G("STATUS"),
i18n.G("CHAOS"),
i18n.G("VERSION"),
i18n.G("UPGRADE"),
i18n.G("AUTOUPDATE"),
}...,
)
}
@ -283,22 +285,22 @@ var (
func init() {
AppListCommand.Flags().BoolVarP(
&status,
"status",
"S",
i18n.G("status"),
i18n.G("S"),
false,
"show app deployment status",
i18n.G("show app deployment status"),
)
AppListCommand.Flags().StringVarP(
&recipeFilter,
"recipe",
"r",
i18n.G("recipe"),
i18n.G("r"),
"",
"show apps of a specific recipe",
i18n.G("show apps of a specific recipe"),
)
AppListCommand.RegisterFlagCompletionFunc(
"recipe",
i18n.G("recipe"),
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return autocomplete.RecipeNameComplete()
},
@ -306,22 +308,22 @@ func init() {
AppListCommand.Flags().BoolVarP(
&internal.MachineReadable,
"machine",
"m",
i18n.G("machine"),
i18n.G("m"),
false,
"print machine-readable output",
i18n.G("print machine-readable output"),
)
AppListCommand.Flags().StringVarP(
&listAppServer,
"server",
"s",
i18n.G("server"),
i18n.G("s"),
"",
"show apps of a specific server",
i18n.G("show apps of a specific server"),
)
AppListCommand.RegisterFlagCompletionFunc(
"server",
i18n.G("server"),
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return autocomplete.ServerNameComplete()
},

View File

@ -2,12 +2,12 @@ package app
import (
"context"
"fmt"
"coopcloud.tech/abra/cli/internal"
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/logs"
"coopcloud.tech/abra/pkg/upstream/stack"
@ -15,9 +15,9 @@ import (
)
var AppLogsCommand = &cobra.Command{
Use: "logs <domain> [service] [flags]",
Aliases: []string{"l"},
Short: "Tail app logs",
Use: i18n.G("logs <domain> [service] [flags]"),
Aliases: []string{i18n.G("l")},
Short: i18n.G("Tail app logs"),
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -29,8 +29,7 @@ var AppLogsCommand = &cobra.Command{
case 1:
app, err := appPkg.Get(args[0])
if err != nil {
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
return []string{errMsg}, cobra.ShellCompDirectiveError
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
}
return autocomplete.ServiceNameComplete(app.Name)
default:
@ -56,7 +55,7 @@ var AppLogsCommand = &cobra.Command{
}
if !deployMeta.IsDeployed {
log.Fatalf("%s is not deployed?", app.Name)
log.Fatal(i18n.G("%s is not deployed?", app.Name))
}
var serviceNames []string
@ -91,17 +90,17 @@ var (
func init() {
AppLogsCommand.Flags().BoolVarP(
&stdErr,
"stderr",
"s",
i18n.G("stderr"),
i18n.G("s"),
false,
"only tail stderr",
i18n.G("only tail stderr"),
)
AppLogsCommand.Flags().StringVarP(
&sinceLogs,
"since",
"S",
i18n.G("since"),
i18n.G("S"),
"",
"tail logs since YYYY-MM-DDTHH:MM:SSZ",
i18n.G("tail logs since YYYY-MM-DDTHH:MM:SSZ"),
)
}

View File

@ -1,6 +1,7 @@
package app
import (
"errors"
"fmt"
"coopcloud.tech/abra/cli/internal"
@ -10,6 +11,7 @@ import (
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/secret"
@ -19,7 +21,7 @@ import (
"github.com/spf13/cobra"
)
var appNewDescription = `Creates a new app from a default recipe.
var appNewDescription = i18n.G(`Creates a new app from a default recipe.
This new app configuration is stored in your $ABRA_DIR directory under the
appropriate server.
@ -39,12 +41,12 @@ store them somewhere safe.
You can use the "--pass/-P" to store these generated passwords locally in a
pass store (see passwordstore.org for more). The pass command must be available
on your $PATH.`
on your $PATH.`)
var AppNewCommand = &cobra.Command{
Use: "new [recipe] [version] [flags]",
Aliases: []string{"n"},
Short: "Create a new app",
Use: i18n.G("new [recipe] [version] [flags]"),
Aliases: []string{i18n.G("n")},
Short: i18n.G("Create a new app"),
Long: appNewDescription,
Args: cobra.RangeArgs(0, 2),
ValidArgsFunction: func(
@ -65,7 +67,7 @@ var AppNewCommand = &cobra.Command{
recipe := internal.ValidateRecipe(args, cmd.Name())
if len(args) == 2 && internal.Chaos {
log.Fatal("cannot use [version] and --chaos together")
log.Fatal(i18n.G("cannot use [version] and --chaos together"))
}
var recipeVersion string
@ -113,7 +115,7 @@ var AppNewCommand = &cobra.Command{
if recipeVersion == "" {
head, err := recipe.Head()
if err != nil {
log.Fatalf("failed to retrieve latest commit for %s: %s", recipe.Name, err)
log.Fatal(i18n.G("failed to retrieve latest commit for %s: %s", recipe.Name, err))
}
recipeVersion = formatter.SmallSHA(head.String())
@ -130,7 +132,7 @@ var AppNewCommand = &cobra.Command{
}
sanitisedAppName := appPkg.SanitiseAppName(appDomain)
log.Debugf("%s sanitised as %s for new app", appDomain, sanitisedAppName)
log.Debug(i18n.G("%s sanitised as %s for new app", appDomain, sanitisedAppName))
if err := appPkg.TemplateAppEnvSample(
recipe,
@ -182,7 +184,7 @@ var AppNewCommand = &cobra.Command{
log.Fatal(err)
}
headers := []string{"NAME", "VALUE"}
headers := []string{i18n.G("NAME"), i18n.G("VALUE")}
secretsTable.Headers(headers...)
for name, val := range appSecrets {
@ -194,7 +196,7 @@ var AppNewCommand = &cobra.Command{
newAppServer = "local"
}
log.Infof("%s created (version: %s)", appDomain, recipeVersion)
log.Info(i18n.G("%s created (version: %s)", appDomain, recipeVersion))
if len(appSecrets) > 0 {
rows := [][]string{}
@ -202,15 +204,15 @@ var AppNewCommand = &cobra.Command{
rows = append(rows, []string{k, v})
}
overview := formatter.CreateOverview("SECRETS OVERVIEW", rows)
overview := formatter.CreateOverview(i18n.G("SECRETS OVERVIEW"), rows)
fmt.Println(overview)
log.Warnf(
log.Warn(i18n.G(
"secrets are %s shown again, please save them %s",
formatter.BoldUnderlineStyle.Render("NOT"),
formatter.BoldUnderlineStyle.Render("NOW"),
)
))
}
app, err := app.Get(appDomain)
@ -219,7 +221,7 @@ var AppNewCommand = &cobra.Command{
}
if err := app.WriteRecipeVersion(recipeVersion, false); err != nil {
log.Fatalf("writing recipe version failed: %s", err)
log.Fatal(i18n.G("writing recipe version failed: %s", err))
}
},
}
@ -231,7 +233,7 @@ type AppSecrets map[string]string
func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secret, sanitisedAppName string) (AppSecrets, error) {
// NOTE(d1): trim to match app.StackName() implementation
if len(sanitisedAppName) > config.MAX_SANITISED_APP_NAME_LENGTH {
log.Debugf("trimming %s to %s to avoid runtime limits", sanitisedAppName, sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH])
log.Debug(i18n.G("trimming %s to %s to avoid runtime limits", sanitisedAppName, sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]))
sanitisedAppName = sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]
}
@ -261,7 +263,7 @@ func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secr
func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
if appDomain == "" && !internal.NoInput {
prompt := &survey.Input{
Message: "Specify app domain",
Message: i18n.G("Specify app domain"),
Default: fmt.Sprintf("%s.%s", recipe.Name, server),
}
if err := survey.AskOne(prompt, &appDomain); err != nil {
@ -270,7 +272,7 @@ func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
}
if appDomain == "" {
return fmt.Errorf("no domain provided")
return fmt.Errorf(i18n.G("no domain provided"))
}
return nil
@ -279,13 +281,13 @@ func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
// promptForSecrets asks if we should generate secrets for a new app.
func promptForSecrets(recipeName string, secretsConfig map[string]secret.Secret) error {
if len(secretsConfig) == 0 {
log.Debugf("%s has no secrets to generate, skipping...", recipeName)
log.Debug(i18n.G("%s has no secrets to generate, skipping...", recipeName))
return nil
}
if !generateSecrets && !internal.NoInput {
prompt := &survey.Confirm{
Message: "Generate app secrets?",
Message: i18n.G("Generate app secrets?"),
}
if err := survey.AskOne(prompt, &generateSecrets); err != nil {
return err
@ -304,13 +306,13 @@ func ensureServerFlag() error {
if len(servers) == 1 {
newAppServer = servers[0]
log.Infof("single server detected, choosing %s automatically", newAppServer)
log.Info(i18n.G("single server detected, choosing %s automatically", newAppServer))
return nil
}
if newAppServer == "" && !internal.NoInput {
prompt := &survey.Select{
Message: "Select app server:",
Message: i18n.G("Select app server:"),
Options: servers,
}
if err := survey.AskOne(prompt, &newAppServer); err != nil {
@ -319,7 +321,7 @@ func ensureServerFlag() error {
}
if newAppServer == "" {
return fmt.Errorf("no server provided")
return errors.New(i18n.G("no server provided"))
}
return nil
@ -335,14 +337,14 @@ var (
func init() {
AppNewCommand.Flags().StringVarP(
&newAppServer,
"server",
"s",
i18n.G("server"),
i18n.G("s"),
"",
"specify server for new app",
i18n.G("specify server for new app"),
)
AppNewCommand.RegisterFlagCompletionFunc(
"server",
i18n.G("server"),
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return autocomplete.ServerNameComplete()
},
@ -350,34 +352,34 @@ func init() {
AppNewCommand.Flags().StringVarP(
&appDomain,
"domain",
"D",
i18n.G("domain"),
i18n.G("D"),
"",
"domain name for app",
i18n.G("domain name for app"),
)
AppNewCommand.Flags().BoolVarP(
&saveInPass,
"pass",
"p",
i18n.G("pass"),
i18n.G("p"),
false,
"store secrets in a local pass store",
i18n.G("store secrets in a local pass store"),
)
AppNewCommand.Flags().BoolVarP(
&generateSecrets,
"secrets",
"S",
i18n.G("secrets"),
i18n.G("S"),
false,
"automatically generate secrets",
i18n.G("automatically generate secrets"),
)
AppNewCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
}

View File

@ -13,6 +13,7 @@ import (
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
abraService "coopcloud.tech/abra/pkg/service"
stack "coopcloud.tech/abra/pkg/upstream/stack"
@ -24,9 +25,9 @@ import (
)
var AppPsCommand = &cobra.Command{
Use: "ps <domain> [flags]",
Aliases: []string{"p"},
Short: "Check app deployment status",
Use: i18n.G("ps <domain> [flags]"),
Aliases: []string{i18n.G("p")},
Short: i18n.G("Check app deployment status"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -52,7 +53,7 @@ var AppPsCommand = &cobra.Command{
}
if !deployMeta.IsDeployed {
log.Fatalf("%s is not deployed?", app.Name)
log.Fatal(i18n.G("%s is not deployed?", app.Name))
}
chaosVersion := config.CHAOS_DEFAULT
@ -115,11 +116,11 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
"version": deployedVersion,
"chaos": chaosVersion,
"service": service.Name,
"image": "unknown",
"created": "unknown",
"status": "unknown",
"state": "unknown",
"ports": "unknown",
"image": i18n.G("unknown"),
"created": i18n.G("unknown"),
"status": i18n.G("unknown"),
"state": i18n.G("unknown"),
"ports": i18n.G("unknown"),
}
} else {
container := containers[0]
@ -161,7 +162,7 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
if internal.MachineReadable {
rendered, err := json.Marshal(allContainerStats)
if err != nil {
log.Fatal("unable to convert to JSON: %s", err)
log.Fatal(i18n.G("unable to convert to JSON: %s", err))
}
fmt.Println(string(rendered))
@ -175,11 +176,11 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
}
headers := []string{
"SERVICE",
"STATUS",
"IMAGE",
"VERSION",
"CHAOS",
i18n.G("SERVICE"),
i18n.G("STATUS"),
i18n.G("IMAGE"),
i18n.G("VERSION"),
i18n.G("CHAOS"),
}
table.
@ -194,17 +195,17 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
func init() {
AppPsCommand.Flags().BoolVarP(
&internal.MachineReadable,
"machine",
"m",
i18n.G("machine"),
i18n.G("m"),
false,
"print machine-readable output",
i18n.G("print machine-readable output"),
)
AppPsCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
}

View File

@ -2,12 +2,12 @@ package app
import (
"context"
"fmt"
"os"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack"
"github.com/AlecAivazis/survey/v2"
@ -16,10 +16,10 @@ import (
)
var AppRemoveCommand = &cobra.Command{
Use: "remove <domain> [flags]",
Aliases: []string{"rm"},
Short: "Remove all app data, locally and remotely",
Long: `Remove everything related to an app which is already undeployed.
Use: i18n.G("remove <domain> [flags]"),
Aliases: []string{i18n.G("rm")},
Short: i18n.G("Remove all app data, locally and remotely"),
Long: i18n.G(`Remove everything related to an app which is already undeployed.
By default, it will prompt for confirmation before proceeding. All secrets,
volumes and the local app env file will be deleted.
@ -34,8 +34,8 @@ Please note, if you delete the local app env file without removing volumes and
secrets first, Abra will *not* be able to help you remove them afterwards.
To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
flag.`,
Example: " abra app remove 1312.net",
flag.`),
Example: i18n.G(" abra app remove 1312.net"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -47,16 +47,16 @@ flag.`,
app := internal.ValidateApp(args)
if !internal.Force && !internal.NoInput {
log.Warnf("ALERTA ALERTA: deleting %s data and config (local/remote)", app.Name)
log.Warn(i18n.G("ALERTA ALERTA: deleting %s data and config (local/remote)", app.Name))
response := false
prompt := &survey.Confirm{Message: "are you sure?"}
prompt := &survey.Confirm{Message: i18n.G("are you sure?")}
if err := survey.AskOne(prompt, &response); err != nil {
log.Fatal(err)
}
if !response {
log.Fatal("aborting as requested")
log.Fatal(i18n.G("aborting as requested"))
}
}
@ -70,7 +70,7 @@ flag.`,
log.Fatal(err)
}
if deployMeta.IsDeployed {
log.Fatalf("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name)
log.Fatal(i18n.G("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name))
}
fs, err := app.Filters(false, false)
@ -86,12 +86,12 @@ flag.`,
if len(configNames) > 0 {
if err := client.RemoveConfigs(cl, context.Background(), configNames, internal.Force); err != nil {
log.Fatalf("removing configs failed: %s", err)
log.Fatal(i18n.G("removing configs failed: %s", err))
}
log.Infof("%d config(s) removed successfully", len(configNames))
log.Info(i18n.G("%d config(s) removed successfully", len(configNames)))
} else {
log.Info("no configs to remove")
log.Info(i18n.G("no configs to remove"))
}
secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: fs})
@ -113,10 +113,10 @@ flag.`,
if err != nil {
log.Fatal(err)
}
log.Info(fmt.Sprintf("secret: %s removed", name))
log.Info(i18n.G("secret: %s removed", name))
}
} else {
log.Info("no secrets to remove")
log.Info(i18n.G("no secrets to remove"))
}
fs, err = app.Filters(false, true)
@ -133,28 +133,28 @@ flag.`,
if len(volumeNames) > 0 {
err := client.RemoveVolumes(cl, context.Background(), volumeNames, internal.Force, 5)
if err != nil {
log.Fatalf("removing volumes failed: %s", err)
log.Fatal(i18n.G("removing volumes failed: %s", err))
}
log.Infof("%d volume(s) removed successfully", len(volumeNames))
log.Info(i18n.G("%d volume(s) removed successfully", len(volumeNames)))
} else {
log.Info("no volumes to remove")
log.Info(i18n.G("no volumes to remove"))
}
if err = os.Remove(app.Path); err != nil {
log.Fatal(err)
}
log.Info(fmt.Sprintf("file: %s removed", app.Path))
log.Info(i18n.G("file: %s removed", app.Path))
},
}
func init() {
AppRemoveCommand.Flags().BoolVarP(
&internal.Force,
"force",
"f",
i18n.G("force"),
i18n.G("f"),
false,
"perform action without further prompt",
i18n.G("perform action without further prompt"),
)
}

View File

@ -8,6 +8,7 @@ import (
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/ui"
upstream "coopcloud.tech/abra/pkg/upstream/service"
@ -17,19 +18,19 @@ import (
)
var AppRestartCommand = &cobra.Command{
Use: "restart <domain> [[service] | --all-services] [flags]",
Aliases: []string{"re"},
Short: "Restart an app",
Long: `This command restarts services within a deployed app.
Use: i18n.G("restart <domain> [[service] | --all-services] [flags]"),
Aliases: []string{i18n.G("re")},
Short: i18n.G("Restart an app"),
Long: i18n.G(`This command restarts services within a deployed app.
Run "abra app ps <domain>" to see a list of service names.
Pass "--all-services/-a" to restart all services.`,
Example: ` # restart a single app service
Pass "--all-services/-a" to restart all services.`),
Example: i18n.G(` # restart a single app service
abra app restart 1312.net app
# restart all app services
abra app restart 1312.net -a`,
abra app restart 1312.net -a`),
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -60,11 +61,11 @@ Pass "--all-services/-a" to restart all services.`,
}
if serviceName == "" && !allServices {
log.Fatal("missing [service]")
log.Fatal(i18n.G("missing [service]"))
}
if serviceName != "" && allServices {
log.Fatal("cannot use [service] and --all-services/-a together")
log.Fatal(i18n.G("cannot use [service] and --all-services/-a together"))
}
var serviceNames []string
@ -89,7 +90,7 @@ Pass "--all-services/-a" to restart all services.`,
}
if !deployMeta.IsDeployed {
log.Fatalf("%s is not deployed?", app.Name)
log.Fatal(i18n.G("%s is not deployed?", app.Name))
}
for _, serviceName := range serviceNames {
@ -104,7 +105,7 @@ Pass "--all-services/-a" to restart all services.`,
log.Fatal(err)
}
log.Debugf("attempting to scale %s to 0", stackServiceName)
log.Debug(i18n.G("attempting to scale %s to 0", stackServiceName))
if err := upstream.RunServiceScale(context.Background(), cl, stackServiceName, 0); err != nil {
log.Fatal(err)
@ -128,8 +129,8 @@ Pass "--all-services/-a" to restart all services.`,
log.Fatal(err)
}
log.Debugf("%s has been scaled to 0", stackServiceName)
log.Debugf("attempting to scale %s to 1", stackServiceName)
log.Debug(i18n.G("%s has been scaled to 0", stackServiceName))
log.Debug(i18n.G("attempting to scale %s to 1", stackServiceName))
if err := upstream.RunServiceScale(context.Background(), cl, stackServiceName, 1); err != nil {
log.Fatal(err)
@ -139,8 +140,8 @@ Pass "--all-services/-a" to restart all services.`,
log.Fatal(err)
}
log.Debugf("%s has been scaled to 1", stackServiceName)
log.Infof("%s service successfully restarted", serviceName)
log.Debug(i18n.G("%s has been scaled to 1", stackServiceName))
log.Info(i18n.G("%s service successfully restarted", serviceName))
}
},
}
@ -150,16 +151,16 @@ var allServices bool
func init() {
AppRestartCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
AppRestartCommand.Flags().BoolVarP(
&allServices,
"all-services",
"a",
i18n.G("all-services"),
i18n.G("a"),
false,
"restart all services",
i18n.G("restart all services"),
)
}

View File

@ -7,17 +7,18 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/spf13/cobra"
)
var AppRestoreCommand = &cobra.Command{
Use: "restore <domain> [flags]",
Aliases: []string{"rs"},
Short: "Restore a snapshot",
Long: `Snapshots are restored while apps are deployed.
Use: i18n.G("restore <domain> [flags]"),
Aliases: []string{i18n.G("rs")},
Short: i18n.G("Restore a snapshot"),
Long: i18n.G(`Snapshots are restored while apps are deployed.
Some restore scenarios may require service / app restarts.`,
Some restore scenarios may require service / app restarts.`),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -48,34 +49,34 @@ Some restore scenarios may require service / app restarts.`,
}
if snapshot != "" {
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
log.Debug(i18n.G("including SNAPSHOT=%s in backupbot exec invocation", snapshot))
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
}
if targetPath != "" {
log.Debugf("including TARGET=%s in backupbot exec invocation", targetPath)
log.Debug(i18n.G("including TARGET=%s in backupbot exec invocation", targetPath))
execEnv = append(execEnv, fmt.Sprintf("TARGET=%s", targetPath))
}
if internal.NoInput {
log.Debugf("including NONINTERACTIVE=%v in backupbot exec invocation", internal.NoInput)
log.Debug(i18n.G("including NONINTERACTIVE=%v in backupbot exec invocation", internal.NoInput))
execEnv = append(execEnv, fmt.Sprintf("NONINTERACTIVE=%v", internal.NoInput))
}
if len(volumes) > 0 {
allVolumes := strings.Join(volumes, ",")
log.Debugf("including VOLUMES=%s in backupbot exec invocation", allVolumes)
log.Debug(i18n.G("including VOLUMES=%s in backupbot exec invocation", allVolumes))
execEnv = append(execEnv, fmt.Sprintf("VOLUMES=%s", allVolumes))
}
if len(services) > 0 {
allServices := strings.Join(services, ",")
log.Debugf("including CONTAINER=%s in backupbot exec invocation", allServices)
log.Debug(i18n.G("including CONTAINER=%s in backupbot exec invocation", allServices))
execEnv = append(execEnv, fmt.Sprintf("CONTAINER=%s", allServices))
}
if hooks {
log.Debugf("including NO_COMMANDS=%v in backupbot exec invocation", false)
log.Debug(i18n.G("including NO_COMMANDS=%v in backupbot exec invocation", false))
execEnv = append(execEnv, fmt.Sprintf("NO_COMMANDS=%v", false))
}
@ -95,41 +96,41 @@ var (
func init() {
AppRestoreCommand.Flags().StringVarP(
&targetPath,
"target",
"t",
i18n.G("target"),
i18n.G("t"),
"/",
"target path",
i18n.G("target path"),
)
AppRestoreCommand.Flags().StringArrayVarP(
&services,
"services",
"s",
i18n.G("services"),
i18n.G("s"),
[]string{},
"restore specific services",
i18n.G("restore specific services"),
)
AppRestoreCommand.Flags().StringArrayVarP(
&volumes,
"volumes",
"v",
i18n.G("volumes"),
i18n.G("v"),
[]string{},
"restore specific volumes",
i18n.G("restore specific volumes"),
)
AppRestoreCommand.Flags().BoolVarP(
&hooks,
"hooks",
"H",
i18n.G("hooks"),
i18n.G("H"),
false,
"enable pre/post-hook command execution",
i18n.G("enable pre/post-hook command execution"),
)
AppRestoreCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
}

View File

@ -1,7 +1,7 @@
package app
import (
"fmt"
"errors"
"coopcloud.tech/abra/pkg/app"
appPkg "coopcloud.tech/abra/pkg/app"
@ -9,6 +9,7 @@ import (
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/lint"
stack "coopcloud.tech/abra/pkg/upstream/stack"
"coopcloud.tech/tagcmp"
@ -21,10 +22,10 @@ import (
)
var AppRollbackCommand = &cobra.Command{
Use: "rollback <domain> [version] [flags]",
Aliases: []string{"rl"},
Short: "Roll an app back to a previous version",
Long: `This command rolls an app back to a previous version.
Use: i18n.G("rollback <domain> [version] [flags]"),
Aliases: []string{i18n.G("rl")},
Short: i18n.G("Roll an app back to a previous version"),
Long: i18n.G(`This command rolls an app back to a previous version.
Unlike "abra app deploy", chaos operations are not supported here. Only recipe
versions are supported values for "[version]".
@ -37,12 +38,12 @@ are available. The live deployment version is the "source of truth" in this
case. The stored .env version is not consulted.
A downgrade can be destructive, please ensure you have a copy of your app data
beforehand. See "abra app backup" for more.`,
Example: ` # standard rollback
beforehand. See "abra app backup" for more.`),
Example: i18n.G(` # standard rollback
abra app rollback 1312.net
# rollback to specific version
abra app rollback 1312.net 2.0.0+1.2.3`,
abra app rollback 1312.net 2.0.0+1.2.3`),
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -54,8 +55,7 @@ beforehand. See "abra app backup" for more.`,
case 1:
app, err := appPkg.Get(args[0])
if err != nil {
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
return []string{errMsg}, cobra.ShellCompDirectiveError
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
}
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
default:
@ -117,7 +117,7 @@ beforehand. See "abra app backup" for more.`,
}
if !downgradeAvailable {
log.Info("no available downgrades")
log.Info(i18n.G("no available downgrades"))
return
}
}
@ -139,10 +139,10 @@ beforehand. See "abra app backup" for more.`,
}
if chosenDowngrade == "" {
log.Fatal("unknown deployed version, unable to downgrade")
log.Fatal(i18n.G("unknown deployed version, unable to downgrade"))
}
log.Debugf("choosing %s as version to rollback", chosenDowngrade)
log.Debug(i18n.G("choosing %s as version to rollback", chosenDowngrade))
if _, err := app.Recipe.EnsureVersion(chosenDowngrade); err != nil {
log.Fatal(err)
@ -199,7 +199,7 @@ beforehand. See "abra app backup" for more.`,
log.Fatal(err)
}
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
log.Debug(i18n.G("set waiting timeout to %d second(s)", stack.WaitTimeout))
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
if err != nil {
@ -224,7 +224,7 @@ beforehand. See "abra app backup" for more.`,
}
if err := app.WriteRecipeVersion(chosenDowngrade, false); err != nil {
log.Fatalf("writing recipe version failed: %s", err)
log.Fatal(i18n.G("writing recipe version failed: %s", err))
}
},
}
@ -235,12 +235,12 @@ func chooseDowngrade(
deployMeta stack.DeployMeta,
chosenDowngrade *string,
) error {
msg := fmt.Sprintf("please select a downgrade (version: %s):", deployMeta.Version)
msg := i18n.G("please select a downgrade (version: %s):", deployMeta.Version)
if deployMeta.IsChaos {
chaosVersion := formatter.BoldDirtyDefault(deployMeta.ChaosVersion)
msg = fmt.Sprintf(
msg = i18n.G(
"please select a downgrade (version: %s, chaos: %s):",
deployMeta.Version,
chaosVersion,
@ -267,21 +267,21 @@ func validateDowngradeVersionArg(
) error {
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
if err != nil {
return fmt.Errorf("current deployment '%s' is not a known version for %s", deployMeta.Version, app.Recipe.Name)
return errors.New(i18n.G("current deployment '%s' is not a known version for %s", deployMeta.Version, app.Recipe.Name))
}
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
if err != nil {
return fmt.Errorf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
return errors.New(i18n.G("'%s' is not a known version for %s", specificVersion, app.Recipe.Name))
}
if parsedSpecificVersion.IsGreaterThan(parsedDeployedVersion) &&
!parsedSpecificVersion.Equals(parsedDeployedVersion) {
return fmt.Errorf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
return errors.New(i18n.G("%s is not a downgrade for %s?", deployMeta.Version, specificVersion))
}
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
return fmt.Errorf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
return errors.New(i18n.G("%s is not a downgrade for %s?", deployMeta.Version, specificVersion))
}
return nil
@ -320,24 +320,25 @@ func ensureDowngradesAvailable(
func init() {
AppRollbackCommand.Flags().BoolVarP(
&internal.Force,
"force",
"f",
i18n.G("force"),
i18n.G("f"),
false,
"perform action without further prompt",
i18n.G("perform action without further prompt"),
)
AppRollbackCommand.Flags().BoolVarP(
&internal.NoDomainChecks,
"no-domain-checks",
"D",
i18n.G("no-domain-checks"),
i18n.G("D"),
false,
"disable public DNS checks",
i18n.G("disable public DNS checks"),
)
AppRollbackCommand.Flags().BoolVarP(
&internal.DontWaitConverge, "no-converge-checks",
"c",
&internal.DontWaitConverge,
i18n.G("no-converge-checks"),
i18n.G("c"),
false,
"disable converge logic checks",
i18n.G("disable converge logic checks"),
)
}

View File

@ -8,6 +8,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
containerPkg "coopcloud.tech/abra/pkg/container"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/container"
"github.com/docker/cli/cli/command"
@ -17,17 +18,17 @@ import (
)
var AppRunCommand = &cobra.Command{
Use: "run <domain> <service> <cmd> [[args] [flags] | [flags] -- [args]]",
Aliases: []string{"r"},
Short: "Run a command inside a service container",
Example: ` # run <cmd> with args/flags
Use: i18n.G("run <domain> <service> <cmd> [[args] [flags] | [flags] -- [args]]"),
Aliases: []string{i18n.G("r")},
Short: i18n.G("Run a command inside a service container"),
Example: i18n.G(` # run <cmd> with args/flags
abra app run 1312.net app -- ls -lha
# run <cmd> without args/flags
abra app run 1312.net app bash --user nobody
# run <cmd> with both kinds of args/flags
abra app run 1312.net app --user nobody -- ls -lha`,
abra app run 1312.net app --user nobody -- ls -lha`),
Args: cobra.MinimumNArgs(3),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -98,17 +99,17 @@ var (
func init() {
AppRunCommand.Flags().BoolVarP(&noTTY,
"no-tty",
"t",
i18n.G("no-tty"),
i18n.G("t"),
false,
"do not request a TTY",
i18n.G("do not request a TTY"),
)
AppRunCommand.Flags().StringVarP(
&runAsUser,
"user",
"u",
i18n.G("user"),
i18n.G("u"),
"",
"run command as user",
i18n.G("run command as user"),
)
}

View File

@ -12,6 +12,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/secret"
"github.com/docker/docker/api/types"
@ -20,9 +21,9 @@ import (
)
var AppSecretGenerateCommand = &cobra.Command{
Use: "generate <domain> [[secret] [version] | --all] [flags]",
Aliases: []string{"g"},
Short: "Generate secrets",
Use: i18n.G("generate <domain> [[secret] [version] | --all] [flags]"),
Aliases: []string{i18n.G("g")},
Short: i18n.G("Generate secrets"),
Args: cobra.RangeArgs(1, 3),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -34,8 +35,7 @@ var AppSecretGenerateCommand = &cobra.Command{
case 1:
app, err := appPkg.Get(args[0])
if err != nil {
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
return []string{errMsg}, cobra.ShellCompDirectiveError
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
}
return autocomplete.SecretComplete(app.Recipe.Name)
default:
@ -50,11 +50,11 @@ var AppSecretGenerateCommand = &cobra.Command{
}
if len(args) <= 2 && !generateAllSecrets {
log.Fatal("missing arguments [secret]/[version] or '--all'")
log.Fatal(i18n.G("missing arguments [secret]/[version] or '--all'"))
}
if len(args) > 2 && generateAllSecrets {
log.Fatal("cannot use '[secret] [version]' and '--all' together")
log.Fatal(i18n.G("cannot use '[secret] [version]' and '--all' together"))
}
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
@ -72,7 +72,7 @@ var AppSecretGenerateCommand = &cobra.Command{
secretVersion := args[2]
s, ok := secrets[secretName]
if !ok {
log.Fatalf("%s doesn't exist in the env config?", secretName)
log.Fatal(i18n.G("%s doesn't exist in the env config?", secretName))
}
s.Version = secretVersion
secrets = map[string]secret.Secret{
@ -99,11 +99,11 @@ var AppSecretGenerateCommand = &cobra.Command{
}
if len(secretVals) == 0 {
log.Warn("no secrets generated")
log.Warn(i18n.G("no secrets generated"))
os.Exit(1)
}
headers := []string{"NAME", "VALUE"}
headers := []string{i18n.G("NAME"), i18n.G("VALUE")}
table, err := formatter.CreateTable()
if err != nil {
log.Fatal(err)
@ -121,7 +121,7 @@ var AppSecretGenerateCommand = &cobra.Command{
if internal.MachineReadable {
out, err := formatter.ToJSON(headers, rows)
if err != nil {
log.Fatal("unable to render to JSON: %s", err)
log.Fatal(i18n.G("unable to render to JSON: %s", err))
}
fmt.Println(out)
return
@ -131,31 +131,31 @@ var AppSecretGenerateCommand = &cobra.Command{
log.Fatal(err)
}
log.Warnf(
log.Warn(i18n.G(
"generated secrets %s shown again, please take note of them %s",
formatter.BoldStyle.Render("NOT"),
formatter.BoldStyle.Render("NOW"),
)
formatter.BoldStyle.Render(i18n.G("NOT")),
formatter.BoldStyle.Render(i18n.G("NOW")),
))
},
}
var AppSecretInsertCommand = &cobra.Command{
Use: "insert <domain> <secret> <version> <data> [flags]",
Aliases: []string{"i"},
Short: "Insert secret",
Long: `This command inserts a secret into an app environment.
Use: i18n.G("insert <domain> <secret> <version> <data> [flags]"),
Aliases: []string{i18n.G("i")},
Short: i18n.G("Insert secret"),
Long: i18n.G(`This command inserts a secret into an app environment.
Arbitrary secret insertion is not supported. Secrets that are inserted must
match those configured in the recipe beforehand.
This can be useful when you want to manually generate secrets for an app
environment. Typically, you can let Abra generate them for you on app creation
(see "abra app new --secrets/-S" for more).`,
Example: ` # insert regular secret
(see "abra app new --secrets/-S" for more).`),
Example: i18n.G(` # insert regular secret
abra app secret insert 1312.net my_secret v1 mySuperSecret
# insert secret as file
abra app secret insert 1312.net my_secret v1 secret.txt -f`,
abra app secret insert 1312.net my_secret v1 secret.txt -f`),
Args: cobra.MinimumNArgs(4),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -167,8 +167,7 @@ environment. Typically, you can let Abra generate them for you on app creation
case 1:
app, err := appPkg.Get(args[0])
if err != nil {
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
return []string{errMsg}, cobra.ShellCompDirectiveError
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
}
return autocomplete.SecretComplete(app.Recipe.Name)
default:
@ -208,13 +207,13 @@ environment. Typically, you can let Abra generate them for you on app creation
}
}
if !isRecipeSecret {
log.Fatalf("no secret %s available for recipe %s?", name, app.Recipe.Name)
log.Fatal(i18n.G("no secret %s available for recipe %s?", name, app.Recipe.Name))
}
if insertFromFile {
raw, err := os.ReadFile(data)
if err != nil {
log.Fatalf("reading secret from file: %s", err)
log.Fatal(i18n.G("reading secret from file: %s", err))
}
data = string(raw)
}
@ -228,7 +227,7 @@ environment. Typically, you can let Abra generate them for you on app creation
log.Fatal(err)
}
log.Infof("%s successfully stored on server", secretName)
log.Info(i18n.G("%s successfully stored on server", secretName))
if storeInPass {
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
@ -244,28 +243,28 @@ func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string
return err
}
log.Infof("deleted %s successfully from server", secretName)
log.Info(i18n.G("deleted %s successfully from server", secretName))
if removeFromPass {
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
return err
}
log.Infof("deleted %s successfully from local pass store", secretName)
log.Info(i18n.G("deleted %s successfully from local pass store", secretName))
}
return nil
}
var AppSecretRmCommand = &cobra.Command{
Use: "remove <domain> [[secret] | --all] [flags]",
Aliases: []string{"rm"},
Short: "Remove a secret",
Long: `This command removes a secret from an app environment.
Use: i18n.G("remove <domain> [[secret] | --all] [flags]"),
Aliases: []string{i18n.G("rm")},
Short: i18n.G("Remove a secret"),
Long: i18n.G(`This command removes a secret from an app environment.
Arbitrary secret removal is not supported. Secrets that are removed must
match those configured in the recipe beforehand.`,
Example: " abra app secret rm 1312.net oauth_key",
match those configured in the recipe beforehand.`),
Example: i18n.G(" abra app secret rm 1312.net oauth_key"),
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -278,8 +277,7 @@ match those configured in the recipe beforehand.`,
if !rmAllSecrets {
app, err := appPkg.Get(args[0])
if err != nil {
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
return []string{errMsg}, cobra.ShellCompDirectiveError
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
}
return autocomplete.SecretComplete(app.Recipe.Name)
}
@ -306,11 +304,11 @@ match those configured in the recipe beforehand.`,
}
if len(args) == 2 && rmAllSecrets {
log.Fatal("cannot use [secret] and --all/-a together")
log.Fatal(i18n.G("cannot use [secret] and --all/-a together"))
}
if len(args) != 2 && !rmAllSecrets {
log.Fatal("no secret(s) specified?")
log.Fatal(i18n.G("no secret(s) specified?"))
}
cl, err := client.New(app.Server)
@ -361,19 +359,19 @@ match those configured in the recipe beforehand.`,
}
if !match && secretToRm != "" {
log.Fatalf("%s doesn't exist on server?", secretToRm)
log.Fatal(i18n.G("%s doesn't exist on server?", secretToRm))
}
if !match {
log.Fatal("no secrets to remove?")
log.Fatal(i18n.G("no secrets to remove?"))
}
},
}
var AppSecretLsCommand = &cobra.Command{
Use: "list <domain>",
Aliases: []string{"ls"},
Short: "List all secrets",
Use: i18n.G("list <domain>"),
Aliases: []string{i18n.G("ls")},
Short: i18n.G("List all secrets"),
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -393,7 +391,7 @@ var AppSecretLsCommand = &cobra.Command{
log.Fatal(err)
}
headers := []string{"NAME", "VERSION", "GENERATED NAME", "CREATED ON SERVER"}
headers := []string{i18n.G("NAME"), i18n.G("VERSION"), i18n.G("GENERATED NAME"), i18n.G("CREATED ON SERVER")}
table, err := formatter.CreateTable()
if err != nil {
log.Fatal(err)
@ -423,7 +421,7 @@ var AppSecretLsCommand = &cobra.Command{
if internal.MachineReadable {
out, err := formatter.ToJSON(headers, rows)
if err != nil {
log.Fatal("unable to render to JSON: %s", err)
log.Fatal(i18n.G("unable to render to JSON: %s", err))
}
fmt.Println(out)
return
@ -436,14 +434,14 @@ var AppSecretLsCommand = &cobra.Command{
return
}
log.Warnf("no secrets stored for %s", app.Name)
log.Warn(i18n.G("no secrets stored for %s", app.Name))
},
}
var AppSecretCommand = &cobra.Command{
Use: "secret [cmd] [args] [flags]",
Aliases: []string{"s"},
Short: "Manage app secrets",
Use: i18n.G("secret [cmd] [args] [flags]"),
Aliases: []string{i18n.G("s")},
Short: i18n.G("Manage app secrets"),
}
var (
@ -458,105 +456,105 @@ var (
func init() {
AppSecretGenerateCommand.Flags().BoolVarP(
&internal.MachineReadable,
"machine",
"m",
i18n.G("machine"),
i18n.G("m"),
false,
"print machine-readable output",
i18n.G("print machine-readable output"),
)
AppSecretGenerateCommand.Flags().BoolVarP(
&storeInPass,
"pass",
"p",
i18n.G("pass"),
i18n.G("p"),
false,
"store generated secrets in a local pass store",
i18n.G("store generated secrets in a local pass store"),
)
AppSecretGenerateCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
AppSecretGenerateCommand.Flags().BoolVarP(
&generateAllSecrets,
"all",
"a",
i18n.G("all"),
i18n.G("a"),
false,
"generate all secrets",
i18n.G("generate all secrets"),
)
AppSecretInsertCommand.Flags().BoolVarP(
&storeInPass,
"pass",
"p",
i18n.G("pass"),
i18n.G("p"),
false,
"store generated secrets in a local pass store",
i18n.G("store generated secrets in a local pass store"),
)
AppSecretInsertCommand.Flags().BoolVarP(
&insertFromFile,
"file",
"f",
i18n.G("file"),
i18n.G("f"),
false,
"treat input as a file",
i18n.G("treat input as a file"),
)
AppSecretInsertCommand.Flags().BoolVarP(
&trimInput,
"trim",
"t",
i18n.G("trim"),
i18n.G("t"),
false,
"trim input",
i18n.G("trim input"),
)
AppSecretInsertCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
AppSecretRmCommand.Flags().BoolVarP(
&rmAllSecrets,
"all",
"a",
i18n.G("all"),
i18n.G("a"),
false,
"remove all secrets",
i18n.G("remove all secrets"),
)
AppSecretRmCommand.Flags().BoolVarP(
&removeFromPass,
"pass",
"p",
i18n.G("pass"),
i18n.G("p"),
false,
"remove generated secrets from a local pass store",
i18n.G("remove generated secrets from a local pass store"),
)
AppSecretRmCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
AppSecretLsCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
AppSecretLsCommand.Flags().BoolVarP(
&internal.MachineReadable,
"machine",
"m",
i18n.G("machine"),
i18n.G("m"),
false,
"print machine-readable output",
i18n.G("print machine-readable output"),
)
}

View File

@ -9,6 +9,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/service"
stack "coopcloud.tech/abra/pkg/upstream/stack"
@ -17,9 +18,9 @@ import (
)
var AppServicesCommand = &cobra.Command{
Use: "services <domain> [flags]",
Aliases: []string{"sr"},
Short: "Display all services of an app",
Use: i18n.G("services <domain> [flags]"),
Aliases: []string{i18n.G("sr")},
Short: i18n.G("Display all services of an app"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -45,7 +46,7 @@ var AppServicesCommand = &cobra.Command{
}
if !deployMeta.IsDeployed {
log.Fatalf("%s is not deployed?", app.Name)
log.Fatal(i18n.G("%s is not deployed?", app.Name))
}
filters, err := app.Filters(true, true)
@ -63,7 +64,7 @@ var AppServicesCommand = &cobra.Command{
log.Fatal(err)
}
headers := []string{"SERVICE (SHORT)", "SERVICE (LONG)"}
headers := []string{i18n.G("SERVICE (SHORT)"), i18n.G("SERVICE (LONG)")}
table.Headers(headers...)
var rows [][]string

View File

@ -10,6 +10,7 @@ import (
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/docker/api/types/filters"
@ -18,15 +19,15 @@ import (
)
var AppUndeployCommand = &cobra.Command{
Use: "undeploy <domain> [flags]",
Aliases: []string{"un"},
Short: "Undeploy an app",
Long: `This does not destroy any application data.
Use: i18n.G("undeploy <domain> [flags]"),
Aliases: []string{i18n.G("un")},
Short: i18n.G("Undeploy an app"),
Long: i18n.G(`This does not destroy any application data.
However, you should remain vigilant, as your swarm installation will consider
any previously attached volumes as eligible for pruning once undeployed.
Passing "--prune/-p" does not remove those volumes.`,
Passing "--prune/-p" does not remove those volumes.`),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -47,7 +48,7 @@ Passing "--prune/-p" does not remove those volumes.`,
log.Fatal(err)
}
log.Debugf("checking whether %s is already deployed", stackName)
log.Debug(i18n.G("checking whether %s is already deployed", stackName))
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
if err != nil {
@ -55,7 +56,7 @@ Passing "--prune/-p" does not remove those volumes.`,
}
if !deployMeta.IsDeployed {
log.Fatalf("%s is not deployed?", app.Name)
log.Fatal(i18n.G("%s is not deployed?", app.Name))
}
if err := internal.DeployOverview(
@ -84,7 +85,7 @@ Passing "--prune/-p" does not remove those volumes.`,
log.Fatal(err)
}
log.Info("initialising undeploy")
log.Info(i18n.G("initialising undeploy"))
rmOpts := stack.Remove{
Namespaces: []string{stackName},
@ -100,10 +101,10 @@ Passing "--prune/-p" does not remove those volumes.`,
}
}
log.Info("undeploy succeeded 🟢")
log.Info(i18n.G("undeploy succeeded 🟢"))
if err := app.WriteRecipeVersion(deployMeta.Version, false); err != nil {
log.Fatalf("writing recipe version failed: %s", err)
log.Fatal(i18n.G("writing recipe version failed: %s", err))
}
},
}
@ -124,14 +125,14 @@ func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
}
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
log.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
log.Info(i18n.G("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed))
nr, err := cl.NetworksPrune(ctx, pruneFilters)
if err != nil {
return err
}
log.Infof("networks pruned: %d", len(nr.NetworksDeleted))
log.Info(i18n.G("networks pruned: %d", len(nr.NetworksDeleted)))
ir, err := cl.ImagesPrune(ctx, pruneFilters)
if err != nil {
@ -139,7 +140,7 @@ func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
}
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
log.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
log.Info(i18n.G("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed))
return nil
}
@ -151,9 +152,9 @@ var (
func init() {
AppUndeployCommand.Flags().BoolVarP(
&prune,
"prune",
"p",
i18n.G("prune"),
i18n.G("p"),
false,
"prune unused containers, networks, and dangling images",
i18n.G("prune unused containers, networks, and dangling images"),
)
}

View File

@ -2,6 +2,7 @@ package app
import (
"context"
"errors"
"fmt"
"strings"
@ -13,6 +14,7 @@ import (
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
@ -24,10 +26,10 @@ import (
)
var AppUpgradeCommand = &cobra.Command{
Use: "upgrade <domain> [version] [flags]",
Aliases: []string{"up"},
Short: "Upgrade an app",
Long: `Upgrade an app.
Use: i18n.G("upgrade <domain> [version] [flags]"),
Aliases: []string{i18n.G("up")},
Short: i18n.G("Upgrade an app"),
Long: i18n.G(`Upgrade an app.
Unlike "abra app deploy", chaos operations are not supported here. Only recipe
versions are supported values for "[version]".
@ -40,7 +42,7 @@ are available. The live deployment version is the "source of truth" in this
case. The stored .env version is not consulted.
An upgrade can be destructive, please ensure you have a copy of your app data
beforehand. See "abra app backup" for more.`,
beforehand. See "abra app backup" for more.`),
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -53,8 +55,7 @@ beforehand. See "abra app backup" for more.`,
case 1:
app, err := appPkg.Get(args[0])
if err != nil {
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
return []string{errMsg}, cobra.ShellCompDirectiveError
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
}
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
default:
@ -123,7 +124,7 @@ beforehand. See "abra app backup" for more.`,
}
if !upgradeAvailable {
log.Info("no available upgrades")
log.Info(i18n.G("no available upgrades"))
return
}
}
@ -145,10 +146,10 @@ beforehand. See "abra app backup" for more.`,
}
if chosenUpgrade == "" {
log.Fatal("unknown deployed version, unable to upgrade")
log.Fatal(i18n.G("unknown deployed version, unable to upgrade"))
}
log.Debugf("choosing %s as version to upgrade", chosenUpgrade)
log.Debug(i18n.G("choosing %s as version to upgrade", chosenUpgrade))
// Get the release notes before checking out the new version in the
// recipe. This enables us to get release notes, that were added after
@ -204,7 +205,7 @@ beforehand. See "abra app backup" for more.`,
for _, envVar := range envVars {
if !envVar.Present {
upgradeWarnMessages = append(upgradeWarnMessages,
fmt.Sprintf("%s missing from %s.env", envVar.Name, app.Domain),
i18n.G("%s missing from %s.env", envVar.Name, app.Domain),
)
}
}
@ -236,7 +237,7 @@ beforehand. See "abra app backup" for more.`,
log.Fatal(err)
}
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
log.Debug(i18n.G("set waiting timeout to %d second(s)", stack.WaitTimeout))
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
if err != nil {
@ -262,15 +263,15 @@ beforehand. See "abra app backup" for more.`,
postDeployCmds, ok := app.Env["POST_UPGRADE_CMDS"]
if ok && !internal.DontWaitConverge {
log.Debugf("run the following post-deploy commands: %s", postDeployCmds)
log.Debug(i18n.G("run the following post-deploy commands: %s", postDeployCmds))
if err := internal.PostCmds(cl, app, postDeployCmds); err != nil {
log.Fatalf("attempting to run post deploy commands, saw: %s", err)
log.Fatal(i18n.G("attempting to run post deploy commands, saw: %s", err))
}
}
if err := app.WriteRecipeVersion(chosenUpgrade, false); err != nil {
log.Fatalf("writing recipe version failed: %s", err)
log.Fatal(i18n.G("writing recipe version failed: %s", err))
}
},
}
@ -281,12 +282,12 @@ func chooseUpgrade(
deployMeta stack.DeployMeta,
chosenUpgrade *string,
) error {
msg := fmt.Sprintf("please select an upgrade (version: %s):", deployMeta.Version)
msg := i18n.G("please select an upgrade (version: %s):", deployMeta.Version)
if deployMeta.IsChaos {
chaosVersion := formatter.BoldDirtyDefault(deployMeta.ChaosVersion)
msg = fmt.Sprintf(
msg = i18n.G(
"please select an upgrade (version: %s, chaos: %s):",
deployMeta.Version,
chaosVersion,
@ -314,18 +315,18 @@ func getReleaseNotes(
) error {
parsedChosenUpgrade, err := tagcmp.Parse(chosenUpgrade)
if err != nil {
return fmt.Errorf("parsing chosen upgrade version failed: %s", err)
return errors.New(i18n.G("parsing chosen upgrade version failed: %s", err))
}
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
if err != nil {
return fmt.Errorf("parsing deployment version failed: %s", err)
return errors.New(i18n.G("parsing deployment version failed: %s", err))
}
for _, version := range internal.SortVersionsDesc(versions) {
parsedVersion, err := tagcmp.Parse(version)
if err != nil {
return fmt.Errorf("parsing recipe version failed: %s", err)
return errors.New(i18n.G("parsing recipe version failed: %s", err))
}
if parsedVersion.IsGreaterThan(parsedDeployedVersion) &&
@ -358,13 +359,13 @@ func ensureUpgradesAvailable(
) (bool, error) {
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
if err != nil {
return false, fmt.Errorf("parsing deployed version failed: %s", err)
return false, errors.New(i18n.G("parsing deployed version failed: %s", err))
}
for _, version := range versions {
parsedVersion, err := tagcmp.Parse(version)
if err != nil {
return false, fmt.Errorf("parsing recipe version failed: %s", err)
return false, errors.New(i18n.G("parsing recipe version failed: %s", err))
}
if parsedVersion.IsGreaterThan(parsedDeployedVersion) &&
@ -388,21 +389,21 @@ func validateUpgradeVersionArg(
) error {
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
if err != nil {
return fmt.Errorf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
return errors.New(i18n.G("'%s' is not a known version for %s", specificVersion, app.Recipe.Name))
}
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
if err != nil {
return fmt.Errorf("'%s' is not a known version", deployMeta.Version)
return errors.New(i18n.G("'%s' is not a known version", deployMeta.Version))
}
if parsedSpecificVersion.IsLessThan(parsedDeployedVersion) &&
!parsedSpecificVersion.Equals(parsedDeployedVersion) {
return fmt.Errorf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
return errors.New(i18n.G("%s is not an upgrade for %s?", deployMeta.Version, specificVersion))
}
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
return fmt.Errorf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
return errors.New(i18n.G("%s is not an upgrade for %s?", deployMeta.Version, specificVersion))
}
return nil
@ -411,7 +412,7 @@ func validateUpgradeVersionArg(
// ensureDeployed ensures the app is deployed and if so, returns deployment
// meta info.
func ensureDeployed(cl *dockerClient.Client, app app.App) (stack.DeployMeta, error) {
log.Debugf("checking whether %s is already deployed", app.StackName())
log.Debug(i18n.G("checking whether %s is already deployed", app.StackName()))
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
if err != nil {
@ -419,7 +420,7 @@ func ensureDeployed(cl *dockerClient.Client, app app.App) (stack.DeployMeta, err
}
if !deployMeta.IsDeployed {
return stack.DeployMeta{}, fmt.Errorf("%s is not deployed?", app.Name)
return stack.DeployMeta{}, errors.New(i18n.G("%s is not deployed?", app.Name))
}
return deployMeta, nil
@ -430,32 +431,33 @@ var showReleaseNotes bool
func init() {
AppUpgradeCommand.Flags().BoolVarP(
&internal.Force,
"force",
"f",
i18n.G("force"),
i18n.G("f"),
false,
"perform action without further prompt",
i18n.G("perform action without further prompt"),
)
AppUpgradeCommand.Flags().BoolVarP(
&internal.NoDomainChecks,
"no-domain-checks",
"D",
i18n.G("no-domain-checks"),
i18n.G("D"),
false,
"disable public DNS checks",
i18n.G("disable public DNS checks"),
)
AppUpgradeCommand.Flags().BoolVarP(
&internal.DontWaitConverge, "no-converge-checks",
"c",
&internal.DontWaitConverge,
i18n.G("no-converge-checks"),
i18n.G("c"),
false,
"disable converge logic checks",
i18n.G("disable converge logic checks"),
)
AppUpgradeCommand.Flags().BoolVarP(
&showReleaseNotes,
"releasenotes",
"r",
i18n.G("releasenotes"),
i18n.G("r"),
false,
"only show release notes",
i18n.G("only show release notes"),
)
}

View File

@ -8,6 +8,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack"
"github.com/AlecAivazis/survey/v2"
@ -15,9 +16,9 @@ import (
)
var AppVolumeListCommand = &cobra.Command{
Use: "list <domain> [flags]",
Aliases: []string{"ls"},
Short: "List volumes associated with an app",
Use: i18n.G("list <domain> [flags]"),
Aliases: []string{i18n.G("ls")},
Short: i18n.G("List volumes associated with an app"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -43,7 +44,7 @@ var AppVolumeListCommand = &cobra.Command{
log.Fatal(err)
}
headers := []string{"NAME", "ON SERVER"}
headers := []string{i18n.G("NAME"), i18n.G("ON SERVER")}
table, err := formatter.CreateTable()
if err != nil {
@ -67,14 +68,14 @@ var AppVolumeListCommand = &cobra.Command{
return
}
log.Warnf("no volumes created for %s", app.Name)
log.Warn(i18n.G("no volumes created for %s", app.Name))
},
}
var AppVolumeRemoveCommand = &cobra.Command{
Use: "remove <domain> [volume] [flags]",
Short: "Remove volume(s) associated with an app",
Long: `Remove volumes associated with an app.
Use: i18n.G("remove <domain> [volume] [flags]"),
Short: i18n.G("Remove volume(s) associated with an app"),
Long: i18n.G(`Remove volumes associated with an app.
The app in question must be undeployed before you try to remove volumes. See
"abra app undeploy <domain>" for more.
@ -83,13 +84,13 @@ The command is interactive and will show a multiple select input which allows
you to make a seclection. Use the "?" key to see more help on navigating this
interface.
Passing "--force/-f" will select all volumes for removal. Be careful.`,
Example: ` # delete volumes interactively
Passing "--force/-f" will select all volumes for removal. Be careful.`),
Example: i18n.G(` # delete volumes interactively
abra app volume rm 1312.net
# delete specific volume
abra app volume rm 1312.net my_volume`,
Aliases: []string{"rm"},
abra app volume rm 1312.net my_volume`),
Aliases: []string{i18n.G("rm")},
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -116,7 +117,7 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
}
if deployMeta.IsDeployed {
log.Fatalf("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name)
log.Fatal(i18n.G("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name))
}
filters, err := app.Filters(false, true)
@ -141,15 +142,15 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
}
if !exactMatch {
log.Fatalf("unable to remove volume: no volume with name '%s'?", volumeToDelete)
log.Fatal(i18n.G("unable to remove volume: no volume with name '%s'?", volumeToDelete))
}
err := client.RemoveVolumes(cl, context.Background(), []string{fullVolumeToDeleteName}, internal.Force, 5)
if err != nil {
log.Fatalf("removing volume %s failed: %s", volumeToDelete, err)
log.Fatal(i18n.G("removing volume %s failed: %s", volumeToDelete, err))
}
log.Infof("volume %s removed successfully", volumeToDelete)
log.Info(i18n.G("volume %s removed successfully", volumeToDelete))
return
}
@ -157,8 +158,8 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
var volumesToRemove []string
if !internal.Force && !internal.NoInput {
volumesPrompt := &survey.MultiSelect{
Message: "which volumes do you want to remove?",
Help: "'x' indicates selected, enter / return to confirm, ctrl-c to exit, vim mode is enabled",
Message: i18n.G("which volumes do you want to remove?"),
Help: i18n.G("'x' indicates selected, enter / return to confirm, ctrl-c to exit, vim mode is enabled"),
VimMode: true,
Options: volumeNames,
Default: volumeNames,
@ -175,28 +176,28 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
if len(volumesToRemove) > 0 {
err := client.RemoveVolumes(cl, context.Background(), volumesToRemove, internal.Force, 5)
if err != nil {
log.Fatalf("removing volumes failed: %s", err)
log.Fatal(i18n.G("removing volumes failed: %s", err))
}
log.Infof("%d volumes removed successfully", len(volumesToRemove))
log.Info(i18n.G("%d volumes removed successfully", len(volumesToRemove)))
} else {
log.Info("no volumes removed")
log.Info(i18n.G("no volumes removed"))
}
},
}
var AppVolumeCommand = &cobra.Command{
Use: "volume [cmd] [args] [flags]",
Aliases: []string{"vl"},
Short: "Manage app volumes",
Use: i18n.G("volume [cmd] [args] [flags]"),
Aliases: []string{i18n.G("vl")},
Short: i18n.G("Manage app volumes"),
}
func init() {
AppVolumeRemoveCommand.Flags().BoolVarP(
&internal.Force,
"force",
"f",
i18n.G("force"),
i18n.G("f"),
false,
"perform action without further prompt",
i18n.G("perform action without further prompt"),
)
}

View File

@ -13,6 +13,7 @@ import (
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/go-git/go-git/v5"
@ -20,10 +21,10 @@ import (
)
var CatalogueGenerateCommand = &cobra.Command{
Use: "generate [recipe] [flags]",
Aliases: []string{"g"},
Short: "Generate the recipe catalogue",
Long: `Generate a new copy of the recipe catalogue.
Use: i18n.G("generate [recipe] [flags]"),
Aliases: []string{i18n.G("g")},
Short: i18n.G("Generate the recipe catalogue"),
Long: i18n.G(`Generate a new copy of the recipe catalogue.
N.B. this command **will** wipe local unstaged changes from your local recipes
if present. "--chaos/-C" on this command refers to the catalogue repository
@ -39,7 +40,7 @@ use those details.
Push your new release to git.coopcloud.tech with "--publish/-p". This requires
that you have permission to git push to these repositories and have your SSH
keys configured on your account.`,
keys configured on your account.`),
Args: cobra.RangeArgs(0, 1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -85,7 +86,7 @@ keys configured on your account.`,
var warnings []string
catl := make(recipe.RecipeCatalogue)
catlBar := formatter.CreateProgressbar(barLength, "collecting catalogue metadata")
catlBar := formatter.CreateProgressbar(barLength, i18n.G("collecting catalogue metadata"))
for _, recipeMeta := range repos {
if recipeName != "" && recipeName != recipeMeta.Name {
if !internal.Debug {
@ -171,7 +172,7 @@ keys configured on your account.`,
}
}
log.Infof("generated recipe catalogue: %s", config.RECIPES_JSON)
log.Info(i18n.G("generated recipe catalogue: %s", config.RECIPES_JSON))
cataloguePath := path.Join(config.ABRA_DIR, "catalogue")
if publishChanges {
@ -183,11 +184,11 @@ keys configured on your account.`,
if isClean {
if !internal.Dry {
log.Fatalf("no changes discovered in %s, nothing to publish?", cataloguePath)
log.Fatal(i18n.G("no changes discovered in %s, nothing to publish?", cataloguePath))
}
}
msg := "chore: publish new catalogue release changes"
msg := i18n.G("chore: publish new catalogue release changes")
if err := gitPkg.Commit(cataloguePath, msg, internal.Dry); err != nil {
log.Fatal(err)
}
@ -219,19 +220,19 @@ keys configured on your account.`,
if !internal.Dry && publishChanges {
url := fmt.Sprintf("%s/%s/commit/%s", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME, head.Hash())
log.Infof("new changes published: %s", url)
log.Info(i18n.G("new changes published: %s", url))
}
if internal.Dry {
log.Info("dry run: no changes published")
log.Info(i18n.G("dry run: no changes published"))
}
},
}
// CatalogueCommand defines the `abra catalogue` command and sub-commands.
var CatalogueCommand = &cobra.Command{
Use: "catalogue [cmd] [args] [flags]",
Short: "Manage the recipe catalogue",
Use: i18n.G("catalogue [cmd] [args] [flags]"),
Short: i18n.G("Manage the recipe catalogue"),
Aliases: []string{"c"},
}
@ -243,33 +244,33 @@ var (
func init() {
CatalogueGenerateCommand.Flags().BoolVarP(
&publishChanges,
"publish",
"p",
i18n.G("publish"),
i18n.G("p"),
false,
"publish changes to git.coopcloud.tech",
i18n.G("publish changes to git.coopcloud.tech"),
)
CatalogueGenerateCommand.Flags().BoolVarP(
&internal.Dry,
"dry-run",
"r",
i18n.G("dry-run"),
i18n.G("r"),
false,
"report changes that would be made",
i18n.G("report changes that would be made"),
)
CatalogueGenerateCommand.Flags().BoolVarP(
&skipUpdates,
"skip-updates",
"s",
i18n.G("skip-updates"),
i18n.G("s"),
false,
"skip updating recipe repositories",
i18n.G("skip updating recipe repositories"),
)
CatalogueGenerateCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
}

View File

@ -3,13 +3,14 @@ package cli
import (
"os"
"coopcloud.tech/abra/pkg/i18n"
"github.com/spf13/cobra"
)
var AutocompleteCommand = &cobra.Command{
Use: "autocomplete [bash|zsh|fish|powershell]",
Short: "Generate autocompletion script",
Long: `To load completions:
Use: i18n.G("autocomplete [bash|zsh|fish|powershell]"),
Short: i18n.G("Generate autocompletion script"),
Long: i18n.G(`To load completions:
Bash:
# Load autocompletion for the current Bash session
@ -43,7 +44,7 @@ PowerShell:
# To load autocompletions for every new session, run:
PS> abra autocomplete powershell > abra.ps1
# and source this file from your PowerShell profile.`,
# and source this file from your PowerShell profile.`),
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),

View File

@ -7,6 +7,7 @@ import (
"coopcloud.tech/abra/pkg/config"
containerPkg "coopcloud.tech/abra/pkg/container"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/service"
"coopcloud.tech/abra/pkg/upstream/container"
@ -22,10 +23,10 @@ func RetrieveBackupBotContainer(cl *dockerClient.Client) (types.Container, error
ctx := context.Background()
chosenService, err := service.GetServiceByLabel(ctx, cl, config.BackupbotLabel, NoInput)
if err != nil {
return types.Container{}, fmt.Errorf("no backupbot discovered, is it deployed?")
return types.Container{}, fmt.Error(i18n.G("no backupbot discovered, is it deployed?"))
}
log.Debugf("retrieved %s as backup enabled service", chosenService.Spec.Name)
log.Debug(i18n.G("retrieved %s as backup enabled service", chosenService.Spec.Name))
filters := filters.NewArgs()
filters.Add("name", chosenService.Spec.Name)
@ -58,7 +59,7 @@ func RunBackupCmdRemote(
Tty: true,
}
log.Debugf("running backup %s on %s with exec config %v", backupCmd, containerID, execBackupListOpts)
log.Debug(i18n.G("running backup %s on %s with exec config %v", backupCmd, containerID, execBackupListOpts))
// FIXME: avoid instantiating a new CLI
dcli, err := command.NewDockerCli()

View File

@ -6,6 +6,7 @@ var (
NoInput bool
Offline bool
IgnoreEnvVersion bool
Locale string
// NOTE(d1): sub-command specific
Chaos bool

View File

@ -11,6 +11,7 @@ import (
appPkg "coopcloud.tech/abra/pkg/app"
containerPkg "coopcloud.tech/abra/pkg/container"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/container"
"github.com/docker/cli/cli/command"
@ -34,7 +35,7 @@ func RunCmdRemote(
return err
}
log.Debugf("retrieved %s as target container on %s", formatter.ShortenID(targetContainer.ID), app.Server)
log.Debug(i18n.G("retrieved %s as target container on %s", formatter.ShortenID(targetContainer.ID), app.Server))
toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip}
content, err := archive.TarWithOptions(abraSh, toTarOpts)
@ -65,7 +66,7 @@ func RunCmdRemote(
}
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
log.Infof("%s does not exist for %s, use /bin/sh as fallback", shell, app.Name)
log.Info(i18n.G("%s does not exist for %s, use /bin/sh as fallback", shell, app.Name))
shell = "/bin/sh"
}
@ -76,10 +77,10 @@ func RunCmdRemote(
cmd = []string{shell, "-c", fmt.Sprintf("TARGET=%s; APP_NAME=%s; STACK_NAME=%s; . /tmp/abra.sh; %s", serviceName, app.Name, app.StackName(), cmdName)}
}
log.Debugf("running command: %s", strings.Join(cmd, " "))
log.Debug(i18n.G("running command: %s", strings.Join(cmd, " ")))
if remoteUser != "" {
log.Debugf("running command with user %s", remoteUser)
log.Debug(i18n.G("running command with user %s", remoteUser))
execCreateOpts.User = remoteUser
}
@ -88,7 +89,7 @@ func RunCmdRemote(
execCreateOpts.Tty = true
if disableTTY {
execCreateOpts.Tty = false
log.Debugf("not requesting a remote TTY")
log.Debug(i18n.G("not requesting a remote TTY"))
}
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
@ -105,7 +106,7 @@ func EnsureCommand(abraSh, recipeName, execCmd string) error {
}
if !strings.Contains(string(bytes), execCmd) {
return fmt.Errorf("%s doesn't have a %s function", recipeName, execCmd)
return fmt.Error(i18n.G("%s doesn't have a %s function", recipeName, execCmd))
}
return nil

View File

@ -1,6 +1,7 @@
package internal
import (
"errors"
"fmt"
"os"
"sort"
@ -9,6 +10,7 @@ import (
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2"
@ -70,18 +72,18 @@ func DeployOverview(
}
rows := [][]string{
{"DOMAIN", domain},
{"RECIPE", app.Recipe.Name},
{"SERVER", server},
{"CONFIG", deployConfig},
{i18n.G("DOMAIN"), domain},
{i18n.G("RECIPE"), app.Recipe.Name},
{i18n.G("SERVER"), server},
{i18n.G("CONFIG"), deployConfig},
{"", ""},
{"CURRENT DEPLOYMENT", formatter.BoldDirtyDefault(deployedVersion)},
{"ENV VERSION", formatter.BoldDirtyDefault(envVersion)},
{"NEW DEPLOYMENT", formatter.BoldDirtyDefault(toDeployVersion)},
{i18n.G("CURRENT DEPLOYMENT"), formatter.BoldDirtyDefault(deployedVersion)},
{i18n.G("ENV VERSION"), formatter.BoldDirtyDefault(envVersion)},
{i18n.G("NEW DEPLOYMENT"), formatter.BoldDirtyDefault(toDeployVersion)},
}
deployType := getDeployType(deployedVersion, toDeployVersion)
overview := formatter.CreateOverview(fmt.Sprintf("%s OVERVIEW", deployType), rows)
overview := formatter.CreateOverview(i18n.G("%s OVERVIEW", deployType), rows)
fmt.Println(overview)
@ -104,7 +106,7 @@ func DeployOverview(
}
if !response {
log.Fatal("deployment cancelled")
log.Fatal(i18n.G("deployment cancelled"))
}
return nil
@ -112,32 +114,32 @@ func DeployOverview(
func getDeployType(currentVersion, newVersion string) string {
if newVersion == config.NO_DOMAIN_DEFAULT {
return "UNDEPLOY"
return i18n.G("UNDEPLOY")
}
if strings.Contains(newVersion, "+U") {
return "CHAOS DEPLOY"
return i18n.G("CHAOS DEPLOY")
}
if strings.Contains(currentVersion, "+U") {
return "UNCHAOS DEPLOY"
return i18n.G("UNCHAOS DEPLOY")
}
if currentVersion == newVersion {
return "REDEPLOY"
return ("REDEPLOY")
}
if currentVersion == config.NO_VERSION_DEFAULT {
return "NEW DEPLOY"
return i18n.G("NEW DEPLOY")
}
currentParsed, err := tagcmp.Parse(currentVersion)
if err != nil {
return "DEPLOY"
return i18n.G("DEPLOY")
}
newParsed, err := tagcmp.Parse(newVersion)
if err != nil {
return "DEPLOY"
return i18n.G("DEPLOY")
}
if currentParsed.IsLessThan(newParsed) {
return "UPGRADE"
return i18n.G("UPGRADE")
}
return "DOWNGRADE"
return i18n.G("DOWNGRADE")
}
// PostCmds parses a string of commands and executes them inside of the respective services
@ -146,7 +148,7 @@ func getDeployType(currentVersion, newVersion string) string {
func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
if _, err := os.Stat(app.Recipe.AbraShPath); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name)
return errors.New(i18n.G("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name))
}
return err
}
@ -154,7 +156,7 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
for _, command := range strings.Split(commands, "|") {
commandParts := strings.Split(command, " ")
if len(commandParts) < 2 {
return fmt.Errorf("not enough arguments: %s", command)
return errors.New(i18n.G("not enough arguments: %s", command))
}
targetServiceName := commandParts[0]
cmdName := commandParts[1]
@ -162,7 +164,7 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
if len(commandParts) > 2 {
parsedCmdArgs = fmt.Sprintf("%s ", strings.Join(commandParts[2:], " "))
}
log.Infof("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName)
log.Info(i18n.G("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName))
if err := EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
return err
@ -184,7 +186,7 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
return fmt.Errorf("no service %s for %s?", targetServiceName, app.Name)
}
log.Debugf("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName)
log.Debug(i18n.G("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName))
requestTTY := true
if err := RunCmdRemote(

View File

@ -1,9 +1,11 @@
package internal
import (
"errors"
"fmt"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2"
@ -13,7 +15,7 @@ import (
// PromptBumpType prompts for version bump type
func PromptBumpType(tagString, latestRelease string) error {
if (!Major && !Minor && !Patch) && tagString == "" {
fmt.Printf(`
fmt.Print(i18n.G(`
You need to make a decision about what kind of an update this new recipe
version is. If someone else performs this upgrade, do they have to do some
migration work or take care of some breaking changes? This can be signaled in
@ -36,12 +38,12 @@ Here is a semver cheat sheet (more on https://semver.org):
should also Just Work and is mostly to do with minor bug fixes
and/or security patches. "nothing to worry about".
`, latestRelease)
`, latestRelease))
var chosenBumpType string
prompt := &survey.Select{
Message: fmt.Sprintf("select recipe version increment type"),
Options: []string{"major", "minor", "patch"},
Options: []string{i18n.G("major"), i18n.G("minor"), i18n.G("patch")},
}
if err := survey.AskOne(prompt, &chosenBumpType); err != nil {
@ -59,13 +61,13 @@ func GetBumpType() string {
var bumpType string
if Major {
bumpType = "major"
bumpType = i18n.G("major")
} else if Minor {
bumpType = "minor"
bumpType = i18n.G("minor")
} else if Patch {
bumpType = "patch"
bumpType = i18n.G("patch")
} else {
log.Fatal("no version bump type specififed?")
log.Fatal(i18n.G("no version bump type specififed?"))
}
return bumpType
@ -73,14 +75,14 @@ func GetBumpType() string {
// SetBumpType figures out which bump type is specified
func SetBumpType(bumpType string) {
if bumpType == "major" {
if bumpType == i18n.G("major") {
Major = true
} else if bumpType == "minor" {
} else if bumpType == i18n.G("minor") {
Minor = true
} else if bumpType == "patch" {
} else if bumpType == i18n.G("patch") {
Patch = true
} else {
log.Fatal("no version bump type specififed?")
log.Fatal(i18n.G("no version bump type specififed?"))
}
}
@ -107,7 +109,7 @@ func GetMainAppImage(recipe recipe.Recipe) (string, error) {
}
if path == "" {
return path, fmt.Errorf("%s has no main 'app' service?", recipe.Name)
return path, errors.New(i18n.G("%s has no main 'app' service?", recipe.Name))
}
return path, nil

View File

@ -5,6 +5,7 @@ import (
"coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2"
@ -31,7 +32,7 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
localRecipes, err := recipe.GetRecipesLocal()
if err != nil {
log.Debugf("can't read local recipes: %s", err)
log.Debug(i18n.G("can't read local recipes: %s", err))
} else {
for _, recipeLocal := range localRecipes {
if _, ok := knownRecipes[recipeLocal]; !ok {
@ -46,7 +47,7 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
if recipeName == "" && !NoInput {
prompt := &survey.Select{
Message: "Select recipe",
Message: i18n.G("Select recipe"),
Options: recipes,
}
if err := survey.AskOne(prompt, &recipeName); err != nil {
@ -55,12 +56,12 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
}
if recipeName == "" {
log.Fatal("no recipe name provided")
log.Fatal(i18n.G("no recipe name provided"))
}
if _, ok := knownRecipes[recipeName]; !ok {
if !strings.Contains(recipeName, "/") {
log.Fatalf("no recipe '%s' exists?", recipeName)
log.Fatal(i18n.G("no recipe '%s' exists?", recipeName))
}
}
@ -71,20 +72,20 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
_, err = chosenRecipe.GetComposeConfig(nil)
if err != nil {
if cmdName == "generate" {
if cmdName == i18n.G("generate") {
if strings.Contains(err.Error(), "missing a compose") {
log.Fatal(err)
}
log.Warn(err)
} else {
if strings.Contains(err.Error(), "template_driver is not allowed") {
log.Warnf("ensure %s recipe compose.* files include \"version: '3.8'\"", recipeName)
log.Warn(i18n.G("ensure %s recipe compose.* files include \"version: '3.8'\"", recipeName))
}
log.Fatalf("unable to validate recipe: %s", err)
log.Fatal(i18n.G("unable to validate recipe: %s", err))
}
}
log.Debugf("validated %s as recipe argument", recipeName)
log.Debug(i18n.G("validated %s as recipe argument", recipeName))
return chosenRecipe
}
@ -92,7 +93,7 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
// ValidateApp ensures the app name arg is valid.
func ValidateApp(args []string) app.App {
if len(args) == 0 {
log.Fatal("no app provided")
log.Fatal(i18n.G("no app provided"))
}
appName := args[0]
@ -102,7 +103,7 @@ func ValidateApp(args []string) app.App {
log.Fatal(err)
}
log.Debugf("validated %s as app argument", appName)
log.Debug(i18n.G("validated %s as app argument", appName))
return app
}
@ -116,8 +117,8 @@ func ValidateDomain(args []string) string {
if domainName == "" && !NoInput {
prompt := &survey.Input{
Message: "Specify a domain name",
Default: "example.com",
Message: i18n.G("Specify a domain name"),
Default: "1312.net",
}
if err := survey.AskOne(prompt, &domainName); err != nil {
log.Fatal(err)
@ -125,10 +126,10 @@ func ValidateDomain(args []string) string {
}
if domainName == "" {
log.Fatal("no domain provided")
log.Fatal(i18n.G("no domain provided"))
}
log.Debugf("validated %s as domain argument", domainName)
log.Debug(i18n.G("validated %s as domain argument", domainName))
return domainName
}
@ -147,7 +148,7 @@ func ValidateServer(args []string) string {
if serverName == "" && !NoInput {
prompt := &survey.Select{
Message: "Specify a server name",
Message: i18n.G("Specify a server name"),
Options: serverNames,
}
if err := survey.AskOne(prompt, &serverName); err != nil {
@ -163,14 +164,14 @@ func ValidateServer(args []string) string {
}
if serverName == "" {
log.Fatal("no server provided")
log.Fatal(i18n.G("no server provided"))
}
if !matched {
log.Fatal("server doesn't exist?")
log.Fatal(i18n.G("server doesn't exist?"))
}
log.Debugf("validated %s as server argument", serverName)
log.Debug(i18n.G("validated %s as server argument", serverName))
return serverName
}

View File

@ -4,15 +4,16 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/spf13/cobra"
)
var RecipeDiffCommand = &cobra.Command{
Use: "diff <recipe> [flags]",
Aliases: []string{"d"},
Short: "Show unstaged changes in recipe config",
Long: "This command requires /usr/bin/git.",
Use: i18n.G("diff <recipe> [flags]"),
Aliases: []string{i18n.G("d")},
Short: i18n.G("Show unstaged changes in recipe config"),
Long: i18n.G("This command requires /usr/bin/git."),
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,

View File

@ -6,6 +6,7 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/go-git/go-git/v5"
@ -14,19 +15,19 @@ import (
)
var RecipeFetchCommand = &cobra.Command{
Use: "fetch [recipe | --all] [flags]",
Aliases: []string{"f"},
Short: "Clone recipe(s) locally",
Long: `Using "--force/-f" Git syncs an existing recipe. It does not erase unstaged changes.`,
Use: i18n.G("fetch [recipe | --all] [flags]"),
Aliases: []string{i18n.G("f")},
Short: i18n.G("Clone recipe(s) locally"),
Long: i18n.G(`Using "--force/-f" Git syncs an existing recipe. It does not erase unstaged changes.`),
Args: cobra.RangeArgs(0, 1),
Example: ` # fetch from recipe catalogue
Example: i18n.G(` # fetch from recipe catalogue
abra recipe fetch gitea
# fetch from remote recipe
abra recipe fetch git.foo.org/recipes/myrecipe
# fetch with ssh remote for hacking
abra recipe fetch gitea --ssh`,
abra recipe fetch gitea --ssh`),
ValidArgsFunction: func(
cmd *cobra.Command,
args []string,
@ -40,18 +41,18 @@ var RecipeFetchCommand = &cobra.Command{
}
if recipeName == "" && !fetchAllRecipes {
log.Fatal("missing [recipe] or --all/-a")
log.Fatal(i18n.G("missing [recipe] or --all/-a"))
}
if recipeName != "" && fetchAllRecipes {
log.Fatal("cannot use [recipe] and --all/-a together")
log.Fatal(i18n.G("cannot use [recipe] and --all/-a together"))
}
if recipeName != "" {
r := recipe.Get(recipeName)
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
if !force {
log.Warnf("%s is already fetched", r.Name)
log.Warn(i18n.G("%s is already fetched", r.Name))
return
}
}
@ -60,24 +61,24 @@ var RecipeFetchCommand = &cobra.Command{
if sshRemote {
if r.SSHURL == "" {
log.Warnf("unable to discover SSH remote for %s", r.Name)
log.Warn(i18n.G("unable to discover SSH remote for %s", r.Name))
return
}
repo, err := git.PlainOpen(r.Dir)
if err != nil {
log.Fatalf("unable to open %s: %s", r.Dir, err)
log.Fatal(i18n.G("unable to open %s: %s", r.Dir, err))
}
if err = repo.DeleteRemote("origin"); err != nil {
log.Fatalf("unable to remove default remote in %s: %s", r.Dir, err)
log.Fatal(i18n.G("unable to remove default remote in %s: %s", r.Dir, err))
}
if _, err := repo.CreateRemote(&gitCfg.RemoteConfig{
Name: "origin",
URLs: []string{r.SSHURL},
}); err != nil {
log.Fatalf("unable to set SSH remote in %s: %s", r.Dir, err)
log.Fatal(i18n.G("unable to set SSH remote in %s: %s", r.Dir, err))
}
}
@ -89,7 +90,7 @@ var RecipeFetchCommand = &cobra.Command{
log.Fatal(err)
}
catlBar := formatter.CreateProgressbar(len(catalogue), "fetching latest recipes...")
catlBar := formatter.CreateProgressbar(len(catalogue), i18n.G("fetching latest recipes..."))
ensureCtx := internal.GetEnsureContext()
for recipeName := range catalogue {
r := recipe.Get(recipeName)
@ -110,25 +111,25 @@ var (
func init() {
RecipeFetchCommand.Flags().BoolVarP(
&fetchAllRecipes,
"all",
"a",
i18n.G("all"),
i18n.G("a"),
false,
"fetch all recipes",
i18n.G("fetch all recipes"),
)
RecipeFetchCommand.Flags().BoolVarP(
&sshRemote,
"ssh",
"s",
i18n.G("ssh"),
i18n.G("s"),
false,
"automatically set ssh remote",
i18n.G("automatically set ssh remote"),
)
RecipeFetchCommand.Flags().BoolVarP(
&force,
"force",
"f",
i18n.G("force"),
i18n.G("f"),
false,
"force re-fetch",
i18n.G("force re-fetch"),
)
}

View File

@ -4,15 +4,16 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/log"
"github.com/spf13/cobra"
)
var RecipeLintCommand = &cobra.Command{
Use: "lint <recipe> [flags]",
Short: "Lint a recipe",
Aliases: []string{"l"},
Use: i18n.G("lint <recipe> [flags]"),
Short: i18n.G("Lint a recipe"),
Aliases: []string{i18n.G("l")},
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -28,12 +29,12 @@ var RecipeLintCommand = &cobra.Command{
}
headers := []string{
"ref",
"rule",
"severity",
"satisfied",
"skipped",
"resolve",
i18n.G("ref"),
i18n.G("rule"),
i18n.G("severity"),
i18n.G("satisfied"),
i18n.G("skipped"),
i18n.G("resolve"),
}
table, err := formatter.CreateTable()
@ -49,7 +50,7 @@ var RecipeLintCommand = &cobra.Command{
for level := range lint.LintRules {
for _, rule := range lint.LintRules[level] {
if onlyError && rule.Level != "error" {
log.Debugf("skipping %s, does not have level \"error\"", rule.Ref)
log.Debug(i18n.G("skipping %s, does not have level \"error\"", rule.Ref))
continue
}
@ -70,7 +71,7 @@ var RecipeLintCommand = &cobra.Command{
warnMessages = append(warnMessages, err.Error())
}
if !ok && rule.Level == "error" {
if !ok && rule.Level == i18n.G("error") {
hasError = true
}
@ -111,7 +112,7 @@ var RecipeLintCommand = &cobra.Command{
}
if hasError {
log.Warnf("critical errors present in %s config", recipe.Name)
log.Warn(i18n.G("critical errors present in %s config", recipe.Name))
}
}
},
@ -124,17 +125,17 @@ var (
func init() {
RecipeLintCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
RecipeLintCommand.Flags().BoolVarP(
&onlyError,
"error",
"e",
i18n.G("error"),
i18n.G("e"),
false,
"only show errors",
i18n.G("only show errors"),
)
}

View File

@ -8,15 +8,16 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/spf13/cobra"
)
var RecipeListCommand = &cobra.Command{
Use: "list",
Short: "List recipes",
Aliases: []string{"ls"},
Use: i18n.G("list"),
Short: i18n.G("List recipes"),
Aliases: []string{i18n.G("ls")},
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
@ -33,14 +34,14 @@ var RecipeListCommand = &cobra.Command{
}
headers := []string{
"name",
"category",
"status",
"healthcheck",
"backups",
"email",
"tests",
"SSO",
i18n.G("name"),
i18n.G("category"),
i18n.G("status"),
i18n.G("healthcheck"),
i18n.G("backups"),
i18n.G("email"),
i18n.G("tests"),
i18n.G("SSO"),
}
table.Headers(headers...)
@ -73,7 +74,7 @@ var RecipeListCommand = &cobra.Command{
if internal.MachineReadable {
out, err := formatter.ToJSON(headers, rows)
if err != nil {
log.Fatal("unable to render to JSON: %s", err)
log.Fatal(i18n.G("unable to render to JSON: %s", err))
}
fmt.Println(out)
return
@ -93,17 +94,17 @@ var (
func init() {
RecipeListCommand.Flags().BoolVarP(
&internal.MachineReadable,
"machine",
"m",
i18n.G("machine"),
i18n.G("m"),
false,
"print machine-readable output",
i18n.G("print machine-readable output"),
)
RecipeListCommand.Flags().StringVarP(
&pattern,
"pattern",
"p",
i18n.G("pattern"),
i18n.G("p"),
"",
"filter by recipe",
i18n.G("filter by recipe"),
)
}

View File

@ -10,6 +10,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/spf13/cobra"
@ -30,10 +31,10 @@ type recipeMetadata struct {
}
var RecipeNewCommand = &cobra.Command{
Use: "new <recipe> [flags]",
Aliases: []string{"n"},
Short: "Create a new recipe",
Long: `A community managed recipe template is used.`,
Use: i18n.G("new <recipe> [flags]"),
Aliases: []string{i18n.G("n")},
Short: i18n.G("Create a new recipe"),
Long: i18n.G(`A community managed recipe template is used.`),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -46,10 +47,10 @@ var RecipeNewCommand = &cobra.Command{
r := recipe.Get(recipeName)
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
log.Fatalf("%s recipe directory already exists?", r.Dir)
log.Fatal(i18n.G("%s recipe directory already exists?", r.Dir))
}
url := fmt.Sprintf("%s/example.git", config.REPOS_BASE_URL)
url := i18n.G("%s/example.git", config.REPOS_BASE_URL)
if err := git.Clone(r.Dir, url); err != nil {
log.Fatal(err)
}
@ -58,7 +59,7 @@ var RecipeNewCommand = &cobra.Command{
if err := os.RemoveAll(gitRepo); err != nil {
log.Fatal(err)
}
log.Debugf("removed .git repo in %s", gitRepo)
log.Debug(i18n.G("removed .git repo in %s", gitRepo))
meta := newRecipeMeta(recipeName)
@ -82,8 +83,8 @@ var RecipeNewCommand = &cobra.Command{
log.Fatal(err)
}
log.Infof("new recipe '%s' created: %s", recipeName, path.Join(r.Dir))
log.Info("happy hacking 🎉")
log.Info(i18n.G("new recipe '%s' created: %s", recipeName, path.Join(r.Dir)))
log.Info(i18n.G("happy hacking 🎉"))
},
}
@ -111,17 +112,17 @@ var (
func init() {
RecipeNewCommand.Flags().StringVarP(
&gitName,
"git-name",
"N",
i18n.G("git-name"),
i18n.G("N"),
"",
"Git (user) name to do commits with",
i18n.G("Git (user) name to do commits with"),
)
RecipeNewCommand.Flags().StringVarP(
&gitEmail,
"git-email",
"e",
i18n.G("git-email"),
i18n.G("e"),
"",
"Git email name to do commits with",
i18n.G("Git email name to do commits with"),
)
}

View File

@ -1,13 +1,16 @@
package recipe
import "github.com/spf13/cobra"
import (
"coopcloud.tech/abra/pkg/i18n"
"github.com/spf13/cobra"
)
// RecipeCommand defines all recipe related sub-commands.
var RecipeCommand = &cobra.Command{
Use: "recipe [cmd] [args] [flags]",
Aliases: []string{"r"},
Short: "Manage recipes",
Long: `A recipe is a blueprint for an app.
Use: i18n.G("recipe [cmd] [args] [flags]"),
Aliases: []string{i18n.G("r")},
Short: i18n.G("Manage recipes"),
Long: i18n.G(`A recipe is a blueprint for an app.
It is a bunch of config files which describe how to deploy and maintain an app.
Recipes are maintained by the Co-op Cloud community and you can use Abra to
@ -15,5 +18,5 @@ read them, deploy them and create apps for you.
Anyone who uses a recipe can become a maintainer. Maintainers typically make
sure the recipe is in good working order and the config upgraded in a timely
manner.`,
manner.`),
}

View File

@ -12,6 +12,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp"
@ -22,10 +23,10 @@ import (
)
var RecipeReleaseCommand = &cobra.Command{
Use: "release <recipe> [version] [flags]",
Aliases: []string{"rl"},
Short: "Release a new recipe version",
Long: `Create a new version of a recipe.
Use: i18n.G("release <recipe> [version] [flags]"),
Aliases: []string{i18n.G("rl")},
Short: i18n.G("Release a new recipe version"),
Long: i18n.G(`Create a new version of a recipe.
These versions are then published on the Co-op Cloud recipe catalogue. These
versions take the following form:
@ -44,7 +45,7 @@ major and therefore require intervention while doing the upgrade work.
Publish your new release to git.coopcloud.tech with "--publish/-p". This
requires that you have permission to git push to these repositories and have
your SSH keys configured on your account.`,
your SSH keys configured on your account.`),
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -74,7 +75,7 @@ your SSH keys configured on your account.`,
mainAppVersion := imagesTmp[mainApp]
if mainAppVersion == "" {
log.Fatalf("main app service version for %s is empty?", recipe.Name)
log.Fatal(i18n.G("main app service version for %s is empty?", recipe.Name))
}
var tagString string
@ -84,12 +85,12 @@ your SSH keys configured on your account.`,
if tagString != "" {
if _, err := tagcmp.Parse(tagString); err != nil {
log.Fatalf("cannot parse %s, invalid tag specified?", tagString)
log.Fatal(i18n.G("cannot parse %s, invalid tag specified?", tagString))
}
}
if (internal.Major || internal.Minor || internal.Patch) && tagString != "" {
log.Fatal("cannot specify tag and bump type at the same time")
log.Fatal(i18n.G("cannot specify tag and bump type at the same time"))
}
if tagString != "" {
@ -117,19 +118,19 @@ your SSH keys configured on your account.`,
}
if !isClean {
log.Infof("%s currently has these unstaged changes 👇", recipe.Name)
log.Info(i18n.G("%s currently has these unstaged changes 👇", recipe.Name))
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
log.Fatal(err)
}
}
if len(tags) > 0 {
log.Warnf("previous git tags detected, assuming this is a new semver release")
log.Warn(i18n.G("previous git tags detected, assuming this is a new semver release"))
if err := createReleaseFromPreviousTag(tagString, mainAppVersion, recipe, tags); err != nil {
log.Fatal(err)
}
} else {
log.Warnf("no tag specified and no previous tag available for %s, assuming this is the initial release", recipe.Name)
log.Warn(i18n.G("no tag specified and no previous tag available for %s, assuming this is the initial release", recipe.Name))
if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil {
if cleanUpErr := cleanUpTag(recipe, tagString); err != nil {
@ -181,7 +182,7 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
}
if missingTag {
return services, fmt.Errorf("app service is missing image tag?")
return services, fmt.Error(i18n.G("app service is missing image tag?"))
}
return services, nil
@ -245,7 +246,7 @@ func btoi(b bool) int {
// getTagCreateOptions constructs git tag create options
func getTagCreateOptions(tag string) (git.CreateTagOptions, error) {
msg := fmt.Sprintf("chore: publish %s release", tag)
msg := i18n.G("chore: publish %s release", tag)
return git.CreateTagOptions{Message: msg}, nil
}
@ -273,13 +274,13 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
if _, err := os.Stat(nextReleaseNotePath); err == nil {
// release/next note exists. Move it to release/<tag>
if internal.Dry {
log.Debugf("dry run: move release note from 'next' to %s", tag)
log.Debug(i18n.G("dry run: move release note from 'next' to %s", tag))
return nil
}
if !internal.NoInput {
prompt := &survey.Confirm{
Message: "Use release note in release/next?",
Message: i18n.G("Use release note in release/next?"),
}
if err := survey.AskOne(prompt, &addNextAsReleaseNotes); err != nil {
@ -313,7 +314,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
}
prompt := &survey.Input{
Message: "Release Note (leave empty for no release note)",
Message: i18n.G("Release Note (leave empty for no release note)"),
}
var releaseNote string
@ -338,7 +339,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
func commitRelease(recipe recipe.Recipe, tag string) error {
if internal.Dry {
log.Debugf("dry run: no changes committed")
log.Debug(i18n.G("dry run: no changes committed"))
return nil
}
@ -349,7 +350,7 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
if isClean {
if !internal.Dry {
return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir)
return fmt.Error(i18n.G("no changes discovered in %s, nothing to publish?", recipe.Dir))
}
}
@ -363,7 +364,7 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
func tagRelease(tagString string, repo *git.Repository) error {
if internal.Dry {
log.Debugf("dry run: no git tag created (%s)", tagString)
log.Debug(i18n.G("dry run: no git tag created (%s)", tagString))
return nil
}
@ -383,20 +384,20 @@ func tagRelease(tagString string, repo *git.Repository) error {
}
hash := formatter.SmallSHA(head.Hash().String())
log.Debugf(fmt.Sprintf("created tag %s at %s", tagString, hash))
log.Debug(i18n.G("created tag %s at %s", tagString, hash))
return nil
}
func pushRelease(recipe recipe.Recipe, tagString string) error {
if internal.Dry {
log.Info("dry run: no changes published")
log.Info(i18n.G("dry run: no changes published"))
return nil
}
if !publish && !internal.NoInput {
prompt := &survey.Confirm{
Message: "publish new release?",
Message: i18n.G("publish new release?"),
}
if err := survey.AskOne(prompt, &publish); err != nil {
@ -409,9 +410,9 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
return err
}
url := fmt.Sprintf("%s/src/tag/%s", recipe.GitURL, tagString)
log.Infof("new release published: %s", url)
log.Info(i18n.G("new release published: %s", url))
} else {
log.Info("no -p/--publish passed, not publishing")
log.Info(i18n.G("no -p/--publish passed, not publishing"))
}
return nil
@ -426,7 +427,7 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
bumpType := btoi(internal.Major)*4 + btoi(internal.Minor)*2 + btoi(internal.Patch)
if bumpType != 0 {
if (bumpType & (bumpType - 1)) != 0 {
return fmt.Errorf("you can only use one of: --major, --minor, --patch")
return errors.New(i18n.G("you can only use one of: --major, --minor, --patch"))
}
}
@ -483,12 +484,12 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
}
if lastGitTag.String() == tagString {
log.Fatalf("latest git tag (%s) and synced label (%s) are the same?", lastGitTag, tagString)
log.Fatal(i18n.G("latest git tag (%s) and synced label (%s) are the same?", lastGitTag, tagString))
}
if !internal.NoInput {
prompt := &survey.Confirm{
Message: fmt.Sprintf("current: %s, new: %s, correct?", lastGitTag, tagString),
Message: i18n.G("current: %s, new: %s, correct?", lastGitTag, tagString),
}
var ok bool
@ -497,7 +498,7 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
}
if !ok {
log.Fatal("exiting as requested")
log.Fatal(i18n.G("exiting as requested"))
}
}
@ -506,15 +507,15 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
}
if err := commitRelease(recipe, tagString); err != nil {
log.Fatalf("failed to commit changes: %s", err.Error())
log.Fatal(i18n.G("failed to commit changes: %s", err.Error()))
}
if err := tagRelease(tagString, repo); err != nil {
log.Fatalf("failed to tag release: %s", err.Error())
log.Fatal(i18n.G("failed to tag release: %s", err.Error()))
}
if err := pushRelease(recipe, tagString); err != nil {
log.Fatalf("failed to publish new release: %s", err.Error())
log.Fatal(i18n.G("failed to publish new release: %s", err.Error()))
}
return nil
@ -533,7 +534,7 @@ func cleanUpTag(recipe recipe.Recipe, tag string) error {
}
}
log.Debugf("removed freshly created tag %s", tag)
log.Debug(i18n.G("removed freshly created tag %s", tag))
return nil
}
@ -545,20 +546,20 @@ func getLabelVersion(recipe recipe.Recipe, prompt bool) (string, error) {
}
if initTag == "" {
log.Fatalf("unable to read version for %s from synced label. Did you try running \"abra recipe sync %s\" already?", recipe.Name, recipe.Name)
log.Fatal(i18n.G("unable to read version for %s from synced label. Did you try running \"abra recipe sync %s\" already?", recipe.Name, recipe.Name))
}
log.Warnf("discovered %s as currently synced recipe label", initTag)
log.Warn(i18n.G("discovered %s as currently synced recipe label", initTag))
if prompt && !internal.NoInput {
var response bool
prompt := &survey.Confirm{Message: fmt.Sprintf("use %s as the new version?", initTag)}
prompt := &survey.Confirm{Message: i18n.G("use %s as the new version?", initTag)}
if err := survey.AskOne(prompt, &response); err != nil {
return "", err
}
if !response {
return "", fmt.Errorf("please fix your synced label for %s and re-run this command", recipe.Name)
return "", errors.New(i18n.G("please fix your synced label for %s and re-run this command", recipe.Name))
}
}
@ -572,42 +573,41 @@ var (
func init() {
RecipeReleaseCommand.Flags().BoolVarP(
&internal.Dry,
"dry-run",
"r",
i18n.G("dry-run"),
i18n.G("r"),
false,
"report changes that would be made",
i18n.G("report changes that would be made"),
)
RecipeReleaseCommand.Flags().BoolVarP(
&internal.Major,
"major",
"x",
i18n.G("major"),
i18n.G("x"),
false,
"increase the major part of the version",
i18n.G("increase the major part of the version"),
)
RecipeReleaseCommand.Flags().BoolVarP(
&internal.Minor,
"minor",
"y",
i18n.G("minor"),
i18n.G("y"),
false,
"increase the minor part of the version",
i18n.G("increase the minor part of the version"),
)
RecipeReleaseCommand.Flags().BoolVarP(
&internal.Patch,
"patch",
"z",
i18n.G("patch"),
i18n.G("z"),
false,
"increase the patch part of the version",
i18n.G("increase the patch part of the version"),
)
RecipeReleaseCommand.Flags().BoolVarP(
&publish,
"publish",
"p",
i18n.G("publish"),
i18n.G("p"),
false,
"publish changes to git.coopcloud.tech",
i18n.G("publish changes to git.coopcloud.tech"),
)
}

View File

@ -3,16 +3,17 @@ package recipe
import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/go-git/go-git/v5"
"github.com/spf13/cobra"
)
var RecipeResetCommand = &cobra.Command{
Use: "reset <recipe> [flags]",
Aliases: []string{"rs"},
Short: "Remove all unstaged changes from recipe config",
Long: "WARNING: this will delete your changes. Be Careful.",
Use: i18n.G("reset <recipe> [flags]"),
Aliases: []string{i18n.G("rs")},
Short: i18n.G("Remove all unstaged changes from recipe config"),
Long: i18n.G("WARNING: this will delete your changes. Be Careful."),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,

View File

@ -7,6 +7,7 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2"
@ -16,10 +17,10 @@ import (
)
var RecipeSyncCommand = &cobra.Command{
Use: "sync <recipe> [version] [flags]",
Aliases: []string{"s"},
Short: "Sync recipe version label",
Long: `Generate labels for the main recipe service.
Use: i18n.G("sync <recipe> [version] [flags]"),
Aliases: []string{i18n.G("s")},
Short: i18n.G("Sync recipe version label"),
Long: i18n.G(`Generate labels for the main recipe service.
By convention, the service named "app" using the following format:
@ -27,7 +28,7 @@ By convention, the service named "app" using the following format:
Where [version] can be specifed on the command-line or Abra can attempt to
auto-generate it for you. The <recipe> configuration will be updated on the
local file system.`,
local file system.`),
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -68,11 +69,11 @@ local file system.`,
}
if len(tags) == 0 && nextTag == "" {
log.Warnf("no git tags found for %s", recipe.Name)
log.Warn(i18n.G("no git tags found for %s", recipe.Name))
if internal.NoInput {
log.Fatalf("unable to continue, input required for initial version")
log.Fatal(i18n.G("unable to continue, input required for initial version"))
}
fmt.Println(fmt.Sprintf(`
fmt.Println(i18n.G(`
The following options are two types of initial semantic version that you can
pick for %s that will be published in the recipe catalogue. This follows the
semver convention (more on https://semver.org), here is a short cheatsheet
@ -92,7 +93,7 @@ likely to change.
`, recipe.Name, recipe.Name))
var chosenVersion string
edPrompt := &survey.Select{
Message: "which version do you want to begin with?",
Message: i18n.G("which version do you want to begin with?"),
Options: []string{"0.1.0", "1.0.0"},
}
@ -125,7 +126,7 @@ likely to change.
if err := iter.ForEach(func(ref *plumbing.Reference) error {
obj, err := repo.TagObject(ref.Hash())
if err != nil {
log.Fatal("Tag at commit ", ref.Hash(), " is unannotated or otherwise broken. Please fix it.")
log.Fatal(i18n.G("tag at commit %s is unannotated or otherwise broken", ref.Hash()))
return err
}
@ -150,7 +151,7 @@ likely to change.
if bumpType != 0 {
// a bitwise check if the number is a power of 2
if (bumpType & (bumpType - 1)) != 0 {
log.Fatal("you can only use one version flag: --major, --minor or --patch")
log.Fatal(i18n.G("you can only use one version flag: --major, --minor or --patch"))
}
}
@ -184,22 +185,22 @@ likely to change.
}
newTag.Metadata = mainAppVersion
log.Debugf("choosing %s as new version for %s", newTag.String(), recipe.Name)
log.Debug(i18n.G("choosing %s as new version for %s", newTag.String(), recipe.Name))
nextTag = newTag.String()
}
if _, err := tagcmp.Parse(nextTag); err != nil {
log.Fatalf("invalid version %s specified", nextTag)
log.Fatal(i18n.G("invalid version %s specified", nextTag))
}
mainService := "app"
label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", nextTag)
label := i18n.G("coop-cloud.${STACK_NAME}.version=%s", nextTag)
if !internal.Dry {
if err := recipe.UpdateLabel("compose.y*ml", mainService, label); err != nil {
log.Fatal(err)
}
} else {
log.Infof("dry run: not syncing label %s for recipe %s", nextTag, recipe.Name)
log.Info(i18n.G("dry run: not syncing label %s for recipe %s", nextTag, recipe.Name))
}
isClean, err := gitPkg.IsClean(recipe.Dir)
@ -207,7 +208,7 @@ likely to change.
log.Fatal(err)
}
if !isClean {
log.Infof("%s currently has these unstaged changes 👇", recipe.Name)
log.Info(i18n.G("%s currently has these unstaged changes 👇", recipe.Name))
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
log.Fatal(err)
}
@ -218,33 +219,33 @@ likely to change.
func init() {
RecipeSyncCommand.Flags().BoolVarP(
&internal.Dry,
"dry-run",
"r",
i18n.G("dry-run"),
i18n.G("r"),
false,
"report changes that would be made",
i18n.G("report changes that would be made"),
)
RecipeSyncCommand.Flags().BoolVarP(
&internal.Major,
"major",
"x",
i18n.G("major"),
i18n.G("x"),
false,
"increase the major part of the version",
i18n.G("increase the major part of the version"),
)
RecipeSyncCommand.Flags().BoolVarP(
&internal.Minor,
"minor",
"y",
i18n.G("minor"),
i18n.G("y"),
false,
"increase the minor part of the version",
i18n.G("increase the minor part of the version"),
)
RecipeSyncCommand.Flags().BoolVarP(
&internal.Patch,
"patch",
"z",
i18n.G("patch"),
i18n.G("z"),
false,
"increase the patch part of the version",
i18n.G("increase the patch part of the version"),
)
}

View File

@ -14,6 +14,7 @@ import (
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/formatter"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp"
@ -37,10 +38,10 @@ type anUpgrade struct {
}
var RecipeUpgradeCommand = &cobra.Command{
Use: "upgrade <recipe> [flags]",
Aliases: []string{"u"},
Short: "Upgrade recipe image tags",
Long: `Upgrade a given <recipe> configuration.
Use: i18n.G("upgrade <recipe> [flags]"),
Aliases: []string{i18n.G("u")},
Short: i18n.G("Upgrade recipe image tags"),
Long: i18n.G(`Upgrade a given <recipe> configuration.
It will update the relevant compose file tags on the local file system.
@ -52,7 +53,7 @@ The command is interactive and will show a select input which allows you to
make a seclection. Use the "?" key to see more help on navigating this
interface.
You may invoke this command in "wizard" mode and be prompted for input.`,
You may invoke this command in "wizard" mode and be prompted for input.`),
Args: cobra.RangeArgs(0, 1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -71,7 +72,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
if bumpType != 0 {
// a bitwise check if the number is a power of 2
if (bumpType & (bumpType - 1)) != 0 {
log.Fatal("you can only use one of: --major, --minor, --patch.")
log.Fatal(i18n.G("you can only use one of: --major, --minor, --patch."))
}
}
@ -87,7 +88,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
versionsPath := path.Join(recipe.Dir, "versions")
servicePins := make(map[string]imgPin)
if _, err := os.Stat(versionsPath); err == nil {
log.Debugf("found versions file for %s", recipe.Name)
log.Debug(i18n.G("found versions file for %s", recipe.Name))
file, err := os.Open(versionsPath)
if err != nil {
log.Fatal(err)
@ -97,7 +98,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
line := scanner.Text()
splitLine := strings.Split(line, " ")
if splitLine[0] != "pin" || len(splitLine) != 3 {
log.Fatalf("malformed version pin specification: %s", line)
log.Fatal(i18n.G("malformed version pin specification: %s", line))
}
pinSlice := strings.Split(splitLine[2], ":")
pinTag, err := tagcmp.Parse(pinSlice[1])
@ -115,7 +116,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
}
versionsPresent = true
} else {
log.Debugf("did not find versions file for %s", recipe.Name)
log.Debug(i18n.G("did not find versions file for %s", recipe.Name))
}
config, err := recipe.GetComposeConfig(nil)
@ -135,26 +136,26 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
}
image := reference.Path(img)
log.Debugf("retrieved %s from remote registry for %s", regVersions, image)
log.Debug(i18n.G("retrieved %s from remote registry for %s", regVersions, image))
image = formatter.StripTagMeta(image)
switch img.(type) {
case reference.NamedTagged:
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
log.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag())
log.Debug(i18n.G("%s not considered semver-like", img.(reference.NamedTagged).Tag()))
}
default:
log.Warnf("unable to read tag for image %s, is it missing? skipping upgrade for %s", image, service.Name)
log.Warn(i18n.G("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 {
log.Warnf("unable to parse %s, error was: %s, skipping upgrade for %s", image, err.Error(), service.Name)
log.Warn(i18n.G("unable to parse %s, error was: %s, skipping upgrade for %s", image, err.Error(), service.Name))
continue
}
log.Debugf("parsed %s for %s", tag, service.Name)
log.Debug(i18n.G("parsed %s for %s", tag, service.Name))
var compatible []tagcmp.Tag
for _, regVersion := range regVersions {
@ -168,12 +169,12 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
}
}
log.Debugf("detected potential upgradable tags %s for %s", compatible, service.Name)
log.Debug(i18n.G("detected potential upgradable tags %s for %s", compatible, service.Name))
sort.Sort(tagcmp.ByTagDesc(compatible))
if len(compatible) == 0 && !allTags {
log.Info(fmt.Sprintf("no new versions available for %s, assuming %s is the latest (use -a/--all-tags to see all anyway)", image, tag))
log.Info(i18n.G("no new versions available for %s, assuming %s is the latest (use -a/--all-tags to see all anyway)", image, tag))
continue // skip on to the next tag and don't update any compose files
}
@ -195,7 +196,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
}
}
log.Debugf("detected compatible upgradable tags %s for %s", compatibleStrings, service.Name)
log.Debug(i18n.G("detected compatible upgradable tags %s for %s", compatibleStrings, service.Name))
var upgradeTag string
_, ok := servicePins[service.Name]
@ -212,13 +213,13 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
}
}
if contains {
log.Infof("upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString)
log.Info(i18n.G("upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString))
} else {
log.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString)
log.Info(i18n.G("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString))
continue
}
} else {
log.Fatalf("service %s is at version %s, but pinned to %s, please correct your compose.yml file manually!", service.Name, tag.String(), pinnedTag.String())
log.Fatal(i18n.G("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 {
@ -235,17 +236,17 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
}
}
if upgradeTag == "" {
log.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)
log.Warn(i18n.G("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)
msg := i18n.G("upgrade to which tag? (service: %s, image: %s, tag: %s)", service.Name, image, tag)
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) || allTags {
tag := img.(reference.NamedTagged).Tag()
if !allTags {
log.Warn(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", tag))
log.Warn(i18n.G("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 = i18n.G("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
compatibleStrings = []string{"skip"}
for _, regVersion := range regVersions {
compatibleStrings = append(compatibleStrings, regVersion)
@ -276,7 +277,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
} else {
prompt := &survey.Select{
Message: msg,
Help: "enter / return to confirm, choose 'skip' to not upgrade this tag, vim mode is enabled",
Help: i18n.G("enter / return to confirm, choose 'skip' to not upgrade this tag, vim mode is enabled"),
VimMode: true,
Options: compatibleStrings,
}
@ -292,11 +293,11 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
log.Fatal(err)
}
if ok {
log.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image)
log.Info(i18n.G("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image))
}
} else {
if !internal.NoInput {
log.Warnf("not upgrading %s, skipping as requested", image)
log.Warn(i18n.G("not upgrading %s, skipping as requested", image))
}
}
}
@ -314,7 +315,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
}
for _, upgrade := range upgradeList {
log.Infof("can upgrade service: %s, image: %s, tag: %s ::", upgrade.Service, upgrade.Image, upgrade.Tag)
log.Info(i18n.G("can upgrade service: %s, image: %s, tag: %s ::", upgrade.Service, upgrade.Image, upgrade.Tag))
for _, utag := range upgrade.UpgradeTags {
log.Infof(" %s", utag)
}
@ -326,7 +327,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
log.Fatal(err)
}
if !isClean {
log.Infof("%s currently has these unstaged changes 👇", recipe.Name)
log.Info(i18n.G("%s currently has these unstaged changes 👇", recipe.Name))
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
log.Fatal(err)
}
@ -341,41 +342,41 @@ var (
func init() {
RecipeUpgradeCommand.Flags().BoolVarP(
&internal.Major,
"major",
"x",
i18n.G("major"),
i18n.G("x"),
false,
"increase the major part of the version",
i18n.G("increase the major part of the version"),
)
RecipeUpgradeCommand.Flags().BoolVarP(
&internal.Minor,
"minor",
"y",
i18n.G("minor"),
i18n.G("y"),
false,
"increase the minor part of the version",
i18n.G("increase the minor part of the version"),
)
RecipeUpgradeCommand.Flags().BoolVarP(
&internal.Patch,
"patch",
"z",
i18n.G("patch"),
i18n.G("z"),
false,
"increase the patch part of the version",
i18n.G("increase the patch part of the version"),
)
RecipeUpgradeCommand.Flags().BoolVarP(
&internal.MachineReadable,
"machine",
"m",
i18n.G("machine"),
i18n.G("m"),
false,
"print machine-readable output",
i18n.G("print machine-readable output"),
)
RecipeUpgradeCommand.Flags().BoolVarP(
&allTags,
"all-tags",
"a",
i18n.G("all-tags"),
i18n.G("a"),
false,
"list all tags, not just upgrades",
i18n.G("list all tags, not just upgrades"),
)
}

View File

@ -7,15 +7,16 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/spf13/cobra"
)
var RecipeVersionCommand = &cobra.Command{
Use: "versions <recipe> [flags]",
Aliases: []string{"v"},
Short: "List recipe versions",
Use: i18n.G("versions <recipe> [flags]"),
Aliases: []string{i18n.G("v")},
Short: i18n.G("List recipe versions"),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -35,7 +36,7 @@ var RecipeVersionCommand = &cobra.Command{
recipeMeta, ok := catl[recipe.Name]
if !ok {
warnMessages = append(warnMessages, "retrieved versions from local recipe repository")
warnMessages = append(warnMessages, i18n.G("retrieved versions from local recipe repository"))
recipeVersions, warnMsg, err := recipe.GetRecipeVersions()
if err != nil {
@ -49,7 +50,7 @@ var RecipeVersionCommand = &cobra.Command{
}
if len(recipeMeta.Versions) == 0 {
log.Fatalf("%s has no published versions?", recipe.Name)
log.Fatal(i18n.G("%s has no published versions?", recipe.Name))
}
for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
@ -58,7 +59,7 @@ var RecipeVersionCommand = &cobra.Command{
log.Fatal(err)
}
table.Headers("SERVICE", "IMAGE", "TAG", "VERSION")
table.Headers(i18n.G("SERVICE"), i18n.G("IMAGE"), i18n.G("TAG"), i18n.G("VERSION"))
for version, meta := range recipeMeta.Versions[i] {
var allRows [][]string
@ -99,10 +100,10 @@ var RecipeVersionCommand = &cobra.Command{
if internal.MachineReadable {
sort.Slice(allRows, sortServiceByName(allRows))
headers := []string{"VERSION", "SERVICE", "NAME", "TAG"}
headers := []string{i18n.G("VERSION"), i18n.G("SERVICE"), i18n.G("NAME"), i18n.G("TAG")}
out, err := formatter.ToJSON(headers, allRows)
if err != nil {
log.Fatal("unable to render to JSON: %s", err)
log.Fatal(i18n.G("unable to render to JSON: %s", err))
}
fmt.Println(out)
continue
@ -127,9 +128,9 @@ func sortServiceByName(versions [][]string) func(i, j int) bool {
func init() {
RecipeVersionCommand.Flags().BoolVarP(
&internal.MachineReadable,
"machine",
"m",
i18n.G("machine"),
i18n.G("m"),
false,
"print machine-readable output",
i18n.G("print machine-readable output"),
)
}

View File

@ -1,6 +1,7 @@
package cli
import (
"errors"
"fmt"
"os"
@ -10,6 +11,8 @@ import (
"coopcloud.tech/abra/cli/recipe"
"coopcloud.tech/abra/cli/server"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
charmLog "github.com/charmbracelet/log"
"github.com/spf13/cobra"
@ -18,19 +21,20 @@ import (
func Run(version, commit string) {
rootCmd := &cobra.Command{
Use: "abra [cmd] [args] [flags]",
Short: "The Co-op Cloud command-line utility belt 🎩🐇",
Use: i18n.G("abra [cmd] [args] [flags]"),
Short: i18n.G("The Co-op Cloud command-line utility belt 🎩🐇"),
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
ValidArgs: []string{
"app",
"autocomplete",
"catalogue",
"man",
"recipe",
"server",
"upgrade",
i18n.G("app"),
i18n.G("autocomplete"),
i18n.G("catalogue"),
i18n.G("man"),
i18n.G("recipe"),
i18n.G("server"),
i18n.G("upgrade"),
},
PersistentPreRun: func(cmd *cobra.Command, args []string) {
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
dirs := []map[string]os.FileMode{
{config.ABRA_DIR: 0764},
{config.SERVERS_DIR: 0700},
@ -42,7 +46,7 @@ func Run(version, commit string) {
for path, perm := range dir {
if err := os.Mkdir(path, perm); err != nil {
if !os.IsExist(err) {
log.Fatal(err)
return errors.New(i18n.G("unable to create %s: %s", path, err))
}
continue
}
@ -62,24 +66,38 @@ func Run(version, commit string) {
log.SetReportCaller(true)
}
log.Debugf("abra version %s, commit %s", version, commit)
if internal.Locale != "" {
// FIXME(d1): get list of valid locales from pkg/locales/*
switch internal.Locale {
case "es":
default:
return errors.New(i18n.G("unsupported --lang: %s (want: es)", internal.Locale))
}
}
log.Debug(i18n.G(
"abra version: %s, commit: %s, lang: %s",
version, formatter.SmallSHA(commit), internal.Locale,
))
return nil
},
}
rootCmd.CompletionOptions.DisableDefaultCmd = true
manCommand := &cobra.Command{
Use: "man [flags]",
Use: i18n.G("man [flags]"),
Aliases: []string{"m"},
Short: "Generate manpage",
Example: ` # generate the man pages into /usr/local/share/man/man1
Short: i18n.G("Generate manpage"),
Example: i18n.G(` # generate the man pages into /usr/local/share/man/man1
abra_path=$(which abra) # pass abra absolute path to sudo below
sudo $abra_path man
sudo mandb
# read the man pages
man abra
man abra-app-deploy`,
man abra-app-deploy`),
Run: func(cmd *cobra.Command, args []string) {
header := &doc.GenManHeader{
Title: "ABRA",
@ -88,7 +106,7 @@ func Run(version, commit string) {
manDir := "/usr/local/share/man/man1"
if _, err := os.Stat(manDir); os.IsNotExist(err) {
log.Fatalf("unable to proceed, '%s' does not exist?")
log.Fatal(i18n.G("unable to proceed, %s does not exist?", manDir))
}
err := doc.GenManTree(rootCmd, header, manDir)
@ -96,7 +114,7 @@ func Run(version, commit string) {
log.Fatal(err)
}
log.Info("don't forget to run 'sudo mandb'")
log.Info(i18n.G("don't forget to run 'sudo mandb'"))
},
}
@ -105,7 +123,7 @@ func Run(version, commit string) {
"debug",
"d",
false,
"show debug messages",
i18n.G("show debug messages"),
)
rootCmd.PersistentFlags().BoolVarP(
@ -113,7 +131,7 @@ func Run(version, commit string) {
"no-input",
"n",
false,
"toggle non-interactive mode",
i18n.G("toggle non-interactive mode"),
)
rootCmd.PersistentFlags().BoolVarP(
@ -121,7 +139,7 @@ func Run(version, commit string) {
"offline",
"o",
false,
"prefer offline & filesystem access",
i18n.G("prefer offline & filesystem access"),
)
rootCmd.PersistentFlags().BoolVarP(
@ -129,7 +147,16 @@ func Run(version, commit string) {
"ignore-env-version",
"i",
false,
"ignore .env version checkout",
i18n.G("ignore .env version checkout"),
)
rootCmd.PersistentFlags().StringVarP(
&internal.Locale,
"lang",
"l",
"",
// FIXME(d1): provide supported list of language via *.po file listing
i18n.G("force select language (supported: XXX)"),
)
catalogue.CatalogueCommand.AddCommand(

View File

@ -10,6 +10,7 @@ import (
"coopcloud.tech/abra/pkg/config"
contextPkg "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/dns"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/server"
sshPkg "coopcloud.tech/abra/pkg/ssh"
@ -17,10 +18,10 @@ import (
)
var ServerAddCommand = &cobra.Command{
Use: "add [[server] | --local] [flags]",
Aliases: []string{"a"},
Short: "Add a new server",
Long: `Add a new server to your configuration so that it can be managed by Abra.
Use: i18n.G("add [[server] | --local] [flags]"),
Aliases: []string{i18n.G("a")},
Short: i18n.G("Add a new server"),
Long: i18n.G(`Add a new server to your configuration so that it can be managed by Abra.
Abra relies on the standard SSH command-line and ~/.ssh/config for client
connection details. You must configure an entry per-host in your ~/.ssh/config
@ -35,8 +36,8 @@ for each server:
If "--local" is passed, then Abra assumes that the current local server is
intended as the target server. This is useful when you want to have your entire
Co-op Cloud config located on the server itself, and not on your local
developer machine. The domain is then set to "default".`,
Example: " abra server add 1312.net",
developer machine. The domain is then set to "default".`),
Example: i18n.G(" abra server add 1312.net"),
Args: cobra.RangeArgs(0, 1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -49,11 +50,11 @@ developer machine. The domain is then set to "default".`,
},
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 && local {
log.Fatal("cannot use [server] and --local together")
log.Fatal(i18n.G("cannot use [server] and --local together"))
}
if len(args) == 0 && !local {
log.Fatal("missing argument or --local/-l flag")
log.Fatal(i18n.G("missing argument or --local/-l flag"))
}
name := "default"
@ -72,7 +73,7 @@ developer machine. The domain is then set to "default".`,
log.Fatal(err)
}
log.Debugf("attempting to create client for %s", name)
log.Debug(i18n.G("attempting to create client for %s", name))
if _, err := client.New(name, timeout); err != nil {
cleanUp(name)
@ -80,9 +81,9 @@ developer machine. The domain is then set to "default".`,
}
if created {
log.Info("local server successfully added")
log.Info(i18n.G("local server successfully added"))
} else {
log.Warn("local server already exists")
log.Warn(i18n.G("local server already exists"))
}
return
@ -96,27 +97,27 @@ developer machine. The domain is then set to "default".`,
created, err := newContext(name)
if err != nil {
cleanUp(name)
log.Fatalf("unable to create local context: %s", err)
log.Fatal(i18n.G("unable to create local context: %s", err))
}
log.Debugf("attempting to create client for %s", name)
log.Debug(i18n.G("attempting to create client for %s", name))
if _, err := client.New(name, timeout); err != nil {
cleanUp(name)
log.Fatalf("ssh %s error: %s", name, sshPkg.Fatal(name, err))
log.Fatal(i18n.G("ssh %s error: %s", name, sshPkg.Fatal(name, err)))
}
if created {
log.Infof("%s successfully added", name)
log.Info(i18n.G("%s successfully added", name))
if _, err := dns.EnsureIPv4(name); err != nil {
log.Warnf("unable to resolve IPv4 for %s", name)
log.Warn(i18n.G("unable to resolve IPv4 for %s", name))
}
return
}
log.Warnf("%s already exists", name)
log.Warn(i18n.G("%s already exists", name))
},
}
@ -124,7 +125,7 @@ developer machine. The domain is then set to "default".`,
// "server add" attempt.
func cleanUp(name string) {
if name != "default" {
log.Debugf("serverAdd: cleanUp: cleaning up context for %s", name)
log.Debug(i18n.G("serverAdd: cleanUp: cleaning up context for %s", name))
if err := client.DeleteContext(name); err != nil {
log.Fatal(err)
}
@ -133,16 +134,16 @@ func cleanUp(name string) {
serverDir := filepath.Join(config.SERVERS_DIR, name)
files, err := config.GetAllFilesInDirectory(serverDir)
if err != nil {
log.Fatalf("serverAdd: cleanUp: unable to list files in %s: %s", serverDir, err)
log.Fatal(i18n.G("serverAdd: cleanUp: unable to list files in %s: %s", serverDir, err))
}
if len(files) > 0 {
log.Debugf("serverAdd: cleanUp: %s is not empty, aborting cleanup", serverDir)
log.Debug(i18n.G("serverAdd: cleanUp: %s is not empty, aborting cleanup", serverDir))
return
}
if err := os.RemoveAll(serverDir); err != nil {
log.Fatalf("serverAdd: cleanUp: failed to remove %s: %s", serverDir, err)
log.Fatal(i18n.G("serverAdd: cleanUp: failed to remove %s: %s", serverDir, err))
}
}
@ -159,12 +160,12 @@ func newContext(name string) (bool, error) {
for _, context := range contexts {
if context.Name == name {
log.Debugf("context for %s already exists", name)
log.Debug(i18n.G("context for %s already exists", name))
return false, nil
}
}
log.Debugf("creating context with domain %s", name)
log.Debugf(i18n.G("creating context with domain %s", name))
if err := client.CreateContext(name); err != nil {
return false, nil
@ -180,7 +181,7 @@ func createServerDir(name string) (bool, error) {
return false, err
}
log.Debugf("server dir for %s already created", name)
log.Debug(i18n.G("server dir for %s already created", name))
return false, nil
}
@ -195,9 +196,9 @@ var (
func init() {
ServerAddCommand.Flags().BoolVarP(
&local,
"local",
"l",
i18n.G("local"),
i18n.G("l"),
false,
"use local server",
i18n.G("use local server"),
)
}

View File

@ -8,15 +8,16 @@ import (
"coopcloud.tech/abra/pkg/config"
contextPkg "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/docker/cli/cli/connhelper/ssh"
"github.com/spf13/cobra"
)
var ServerListCommand = &cobra.Command{
Use: "list [flags]",
Aliases: []string{"ls"},
Short: "List managed servers",
Use: i18n.G("list [flags]"),
Aliases: []string{i18n.G("ls")},
Short: i18n.G("List managed servers"),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
dockerContextStore := contextPkg.NewDefaultDockerContextStore()
@ -30,7 +31,7 @@ var ServerListCommand = &cobra.Command{
log.Fatal(err)
}
headers := []string{"NAME", "HOST"}
headers := []string{i18n.G("NAME"), i18n.G("HOST")}
table.Headers(headers...)
serverNames, err := config.ReadServerNames()
@ -55,7 +56,7 @@ var ServerListCommand = &cobra.Command{
}
if sp.Host == "" {
sp.Host = "unknown"
sp.Host = i18n.G("unknown")
}
row = []string{serverName, sp.Host}
@ -65,9 +66,9 @@ var ServerListCommand = &cobra.Command{
if len(row) == 0 {
if serverName == "default" {
row = []string{serverName, "local"}
row = []string{serverName, i18n.G("local")}
} else {
row = []string{serverName, "unknown"}
row = []string{serverName, i18n.G("unknown")}
}
rows = append(rows, row)
}
@ -78,7 +79,7 @@ var ServerListCommand = &cobra.Command{
if internal.MachineReadable {
out, err := formatter.ToJSON(headers, rows)
if err != nil {
log.Fatal("unable to render to JSON: %s", err)
log.Fatal(i18n.G("unable to render to JSON: %s", err))
}
fmt.Println(out)
@ -95,9 +96,9 @@ var ServerListCommand = &cobra.Command{
func init() {
ServerListCommand.Flags().BoolVarP(
&internal.MachineReadable,
"machine",
"m",
i18n.G("machine"),
i18n.G("m"),
false,
"print machine-readable output",
i18n.G("print machine-readable output"),
)
}

View File

@ -5,19 +5,20 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/docker/docker/api/types/filters"
"github.com/spf13/cobra"
)
var ServerPruneCommand = &cobra.Command{
Use: "prune <server> [flags]",
Aliases: []string{"p"},
Short: "Prune resources on a server",
Long: `Prunes unused containers, networks, and dangling images.
Use: i18n.G("prune <server> [flags]"),
Aliases: []string{i18n.G("p")},
Short: i18n.G("Prune resources on a server"),
Long: i18n.G(`Prunes unused containers, networks, and dangling images.
Use "--volumes/-v" to remove volumes that are not associated with a deployed
app. This can result in unwanted data loss if not used carefully.`,
app. This can result in unwanted data loss if not used carefully.`),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -41,18 +42,18 @@ app. This can result in unwanted data loss if not used carefully.`,
}
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
log.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
log.Info(i18n.G("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed))
nr, err := cl.NetworksPrune(cmd.Context(), filterArgs)
if err != nil {
log.Fatal(err)
}
log.Infof("networks pruned: %d", len(nr.NetworksDeleted))
log.Info(i18n.G("networks pruned: %d", len(nr.NetworksDeleted)))
pruneFilters := filters.NewArgs()
if allFilter {
log.Debugf("removing all images, not only dangling ones")
log.Debug(i18n.G("removing all images, not only dangling ones"))
pruneFilters.Add("dangling", "false")
}
@ -62,7 +63,7 @@ app. This can result in unwanted data loss if not used carefully.`,
}
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
log.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
log.Info(i18n.G("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed))
if volumesFilter {
vr, err := cl.VolumesPrune(cmd.Context(), filterArgs)
@ -71,7 +72,7 @@ app. This can result in unwanted data loss if not used carefully.`,
}
volSpaceReclaimed := formatter.ByteCountSI(vr.SpaceReclaimed)
log.Infof("volumes pruned: %d; space reclaimed: %s", len(vr.VolumesDeleted), volSpaceReclaimed)
log.Info(i18n.G("volumes pruned: %d; space reclaimed: %s", len(vr.VolumesDeleted), volSpaceReclaimed))
}
return
@ -86,17 +87,17 @@ var (
func init() {
ServerPruneCommand.Flags().BoolVarP(
&allFilter,
"all",
"a",
i18n.G("all"),
i18n.G("a"),
false,
"remove all unused images",
i18n.G("remove all unused images"),
)
ServerPruneCommand.Flags().BoolVarP(
&volumesFilter,
"volumes",
"v",
i18n.G("volumes"),
i18n.G("v"),
false,
"remove volumes",
i18n.G("remove volumes"),
)
}

View File

@ -8,19 +8,20 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"github.com/spf13/cobra"
)
var ServerRemoveCommand = &cobra.Command{
Use: "remove <server> [flags]",
Aliases: []string{"rm"},
Short: "Remove a managed server",
Long: `Remove a managed server.
Use: i18n.G("remove <server> [flags]"),
Aliases: []string{i18n.G("rm")},
Short: i18n.G("Remove a managed server"),
Long: i18n.G(`Remove a managed server.
Abra will remove the internal bookkeeping ($ABRA_DIR/servers/...) and
underlying client connection context. This server will then be lost in time,
like tears in rain.`,
like tears in rain.`),
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -39,7 +40,7 @@ like tears in rain.`,
log.Fatal(err)
}
log.Infof("%s is now lost in time, like tears in rain", serverName)
log.Info(i18n.G("%s is now lost in time, like tears in rain", serverName))
return
},

View File

@ -1,10 +1,13 @@
package server
import "github.com/spf13/cobra"
import (
"coopcloud.tech/abra/pkg/i18n"
"github.com/spf13/cobra"
)
// ServerCommand defines the `abra server` command and its subcommands
var ServerCommand = &cobra.Command{
Use: "server [cmd] [args] [flags]",
Aliases: []string{"s"},
Short: "Manage servers",
Use: i18n.G("server [cmd] [args] [flags]"),
Aliases: []string{i18n.G("s")},
Short: i18n.G("Manage servers"),
}

View File

@ -2,6 +2,7 @@ package updater
import (
"context"
"errors"
"fmt"
"os"
"strconv"
@ -11,6 +12,7 @@ import (
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/upstream/convert"
@ -30,14 +32,14 @@ const SERVER = "localhost"
// NotifyCommand checks for available upgrades.
var NotifyCommand = &cobra.Command{
Use: "notify [flags]",
Aliases: []string{"n"},
Short: "Check for available upgrades",
Long: `Notify on new versions for deployed apps.
Use: i18n.G("notify [flags]"),
Aliases: []string{i18n.G("n")},
Short: i18n.G("Check for available upgrades"),
Long: i18n.G(`Notify on new versions for deployed apps.
If a new patch/minor version is available, a notification is printed.
Use "--major/-m" to include new major versions.`,
Use "--major/-m" to include new major versions.`),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
cl, err := client.New("default")
@ -69,10 +71,10 @@ Use "--major/-m" to include new major versions.`,
// UpgradeCommand upgrades apps.
var UpgradeCommand = &cobra.Command{
Use: "upgrade [[stack] [recipe] | --all] [flags]",
Aliases: []string{"u"},
Short: "Upgrade apps",
Long: `Upgrade an app by specifying stack name and recipe.
Use: i18n.G("upgrade [[stack] [recipe] | --all] [flags]"),
Aliases: []string{i18n.G("u")},
Short: i18n.G("Upgrade apps"),
Long: i18n.G(`Upgrade an app by specifying stack name and recipe.
Use "--all" to upgrade every deployed app.
@ -83,7 +85,7 @@ available, the app is upgraded.
To include major versions use the "--major/-m" flag. You probably don't want
that as it will break things. Only apps that are not deployed with "--chaos/-C"
are upgraded, to update chaos deployments use the "--chaos/-C" flag. Use it
with care.`,
with care.`),
Args: cobra.RangeArgs(0, 2),
// TODO(d1): complete stack/recipe
// ValidArgsFunction: func(
@ -98,7 +100,7 @@ with care.`,
}
if !updateAll && len(args) != 2 {
log.Fatal("missing arguments or --all/-a flag")
log.Fatal(i18n.G("missing arguments or --all/-a flag"))
}
if !updateAll {
@ -150,7 +152,7 @@ func getLabel(cl *dockerclient.Client, stackName string, label string) (string,
}
}
log.Debugf("no %s label found for %s", label, stackName)
log.Debug(i18n.G("no %s label found for %s", label, stackName))
return "", nil
}
@ -171,7 +173,7 @@ func getBoolLabel(cl *dockerclient.Client, stackName string, label string) (bool
return value, nil
}
log.Debugf("boolean label %s could not be found for %s, set default to false.", label, stackName)
log.Debug(i18n.G("boolean label %s could not be found for %s, set default to false.", label, stackName))
return false, nil
}
@ -192,12 +194,12 @@ func getEnv(cl *dockerclient.Client, stackName string) (envfile.AppEnv, error) {
for _, envString := range envList {
splitString := strings.SplitN(envString, "=", 2)
if len(splitString) != 2 {
log.Debugf("can't separate key from value: %s (this variable is probably unset)", envString)
log.Debug(i18n.G("can't separate key from value: %s (this variable is probably unset)", envString))
continue
}
k := splitString[0]
v := splitString[1]
log.Debugf("for %s read env %s with value: %s from docker service", stackName, k, v)
log.Debugf(i18n.G("for %s read env %s with value: %s from docker service", stackName, k, v))
envMap[k] = v
}
}
@ -219,14 +221,14 @@ func getLatestUpgrade(cl *dockerclient.Client, stackName string, recipeName stri
}
if len(availableUpgrades) == 0 {
log.Debugf("no available upgrades for %s", stackName)
log.Debugf(i18n.G("no available upgrades for %s", stackName))
return "", nil
}
var chosenUpgrade string
if len(availableUpgrades) > 0 {
chosenUpgrade = availableUpgrades[len(availableUpgrades)-1]
log.Infof("%s (%s) can be upgraded from version %s to %s", stackName, recipeName, deployedVersion, chosenUpgrade)
log.Info(i18n.G("%s (%s) can be upgraded from version %s to %s", stackName, recipeName, deployedVersion, chosenUpgrade))
}
return chosenUpgrade, nil
@ -234,7 +236,7 @@ func getLatestUpgrade(cl *dockerclient.Client, stackName string, recipeName stri
// getDeployedVersion returns the currently deployed version of an app.
func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName string) (string, error) {
log.Debugf("retrieve deployed version whether %s is already deployed", stackName)
log.Debug(i18n.G("retrieve deployed version whether %s is already deployed", stackName))
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
if err != nil {
@ -242,11 +244,11 @@ func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName st
}
if !deployMeta.IsDeployed {
return "", fmt.Errorf("%s is not deployed?", stackName)
return "", errors.New(i18n.G("%s is not deployed?", stackName))
}
if deployMeta.Version == "unknown" {
return "", fmt.Errorf("failed to determine deployed version of %s", stackName)
return "", errors.New(i18n.G("failed to determine deployed version of %s", stackName))
}
return deployMeta.Version, nil
@ -268,7 +270,7 @@ func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName
}
if len(versions) == 0 {
log.Warnf("no published releases for %s in the recipe catalogue?", recipeName)
log.Warn(i18n.G("no published releases for %s in the recipe catalogue?", recipeName))
return nil, nil
}
@ -294,7 +296,7 @@ func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName
}
}
log.Debugf("available updates for %s: %s", stackName, availableUpgrades)
log.Debug(i18n.G("available updates for %s: %s", stackName, availableUpgrades))
return availableUpgrades, nil
}
@ -371,7 +373,7 @@ func createDeployConfig(r recipe.Recipe, stackName string, env envfile.AppEnv) (
// tryUpgrade performs the upgrade if all the requirements are fulfilled.
func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error {
if recipeName == "" {
log.Debugf("don't update %s due to missing recipe name", stackName)
log.Debug(i18n.G("don't update %s due to missing recipe name", stackName))
return nil
}
@ -381,7 +383,7 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error {
}
if chaos && !internal.Chaos {
log.Debugf("don't update %s due to chaos deployment", stackName)
log.Debug(i18n.G("don't update %s due to chaos deployment", stackName))
return nil
}
@ -391,7 +393,7 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error {
}
if !updatesEnabled {
log.Debugf("don't update %s due to disabled auto updates or missing ENABLE_AUTO_UPDATE env", stackName)
log.Debug(i18n.G("don't update %s due to disabled auto updates or missing ENABLE_AUTO_UPDATE env", stackName))
return nil
}
@ -401,7 +403,7 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error {
}
if upgradeVersion == "" {
log.Debugf("don't update %s due to no new version", stackName)
log.Debug(i18n.G("don't update %s due to no new version", stackName))
return nil
}
@ -439,7 +441,7 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion stri
return err
}
log.Infof("upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion)
log.Info(i18n.G("upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion))
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
if err != nil {
@ -466,9 +468,9 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion stri
func newKadabraApp(version, commit string) *cobra.Command {
rootCmd := &cobra.Command{
Use: "kadabra [cmd] [flags]",
Use: i18n.G("kadabra [cmd] [flags]"),
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
Short: "The Co-op Cloud auto-updater 🤖 🚀",
Short: i18n.G("The Co-op Cloud auto-updater 🤖 🚀"),
PersistentPreRun: func(cmd *cobra.Command, args []string) {
log.Logger.SetStyles(charmLog.DefaultStyles())
charmLog.SetDefault(log.Logger)
@ -479,18 +481,24 @@ func newKadabraApp(version, commit string) *cobra.Command {
log.SetReportCaller(true)
}
log.Debugf("kadabra version %s, commit %s", version, commit)
log.Debug(i18n.G("kadabra version %s, commit %s", version, commit))
},
}
rootCmd.PersistentFlags().BoolVarP(
&internal.Debug, "debug", "d", false,
"show debug messages",
&internal.Debug,
i18n.G("debug"),
i18n.G("d"),
false,
i18n.G("show debug messages"),
)
rootCmd.PersistentFlags().BoolVarP(
&internal.NoInput, "no-input", "n", false,
"toggle non-interactive mode",
&internal.NoInput,
i18n.G("no-input"),
i18n.G("n"),
false,
i18n.G("toggle non-interactive mode"),
)
rootCmd.AddCommand(
@ -526,25 +534,25 @@ func init() {
UpgradeCommand.Flags().BoolVarP(
&internal.Chaos,
"chaos",
"C",
i18n.G("chaos"),
i18n.G("C"),
false,
"ignore uncommitted recipes changes",
i18n.G("ignore uncommitted recipes changes"),
)
UpgradeCommand.Flags().BoolVarP(
&includeMajorUpdates,
"major",
"m",
i18n.G("major"),
i18n.G("m"),
false,
"check for major updates",
i18n.G("check for major updates"),
)
UpgradeCommand.Flags().BoolVarP(
&updateAll,
"all",
"a",
i18n.G("all"),
i18n.G("a"),
false,
"update all deployed apps",
i18n.G("update all deployed apps"),
)
}

View File

@ -7,22 +7,23 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/i18n"
"github.com/spf13/cobra"
)
// UpgradeCommand upgrades abra in-place.
var UpgradeCommand = &cobra.Command{
Use: "upgrade [flags]",
Use: i18n.G("upgrade [flags]"),
Aliases: []string{"u"},
Short: "Upgrade abra",
Long: `Upgrade abra in-place with the latest stable or release candidate.
Short: i18n.G("Upgrade abra"),
Long: i18n.G(`Upgrade abra in-place with the latest stable or release candidate.
By default, the latest stable release is downloaded.
Use "--rc/-r" to install the latest release candidate. Please bear in mind that
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
for the testing efforts 💗`,
Example: " abra upgrade --rc",
for the testing efforts 💗`),
Example: i18n.G(" abra upgrade --rc"),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
mainURL := "https://install.abra.coopcloud.tech"
@ -33,7 +34,7 @@ for the testing efforts 💗`,
c = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL))
}
log.Debugf("attempting to run %s", c)
log.Debugf(i18n.G("attempting to run %s", c))
if err := internal.RunCmd(c); err != nil {
log.Fatal(err)
@ -51,6 +52,6 @@ func init() {
"rc",
"r",
false,
"install release candidate (may contain bugs)",
i18n.G("install release candidate (may contain bugs)"),
)
}