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 d9cffbe6d1
68 changed files with 735 additions and 729 deletions

View File

@ -6,7 +6,7 @@ import (
)
var AppCommand = &cobra.Command{
Use: "app [cmd] [args] [flags]",
Use: gotext.Get("app [cmd] [args] [flags]"),
Aliases: []string{"a"},
Short: gotext.Get("Manage apps"),
}

View File

@ -7,13 +7,14 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
var AppBackupListCommand = &cobra.Command{
Use: "list <domain> [flags]",
Use: gotext.Get("list <domain> [flags]"),
Aliases: []string{"ls"},
Short: "List the contents of a snapshot",
Short: gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("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]",
Use: gotext.Get("download <domain> [flags]"),
Aliases: []string{"d"},
Short: "Download a snapshot",
Long: `Downloads a backup.tar.gz to the current working directory.
Short: gotext.Get("Download a snapshot"),
Long: gotext.Get(`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(gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("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]",
Use: gotext.Get("create <domain> [flags]"),
Aliases: []string{"c"},
Short: "Create a new snapshot",
Short: gotext.Get("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(gotext.Get("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]",
Use: gotext.Get("snapshots <domain> [flags]"),
Aliases: []string{"s"},
Short: "List all snapshots",
Short: gotext.Get("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]",
Use: gotext.Get("backup [cmd] [args] [flags]"),
Aliases: []string{"b"},
Short: "Manage app backups",
Short: gotext.Get("Manage app backups"),
}
var (
@ -230,7 +231,7 @@ func init() {
"snapshot",
"s",
"",
"list specific snapshot",
gotext.Get("list specific snapshot"),
)
AppBackupListCommand.Flags().BoolVarP(
@ -238,7 +239,7 @@ func init() {
"all",
"a",
false,
"show all paths",
gotext.Get("show all paths"),
)
AppBackupListCommand.Flags().BoolVarP(
@ -246,7 +247,7 @@ func init() {
"timestamps",
"t",
false,
"include timestamps",
gotext.Get("include timestamps"),
)
AppBackupDownloadCommand.Flags().StringVarP(
@ -254,7 +255,7 @@ func init() {
"snapshot",
"s",
"",
"list specific snapshot",
gotext.Get("list specific snapshot"),
)
AppBackupDownloadCommand.Flags().StringVarP(
@ -262,7 +263,7 @@ func init() {
"path",
"p",
"",
"volumes path",
gotext.Get("volumes path"),
)
AppBackupDownloadCommand.Flags().BoolVarP(
@ -270,7 +271,7 @@ func init() {
"secrets",
"S",
false,
"include secrets",
gotext.Get("include secrets"),
)
AppBackupDownloadCommand.Flags().BoolVarP(
@ -278,7 +279,7 @@ func init() {
"volumes",
"v",
false,
"include volumes",
gotext.Get("include volumes"),
)
AppBackupDownloadCommand.Flags().BoolVarP(
@ -286,7 +287,7 @@ func init() {
"chaos",
"C",
false,
"ignore uncommitted recipes changes",
gotext.Get("ignore uncommitted recipes changes"),
)
AppBackupCreateCommand.Flags().StringVarP(
@ -294,7 +295,7 @@ func init() {
"retries",
"r",
"1",
"number of retry attempts",
gotext.Get("number of retry attempts"),
)
AppBackupCreateCommand.Flags().BoolVarP(
@ -302,6 +303,6 @@ func init() {
"chaos",
"C",
false,
"ignore uncommitted recipes changes",
gotext.Get("ignore uncommitted recipes changes"),
)
}

View File

@ -9,14 +9,15 @@ import (
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log"
"github.com/charmbracelet/lipgloss"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
var AppCheckCommand = &cobra.Command{
Use: "check <domain> [flags]",
Use: gotext.Get("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.
Short: gotext.Get("Ensure an app is well configured"),
Long: gotext.Get(`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,
@ -86,6 +87,6 @@ func init() {
"chaos",
"C",
false,
"ignore uncommitted recipes changes",
gotext.Get("ignore uncommitted recipes changes"),
)
}

View File

@ -14,14 +14,15 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
var AppCmdCommand = &cobra.Command{
Use: "command <domain> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]",
Use: gotext.Get("command <domain> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]"),
Aliases: []string{"cmd"},
Short: "Run app commands",
Long: `Run an app specific command.
Short: gotext.Get("Run app commands"),
Long: gotext.Get(`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: gotext.Get(` # 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(gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("%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(gotext.Get("--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(gotext.Get("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(gotext.Get("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(gotext.Get("%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(gotext.Get("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(gotext.Get("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(gotext.Get("parsed following command arguments: %s", parsedCmdArgs))
} else {
log.Debug("did not detect any command arguments")
log.Debug(gotext.Get("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]",
Use: gotext.Get("list <domain> [flags]"),
Aliases: []string{"ls"},
Short: "List all available commands",
Short: gotext.Get("List all available commands"),
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
app := internal.ValidateApp(args)
@ -247,7 +248,7 @@ func init() {
"local",
"l",
false,
"run command locally",
gotext.Get("run command locally"),
)
AppCmdCommand.Flags().StringVarP(
@ -255,7 +256,7 @@ func init() {
"user",
"u",
"",
"request remote user",
gotext.Get("request remote user"),
)
AppCmdCommand.Flags().BoolVarP(
@ -263,7 +264,7 @@ func init() {
"tty",
"T",
false,
"disable remote TTY",
gotext.Get("disable remote TTY"),
)
AppCmdCommand.Flags().BoolVarP(
@ -271,6 +272,6 @@ func init() {
"chaos",
"C",
false,
"ignore uncommitted recipes changes",
gotext.Get("ignore uncommitted recipes changes"),
)
}

View File

@ -13,6 +13,7 @@ import (
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/tagcmp"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
@ -39,20 +40,20 @@ type serverStatus struct {
}
var AppListCommand = &cobra.Command{
Use: "list [flags]",
Use: gotext.Get("list [flags]"),
Aliases: []string{"ls"},
Short: "List all managed apps",
Long: `Generate a report of all managed apps.
Short: gotext.Get("List all managed apps"),
Long: gotext.Get(`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: gotext.Get(` # 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)
@ -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(gotext.Get("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(gotext.Get("unable to retrieve tags for %s: %s", app.Name, err))
}
parsedVersion, err := tagcmp.Parse(version)
@ -215,11 +216,12 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
headers := []string{"RECIPE", "DOMAIN", "SERVER"}
if status {
headers = append(headers, []string{
"STATUS",
"CHAOS",
"VERSION",
"UPGRADE",
"AUTOUPDATE"}...,
gotext.Get("STATUS"),
gotext.Get("CHAOS"),
gotext.Get("VERSION"),
gotext.Get("UPGRADE"),
gotext.Get("AUTOUPDATE"),
}...,
)
}
@ -286,7 +288,7 @@ func init() {
"status",
"S",
false,
"show app deployment status",
gotext.Get("show app deployment status"),
)
AppListCommand.Flags().StringVarP(
@ -294,7 +296,7 @@ func init() {
"recipe",
"r",
"",
"show apps of a specific recipe",
gotext.Get("show apps of a specific recipe"),
)
AppListCommand.RegisterFlagCompletionFunc(
@ -309,7 +311,7 @@ func init() {
"machine",
"m",
false,
"print machine-readable output",
gotext.Get("print machine-readable output"),
)
AppListCommand.Flags().StringVarP(
@ -317,7 +319,7 @@ func init() {
"server",
"s",
"",
"show apps of a specific server",
gotext.Get("show apps of a specific server"),
)
AppListCommand.RegisterFlagCompletionFunc(

View File

@ -16,14 +16,15 @@ import (
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/go-git/go-git/v5"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
var CatalogueGenerateCommand = &cobra.Command{
Use: "generate [recipe] [flags]",
Use: gotext.Get("generate [recipe] [flags]"),
Aliases: []string{"g"},
Short: "Generate the recipe catalogue",
Long: `Generate a new copy of the recipe catalogue.
Short: gotext.Get("Generate the recipe catalogue"),
Long: gotext.Get(`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, gotext.Get("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(gotext.Get("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(gotext.Get("no changes discovered in %s, nothing to publish?", cataloguePath))
}
}
msg := "chore: publish new catalogue release changes"
msg := gotext.Get("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(gotext.Get("new changes published: %s", url))
}
if internal.Dry {
log.Info("dry run: no changes published")
log.Info(gotext.Get("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: gotext.Get("catalogue [cmd] [args] [flags]"),
Short: gotext.Get("Manage the recipe catalogue"),
Aliases: []string{"c"},
}
@ -246,7 +247,7 @@ func init() {
"publish",
"p",
false,
"publish changes to git.coopcloud.tech",
gotext.Get("publish changes to git.coopcloud.tech"),
)
CatalogueGenerateCommand.Flags().BoolVarP(
@ -254,7 +255,7 @@ func init() {
"dry-run",
"r",
false,
"report changes that would be made",
gotext.Get("report changes that would be made"),
)
CatalogueGenerateCommand.Flags().BoolVarP(
@ -262,7 +263,7 @@ func init() {
"skip-updates",
"s",
false,
"skip updating recipe repositories",
gotext.Get("skip updating recipe repositories"),
)
CatalogueGenerateCommand.Flags().BoolVarP(
@ -270,6 +271,6 @@ func init() {
"chaos",
"C",
false,
"ignore uncommitted recipes changes",
gotext.Get("ignore uncommitted recipes changes"),
)
}

View File

@ -3,13 +3,14 @@ package cli
import (
"os"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
var AutocompleteCommand = &cobra.Command{
Use: "autocomplete [bash|zsh|fish|powershell]",
Short: "Generate autocompletion script",
Long: `To load completions:
Use: gotext.Get("autocomplete [bash|zsh|fish|powershell]"),
Short: gotext.Get("Generate autocompletion script"),
Long: gotext.Get(`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

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

View File

@ -1,6 +1,7 @@
package cli
import (
"errors"
"fmt"
"os"
@ -10,27 +11,31 @@ 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/lang"
"coopcloud.tech/abra/pkg/log"
charmLog "github.com/charmbracelet/log"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func Run(version, commit string) {
rootCmd := &cobra.Command{
Use: "abra [cmd] [args] [flags]",
Short: "The Co-op Cloud command-line utility belt 🎩🐇",
Use: gotext.Get("abra [cmd] [args] [flags]"),
Short: gotext.Get("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",
gotext.Get("app"),
gotext.Get("autocomplete"),
gotext.Get("catalogue"),
gotext.Get("man"),
gotext.Get("recipe"),
gotext.Get("server"),
gotext.Get("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 +47,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(gotext.Get("unable to create %s: %s", path, err))
}
continue
}
@ -62,24 +67,57 @@ 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(gotext.Get("unsupported --lang: %s (want: es)", internal.Locale))
}
}
systemLocale := lang.GetLocale()
if internal.Locale == "" && systemLocale != "" {
// FIXME(d1): validate system locale also (as above FIXME)
internal.Locale = systemLocale
}
if internal.Locale == "" {
// NOTE(d1): fallback when no --lang=<ll> / LANG=<ll_CC>
internal.Locale = "en"
}
// NOTE(d1): only required to load locale when not using en_<CC>
// N.B we don't even bother with a _<CC> (country code) atm for en because fuck it
if internal.Locale != "en" {
if err := lang.LoadLocale(internal.Locale); err != nil {
return errors.New(gotext.Get("unable to load supported language %s: %s", internal.Locale, err))
}
}
log.Debug(gotext.Get(
"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: gotext.Get("man [flags]"),
Aliases: []string{"m"},
Short: "Generate manpage",
Example: ` # generate the man pages into /usr/local/share/man/man1
Short: gotext.Get("Generate manpage"),
Example: gotext.Get(` # 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 +126,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(gotext.Get("unable to proceed, %s does not exist?", manDir))
}
err := doc.GenManTree(rootCmd, header, manDir)
@ -96,7 +134,7 @@ func Run(version, commit string) {
log.Fatal(err)
}
log.Info("don't forget to run 'sudo mandb'")
log.Info(gotext.Get("don't forget to run 'sudo mandb'"))
},
}
@ -105,7 +143,7 @@ func Run(version, commit string) {
"debug",
"d",
false,
"show debug messages",
gotext.Get("show debug messages"),
)
rootCmd.PersistentFlags().BoolVarP(
@ -113,7 +151,7 @@ func Run(version, commit string) {
"no-input",
"n",
false,
"toggle non-interactive mode",
gotext.Get("toggle non-interactive mode"),
)
rootCmd.PersistentFlags().BoolVarP(
@ -121,7 +159,7 @@ func Run(version, commit string) {
"offline",
"o",
false,
"prefer offline & filesystem access",
gotext.Get("prefer offline & filesystem access"),
)
rootCmd.PersistentFlags().BoolVarP(
@ -129,7 +167,16 @@ func Run(version, commit string) {
"ignore-env-version",
"i",
false,
"ignore .env version checkout",
gotext.Get("ignore .env version checkout"),
)
rootCmd.PersistentFlags().StringVarP(
&internal.Locale,
"lang",
"l",
"",
// FIXME(d1): provide supported list of language via *.po file listing
gotext.Get("force select language (supported: XXX)"),
)
catalogue.CatalogueCommand.AddCommand(

View File

@ -13,14 +13,15 @@ import (
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/server"
sshPkg "coopcloud.tech/abra/pkg/ssh"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
var ServerAddCommand = &cobra.Command{
Use: "add [[server] | --local] [flags]",
Use: gotext.Get("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.
Short: gotext.Get("Add a new server"),
Long: gotext.Get(`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: gotext.Get(" 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(gotext.Get("cannot use [server] and --local together"))
}
if len(args) == 0 && !local {
log.Fatal("missing argument or --local/-l flag")
log.Fatal(gotext.Get("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(gotext.Get("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(gotext.Get("local server successfully added"))
} else {
log.Warn("local server already exists")
log.Warn(gotext.Get("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(gotext.Get("unable to create local context: %s", err))
}
log.Debugf("attempting to create client for %s", name)
log.Debug(gotext.Get("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(gotext.Get("ssh %s error: %s", name, sshPkg.Fatal(name, err)))
}
if created {
log.Infof("%s successfully added", name)
log.Info(gotext.Get("%s successfully added", name))
if _, err := dns.EnsureIPv4(name); err != nil {
log.Warnf("unable to resolve IPv4 for %s", name)
log.Warn(gotext.Get("unable to resolve IPv4 for %s", name))
}
return
}
log.Warnf("%s already exists", name)
log.Warn(gotext.Get("%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(gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("context for %s already exists", name))
return false, nil
}
}
log.Debugf("creating context with domain %s", name)
log.Debugf(gotext.Get("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(gotext.Get("server dir for %s already created", name))
return false, nil
}
@ -198,6 +199,6 @@ func init() {
"local",
"l",
false,
"use local server",
gotext.Get("use local server"),
)
}

View File

@ -10,13 +10,14 @@ import (
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log"
"github.com/docker/cli/cli/connhelper/ssh"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
var ServerListCommand = &cobra.Command{
Use: "list [flags]",
Use: gotext.Get("list [flags]"),
Aliases: []string{"ls"},
Short: "List managed servers",
Short: gotext.Get("List managed servers"),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
dockerContextStore := contextPkg.NewDefaultDockerContextStore()
@ -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(gotext.Get("unable to render to JSON: %s", err))
}
fmt.Println(out)
@ -98,6 +99,6 @@ func init() {
"machine",
"m",
false,
"print machine-readable output",
gotext.Get("print machine-readable output"),
)
}

View File

@ -7,17 +7,18 @@ import (
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log"
"github.com/docker/docker/api/types/filters"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
var ServerPruneCommand = &cobra.Command{
Use: "prune <server> [flags]",
Use: gotext.Get("prune <server> [flags]"),
Aliases: []string{"p"},
Short: "Prune resources on a server",
Long: `Prunes unused containers, networks, and dangling images.
Short: gotext.Get("Prune resources on a server"),
Long: gotext.Get(`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(gotext.Get("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(gotext.Get("networks pruned: %d", len(nr.NetworksDeleted)))
pruneFilters := filters.NewArgs()
if allFilter {
log.Debugf("removing all images, not only dangling ones")
log.Debug(gotext.Get("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(gotext.Get("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(gotext.Get("volumes pruned: %d; space reclaimed: %s", len(vr.VolumesDeleted), volSpaceReclaimed))
}
return
@ -89,7 +90,7 @@ func init() {
"all",
"a",
false,
"remove all unused images",
gotext.Get("remove all unused images"),
)
ServerPruneCommand.Flags().BoolVarP(
@ -97,6 +98,6 @@ func init() {
"volumes",
"v",
false,
"remove volumes",
gotext.Get("remove volumes"),
)
}

View File

@ -9,18 +9,19 @@ import (
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
var ServerRemoveCommand = &cobra.Command{
Use: "remove <server> [flags]",
Use: gotext.Get("remove <server> [flags]"),
Aliases: []string{"rm"},
Short: "Remove a managed server",
Long: `Remove a managed server.
Short: gotext.Get("Remove a managed server"),
Long: gotext.Get(`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(gotext.Get("%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 (
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
// ServerCommand defines the `abra server` command and its subcommands
var ServerCommand = &cobra.Command{
Use: "server [cmd] [args] [flags]",
Use: gotext.Get("server [cmd] [args] [flags]"),
Aliases: []string{"s"},
Short: "Manage servers",
Short: gotext.Get("Manage servers"),
}

View File

@ -2,6 +2,7 @@ package updater
import (
"context"
"errors"
"fmt"
"os"
"strconv"
@ -21,6 +22,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
dockerclient "github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
"coopcloud.tech/abra/pkg/log"
@ -30,14 +32,14 @@ const SERVER = "localhost"
// NotifyCommand checks for available upgrades.
var NotifyCommand = &cobra.Command{
Use: "notify [flags]",
Use: gotext.Get("notify [flags]"),
Aliases: []string{"n"},
Short: "Check for available upgrades",
Long: `Notify on new versions for deployed apps.
Short: gotext.Get("Check for available upgrades"),
Long: gotext.Get(`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]",
Use: gotext.Get("upgrade [[stack] [recipe] | --all] [flags]"),
Aliases: []string{"u"},
Short: "Upgrade apps",
Long: `Upgrade an app by specifying stack name and recipe.
Short: gotext.Get("Upgrade apps"),
Long: gotext.Get(`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(gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("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(gotext.Get("%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(gotext.Get("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(gotext.Get("%s is not deployed?", stackName))
}
if deployMeta.Version == "unknown" {
return "", fmt.Errorf("failed to determine deployed version of %s", stackName)
return "", errors.New(gotext.Get("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(gotext.Get("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(gotext.Get("available updates for %s: %s", stackName, availableUpgrades))
return availableUpgrades, nil
}

View File

@ -7,22 +7,23 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
// UpgradeCommand upgrades abra in-place.
var UpgradeCommand = &cobra.Command{
Use: "upgrade [flags]",
Use: gotext.Get("upgrade [flags]"),
Aliases: []string{"u"},
Short: "Upgrade abra",
Long: `Upgrade abra in-place with the latest stable or release candidate.
Short: gotext.Get("Upgrade abra"),
Long: gotext.Get(`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: gotext.Get(" 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(gotext.Get("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)",
gotext.Get("install release candidate (may contain bugs)"),
)
}