Compare commits

...

1 Commits

Author SHA1 Message Date
5ad187aee1 feat: translation support
All checks were successful
continuous-integration/drone/push Build is passing
See #483
2025-08-19 18:25:48 +02:00
60 changed files with 620 additions and 546 deletions

View File

@ -3,6 +3,7 @@
[![Build Status](https://build.coopcloud.tech/api/badges/toolshed/abra/status.svg?ref=refs/heads/main)](https://build.coopcloud.tech/toolshed/abra)
[![Go Report Card](https://goreportcard.com/badge/git.coopcloud.tech/toolshed/abra)](https://goreportcard.com/report/git.coopcloud.tech/toolshed/abra)
[![Go Reference](https://pkg.go.dev/badge/coopcloud.tech/abra.svg)](https://pkg.go.dev/coopcloud.tech/abra)
[![Translation status](https://translate.coopcloud.tech/widget/co-op-cloud/svg-badge.svg)](https://translate.coopcloud.tech/engage/co-op-cloud/)
The Co-op Cloud utility belt 🎩🐇

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

@ -12,23 +12,24 @@ import (
"coopcloud.tech/abra/pkg/config"
"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) {
dirs := []map[string]os.FileMode{
@ -62,24 +63,24 @@ func Run(version, commit string) {
log.SetReportCaller(true)
}
log.Debugf("abra version %s, commit %s", version, commit)
log.Debugf(gotext.Get("abra version %s, commit %s", version, commit))
},
}
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 +89,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 +97,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 +106,7 @@ func Run(version, commit string) {
"debug",
"d",
false,
"show debug messages",
gotext.Get("show debug messages"),
)
rootCmd.PersistentFlags().BoolVarP(
@ -113,7 +114,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 +122,7 @@ func Run(version, commit string) {
"offline",
"o",
false,
"prefer offline & filesystem access",
gotext.Get("prefer offline & filesystem access"),
)
rootCmd.PersistentFlags().BoolVarP(
@ -129,7 +130,7 @@ func Run(version, commit string) {
"ignore-env-version",
"i",
false,
"ignore .env version checkout",
gotext.Get("ignore .env version checkout"),
)
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)"),
)
}

View File

@ -1,12 +1,12 @@
package autocomplete
import (
"fmt"
"sort"
"coopcloud.tech/abra/pkg/app"
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/recipe"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cobra"
)
@ -14,7 +14,7 @@ import (
func AppNameComplete() ([]string, cobra.ShellCompDirective) {
appFiles, err := app.LoadAppFiles("")
if err != nil {
err := fmt.Sprintf("autocomplete failed: %s", err)
err := gotext.Get("autocomplete failed: %s", err)
return []string{err}, cobra.ShellCompDirectiveError
}
@ -29,7 +29,7 @@ func AppNameComplete() ([]string, cobra.ShellCompDirective) {
func ServiceNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
serviceNames, err := app.GetAppServiceNames(appName)
if err != nil {
err := fmt.Sprintf("autocomplete failed: %s", err)
err := gotext.Get("autocomplete failed: %s", err)
return []string{err}, cobra.ShellCompDirectiveError
}
@ -40,7 +40,7 @@ func ServiceNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
func RecipeNameComplete() ([]string, cobra.ShellCompDirective) {
catl, err := recipe.ReadRecipeCatalogue(false)
if err != nil {
err := fmt.Sprintf("autocomplete failed: %s", err)
err := gotext.Get("autocomplete failed: %s", err)
return []string{err}, cobra.ShellCompDirectiveError
}
@ -56,7 +56,7 @@ func RecipeNameComplete() ([]string, cobra.ShellCompDirective) {
func RecipeVersionComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
catl, err := recipe.ReadRecipeCatalogue(true)
if err != nil {
err := fmt.Sprintf("autocomplete failed: %s", err)
err := gotext.Get("autocomplete failed: %s", err)
return []string{err}, cobra.ShellCompDirectiveError
}
@ -74,7 +74,7 @@ func RecipeVersionComplete(recipeName string) ([]string, cobra.ShellCompDirectiv
func ServerNameComplete() ([]string, cobra.ShellCompDirective) {
files, err := app.LoadAppFiles("")
if err != nil {
err := fmt.Sprintf("autocomplete failed: %s", err)
err := gotext.Get("autocomplete failed: %s", err)
return []string{err}, cobra.ShellCompDirectiveError
}
@ -90,13 +90,13 @@ func ServerNameComplete() ([]string, cobra.ShellCompDirective) {
func CommandNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
app, err := app.Get(appName)
if err != nil {
err := fmt.Sprintf("autocomplete failed: %s", err)
err := gotext.Get("autocomplete failed: %s", err)
return []string{err}, cobra.ShellCompDirectiveError
}
cmdNames, err := appPkg.ReadAbraShCmdNames(app.Recipe.AbraShPath)
if err != nil {
err := fmt.Sprintf("autocomplete failed: %s", err)
err := gotext.Get("autocomplete failed: %s", err)
return []string{err}, cobra.ShellCompDirectiveError
}
@ -111,7 +111,7 @@ func SecretComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
config, err := r.GetComposeConfig(nil)
if err != nil {
err := fmt.Sprintf("autocomplete failed: %s", err)
err := gotext.Get("autocomplete failed: %s", err)
return []string{err}, cobra.ShellCompDirectiveError
}

View File

@ -1,6 +1,7 @@
package catalogue
import (
"errors"
"fmt"
"os"
"path"
@ -10,13 +11,14 @@ import (
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/log"
"github.com/go-git/go-git/v5"
"github.com/leonelquinteros/gotext"
)
// EnsureCatalogue ensures that the catalogue is cloned locally & present.
func EnsureCatalogue() error {
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
if _, err := os.Stat(catalogueDir); err != nil && os.IsNotExist(err) {
log.Debugf("catalogue is missing, retrieving now")
log.Debug(gotext.Get("catalogue is missing, retrieving now"))
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME)
if err := gitPkg.Clone(catalogueDir, url); err != nil {
@ -35,8 +37,7 @@ func EnsureIsClean() error {
}
if !isClean {
msg := "%s has locally unstaged changes? please commit/remove your changes before proceeding"
return fmt.Errorf(msg, config.CATALOGUE_DIR)
return errors.New(gotext.Get("%s has locally unstaged changes? please commit/remove your changes before proceeding", config.CATALOGUE_DIR))
}
return nil
@ -55,8 +56,7 @@ func EnsureUpToDate() error {
}
if len(remotes) == 0 {
msg := "cannot ensure %s is up-to-date, no git remotes configured"
log.Debugf(msg, config.CATALOGUE_DIR)
log.Debug(gotext.Get("cannot ensure %s is up-to-date, no git remotes configured", config.CATALOGUE_DIR))
return nil
}
@ -81,7 +81,7 @@ func EnsureUpToDate() error {
}
}
log.Debugf("fetched latest git changes for %s", config.CATALOGUE_DIR)
log.Debug(gotext.Get("fetched latest git changes for %s", config.CATALOGUE_DIR))
return nil
}

View File

@ -4,7 +4,6 @@ package client
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"time"
@ -14,6 +13,7 @@ import (
sshPkg "coopcloud.tech/abra/pkg/ssh"
commandconnPkg "coopcloud.tech/abra/pkg/upstream/commandconn"
"github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
)
// Conf is a Docker client configuration.
@ -41,7 +41,7 @@ func New(serverName string, opts ...Opt) (*client.Client, error) {
if serverName != "default" {
context, err := GetContext(serverName)
if err != nil {
return nil, fmt.Errorf("unknown server, run \"abra server add %s\"?", serverName)
return nil, errors.New(gotext.Get("unknown server, run \"abra server add %s\"?", serverName))
}
ctxEndpoint, err := contextPkg.GetContextEndpoint(context)
@ -85,7 +85,7 @@ func New(serverName string, opts ...Opt) (*client.Client, error) {
return nil, err
}
log.Debugf("created client for %s", serverName)
log.Debug(gotext.Get("created client for %s", serverName))
info, err := cl.Info(context.Background())
if err != nil {
@ -94,10 +94,10 @@ func New(serverName string, opts ...Opt) (*client.Client, error) {
if info.Swarm.LocalNodeState == "inactive" {
if serverName != "default" {
return cl, fmt.Errorf("swarm mode not enabled on %s?", serverName)
return cl, errors.New(gotext.Get("swarm mode not enabled on %s?", serverName))
}
return cl, errors.New("swarm mode not enabled on local server?")
return cl, errors.New(gotext.Get("swarm mode not enabled on local server?"))
}
return cl, nil

View File

@ -2,11 +2,12 @@ package client
import (
"context"
"fmt"
"errors"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
)
func GetConfigs(cl *client.Client, ctx context.Context, server string, fs filters.Args) ([]swarm.Config, error) {
@ -31,7 +32,7 @@ func GetConfigNames(configs []swarm.Config) []string {
func RemoveConfigs(cl *client.Client, ctx context.Context, configNames []string, force bool) error {
for _, confName := range configNames {
if err := cl.ConfigRemove(context.Background(), confName); err != nil {
return fmt.Errorf("conf %s: %s", confName, err)
return errors.New(gotext.Get("conf %s: %s", confName, err))
}
}
return nil

View File

@ -10,6 +10,7 @@ import (
dConfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/context/docker"
contextStore "github.com/docker/cli/cli/context/store"
"github.com/leonelquinteros/gotext"
)
type Context = contextStore.Metadata
@ -22,7 +23,7 @@ func CreateContext(contextName string) error {
return err
}
log.Debugf("created the %s context", contextName)
log.Debug(gotext.Get("created the %s context", contextName))
return nil
}
@ -62,7 +63,7 @@ func createContext(name string, host string) error {
func DeleteContext(name string) error {
if name == "default" {
return errors.New("context 'default' cannot be removed")
return errors.New(gotext.Get("context 'default' cannot be removed"))
}
if _, err := GetContext(name); err != nil {

View File

@ -2,11 +2,13 @@ package client
import (
"context"
"errors"
"fmt"
"github.com/containers/image/docker"
"github.com/containers/image/types"
"github.com/distribution/reference"
"github.com/leonelquinteros/gotext"
)
// GetRegistryTags retrieves all tags of an image from a container registry.
@ -15,7 +17,7 @@ func GetRegistryTags(img reference.Named) ([]string, error) {
ref, err := docker.ParseReference(fmt.Sprintf("//%s", img))
if err != nil {
return tags, fmt.Errorf("failed to parse image %s, saw: %s", img, err.Error())
return tags, errors.New(gotext.Get("failed to parse image %s, saw: %s", img, err.Error()))
}
ctx := context.Background()

View File

@ -2,6 +2,7 @@ package client
import (
"context"
"errors"
"fmt"
"time"
@ -9,6 +10,7 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
)
func GetVolumes(cl *client.Client, ctx context.Context, server string, fs filters.Args) ([]*volume.Volume, error) {
@ -54,9 +56,9 @@ func retryFunc(retries int, fn func() error) error {
}
if i+1 < retries {
sleep := time.Duration(i+1) * time.Duration(i+1)
log.Infof("%s: waiting %d seconds before next retry", err, sleep)
log.Infof(gotext.Get("%s: waiting %d seconds before next retry", err, sleep))
time.Sleep(sleep * time.Second)
}
}
return fmt.Errorf("%d retries failed", retries)
return errors.New(gotext.Get("%d retries failed", retries))
}

View File

@ -6,6 +6,7 @@ import (
"path/filepath"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
"gopkg.in/yaml.v3"
)
@ -16,13 +17,13 @@ func LoadAbraConfig() Abra {
wd, _ := os.Getwd()
configFile := findAbraConfig(wd)
if configFile == "" {
log.Debugf("no config file found")
log.Debug(gotext.Get("no config file found"))
return Abra{}
}
data, err := os.ReadFile(configFile)
if err != nil {
// Do nothing, when an error occurs
log.Debugf("error reading config file: %s", err)
log.Debug(gotext.Get("error reading config file: %s", err))
return Abra{}
}
@ -30,10 +31,10 @@ func LoadAbraConfig() Abra {
err = yaml.Unmarshal(data, &config)
if err != nil {
// Do nothing, when an error occurs
log.Debugf("error loading config file: %s", err)
log.Debug(gotext.Get("error loading config file: %s", err))
return Abra{}
}
log.Debugf("config file loaded from: %s", configFile)
log.Debug(gotext.Get("config file loaded from: %s", configFile))
config.configPath = filepath.Dir(configFile)
return config
}
@ -73,26 +74,24 @@ type Abra struct {
// 3. use $HOME/.abra when above two options failed
func (a Abra) GetAbraDir() string {
if dir, exists := os.LookupEnv("ABRA_DIR"); exists && dir != "" {
log.Debug("read abra dir from $ABRA_DIR")
log.Debug(gotext.Get("read abra dir from $ABRA_DIR"))
return dir
}
if a.AbraDir != "" {
log.Debug("read abra dir from config file")
log.Debug(gotext.Get("read abra dir from config file"))
if path.IsAbs(a.AbraDir) {
return a.AbraDir
}
// Make the path absolute
return path.Join(a.configPath, a.AbraDir)
}
log.Debug("using default abra dir")
log.Debug(gotext.Get("using default abra dir"))
return os.ExpandEnv("$HOME/.abra")
}
func (a Abra) GetServersDir() string { return path.Join(a.GetAbraDir(), "servers") }
func (a Abra) GetRecipesDir() string { return path.Join(a.GetAbraDir(), "recipes") }
func (a Abra) GetLogsDir() string { return path.Join(a.GetAbraDir(), "logs") }
func (a Abra) GetVendorDir() string { return path.Join(a.GetAbraDir(), "vendor") }
func (a Abra) GetBackupDir() string { return path.Join(a.GetAbraDir(), "backups") }
func (a Abra) GetCatalogueDir() string { return path.Join(a.GetAbraDir(), "catalogue") }
var config = LoadAbraConfig()
@ -102,8 +101,6 @@ var (
SERVERS_DIR = config.GetServersDir()
RECIPES_DIR = config.GetRecipesDir()
LOGS_DIR = config.GetLogsDir()
VENDOR_DIR = config.GetVendorDir()
BACKUP_DIR = config.GetBackupDir()
CATALOGUE_DIR = config.GetCatalogueDir()
RECIPES_JSON = path.Join(config.GetCatalogueDir(), "recipes.json")
REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"

View File

@ -1,7 +1,7 @@
package config
import (
"fmt"
"errors"
"io/fs"
"io/ioutil"
"os"
@ -10,6 +10,7 @@ import (
"strings"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
)
const MAX_SANITISED_APP_NAME_LENGTH = 45
@ -33,7 +34,7 @@ func GetServers() ([]string, error) {
}
}
log.Debugf("retrieved %v servers: %s", len(filtered), filtered)
log.Debug(gotext.Get("retrieved %v servers: %s", len(filtered), filtered))
return filtered, nil
}
@ -46,7 +47,7 @@ func ReadServerNames() ([]string, error) {
return nil, err
}
log.Debugf("read %s from %s", strings.Join(serverNames, ","), SERVERS_DIR)
log.Debug(gotext.Get("read %s from %s", strings.Join(serverNames, ","), SERVERS_DIR))
return serverNames, nil
}
@ -70,7 +71,7 @@ func GetAllFilesInDirectory(directory string) ([]fs.FileInfo, error) {
realPath, err := filepath.EvalSymlinks(filePath)
if err != nil {
log.Warnf("broken symlink in your abra config folders: %s", filePath)
log.Warnf(gotext.Get("broken symlink in your abra config folders: %s", filePath))
} else {
realFile, err := os.Stat(realPath)
if err != nil {
@ -94,7 +95,7 @@ func GetAllFoldersInDirectory(directory string) ([]string, error) {
return nil, err
}
if len(files) == 0 {
return nil, fmt.Errorf("directory is empty: %s", directory)
return nil, errors.New(gotext.Get("directory is empty: %s", directory))
}
for _, file := range files {
@ -103,7 +104,7 @@ func GetAllFoldersInDirectory(directory string) ([]string, error) {
filePath := path.Join(directory, file.Name())
realDir, err := filepath.EvalSymlinks(filePath)
if err != nil {
log.Warnf("broken symlink in your abra config folders: %s", filePath)
log.Warnf(gotext.Get("broken symlink in your abra config folders: %s", filePath))
} else if stat, err := os.Stat(realDir); err == nil && stat.IsDir() {
// path is a directory
folders = append(folders, file.Name())

View File

@ -2,6 +2,7 @@ package container
import (
"context"
"errors"
"fmt"
"strings"
@ -12,6 +13,7 @@ import (
containerTypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
)
// GetContainer retrieves a container. If noInput is false and the retrievd
@ -26,7 +28,7 @@ func GetContainer(c context.Context, cl *client.Client, filters filters.Args, no
if len(containers) == 0 {
filter := filters.Get("name")[0]
return types.Container{}, fmt.Errorf("no containers matching the %v filter found?", filter)
return types.Container{}, errors.New(gotext.Get("no containers matching the %v filter found?", filter))
}
if len(containers) > 1 {
@ -35,19 +37,19 @@ func GetContainer(c context.Context, cl *client.Client, filters filters.Args, no
containerName := strings.Join(container.Names, " ")
trimmed := strings.TrimPrefix(containerName, "/")
created := formatter.HumanDuration(container.Created)
containersRaw = append(containersRaw, fmt.Sprintf("%s (created %v)", trimmed, created))
containersRaw = append(containersRaw, gotext.Get("%s (created %v)", trimmed, created))
}
if noInput {
err := fmt.Errorf("expected 1 container but found %v: %s", len(containers), strings.Join(containersRaw, " "))
err := errors.New(gotext.Get("expected 1 container but found %v: %s", len(containers), strings.Join(containersRaw, " ")))
return types.Container{}, err
}
log.Warnf("ambiguous container list received, prompting for input")
log.Warnf(gotext.Get("ambiguous container list received, prompting for input"))
var response string
prompt := &survey.Select{
Message: "which container are you looking for?",
Message: gotext.Get("which container are you looking for?"),
Options: containersRaw,
}
@ -64,7 +66,7 @@ func GetContainer(c context.Context, cl *client.Client, filters filters.Args, no
}
}
log.Fatal("failed to match chosen container")
log.Fatal(gotext.Get("failed to match chosen container"))
}
return containers[0], nil
@ -79,5 +81,6 @@ func GetContainerFromStackAndService(cl *client.Client, stack, service string) (
if err != nil {
return types.Container{}, err
}
return container, nil
}

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/cli/cli/context"
contextStore "github.com/docker/cli/cli/context/store"
cliflags "github.com/docker/cli/cli/flags"
"github.com/leonelquinteros/gotext"
)
func NewDefaultDockerContextStore() *command.ContextStoreWithDefault {
@ -30,7 +31,7 @@ func NewDefaultDockerContextStore() *command.ContextStoreWithDefault {
func GetContextEndpoint(ctx contextStore.Metadata) (string, error) {
endpointmeta, ok := ctx.Endpoints["docker"].(context.EndpointMetaBase)
if !ok {
err := errors.New("context lacks Docker endpoint")
err := errors.New(gotext.Get("context lacks Docker endpoint"))
return "", err
}
return endpointmeta.Host, nil

View File

@ -1,19 +1,21 @@
package dns
import (
"fmt"
"errors"
"net"
"github.com/leonelquinteros/gotext"
)
// EnsureIPv4 ensures that an ipv4 address is set for a domain name
func EnsureIPv4(domainName string) (string, error) {
ipv4, err := net.ResolveIPAddr("ip4", domainName)
if err != nil {
return "", fmt.Errorf("%s: unable to resolve IPv4 address: %s", domainName, err)
return "", errors.New(gotext.Get("%s: unable to resolve IPv4 address: %s", domainName, err))
}
if ipv4 == nil {
return "", fmt.Errorf("%s: no IPv4 available", domainName)
return "", errors.New(gotext.Get("%s: no IPv4 available", domainName))
}
return ipv4.String(), nil
@ -33,7 +35,7 @@ func EnsureDomainsResolveSameIPv4(domainName, server string) (string, error) {
}
if domainIPv4 == "" {
return ipv4, fmt.Errorf("cannot resolve ipv4 for %s?", domainName)
return ipv4, errors.New(gotext.Get("cannot resolve ipv4 for %s?", domainName))
}
serverIPv4, err := EnsureIPv4(server)
@ -42,12 +44,16 @@ func EnsureDomainsResolveSameIPv4(domainName, server string) (string, error) {
}
if serverIPv4 == "" {
return ipv4, fmt.Errorf("cannot resolve ipv4 for %s?", server)
return ipv4, errors.New(gotext.Get("cannot resolve ipv4 for %s?", server))
}
if domainIPv4 != serverIPv4 {
err := "app domain %s (%s) does not appear to resolve to app server %s (%s)?"
return ipv4, fmt.Errorf(err, domainName, domainIPv4, server, serverIPv4)
return ipv4, errors.New(
gotext.Get(
"app domain %s (%s) does not appear to resolve to app server %s (%s)?",
domainName, domainIPv4, server, serverIPv4,
),
)
}
return ipv4, nil

View File

@ -2,13 +2,14 @@ package envfile
import (
"bufio"
"fmt"
"errors"
"os"
"regexp"
"strings"
"coopcloud.tech/abra/pkg/log"
"git.coopcloud.tech/toolshed/godotenv"
"github.com/leonelquinteros/gotext"
)
// AppEnv is a map of the values in an apps env config
@ -38,7 +39,7 @@ func ReadEnvWithModifiers(filePath string) (AppEnv, AppModifiers, error) {
return nil, mods, err
}
log.Debugf("read %s from %s", envVars, filePath)
log.Debug(gotext.Get("read %s from %s", envVars, filePath))
return envVars, mods, nil
}
@ -69,16 +70,16 @@ func ReadAbraShEnvVars(abraSh string) (map[string]string, error) {
envVarDef := splitVals[len(splitVals)-1]
keyVal := strings.Split(envVarDef, "=")
if len(keyVal) != 2 {
return envVars, fmt.Errorf("couldn't parse %s", txt)
return envVars, errors.New(gotext.Get("couldn't parse %s", txt))
}
envVars[keyVal[0]] = keyVal[1]
}
}
if len(envVars) > 0 {
log.Debugf("read %s from %s", envVars, abraSh)
log.Debug(gotext.Get("read %s from %s", envVars, abraSh))
} else {
log.Debugf("read 0 env var exports from %s", abraSh)
log.Debug(gotext.Get("read 0 env var exports from %s", abraSh))
}
return envVars, nil

View File

@ -11,6 +11,7 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
"github.com/docker/go-units"
"github.com/leonelquinteros/gotext"
"golang.org/x/term"
"coopcloud.tech/abra/pkg/config"
@ -42,7 +43,7 @@ func RemoveSha(str string) string {
func HumanDuration(timestamp int64) string {
date := time.Unix(timestamp, 0)
now := time.Now().UTC()
return units.HumanDuration(now.Sub(date)) + " ago"
return units.HumanDuration(now.Sub(date)) + gotext.Get(" ago")
}
// CreateTable prepares a table layout for output.
@ -76,7 +77,7 @@ func CreateTable() (*table.Table, error) {
func PrintTable(t *table.Table) error {
if isAbraCI, ok := os.LookupEnv("ABRA_CI"); ok && isAbraCI == "1" {
// NOTE(d1): no width limits for CI testing since we test against outputs
log.Debug("detected ABRA_CI=1")
log.Debug(gotext.Get("detected ABRA_CI=1"))
fmt.Println(t)
return nil
}
@ -130,7 +131,7 @@ func CreateOverview(header string, rows [][]string) string {
}
if len(row) > 2 {
panic("CreateOverview: only accepts rows of len == 2")
panic(gotext.Get("CreateOverview: only accepts rows of len == 2"))
}
lenOffset := 4
@ -234,7 +235,7 @@ func StripTagMeta(image string) string {
}
if originalImage != image {
log.Debugf("stripped %s to %s for parsing", originalImage, image)
log.Debug(gotext.Get("stripped %s to %s for parsing", originalImage, image))
}
return image

View File

@ -3,6 +3,7 @@ package git
import (
"coopcloud.tech/abra/pkg/log"
"github.com/go-git/go-git/v5"
"github.com/leonelquinteros/gotext"
)
// Add adds a file to the git index.
@ -18,7 +19,7 @@ func Add(repoPath, path string, dryRun bool) error {
}
if dryRun {
log.Debugf("dry run: adding %s", path)
log.Debug(gotext.Get("dry run: adding %s", path))
} else {
worktree.Add(path)
}

View File

@ -1,11 +1,13 @@
package git
import (
"errors"
"fmt"
"coopcloud.tech/abra/pkg/log"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/leonelquinteros/gotext"
)
// Check if a branch exists in a repo. Use this and not repository.Branch(),
@ -63,7 +65,7 @@ func GetDefaultBranch(repo *git.Repository, repoPath string) (plumbing.Reference
if !HasBranch(repo, "master") {
if !HasBranch(repo, "main") {
return "", fmt.Errorf("failed to select default branch in %s", repoPath)
return "", errors.New(gotext.Get("failed to select default branch in %s", repoPath))
}
branch = "main"
}
@ -90,11 +92,11 @@ func CheckoutDefaultBranch(repo *git.Repository, repoPath string) (plumbing.Refe
}
if err := worktree.Checkout(checkOutOpts); err != nil {
log.Debugf("failed to check out %s in %s", branch, repoPath)
log.Debug(gotext.Get("failed to check out %s in %s", branch, repoPath))
return branch, err
}
log.Debugf("successfully checked out %v in %s", branch, repoPath)
log.Debug(gotext.Get("successfully checked out %v in %s", branch, repoPath))
return branch, nil
}

View File

@ -2,6 +2,7 @@ package git
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
@ -10,6 +11,7 @@ import (
"coopcloud.tech/abra/pkg/log"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/leonelquinteros/gotext"
)
// gitCloneIgnoreErr checks whether we can ignore a git clone error or not.
@ -44,7 +46,7 @@ func Clone(dir, url string) error {
go func() {
if _, err := os.Stat(dir); os.IsNotExist(err) {
log.Debugf("git clone: %s", url)
log.Debug(gotext.Get("git clone: %s", url))
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
URL: url,
@ -54,16 +56,16 @@ func Clone(dir, url string) error {
})
if err != nil && gitCloneIgnoreErr(err) {
log.Debugf("git clone: %s cloned successfully", dir)
log.Debug(gotext.Get("git clone: %s cloned successfully", dir))
errCh <- nil
}
if err := ctx.Err(); err != nil {
errCh <- fmt.Errorf("git clone %s: cancelled due to interrupt", dir)
errCh <- errors.New(gotext.Get("git clone %s: cancelled due to interrupt", dir))
}
if err != nil {
log.Debug("git clone: main branch failed, attempting master branch")
log.Debug(gotext.Get("git clone: main branch failed, attempting master branch"))
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
URL: url,
@ -73,7 +75,7 @@ func Clone(dir, url string) error {
})
if err != nil && gitCloneIgnoreErr(err) {
log.Debugf("git clone: %s cloned successfully", dir)
log.Debug(gotext.Get("git clone: %s cloned successfully", dir))
errCh <- nil
}
@ -82,9 +84,9 @@ func Clone(dir, url string) error {
}
}
log.Debugf("git clone: %s cloned successfully", dir)
log.Debug(gotext.Get("git clone: %s cloned successfully", dir))
} else {
log.Debugf("git clone: %s already exists", dir)
log.Debug(gotext.Get("git clone: %s already exists", dir))
}
errCh <- nil
@ -95,9 +97,9 @@ func Clone(dir, url string) error {
cancelCtx()
fmt.Println() // NOTE(d1): newline after ^C
if err := os.RemoveAll(dir); err != nil {
return fmt.Errorf("unable to clean up git clone of %s: %s", dir, err)
return errors.New(gotext.Get("unable to clean up git clone of %s: %s", dir, err))
}
return fmt.Errorf("git clone %s: cancelled due to interrupt", dir)
return errors.New(gotext.Get("git clone %s: cancelled due to interrupt", dir))
case err := <-errCh:
return err
}

View File

@ -1,16 +1,17 @@
package git
import (
"fmt"
"errors"
"coopcloud.tech/abra/pkg/log"
"github.com/go-git/go-git/v5"
"github.com/leonelquinteros/gotext"
)
// Commit runs a git commit
func Commit(repoPath, commitMessage string, dryRun bool) error {
if commitMessage == "" {
return fmt.Errorf("no commit message specified?")
return errors.New(gotext.Get("no commit message specified?"))
}
commitRepo, err := git.PlainOpen(repoPath)
@ -38,9 +39,9 @@ func Commit(repoPath, commitMessage string, dryRun bool) error {
if err != nil {
return err
}
log.Debug("git changes commited")
log.Debug(gotext.Get("git changes commited"))
} else {
log.Debug("dry run: no changes commited")
log.Debug(gotext.Get("dry run: no changes commited"))
}
return nil

View File

@ -1,14 +1,16 @@
package git
import (
"fmt"
"errors"
"os"
"github.com/leonelquinteros/gotext"
)
// EnsureGitRepo ensures a git repo .git folder exists
func EnsureGitRepo(repoPath string) error {
if _, err := os.Stat(repoPath); os.IsNotExist(err) {
return fmt.Errorf("no .git directory in %s?", repoPath)
return errors.New(gotext.Get("no .git directory in %s?", repoPath))
}
return nil
}

View File

@ -5,6 +5,7 @@ import (
"os/exec"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
)
// getGitDiffArgs builds the `git diff` invocation args. It removes the usage
@ -26,7 +27,7 @@ func getGitDiffArgs(repoPath string) []string {
// skips if it cannot find the command on the system.
func DiffUnstaged(path string) error {
if _, err := exec.LookPath("git"); err != nil {
log.Warnf("unable to locate git command, cannot output diff")
log.Warnf(gotext.Get("unable to locate git command, cannot output diff"))
return nil
}

View File

@ -1,40 +1,41 @@
package git
import (
"fmt"
"errors"
"coopcloud.tech/abra/pkg/log"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/leonelquinteros/gotext"
)
// Init inits a new repo and commits all the stuff if you want
func Init(repoPath string, commit bool, gitName, gitEmail string) error {
repo, err := git.PlainInit(repoPath, false)
if err != nil {
return fmt.Errorf("git init: %s", err)
return errors.New(gotext.Get("git init: %s", err))
}
if err = SwitchToMain(repo); err != nil {
return fmt.Errorf("git branch rename: %s", err)
return errors.New(gotext.Get("git branch rename: %s", err))
}
log.Debugf("initialised new git repo in %s", repoPath)
log.Debug(gotext.Get("initialised new git repo in %s", repoPath))
if commit {
commitRepo, err := git.PlainOpen(repoPath)
if err != nil {
return fmt.Errorf("git open: %s", err)
return errors.New(gotext.Get("git open: %s", err))
}
commitWorktree, err := commitRepo.Worktree()
if err != nil {
return fmt.Errorf("git worktree: %s", err)
return errors.New(gotext.Get("git worktree: %s", err))
}
if err := commitWorktree.AddWithOptions(&git.AddOptions{All: true}); err != nil {
return fmt.Errorf("git add: %s", err)
return errors.New(gotext.Get("git add: %s", err))
}
var author *object.Signature
@ -43,10 +44,10 @@ func Init(repoPath string, commit bool, gitName, gitEmail string) error {
}
if _, err = commitWorktree.Commit("init", &git.CommitOptions{Author: author}); err != nil {
return fmt.Errorf("git commit: %s", err)
return errors.New(gotext.Get("git commit: %s", err))
}
log.Debugf("init committed all files for new git repo in %s", repoPath)
log.Debug(gotext.Get("init committed all files for new git repo in %s", repoPath))
}
return nil
@ -56,20 +57,20 @@ func Init(repoPath string, commit bool, gitName, gitEmail string) error {
func SwitchToMain(repo *git.Repository) error {
ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.ReferenceName("refs/heads/main"))
if err := repo.Storer.SetReference(ref); err != nil {
return fmt.Errorf("set reference: %s", err)
return errors.New(gotext.Get("set reference: %s", err))
}
cfg, err := repo.Config()
if err != nil {
return fmt.Errorf("repo config: %s", err)
return errors.New(gotext.Get("repo config: %s", err))
}
cfg.Init.DefaultBranch = "main"
if err := repo.SetConfig(cfg); err != nil {
return fmt.Errorf("repo set config: %s", err)
return errors.New(gotext.Get("repo set config: %s", err))
}
log.Debug("set 'main' as the default branch")
log.Debug(gotext.Get("set 'main' as the default branch"))
return nil
}

View File

@ -4,12 +4,13 @@ import (
"coopcloud.tech/abra/pkg/log"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/leonelquinteros/gotext"
)
// Push pushes the latest changes & optionally tags to the default remote
func Push(repoDir string, remote string, tags bool, dryRun bool) error {
if dryRun {
log.Debugf("dry run: no git changes pushed in %s", repoDir)
log.Debug(gotext.Get("dry run: no git changes pushed in %s", repoDir))
return nil
}
@ -27,7 +28,7 @@ func Push(repoDir string, remote string, tags bool, dryRun bool) error {
return err
}
log.Debugf("git changes pushed")
log.Debug(gotext.Get("git changes pushed"))
if tags {
opts.RefSpecs = append(opts.RefSpecs, config.RefSpec("+refs/tags/*:refs/tags/*"))
@ -36,7 +37,7 @@ func Push(repoDir string, remote string, tags bool, dryRun bool) error {
return err
}
log.Debugf("git tags pushed")
log.Debug(gotext.Get("git tags pushed"))
}
return nil

View File

@ -2,7 +2,6 @@ package git
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/user"
@ -13,6 +12,7 @@ import (
"github.com/go-git/go-git/v5"
gitConfigPkg "github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
"github.com/leonelquinteros/gotext"
)
// IsClean checks if a repo has unstaged changes
@ -23,12 +23,12 @@ func IsClean(repoPath string) (bool, error) {
return false, git.ErrRepositoryNotExists
}
return false, fmt.Errorf("unable to open %s: %s", repoPath, err)
return false, errors.New(gotext.Get("unable to open %s: %s", repoPath, err))
}
worktree, err := repo.Worktree()
if err != nil {
return false, fmt.Errorf("unable to open worktree of %s: %s", repoPath, err)
return false, errors.New(gotext.Get("unable to open worktree of %s: %s", repoPath, err))
}
patterns, err := GetExcludesFiles()
@ -42,14 +42,14 @@ func IsClean(repoPath string) (bool, error) {
status, err := worktree.Status()
if err != nil {
return false, fmt.Errorf("unable to query status of %s: %s", repoPath, err)
return false, errors.New(gotext.Get("unable to query status of %s: %s", repoPath, err))
}
if status.String() != "" {
noNewline := strings.TrimSuffix(status.String(), "\n")
log.Debugf("git status: %s: %s", repoPath, noNewline)
log.Debug(gotext.Get("git status: %s: %s", repoPath, noNewline))
} else {
log.Debugf("git status: %s: clean", repoPath)
log.Debug(gotext.Get("git status: %s: clean", repoPath))
}
return status.IsClean(), nil
@ -85,7 +85,7 @@ func parseGitConfig() (*gitConfigPkg.Config, error) {
globalGitConfig := filepath.Join(usr.HomeDir, ".gitconfig")
if _, err := os.Stat(globalGitConfig); err != nil {
if os.IsNotExist(err) {
log.Debugf("no %s exists, not reading any global gitignore config", globalGitConfig)
log.Debug(gotext.Get("no %s exists, not reading any global gitignore config", globalGitConfig))
return cfg, nil
}
return cfg, err
@ -127,7 +127,7 @@ func parseExcludesFile(excludesfile string) ([]gitignore.Pattern, error) {
if _, err := os.Stat(excludesfile); err != nil {
if os.IsNotExist(err) {
log.Debugf("no %s exists, skipping reading gitignore paths", excludesfile)
log.Debug(gotext.Get("no %s exists, skipping reading gitignore paths", excludesfile))
return ps, nil
}
return ps, err
@ -146,7 +146,7 @@ func parseExcludesFile(excludesfile string) ([]gitignore.Pattern, error) {
}
}
log.Debugf("read global ignore paths: %s", strings.Join(pathsRaw, " "))
log.Debug(gotext.Get("read global ignore paths: %s", strings.Join(pathsRaw, " ")))
return ps, nil
}

View File

@ -6,12 +6,13 @@ import (
"coopcloud.tech/abra/pkg/log"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/leonelquinteros/gotext"
)
// CreateRemote creates a new git remote in a repository
func CreateRemote(repo *git.Repository, name, url string, dryRun bool) error {
if dryRun {
log.Debugf("dry run: remote %s (%s) not created", name, url)
log.Debug(gotext.Get("dry run: remote %s (%s) not created", name, url))
return nil
}

View File

@ -1,6 +1,7 @@
package lint
import (
"errors"
"fmt"
"net/http"
"os"
@ -13,11 +14,12 @@ import (
"github.com/distribution/reference"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/leonelquinteros/gotext"
)
var (
Warn = "warn"
Critical = "critical"
Warn = gotext.Get("warn")
Critical = gotext.Get("critical")
)
type LintFunction func(recipe.Recipe) (bool, error)
@ -47,10 +49,10 @@ func (l LintRule) Skip(recipe recipe.Recipe) bool {
if l.SkipCondition != nil {
ok, err := l.SkipCondition(recipe)
if err != nil {
log.Debugf("%s: skip condition: %s", l.Ref, err)
log.Debug(gotext.Get("%s: skip condition: %s", l.Ref, err))
}
if ok {
log.Debugf("skipping %s based on skip condition", l.Ref)
log.Debug(gotext.Get("skipping %s based on skip condition", l.Ref))
return true
}
}
@ -62,117 +64,117 @@ var LintRules = map[string][]LintRule{
"warn": {
{
Ref: "R001",
Level: "warn",
Description: "compose config has expected version",
HowToResolve: "ensure 'version: \"3.8\"' in compose configs",
Level: gotext.Get("warn"),
Description: gotext.Get("compose config has expected version"),
HowToResolve: gotext.Get("ensure 'version: \"3.8\"' in compose configs"),
Function: LintComposeVersion,
},
{
Ref: "R002",
Level: "warn",
Description: "healthcheck enabled for all services",
HowToResolve: "wire up healthchecks",
Level: gotext.Get("warn"),
Description: gotext.Get("healthcheck enabled for all services"),
HowToResolve: gotext.Get("wire up healthchecks"),
Function: LintHealthchecks,
},
{
Ref: "R003",
Level: "warn",
Description: "all images use a tag",
HowToResolve: "use a tag for all images",
Level: gotext.Get("warn"),
Description: gotext.Get("all images use a tag"),
HowToResolve: gotext.Get("use a tag for all images"),
Function: LintAllImagesTagged,
},
{
Ref: "R004",
Level: "warn",
Description: "no unstable tags",
HowToResolve: "tag all images with stable tags",
Level: gotext.Get("warn"),
Description: gotext.Get("no unstable tags"),
HowToResolve: gotext.Get("tag all images with stable tags"),
Function: LintNoUnstableTags,
},
{
Ref: "R005",
Level: "warn",
Description: "tags use semver-like format",
HowToResolve: "use semver-like tags",
Level: gotext.Get("warn"),
Description: gotext.Get("tags use semver-like format"),
HowToResolve: gotext.Get("use semver-like tags"),
Function: LintSemverLikeTags,
},
{
Ref: "R006",
Level: "warn",
Description: "has published catalogue version",
HowToResolve: "publish a recipe version to the catalogue",
Level: gotext.Get("warn"),
Description: gotext.Get("has published catalogue version"),
HowToResolve: gotext.Get("publish a recipe version to the catalogue"),
Function: LintHasPublishedVersion,
},
{
Ref: "R007",
Level: "warn",
Description: "README.md metadata filled in",
HowToResolve: "fill out all the metadata",
Level: gotext.Get("warn"),
Description: gotext.Get("README.md metadata filled in"),
HowToResolve: gotext.Get("fill out all the metadata"),
Function: LintMetadataFilledIn,
},
{
Ref: "R013",
Level: "warn",
Description: "git.coopcloud.tech repo exists",
HowToResolve: "upload your recipe to git.coopcloud.tech/coop-cloud/...",
Level: gotext.Get("warn"),
Description: gotext.Get("git.coopcloud.tech repo exists"),
HowToResolve: gotext.Get("upload your recipe to git.coopcloud.tech/coop-cloud/..."),
Function: LintHasRecipeRepo,
},
{
Ref: "R015",
Level: "warn",
Description: "long secret names",
HowToResolve: "reduce length of secret names to 12 chars",
Level: gotext.Get("warn"),
Description: gotext.Get("long secret names"),
HowToResolve: gotext.Get("reduce length of secret names to 12 chars"),
Function: LintSecretLengths,
},
},
"error": {
{
Ref: "R008",
Level: "error",
Description: ".env.sample provided",
HowToResolve: "create an example .env.sample",
Level: gotext.Get("error"),
Description: gotext.Get(".env.sample provided"),
HowToResolve: gotext.Get("create an example .env.sample"),
Function: LintEnvConfigPresent,
},
{
Ref: "R009",
Level: "error",
Description: "one service named 'app'",
HowToResolve: "name a servce 'app'",
Level: gotext.Get("error"),
Description: gotext.Get("one service named 'app'"),
HowToResolve: gotext.Get("name a servce 'app'"),
Function: LintAppService,
},
{
Ref: "R015",
Level: "error",
Description: "deploy labels stanza present",
HowToResolve: "include \"deploy: labels: ...\" stanza",
Level: gotext.Get("error"),
Description: gotext.Get("deploy labels stanza present"),
HowToResolve: gotext.Get("include \"deploy: labels: ...\" stanza"),
Function: LintDeployLabelsPresent,
},
{
Ref: "R010",
Level: "error",
Description: "traefik routing enabled",
HowToResolve: "include \"traefik.enable=true\" deploy label",
Level: gotext.Get("error"),
Description: gotext.Get("traefik routing enabled"),
HowToResolve: gotext.Get("include \"traefik.enable=true\" deploy label"),
Function: LintTraefikEnabled,
SkipCondition: LintTraefikEnabledSkipCondition,
},
{
Ref: "R011",
Level: "error",
Description: "all services have images",
HowToResolve: "ensure \"image: ...\" set on all services",
Level: gotext.Get("error"),
Description: gotext.Get("all services have images"),
HowToResolve: gotext.Get("ensure \"image: ...\" set on all services"),
Function: LintImagePresent,
},
{
Ref: "R012",
Level: "error",
Description: "config version are vendored",
HowToResolve: "vendor config versions in an abra.sh",
Level: gotext.Get("error"),
Description: gotext.Get("config version are vendored"),
HowToResolve: gotext.Get("vendor config versions in an abra.sh"),
Function: LintAbraShVendors,
},
{
Ref: "R014",
Level: "error",
Description: "only annotated tags used for recipe version",
HowToResolve: "replace lightweight tag with annotated tag",
Level: gotext.Get("error"),
Description: gotext.Get("only annotated tags used for recipe version"),
HowToResolve: gotext.Get("replace lightweight tag with annotated tag"),
Function: LintValidTags,
},
},
@ -182,9 +184,9 @@ var LintRules = map[string][]LintRule{
// used in code paths such as "app deploy" to avoid nasty surprises but not for
// the typical linting commands, which do handle other levels.
func LintForErrors(recipe recipe.Recipe) error {
log.Debugf("linting for critical errors in %s configs", recipe.Name)
log.Debug(gotext.Get("linting for critical errors in %s configs", recipe.Name))
var errors string
var errs string
for level := range LintRules {
if level != "error" {
@ -198,19 +200,19 @@ func LintForErrors(recipe recipe.Recipe) error {
ok, err := rule.Function(recipe)
if err != nil {
errors += fmt.Sprintf("\nlint %s: %s", rule.Ref, err)
errs += gotext.Get("\nlint %s: %s", rule.Ref, err)
}
if !ok {
errors += fmt.Sprintf("\n * %s (%s)", rule.Description, rule.Ref)
errs += fmt.Sprintf("\n * %s (%s)", rule.Description, rule.Ref)
}
}
}
if len(errors) > 0 {
return fmt.Errorf("recipe '%s' failed lint checks:\n"+errors[1:], recipe.Name)
if len(errs) > 0 {
return errors.New(gotext.Get("recipe '%s' failed lint checks:\n"+errs[1:], recipe.Name))
}
log.Debugf("linting successful, %s is well configured", recipe.Name)
log.Debug(gotext.Get("linting successful, %s is well configured", recipe.Name))
return nil
}
@ -256,7 +258,7 @@ func LintAppService(recipe recipe.Recipe) (bool, error) {
func LintTraefikEnabledSkipCondition(r recipe.Recipe) (bool, error) {
sampleEnv, err := r.SampleEnv()
if err != nil {
return false, fmt.Errorf("Unable to discover .env.sample for %s", r.Name)
return false, errors.New(gotext.Get("unable to discover .env.sample for %s", r.Name))
}
if _, ok := sampleEnv["DOMAIN"]; !ok {
@ -476,7 +478,7 @@ func LintSecretLengths(recipe recipe.Recipe) (bool, error) {
}
for name := range config.Secrets {
if len(name) > 12 {
return false, fmt.Errorf("secret %s is longer than 12 characters", name)
return false, errors.New(gotext.Get("secret %s is longer than 12 characters", name))
}
}
@ -486,12 +488,12 @@ func LintSecretLengths(recipe recipe.Recipe) (bool, error) {
func LintValidTags(recipe recipe.Recipe) (bool, error) {
repo, err := git.PlainOpen(recipe.Dir)
if err != nil {
return false, fmt.Errorf("unable to open %s: %s", recipe.Dir, err)
return false, errors.New(gotext.Get("unable to open %s: %s", recipe.Dir, err))
}
iter, err := repo.Tags()
if err != nil {
log.Fatalf("unable to list local tags for %s", recipe.Name)
log.Fatal(gotext.Get("unable to list local tags for %s", recipe.Name))
}
if err := iter.ForEach(func(ref *plumbing.Reference) error {
@ -499,7 +501,7 @@ func LintValidTags(recipe recipe.Recipe) (bool, error) {
if err != nil {
switch err {
case plumbing.ErrObjectNotFound:
return fmt.Errorf("invalid lightweight tag detected")
return errors.New(gotext.Get("invalid lightweight tag detected"))
default:
return err
}

View File

@ -3,6 +3,7 @@ package logs
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"os"
@ -13,6 +14,7 @@ import (
containerTypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
)
type TailOpts struct {
@ -81,7 +83,7 @@ func TailLogs(
}
if _, err = io.Copy(os.Stdout, logs); err != nil && err != io.EOF {
errCh <- fmt.Errorf("tailLogs: unable to copy buffer: %s", err)
errCh <- errors.New(gotext.Get("tailLogs: unable to copy buffer: %s", err))
}
}
}(service.ID)

View File

@ -1,6 +1,7 @@
package recipe
import (
"errors"
"fmt"
"io/ioutil"
"os"
@ -13,6 +14,7 @@ import (
loader "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/distribution/reference"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/leonelquinteros/gotext"
)
// GetComposeFiles gets the list of compose files for an app (or recipe if you
@ -24,7 +26,7 @@ func (r Recipe) GetComposeFiles(appEnv map[string]string) ([]string, error) {
if err := ensurePathExists(r.ComposePath); err != nil {
return []string{}, err
}
log.Debugf("no COMPOSE_FILE detected, loading default: %s", r.ComposePath)
log.Debug(gotext.Get("no COMPOSE_FILE detected, loading default: %s", r.ComposePath))
return []string{r.ComposePath}, nil
}
@ -33,7 +35,7 @@ func (r Recipe) GetComposeFiles(appEnv map[string]string) ([]string, error) {
if err := ensurePathExists(path); err != nil {
return []string{}, err
}
log.Debugf("COMPOSE_FILE detected, loading %s", path)
log.Debug(gotext.Get("COMPOSE_FILE detected, loading %s", path))
return []string{path}, nil
}
@ -42,7 +44,7 @@ func (r Recipe) GetComposeFiles(appEnv map[string]string) ([]string, error) {
numComposeFiles := strings.Count(composeFileEnvVar, ":") + 1
envVars := strings.SplitN(composeFileEnvVar, ":", numComposeFiles)
if len(envVars) != numComposeFiles {
return composeFiles, fmt.Errorf("COMPOSE_FILE (=\"%s\") parsing failed?", composeFileEnvVar)
return composeFiles, errors.New(gotext.Get("COMPOSE_FILE (=\"%s\") parsing failed?", composeFileEnvVar))
}
for _, file := range envVars {
@ -53,8 +55,8 @@ func (r Recipe) GetComposeFiles(appEnv map[string]string) ([]string, error) {
composeFiles = append(composeFiles, path)
}
log.Debugf("COMPOSE_FILE detected (%s), loading %s", composeFileEnvVar, strings.Join(envVars, ", "))
log.Debugf("retrieved %s configs for %s", strings.Join(composeFiles, ", "), r.Name)
log.Debug(gotext.Get("COMPOSE_FILE detected (%s), loading %s", composeFileEnvVar, strings.Join(envVars, ", ")))
log.Debug(gotext.Get("retrieved %s configs for %s", strings.Join(composeFiles, ", "), r.Name))
return composeFiles, nil
}
@ -67,7 +69,7 @@ func (r Recipe) GetComposeConfig(env map[string]string) (*composetypes.Config, e
}
if len(composeFiles) == 0 {
return nil, fmt.Errorf("%s is missing a compose.yml or compose.*.yml file?", r.Name)
return nil, errors.New(gotext.Get("%s is missing a compose.yml or compose.*.yml file?", r.Name))
}
if env == nil {
@ -102,7 +104,7 @@ func (r Recipe) GetVersionLabelLocal() (string, error) {
}
if label == "" {
return label, fmt.Errorf("%s has no version label? try running \"abra recipe sync %s\" first?", r.Name, r.Name)
return label, errors.New(gotext.Get("%s has no version label? try running \"abra recipe sync %s\" first?", r.Name, r.Name))
}
return label, nil
@ -118,7 +120,7 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
return false, err
}
log.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", "))
log.Debug(gotext.Get("considering %s config(s) for tag update", strings.Join(composeFiles, ", ")))
for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}}
@ -148,13 +150,13 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
case reference.NamedTagged:
composeTag = img.(reference.NamedTagged).Tag()
default:
log.Debugf("unable to parse %s, skipping", img)
log.Debug(gotext.Get("unable to parse %s, skipping", img))
continue
}
composeImage := formatter.StripTagMeta(reference.Path(img))
log.Debugf("parsed %s from %s", composeTag, service.Image)
log.Debug(gotext.Get("parsed %s from %s", composeTag, service.Image))
if image == composeImage {
bytes, err := ioutil.ReadFile(composeFile)
@ -166,7 +168,7 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
new := fmt.Sprintf("%s:%s", composeImage, tag)
replacedBytes := strings.Replace(string(bytes), old, new, -1)
log.Debugf("updating %s to %s in %s", old, new, compose.Filename)
log.Debug(gotext.Get("updating %s to %s in %s", old, new, compose.Filename))
if err := os.WriteFile(compose.Filename, []byte(replacedBytes), 0o764); err != nil {
return false, err
@ -186,7 +188,7 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
return err
}
log.Debugf("considering %s config(s) for label update", strings.Join(composeFiles, ", "))
log.Debug(gotext.Get("considering %s config(s) for label update", strings.Join(composeFiles, ", ")))
for _, composeFile := range composeFiles {
opts := stack.Deploy{Composefiles: []string{composeFile}}
@ -224,27 +226,27 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
return err
}
old := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", value)
old := gotext.Get("coop-cloud.${STACK_NAME}.version=%s", value)
replacedBytes := strings.Replace(string(bytes), old, label, -1)
if old == label {
log.Warnf("%s is already set, nothing to do?", label)
log.Warnf(gotext.Get("%s is already set, nothing to do?", label))
return nil
}
log.Debugf("updating %s to %s in %s", old, label, compose.Filename)
log.Debug(gotext.Get("updating %s to %s in %s", old, label, compose.Filename))
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0o764); err != nil {
return err
}
log.Infof("synced label %s to service %s", label, serviceName)
log.Infof(gotext.Get("synced label %s to service %s", label, serviceName))
}
}
if !discovered {
log.Warn("no existing label found, automagic insertion not supported yet")
log.Fatalf("add '- \"%s\"' manually to the 'app' service in %s", label, composeFile)
log.Warn(gotext.Get("no existing label found, automagic insertion not supported yet"))
log.Fatal(gotext.Get("add '- \"%s\"' manually to the 'app' service in %s", label, composeFile))
}
}

View File

@ -1,18 +1,20 @@
package recipe
import (
"errors"
"fmt"
"os"
"path"
"coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/formatter"
"github.com/leonelquinteros/gotext"
)
func (r Recipe) SampleEnv() (map[string]string, error) {
sampleEnv, err := envfile.ReadEnv(r.SampleEnvPath)
if err != nil {
return sampleEnv, fmt.Errorf("unable to discover .env.sample for %s", r.Name)
return sampleEnv, errors.New(gotext.Get("unable to discover .env.sample for %s", r.Name))
}
return sampleEnv, nil
}
@ -31,7 +33,7 @@ func (r Recipe) GetReleaseNotes(version string) (string, error) {
return "", err
}
title := formatter.BoldStyle.Render(fmt.Sprintf("%s release notes:", version))
title := formatter.BoldStyle.Render(gotext.Get("%s release notes:", version))
withTitle := fmt.Sprintf("%s\n%s\n", title, releaseNotes)
return withTitle, nil

View File

@ -1,6 +1,7 @@
package recipe
import (
"errors"
"fmt"
"os"
"slices"
@ -15,6 +16,7 @@ import (
"github.com/distribution/reference"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/leonelquinteros/gotext"
)
type EnsureContext struct {
@ -45,9 +47,9 @@ func (r Recipe) Ensure(ctx EnsureContext) error {
}
if r.EnvVersion != "" && !ctx.IgnoreEnvVersion {
log.Debugf("ensuring env version %s", r.EnvVersion)
log.Debug(gotext.Get("ensuring env version %s", r.EnvVersion))
if strings.Contains(r.EnvVersion, "+U") {
return fmt.Errorf("can not redeploy chaos version (%s) without --chaos", r.EnvVersion)
return errors.New(gotext.Get("can not redeploy chaos version (%s) without --chaos", r.EnvVersion))
}
if _, err := r.EnsureVersion(r.EnvVersion); err != nil {
@ -146,16 +148,16 @@ func (r Recipe) EnsureVersion(version string) (bool, error) {
joinedTags := strings.Join(parsedTags, ", ")
if joinedTags != "" {
log.Debugf("read %s as tags for recipe %s", joinedTags, r.Name)
log.Debug(gotext.Get("read %s as tags for recipe %s", joinedTags, r.Name))
}
var opts *git.CheckoutOptions
if tagRef.String() == "" {
log.Debugf("attempting to checkout '%s' as chaos commit", version)
log.Debug(gotext.Get("attempting to checkout '%s' as chaos commit", version))
hash, err := repo.ResolveRevision(plumbing.Revision(version))
if err != nil {
log.Fatalf("unable to resolve '%s': %s", version, err)
log.Fatal(gotext.Get("unable to resolve '%s': %s", version, err))
}
opts = &git.CheckoutOptions{Hash: *hash, Create: false, Force: true}
@ -173,7 +175,7 @@ func (r Recipe) EnsureVersion(version string) (bool, error) {
return isChaosCommit, nil
}
log.Debugf("successfully checked %s out to %s in %s", r.Name, tagRef.Short(), r.Dir)
log.Debug(gotext.Get("successfully checked %s out to %s in %s", r.Name, tagRef.Short(), r.Dir))
return isChaosCommit, nil
}
@ -182,11 +184,11 @@ func (r Recipe) EnsureVersion(version string) (bool, error) {
func (r Recipe) EnsureIsClean() error {
isClean, err := gitPkg.IsClean(r.Dir)
if err != nil {
return fmt.Errorf("unable to check git clean status in %s: %s", r.Dir, err)
return errors.New(gotext.Get("unable to check git clean status in %s: %s", r.Dir, err))
}
if !isClean {
return fmt.Errorf("%s (%s) has locally unstaged changes?", r.Name, r.Dir)
return errors.New(gotext.Get("%s (%s) has locally unstaged changes?", r.Name, r.Dir))
}
return nil
@ -220,7 +222,7 @@ func (r Recipe) EnsureLatest() error {
}
if err := worktree.Checkout(checkOutOpts); err != nil {
log.Debugf("failed to check out %s in %s", branch, r.Dir)
log.Debug(gotext.Get("failed to check out %s in %s", branch, r.Dir))
return err
}
@ -231,33 +233,33 @@ func (r Recipe) EnsureLatest() error {
func (r Recipe) EnsureUpToDate() error {
repo, err := git.PlainOpen(r.Dir)
if err != nil {
return fmt.Errorf("unable to open %s: %s", r.Dir, err)
return errors.New(gotext.Get("unable to open %s: %s", r.Dir, err))
}
remotes, err := repo.Remotes()
if err != nil {
return fmt.Errorf("unable to read remotes in %s: %s", r.Dir, err)
return errors.New(gotext.Get("unable to read remotes in %s: %s", r.Dir, err))
}
if len(remotes) == 0 {
log.Debugf("cannot ensure %s is up-to-date, no git remotes configured", r.Name)
log.Debug(gotext.Get("cannot ensure %s is up-to-date, no git remotes configured", r.Name))
return nil
}
worktree, err := repo.Worktree()
if err != nil {
return fmt.Errorf("unable to open git work tree in %s: %s", r.Dir, err)
return errors.New(gotext.Get("unable to open git work tree in %s: %s", r.Dir, err))
}
branch, err := gitPkg.CheckoutDefaultBranch(repo, r.Dir)
if err != nil {
return fmt.Errorf("unable to check out default branch in %s: %s", r.Dir, err)
return errors.New(gotext.Get("unable to check out default branch in %s: %s", r.Dir, err))
}
fetchOpts := &git.FetchOptions{Tags: git.AllTags}
if err := repo.Fetch(fetchOpts); err != nil {
if !strings.Contains(err.Error(), "already up-to-date") {
return fmt.Errorf("unable to fetch tags in %s: %s", r.Dir, err)
return errors.New(gotext.Get("unable to fetch tags in %s: %s", r.Dir, err))
}
}
@ -269,11 +271,11 @@ func (r Recipe) EnsureUpToDate() error {
if err := worktree.Pull(opts); err != nil {
if !strings.Contains(err.Error(), "already up-to-date") {
return fmt.Errorf("unable to git pull in %s: %s", r.Dir, err)
return errors.New(gotext.Get("unable to git pull in %s: %s", r.Dir, err))
}
}
log.Debugf("fetched latest git changes for %s", r.Name)
log.Debug(gotext.Get("fetched latest git changes for %s", r.Name))
return nil
}
@ -362,7 +364,7 @@ func (r Recipe) Tags() ([]string, error) {
return version1.IsLessThan(version2)
})
log.Debugf("detected %s as tags for recipe %s", strings.Join(tags, ", "), r.Name)
log.Debug(gotext.Get("detected %s as tags for recipe %s", strings.Join(tags, ", "), r.Name))
return tags, nil
}
@ -373,7 +375,7 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
versions := RecipeVersions{}
log.Debugf("git: opening repository in %s", r.Dir)
log.Debug(gotext.Get("git: opening repository in %s", r.Dir))
repo, err := git.PlainOpen(r.Dir)
if err != nil {
@ -393,7 +395,7 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) {
tag := strings.TrimPrefix(string(ref.Name()), "refs/tags/")
log.Debugf("processing %s for %s", tag, r.Name)
log.Debug(gotext.Get("processing %s for %s", tag, r.Name))
checkOutOpts := &git.CheckoutOptions{
Create: false,
@ -401,11 +403,11 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
Branch: plumbing.ReferenceName(ref.Name()),
}
if err := worktree.Checkout(checkOutOpts); err != nil {
log.Debugf("failed to check out %s in %s", tag, r.Dir)
log.Debug(gotext.Get("failed to check out %s in %s", tag, r.Dir))
return err
}
log.Debugf("git checkout: %s in %s", ref.Name(), r.Dir)
log.Debug(gotext.Get("git checkout: %s in %s", ref.Name(), r.Dir))
config, err := r.GetComposeConfig(nil)
if err != nil {
@ -429,7 +431,7 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
case reference.NamedTagged:
tag = img.(reference.NamedTagged).Tag()
case reference.Named:
warnMsg = append(warnMsg, fmt.Sprintf("%s service is missing image tag?", path))
warnMsg = append(warnMsg, gotext.Get("%s service is missing image tag?", path))
continue
}
@ -453,7 +455,7 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
sortRecipeVersions(versions)
log.Debugf("collected %s for %s", versions, r.Dir)
log.Debug(gotext.Get("collected %s for %s", versions, r.Dir))
var uniqueWarnings []string
for _, w := range warnMsg {

View File

@ -13,6 +13,7 @@ import (
"strings"
"github.com/go-git/go-git/v5"
"github.com/leonelquinteros/gotext"
"coopcloud.tech/abra/pkg/catalogue"
"coopcloud.tech/abra/pkg/config"
@ -70,7 +71,7 @@ func (r RecipeMeta) LatestVersion() string {
version = tag
}
log.Debugf("choosing %s as latest version of %s", version, r.Name)
log.Debug(gotext.Get("choosing %s as latest version of %s", version, r.Name))
return version
}
@ -126,7 +127,7 @@ func Get(name string) Recipe {
if strings.Contains(name, ":") {
split := strings.Split(name, ":")
if len(split) > 2 {
log.Fatalf("version seems invalid: %s", name)
log.Fatal(gotext.Get("version seems invalid: %s", name))
}
name = split[0]
@ -134,7 +135,7 @@ func Get(name string) Recipe {
versionRaw = version
if strings.HasSuffix(version, config.DIRTY_DEFAULT) {
version = strings.Replace(split[1], config.DIRTY_DEFAULT, "", 1)
log.Debugf("removed dirty suffix from .env version: %s -> %s", split[1], version)
log.Debug(gotext.Get("removed dirty suffix from .env version: %s -> %s", split[1], version))
}
}
@ -143,7 +144,7 @@ func Get(name string) Recipe {
if strings.Contains(name, "/") {
u, err := url.Parse(name)
if err != nil {
log.Fatalf("invalid recipe: %s", err)
log.Fatal(gotext.Get("invalid recipe: %s", err))
}
u.Scheme = "https"
gitURL = u.String() + ".git"
@ -171,7 +172,7 @@ func Get(name string) Recipe {
dirty, err := r.IsDirty()
if err != nil && !errors.Is(err, git.ErrRepositoryNotExists) {
log.Fatalf("failed to check git status of %s: %s", r.Name, err)
log.Fatal(gotext.Get("failed to check git status of %s: %s", r.Name, err))
}
r.Dirty = dirty
@ -195,16 +196,16 @@ type Recipe struct {
// String outputs a human-friendly string representation.
func (r Recipe) String() string {
out := fmt.Sprintf("{name: %s, ", r.Name)
out += fmt.Sprintf("version : %s, ", r.EnvVersion)
out += fmt.Sprintf("dirty: %v, ", r.Dirty)
out += fmt.Sprintf("dir: %s, ", r.Dir)
out += fmt.Sprintf("git url: %s, ", r.GitURL)
out += fmt.Sprintf("ssh url: %s, ", r.SSHURL)
out += fmt.Sprintf("compose: %s, ", r.ComposePath)
out += fmt.Sprintf("readme: %s, ", r.ReadmePath)
out += fmt.Sprintf("sample env: %s, ", r.SampleEnvPath)
out += fmt.Sprintf("abra.sh: %s}", r.AbraShPath)
out := gotext.Get("{name: %s, ", r.Name)
out += gotext.Get("version : %s, ", r.EnvVersion)
out += gotext.Get("dirty: %v, ", r.Dirty)
out += gotext.Get("dir: %s, ", r.Dir)
out += gotext.Get("git url: %s, ", r.GitURL)
out += gotext.Get("ssh url: %s, ", r.SSHURL)
out += gotext.Get("compose: %s, ", r.ComposePath)
out += gotext.Get("readme: %s, ", r.ReadmePath)
out += gotext.Get("sample env: %s, ", r.SampleEnvPath)
out += gotext.Get("abra.sh: %s}", r.AbraShPath)
return out
}
@ -233,7 +234,7 @@ func GetRecipeFeaturesAndCategory(r Recipe) (Features, string, []string, error)
feat = Features{}
)
log.Debugf("%s: attempt recipe metadata parse", r.ReadmePath)
log.Debug(gotext.Get("%s: attempt recipe metadata parse", r.ReadmePath))
readmeFS, err := ioutil.ReadFile(r.ReadmePath)
if err != nil {
@ -321,12 +322,12 @@ func GetImageMetadata(imageRowString, recipeName string) (Image, []string, error
if imageRowString != "" {
warnMsgs = append(
warnMsgs,
fmt.Sprintf("%s: image meta has incorrect format: %s", recipeName, imageRowString),
gotext.Get("%s: image meta has incorrect format: %s", recipeName, imageRowString),
)
} else {
warnMsgs = append(
warnMsgs,
fmt.Sprintf("%s: image meta is empty?", recipeName),
gotext.Get("%s: image meta is empty?", recipeName),
)
}
@ -357,14 +358,14 @@ func GetImageMetadata(imageRowString, recipeName string) (Image, []string, error
func GetStringInBetween(recipeName, str, start, end string) (result string, err error) {
s := strings.Index(str, start)
if s == -1 {
return "", fmt.Errorf("%s: marker string %s not found", recipeName, start)
return "", errors.New(gotext.Get("%s: marker string %s not found", recipeName, start))
}
s += len(start)
e := strings.Index(str[s:], end)
if e == -1 {
return "", fmt.Errorf("%s: end marker %s not found", recipeName, end)
return "", errors.New(gotext.Get("%s: end marker %s not found", recipeName, end))
}
return str[s : s+e], nil
@ -402,7 +403,7 @@ func readRecipeCatalogueFS(target interface{}) error {
return err
}
log.Debugf("read recipe catalogue from file system cache in %s", config.RECIPES_JSON)
log.Debug(gotext.Get("read recipe catalogue from file system cache in %s", config.RECIPES_JSON))
return nil
}
@ -431,7 +432,7 @@ func VersionsOfService(recipe, serviceName string, offline bool) ([]string, erro
}
}
log.Debugf("detected versions %s for %s", strings.Join(versions, ", "), recipe)
log.Debug(gotext.Get("detected versions %s for %s", strings.Join(versions, ", "), recipe))
return versions, nil
}
@ -454,11 +455,11 @@ func GetRecipeMeta(recipeName string, offline bool) (RecipeMeta, error) {
recipeMeta, ok := catl[recipeName]
if !ok {
return RecipeMeta{}, RecipeMissingFromCatalogue{
err: fmt.Sprintf("recipe %s does not exist?", recipeName),
err: gotext.Get("recipe %s does not exist?", recipeName),
}
}
log.Debugf("recipe metadata retrieved for %s", recipeName)
log.Debug(gotext.Get("recipe metadata retrieved for %s", recipeName))
return recipeMeta, nil
}
@ -545,13 +546,13 @@ func ReadReposMetadata(debug bool) (RepoCatalogue, error) {
reposMeta := make(RepoCatalogue)
pageIdx := 1
bar := formatter.CreateProgressbar(-1, "collecting recipe listing")
bar := formatter.CreateProgressbar(-1, gotext.Get("collecting recipe listing"))
for {
var reposList []RepoMeta
pagedURL := fmt.Sprintf("%s?page=%v", ReposMetadataURL, pageIdx)
log.Debugf("fetching repo metadata from %s", pagedURL)
log.Debug(gotext.Get("fetching repo metadata from %s", pagedURL))
if err := web.ReadJSON(pagedURL, &reposList); err != nil {
return reposMeta, err
@ -655,7 +656,7 @@ func UpdateRepositories(repos RepoCatalogue, recipeName string, debug bool) erro
cloneLimiter := limit.New(3)
retrieveBar := formatter.CreateProgressbar(barLength, "retrieving recipes")
retrieveBar := formatter.CreateProgressbar(barLength, gotext.Get("retrieving recipes"))
ch := make(chan string, barLength)
for _, repoMeta := range repos {
go func(rm RepoMeta) {

View File

@ -6,12 +6,13 @@ import (
"os/exec"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
)
// PassInsertSecret inserts a secret into a pass store.
func PassInsertSecret(secretValue, secretName, appName, server string) error {
if _, err := exec.LookPath("pass"); err != nil {
return errors.New("pass command not found on $PATH, is it installed?")
return errors.New(gotext.Get("pass command not found on $PATH, is it installed?"))
}
cmd := fmt.Sprintf(
@ -19,13 +20,13 @@ func PassInsertSecret(secretValue, secretName, appName, server string) error {
secretValue, server, appName, secretName,
)
log.Debugf("attempting to run %s", cmd)
log.Debug(gotext.Get("attempting to run %s", cmd))
if err := exec.Command("bash", "-c", cmd).Run(); err != nil {
return err
}
log.Infof("%s inserted into pass store", secretName)
log.Infof(gotext.Get("%s inserted into pass store", secretName))
return nil
}
@ -33,7 +34,7 @@ func PassInsertSecret(secretValue, secretName, appName, server string) error {
// PassRmSecret deletes a secret from a pass store.
func PassRmSecret(secretName, appName, server string) error {
if _, err := exec.LookPath("pass"); err != nil {
return errors.New("pass command not found on $PATH, is it installed?")
return errors.New(gotext.Get("pass command not found on $PATH, is it installed?"))
}
cmd := fmt.Sprintf(
@ -41,13 +42,13 @@ func PassRmSecret(secretName, appName, server string) error {
server, appName, secretName,
)
log.Debugf("attempting to run %s", cmd)
log.Debug(gotext.Get("attempting to run %s", cmd))
if err := exec.Command("bash", "-c", cmd).Run(); err != nil {
return err
}
log.Infof("%s removed from pass store", secretName)
log.Infof(gotext.Get("%s removed from pass store", secretName))
return nil
}

View File

@ -5,6 +5,7 @@ package secret
import (
"context"
"errors"
"fmt"
"slices"
"strconv"
@ -21,6 +22,7 @@ import (
"github.com/decentral1se/passgen"
"github.com/docker/docker/api/types"
dockerClient "github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
)
// Secret represents a secret.
@ -57,7 +59,7 @@ func GeneratePassword(length uint, charset string) (string, error) {
return "", err
}
log.Debugf("generated %s", strings.Join(passwords, ", "))
log.Debug(gotext.Get("generated %s", strings.Join(passwords, ", ")))
return passwords[0], nil
}
@ -75,7 +77,7 @@ func GeneratePassphrase() (string, error) {
return "", err
}
log.Debugf("generated %s", strings.Join(passphrases, ", "))
log.Debug(gotext.Get("generated %s", strings.Join(passphrases, ", ")))
return passphrases[0], nil
}
@ -114,18 +116,18 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
}
if len(enabledSecrets) == 0 {
log.Debugf("not generating app secrets, none enabled in recipe config")
log.Debug(gotext.Get("not generating app secrets, none enabled in recipe config"))
return nil, nil
}
secretValues := map[string]Secret{}
for secretId, secretConfig := range composeConfig.Secrets {
if string(secretConfig.Name[len(secretConfig.Name)-1]) == "_" {
return nil, fmt.Errorf("missing version for secret? (%s)", secretId)
return nil, errors.New(gotext.Get("missing version for secret? (%s)", secretId))
}
if !(slices.Contains(enabledSecrets, secretId)) {
log.Warnf("%s not enabled in recipe config, skipping", secretId)
log.Warnf(gotext.Get("%s not enabled in recipe config, skipping", secretId))
continue
}
@ -134,7 +136,7 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
value := Secret{Version: secretVersion, RemoteName: secretConfig.Name}
if len(value.RemoteName) > config.MAX_DOCKER_SECRET_LENGTH {
return nil, fmt.Errorf("secret %s is > %d chars when combined with %s", secretId, config.MAX_DOCKER_SECRET_LENGTH, stackName)
return nil, errors.New(gotext.Get("secret %s is > %d chars when combined with %s", secretId, config.MAX_DOCKER_SECRET_LENGTH, stackName))
}
// Check if the length modifier is set for this secret.
@ -202,12 +204,12 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
defer wg.Done()
if secret.SkipGenerate {
log.Debugf("skipping generation of %s (generate=false)", secretName)
log.Debug(gotext.Get("skipping generation of %s (generate=false)", secretName))
ch <- nil
return
}
log.Debugf("attempting to generate and store %s on %s", secret.RemoteName, server)
log.Debug(gotext.Get("attempting to generate and store %s on %s", secret.RemoteName, server))
if secret.Length > 0 {
password, err := GeneratePassword(uint(secret.Length), secret.Charset)
@ -218,7 +220,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
if err := client.StoreSecret(cl, secret.RemoteName, password, server); err != nil {
if strings.Contains(err.Error(), "AlreadyExists") {
log.Warnf("%s already exists", secret.RemoteName)
log.Warnf(gotext.Get("%s already exists", secret.RemoteName))
ch <- nil
} else {
ch <- err
@ -238,7 +240,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
if err := client.StoreSecret(cl, secret.RemoteName, passphrase, server); err != nil {
if strings.Contains(err.Error(), "AlreadyExists") {
log.Warnf("%s already exists", secret.RemoteName)
log.Warnf(gotext.Get("%s already exists", secret.RemoteName))
ch <- nil
} else {
ch <- err
@ -263,7 +265,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
}
}
log.Debugf("generated and stored %v on %s", secrets, server)
log.Debug(gotext.Get("generated and stored %v on %s", secrets, server))
return secretsGenerated, nil
}

View File

@ -6,6 +6,7 @@ import (
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
)
// CreateServerDir creates a server directory under ~/.abra.
@ -17,11 +18,11 @@ func CreateServerDir(serverName string) error {
return err
}
log.Debugf("%s already exists", serverPath)
log.Debug(gotext.Get("%s already exists", serverPath))
return nil
}
log.Debugf("successfully created %s", serverPath)
log.Debug(gotext.Get("successfully created %s", serverPath))
return nil
}

View File

@ -2,6 +2,7 @@ package service
import (
"context"
"errors"
"fmt"
"strings"
@ -12,6 +13,7 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
)
// GetService retrieves a service container based on a label. If prompt is true
@ -25,7 +27,7 @@ func GetServiceByLabel(c context.Context, cl *client.Client, label string, promp
}
if len(services) == 0 {
return swarm.Service{}, fmt.Errorf("no services deployed?")
return swarm.Service{}, errors.New(gotext.Get("no services deployed?"))
}
var matchingServices []swarm.Service
@ -36,7 +38,7 @@ func GetServiceByLabel(c context.Context, cl *client.Client, label string, promp
}
if len(matchingServices) == 0 {
return swarm.Service{}, fmt.Errorf("no services deployed matching label '%s'?", label)
return swarm.Service{}, errors.New(gotext.Get("no services deployed matching label '%s'?", label))
}
if len(matchingServices) > 1 {
@ -48,15 +50,15 @@ func GetServiceByLabel(c context.Context, cl *client.Client, label string, promp
}
if !prompt {
err := fmt.Errorf("expected 1 service but found %v: %s", len(matchingServices), strings.Join(servicesRaw, " "))
err := errors.New(gotext.Get("expected 1 service but found %v: %s", len(matchingServices), strings.Join(servicesRaw, " ")))
return swarm.Service{}, err
}
log.Warnf("ambiguous service list received, prompting for input")
log.Warnf(gotext.Get("ambiguous service list received, prompting for input"))
var response string
prompt := &survey.Select{
Message: "which service are you looking for?",
Message: gotext.Get("which service are you looking for?"),
Options: servicesRaw,
}
@ -72,7 +74,7 @@ func GetServiceByLabel(c context.Context, cl *client.Client, label string, promp
}
}
log.Fatal("failed to match chosen service")
log.Fatal(gotext.Get("failed to match chosen service"))
}
return matchingServices[0], nil
@ -90,7 +92,7 @@ func GetService(c context.Context, cl *client.Client, filters filters.Args, prom
if len(services) == 0 {
filter := filters.Get("name")[0]
return swarm.Service{}, fmt.Errorf("no services matching the %v filter found?", filter)
return swarm.Service{}, errors.New(gotext.Get("no services matching the %v filter found?", filter))
}
if len(services) != 1 {
@ -98,19 +100,19 @@ func GetService(c context.Context, cl *client.Client, filters filters.Args, prom
for _, service := range services {
serviceName := service.Spec.Name
created := formatter.HumanDuration(service.CreatedAt.Unix())
servicesRaw = append(servicesRaw, fmt.Sprintf("%s (created %v)", serviceName, created))
servicesRaw = append(servicesRaw, gotext.Get("%s (created %v)", serviceName, created))
}
if !prompt {
err := fmt.Errorf("expected 1 service but found %v: %s", len(services), strings.Join(servicesRaw, " "))
err := errors.New(gotext.Get("expected 1 service but found %v: %s", len(services), strings.Join(servicesRaw, " ")))
return swarm.Service{}, err
}
log.Warnf("ambiguous service list received, prompting for input")
log.Warnf(gotext.Get("ambiguous service list received, prompting for input"))
var response string
prompt := &survey.Select{
Message: "which service are you looking for?",
Message: gotext.Get("which service are you looking for?"),
Options: servicesRaw,
}
@ -126,7 +128,7 @@ func GetService(c context.Context, cl *client.Client, filters filters.Args, prom
}
}
log.Fatal("failed to match chosen service")
log.Fatal(gotext.Get("failed to match chosen service"))
}
return services[0], nil

View File

@ -1,8 +1,10 @@
package ssh
import (
"fmt"
"errors"
"strings"
"github.com/leonelquinteros/gotext"
)
// Fatal is a error output wrapper which aims to make SSH failures easier to
@ -11,17 +13,17 @@ func Fatal(hostname string, err error) error {
out := err.Error()
if strings.Contains(out, "Host key verification failed.") {
return fmt.Errorf("SSH host key verification failed for %s", hostname)
return errors.New(gotext.Get("SSH host key verification failed for %s", hostname))
} else if strings.Contains(out, "Could not resolve hostname") {
return fmt.Errorf("could not resolve hostname for %s", hostname)
return errors.New(gotext.Get("could not resolve hostname for %s", hostname))
} else if strings.Contains(out, "Connection timed out") {
return fmt.Errorf("connection timed out for %s", hostname)
return errors.New(gotext.Get("connection timed out for %s", hostname))
} else if strings.Contains(out, "Permission denied") {
return fmt.Errorf("ssh auth: permission denied for %s", hostname)
return errors.New(gotext.Get("ssh auth: permission denied for %s", hostname))
} else if strings.Contains(out, "Network is unreachable") {
return fmt.Errorf("unable to connect to %s, please check your SSH config", hostname)
return errors.New(gotext.Get("unable to connect to %s, please check your SSH config", hostname))
} else if strings.Contains(out, "Is the docker daemon running") {
return fmt.Errorf("docker: is the daemon running / your user has docker permissions?")
return errors.New(gotext.Get("docker: is the daemon running / your user has docker permissions?"))
}
return err

View File

@ -3,7 +3,6 @@ package ui
import (
"context"
"encoding/json"
"fmt"
"io"
"sort"
"strings"
@ -17,6 +16,7 @@ import (
"github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/leonelquinteros/gotext"
)
var IsRunning bool
@ -79,13 +79,13 @@ type stream struct {
}
func (s stream) String() string {
out := fmt.Sprintf("{decoder: %v, ", s.decoder)
out += fmt.Sprintf("err: %v, ", s.Err)
out += fmt.Sprintf("id: %s, ", s.id)
out += fmt.Sprintf("name: %s, ", s.Name)
out += fmt.Sprintf("reader: %v, ", s.reader)
out += fmt.Sprintf("writer: %v, ", s.writer)
out += fmt.Sprintf("status: %s, ", s.status)
out := gotext.Get("{decoder: %v, ", s.decoder)
out += gotext.Get("err: %v, ", s.Err)
out += gotext.Get("id: %s, ", s.id)
out += gotext.Get("name: %s, ", s.Name)
out += gotext.Get("reader: %v, ", s.reader)
out += gotext.Get("writer: %v, ", s.writer)
out += gotext.Get("status: %s, ", s.status)
return out
}
@ -118,7 +118,7 @@ func (s stream) process() tea.Msg {
func (s stream) healthcheck(m Model) tea.Msg {
filters := filters.NewArgs()
filters.Add("name", fmt.Sprintf("^%s", s.Name))
filters.Add("name", gotext.Get("^%s", s.Name))
containers, err := m.cl.ContainerList(m.ctx, containerTypes.ListOptions{Filters: filters})
if err != nil {
@ -327,10 +327,10 @@ func (m Model) View() string {
status := stream.status
if strings.Contains(stream.status, "converged") && !stream.rollback {
status = "succeeded"
status = gotext.Get("succeeded")
}
if strings.Contains(stream.status, "rolled back") {
status = "rolled back"
status = gotext.Get("rolled back")
}
retries := 0
@ -338,7 +338,7 @@ func (m Model) View() string {
retries = stream.retries
}
output := fmt.Sprintf("%s: %s (retries: %v, healthcheck: %s)",
output := gotext.Get("%s: %s (retries: %v, healthcheck: %s)",
formatter.BoldStyle.Render(short),
status,
retries,

View File

@ -17,7 +17,6 @@ package commandconn
import (
"bytes"
"context"
"fmt"
"io"
"net"
"os"
@ -28,6 +27,7 @@ import (
"time"
"coopcloud.tech/abra/pkg/log"
"github.com/leonelquinteros/gotext"
"github.com/pkg/errors"
exec "golang.org/x/sys/execabs"
)
@ -46,7 +46,7 @@ func New(ctx context.Context, cmd string, args ...string) (net.Conn, error) {
)
c.cmd = exec.CommandContext(ctx, cmd, args...)
// we assume that args never contains sensitive information
log.Debugf("commandconn: starting %s with %v", cmd, args)
log.Debug(gotext.Get("commandconn: starting %s with %v", cmd, args))
c.cmd.Env = os.Environ()
c.cmd.SysProcAttr = &syscall.SysProcAttr{}
setPdeathsig(c.cmd)
@ -62,7 +62,7 @@ func New(ctx context.Context, cmd string, args ...string) (net.Conn, error) {
c.cmd.Stderr = &stderrWriter{
stderrMu: &c.stderrMu,
stderr: &c.stderr,
debugPrefix: fmt.Sprintf("commandconn (%s):", cmd),
debugPrefix: gotext.Get("commandconn (%s):", cmd),
}
c.localAddr = dummyAddr{network: "dummy", s: "dummy-0"}
c.remoteAddr = dummyAddr{network: "dummy", s: "dummy-1"}
@ -138,7 +138,7 @@ func (c *commandConn) kill() error {
return nil
}
}
return errors.Wrapf(werr, "commandconn: failed to wait")
return errors.Wrap(werr, gotext.Get("commandconn: failed to wait"))
}
func (c *commandConn) onEOF(eof error) error {
@ -159,7 +159,7 @@ func (c *commandConn) onEOF(eof error) error {
c.stderrMu.Lock()
stderr := c.stderr.String()
c.stderrMu.Unlock()
return errors.Errorf("command %v did not exit after %v: stderr=%q", c.cmd.Args, eof, stderr)
return errors.New(gotext.Get("command %v did not exit after %v: stderr=%q", c.cmd.Args, eof, stderr))
}
}
c.cmdMutex.Unlock()
@ -169,7 +169,7 @@ func (c *commandConn) onEOF(eof error) error {
c.stderrMu.Lock()
stderr := c.stderr.String()
c.stderrMu.Unlock()
return errors.Errorf("command %v has exited with %v, please make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=%s", c.cmd.Args, werr, stderr)
return errors.New(gotext.Get("command %v has exited with %v, please make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=%s", c.cmd.Args, werr, stderr))
}
func ignorableCloseError(err error) bool {
@ -236,7 +236,7 @@ func (c *commandConn) Write(p []byte) (int, error) {
func (c *commandConn) Close() error {
var err error
if err = c.CloseRead(); err != nil {
log.Warnf("commandConn.Close: CloseRead: %v", err)
log.Warnf(gotext.Get("commandConn.Close: CloseRead: %v", err))
}
if err = c.CloseWrite(); err != nil {
// muted because https://github.com/docker/compose/issues/8544
@ -252,15 +252,15 @@ func (c *commandConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *commandConn) SetDeadline(t time.Time) error {
log.Debugf("unimplemented call: SetDeadline(%v)", t)
log.Debug(gotext.Get("unimplemented call: SetDeadline(%v)", t))
return nil
}
func (c *commandConn) SetReadDeadline(t time.Time) error {
log.Debugf("unimplemented call: SetReadDeadline(%v)", t)
log.Debug(gotext.Get("unimplemented call: SetReadDeadline(%v)", t))
return nil
}
func (c *commandConn) SetWriteDeadline(t time.Time) error {
log.Debugf("unimplemented call: SetWriteDeadline(%v)", t)
log.Debug(gotext.Get("unimplemented call: SetWriteDeadline(%v)", t))
return nil
}

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli/context/docker"
dCliContextStore "github.com/docker/cli/cli/context/store"
dClient "github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
"github.com/pkg/errors"
)
@ -34,7 +35,7 @@ func getConnectionHelper(daemonURL string, sshFlags []string) (*connhelper.Conne
case "ssh":
ctxConnDetails, err := ssh.ParseURL(daemonURL)
if err != nil {
return nil, errors.Wrap(err, "ssh host connection is not valid")
return nil, errors.Wrap(err, gotext.Get("ssh host connection is not valid"))
}
return &connhelper.ConnectionHelper{

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types/container"
apiclient "github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
)
// RunExec runs a command on a remote container. io.Writer corresponds to the
@ -39,7 +40,7 @@ func RunExec(dockerCli command.Cli, client *apiclient.Client, containerID string
execID := response.ID
if execID == "" {
return nil, errors.New("exec ID empty")
return nil, errors.New(gotext.Get("exec ID empty"))
}
if execOptions.Detach {
@ -104,12 +105,12 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, client *apiclie
if execOpts.Tty && dockerCli.In().IsTerminal() {
if err := MonitorTtySize(ctx, client, dockerCli, execID, true); err != nil {
fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
fmt.Fprintln(dockerCli.Err(), gotext.Get("Error monitoring TTY size:"), err)
}
}
if err := <-errCh; err != nil {
log.Debugf("Error hijack: %s", err)
log.Debug(gotext.Get("Error hijack: %s", err))
return out, err
}

View File

@ -2,7 +2,7 @@ package container // https://github.com/docker/cli/blob/master/cli/command/conta
import (
"context"
"fmt"
"errors"
"io"
"runtime"
"sync"
@ -12,6 +12,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/stdcopy"
"github.com/leonelquinteros/gotext"
"github.com/moby/term"
)
@ -39,7 +40,7 @@ type hijackedIOStreamer struct {
func (h *hijackedIOStreamer) stream(ctx context.Context) error {
restoreInput, err := h.setupInput()
if err != nil {
return fmt.Errorf("unable to setup input stream: %s", err)
return errors.New(gotext.Get("unable to setup input stream: %s", err))
}
defer restoreInput()
@ -78,7 +79,7 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
}
if err := setRawTerminal(h.streams); err != nil {
return nil, fmt.Errorf("unable to set IO streams as raw terminal: %s", err)
return nil, errors.New(gotext.Get("unable to set IO streams as raw terminal: %s", err))
}
// Use sync.Once so we may call restore multiple times but ensure we
@ -96,7 +97,7 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
if h.detachKeys != "" {
customEscapeKeys, err := term.ToBytes(h.detachKeys)
if err != nil {
log.Warnf("invalid detach escape keys, using default: %s", err)
log.Warnf(gotext.Get("invalid detach escape keys, using default: %s", err))
} else {
escapeKeys = customEscapeKeys
}
@ -128,10 +129,10 @@ func (h *hijackedIOStreamer) beginOutputStream(restoreInput func()) <-chan error
_, err = stdcopy.StdCopy(h.outputStream, h.errorStream, h.resp.Reader)
}
log.Debug("[hijack] End of stdout")
log.Debug(gotext.Get("[hijack] end of stdout"))
if err != nil {
log.Debugf("Error receiveStdout: %s", err)
log.Debug(gotext.Get("error receiveStdout: %s", err))
}
outputDone <- err
@ -152,7 +153,7 @@ func (h *hijackedIOStreamer) beginInputStream(restoreInput func()) (doneC <-chan
// messages will be in normal type.
restoreInput()
log.Debug("[hijack] End of stdin")
log.Debug(gotext.Get("[hijack] End of stdin"))
if _, ok := err.(term.EscapeError); ok {
detached <- err
@ -163,12 +164,12 @@ func (h *hijackedIOStreamer) beginInputStream(restoreInput func()) (doneC <-chan
// This error will also occur on the receive
// side (from stdout) where it will be
// propagated back to the caller.
log.Debugf("Error sendStdin: %s", err)
log.Debug(gotext.Get("error sendStdin: %s", err))
}
}
if err := h.resp.CloseWrite(); err != nil {
log.Debugf("Couldn't send EOF: %s", err)
log.Debug(gotext.Get("couldn't send EOF: %s", err))
}
close(inputDone)

View File

@ -13,6 +13,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
apiclient "github.com/docker/docker/client"
"github.com/leonelquinteros/gotext"
"github.com/moby/sys/signal"
)
@ -35,7 +36,7 @@ func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id strin
}
if err != nil {
log.Debugf("Error resize: %s\r", err)
log.Debug(gotext.Get("error resize: %s\r", err))
}
return err
}
@ -62,7 +63,7 @@ func initTtySize(ctx context.Context, client *apiclient.Client, cli command.Cli,
}
}
if err != nil {
fmt.Fprintln(cli.Err(), "failed to resize tty, using default size")
fmt.Fprintln(cli.Err(), gotext.Get("failed to resize tty, using default size"))
}
}()
}

View File

@ -18,6 +18,7 @@ import (
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/go-units"
"github.com/leonelquinteros/gotext"
"github.com/pkg/errors"
)
@ -39,7 +40,7 @@ func ParseSecrets(client client.SecretAPIClient, requestedSecrets []*swarmtypes.
for _, secret := range requestedSecrets {
if _, exists := secretRefs[secret.File.Name]; exists {
return nil, errors.Errorf("duplicate secret target for %s not allowed", secret.SecretName)
return nil, errors.New(gotext.Get("duplicate secret target for %s not allowed", secret.SecretName))
}
secretRef := new(swarmtypes.SecretReference)
*secretRef = *secret
@ -68,7 +69,7 @@ func ParseSecrets(client client.SecretAPIClient, requestedSecrets []*swarmtypes.
for _, ref := range secretRefs {
id, ok := foundSecrets[ref.SecretName]
if !ok {
return nil, errors.Errorf("secret not found: %s", ref.SecretName)
return nil, errors.New(gotext.Get("secret not found: %s", ref.SecretName))
}
// set the id for the ref to properly assign in swarm
@ -118,7 +119,7 @@ func ParseConfigs(client client.ConfigAPIClient, requestedConfigs []*swarmtypes.
}
if _, exists := configRefs[config.File.Name]; exists {
return nil, errors.Errorf("duplicate config target for %s not allowed", config.ConfigName)
return nil, errors.New(gotext.Get("duplicate config target for %s not allowed", config.ConfigName))
}
configRefs[config.File.Name] = configRef
@ -149,7 +150,7 @@ func ParseConfigs(client client.ConfigAPIClient, requestedConfigs []*swarmtypes.
for _, ref := range configRefs {
id, ok := foundConfigs[ref.ConfigName]
if !ok {
return nil, errors.Errorf("config not found: %s", ref.ConfigName)
return nil, errors.New(gotext.Get("config not found: %s", ref.ConfigName))
}
// set the id for the ref to properly assign in swarm
@ -164,7 +165,7 @@ func ParseConfigs(client client.ConfigAPIClient, requestedConfigs []*swarmtypes.
for _, ref := range runtimeRefs {
id, ok := foundConfigs[ref.ConfigName]
if !ok {
return nil, errors.Errorf("config not found: %s", ref.ConfigName)
return nil, errors.New(gotext.Get("config not found: %s", ref.ConfigName))
}
ref.ConfigID = id
@ -371,7 +372,7 @@ func convertServiceNetworks(
for networkName, network := range networks {
networkConfig, ok := networkConfigs[networkName]
if !ok && networkName != defaultNetwork {
return nil, errors.Errorf("undefined network %q", networkName)
return nil, errors.New(gotext.Get("undefined network %q", networkName))
}
var aliases []string
if network != nil {
@ -410,7 +411,7 @@ func convertServiceSecrets(
lookup := func(key string) (composetypes.FileObjectConfig, error) {
secretSpec, exists := secretSpecs[key]
if !exists {
return composetypes.FileObjectConfig{}, errors.Errorf("undefined secret %q", key)
return composetypes.FileObjectConfig{}, errors.New(gotext.Get("undefined secret %q", key))
}
return composetypes.FileObjectConfig(secretSpec), nil
}
@ -458,7 +459,7 @@ func convertServiceConfigObjs(
lookup := func(key string) (composetypes.FileObjectConfig, error) {
configSpec, exists := configSpecs[key]
if !exists {
return composetypes.FileObjectConfig{}, errors.Errorf("undefined config %q", key)
return composetypes.FileObjectConfig{}, errors.New(gotext.Get("undefined config %q", key))
}
return composetypes.FileObjectConfig(configSpec), nil
}
@ -600,7 +601,7 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container
)
if healthcheck.Disable {
if len(healthcheck.Test) != 0 {
return nil, errors.Errorf("test and disable can't be set at the same time")
return nil, errors.New(gotext.Get("test and disable can't be set at the same time"))
}
return &container.HealthConfig{
Test: []string{"NONE"},
@ -648,7 +649,7 @@ func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*
MaxAttempts: &attempts,
}, nil
default:
return nil, errors.Errorf("unknown restart policy: %s", restart)
return nil, errors.New(gotext.Get("unknown restart policy: %s", restart))
}
}
@ -770,13 +771,13 @@ func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error)
switch mode {
case "global":
if replicas != nil {
return serviceMode, errors.Errorf("replicas can only be used with replicated mode")
return serviceMode, errors.New(gotext.Get("replicas can only be used with replicated mode"))
}
serviceMode.Global = &swarm.GlobalService{}
case "replicated", "":
serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
default:
return serviceMode, errors.Errorf("Unknown mode: %s", mode)
return serviceMode, errors.New(gotext.Get("unknown mode: %s", mode))
}
return serviceMode, nil
}
@ -809,9 +810,9 @@ func convertCredentialSpec(namespace Namespace, spec composetypes.CredentialSpec
case l == 0:
return nil, nil
case l == 2:
return nil, errors.Errorf("invalid credential spec: cannot specify both %s and %s", o[0], o[1])
return nil, errors.New(gotext.Get("invalid credential spec: cannot specify both %s and %s", o[0], o[1]))
case l > 2:
return nil, errors.Errorf("invalid credential spec: cannot specify both %s, and %s", strings.Join(o[:l-1], ", "), o[l-1])
return nil, errors.New(gotext.Get("invalid credential spec: cannot specify both %s, and %s", strings.Join(o[:l-1], ", "), o[l-1]))
}
swarmCredSpec := swarm.CredentialSpec(spec)
// if we're using a swarm Config for the credential spec, over-write it
@ -830,7 +831,7 @@ func convertCredentialSpec(namespace Namespace, spec composetypes.CredentialSpec
return &swarmCredSpec, nil
}
}
return nil, errors.Errorf("invalid credential spec: spec specifies config %v, but no such config can be found", swarmCredSpec.Config)
return nil, errors.New(gotext.Get("invalid credential spec: spec specifies config %v, but no such config can be found", swarmCredSpec.Config))
}
return &swarmCredSpec, nil
}

View File

@ -3,6 +3,7 @@ package convert // https://github.com/docker/cli/blob/master/cli/compose/convert
import (
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types/mount"
"github.com/leonelquinteros/gotext"
"github.com/pkg/errors"
)
@ -40,10 +41,10 @@ func handleVolumeToMount(
result := createMountFromVolume(volume)
if volume.Tmpfs != nil {
return mount.Mount{}, errors.New("tmpfs options are incompatible with type volume")
return mount.Mount{}, errors.New(gotext.Get("tmpfs options are incompatible with type volume"))
}
if volume.Bind != nil {
return mount.Mount{}, errors.New("bind options are incompatible with type volume")
return mount.Mount{}, errors.New(gotext.Get("bind options are incompatible with type volume"))
}
// Anonymous volumes
if volume.Source == "" {
@ -52,7 +53,7 @@ func handleVolumeToMount(
stackVolume, exists := stackVolumes[volume.Source]
if !exists {
return mount.Mount{}, errors.Errorf("undefined volume %q", volume.Source)
return mount.Mount{}, errors.New(gotext.Get("undefined volume %q", volume.Source))
}
result.Source = namespace.Scope(volume.Source)
@ -86,13 +87,13 @@ func handleBindToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, er
result := createMountFromVolume(volume)
if volume.Source == "" {
return mount.Mount{}, errors.New("invalid bind source, source cannot be empty")
return mount.Mount{}, errors.New(gotext.Get("invalid bind source, source cannot be empty"))
}
if volume.Volume != nil {
return mount.Mount{}, errors.New("volume options are incompatible with type bind")
return mount.Mount{}, errors.New(gotext.Get("volume options are incompatible with type bind"))
}
if volume.Tmpfs != nil {
return mount.Mount{}, errors.New("tmpfs options are incompatible with type bind")
return mount.Mount{}, errors.New(gotext.Get("tmpfs options are incompatible with type bind"))
}
if volume.Bind != nil {
result.BindOptions = &mount.BindOptions{
@ -106,13 +107,13 @@ func handleTmpfsToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, e
result := createMountFromVolume(volume)
if volume.Source != "" {
return mount.Mount{}, errors.New("invalid tmpfs source, source must be empty")
return mount.Mount{}, errors.New(gotext.Get("invalid tmpfs source, source must be empty"))
}
if volume.Bind != nil {
return mount.Mount{}, errors.New("bind options are incompatible with type tmpfs")
return mount.Mount{}, errors.New(gotext.Get("bind options are incompatible with type tmpfs"))
}
if volume.Volume != nil {
return mount.Mount{}, errors.New("volume options are incompatible with type tmpfs")
return mount.Mount{}, errors.New(gotext.Get("volume options are incompatible with type tmpfs"))
}
if volume.Tmpfs != nil {
result.TmpfsOptions = &mount.TmpfsOptions{
@ -126,13 +127,13 @@ func handleNpipeToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, e
result := createMountFromVolume(volume)
if volume.Source == "" {
return mount.Mount{}, errors.New("invalid npipe source, source cannot be empty")
return mount.Mount{}, errors.New(gotext.Get("invalid npipe source, source cannot be empty"))
}
if volume.Volume != nil {
return mount.Mount{}, errors.New("volume options are incompatible with type npipe")
return mount.Mount{}, errors.New(gotext.Get("volume options are incompatible with type npipe"))
}
if volume.Tmpfs != nil {
return mount.Mount{}, errors.New("tmpfs options are incompatible with type npipe")
return mount.Mount{}, errors.New(gotext.Get("tmpfs options are incompatible with type npipe"))
}
if volume.Bind != nil {
result.BindOptions = &mount.BindOptions{
@ -158,5 +159,5 @@ func convertVolumeToMount(
case "npipe":
return handleNpipeToMount(volume)
}
return mount.Mount{}, errors.New("volume type must be volume, bind, tmpfs or npipe")
return mount.Mount{}, errors.New(gotext.Get("volume type must be volume, bind, tmpfs or npipe"))
}

View File

@ -3,11 +3,13 @@ package web
import (
"encoding/json"
"fmt"
"errors"
"io"
"net/http"
"os"
"time"
"github.com/leonelquinteros/gotext"
)
// Timeout is the time it takes before a web request bails out waiting for a
@ -40,7 +42,7 @@ func GetFile(filepath string, url string) (err error) {
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
return errors.New(gotext.Get("bad status: %s", resp.Status))
}
_, err = io.Copy(out, resp.Body)