diff --git a/.gitignore b/.gitignore index 1985cb7e..16e0eb69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.tar.gz *fmtcoverage.html .e2e.env .envrc diff --git a/cli/app/move.go b/cli/app/move.go new file mode 100644 index 00000000..97b8d6ab --- /dev/null +++ b/cli/app/move.go @@ -0,0 +1,352 @@ +package app + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "coopcloud.tech/abra/cli/internal" + "coopcloud.tech/abra/pkg/app" + appPkg "coopcloud.tech/abra/pkg/app" + "coopcloud.tech/abra/pkg/autocomplete" + "coopcloud.tech/abra/pkg/client" + "coopcloud.tech/abra/pkg/config" + containerPkg "coopcloud.tech/abra/pkg/container" + "coopcloud.tech/abra/pkg/i18n" + "coopcloud.tech/abra/pkg/log" + "coopcloud.tech/abra/pkg/secret" + "coopcloud.tech/abra/pkg/upstream/stack" + "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/api/types/volume" + dockerclient "github.com/docker/docker/client" + "github.com/spf13/cobra" +) + +// translators: `abra app move` aliases. use a comma separated list of aliases +// with no spaces in between +var appMoveAliases = i18n.G("m") + +var AppMoveCommand = &cobra.Command{ + // translators: `app move` command + Use: i18n.G("move [flags]"), + Aliases: strings.Split(appMoveAliases, ","), + // translators: Short description for `app move` command + Short: i18n.G("Moves an app to a different server"), + Long: i18n.G(`Move an app to a differnt server. + +This command will migrate an app config and copy secrets and volumes from the +old server to the new one. The app MUST be deployed on the old server before +doing the move. The app will be undeployed from the current server but not +deployed on the new server. + +This command requires the "cat" command to be present on the app containers +when retrieving secrets. Some "distroless" images will not support this. Not +all apps are therefore moveable. Rsync is required on your local machine for +transferring volumes. + +Do not forget to update your DNS records. Don't panic, it might take a while +for the dust to settle after you move an app. If anything goes wrong, you can +always move the app config file to the original server and deploy it there +again. No data is removed from the old server. + +Use "--dry-run/-r" to see which secrets and volumes will be moved.`), + Example: i18n.G(` # move an app + abra app move nextcloud.1312.net myserver.com`), + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: func( + cmd *cobra.Command, + args []string, + toComplete string, + ) ([]string, cobra.ShellCompDirective) { + switch l := len(args); l { + case 0: + return autocomplete.AppNameComplete() + case 1: + return autocomplete.ServerNameComplete() + default: + return nil, cobra.ShellCompDirectiveDefault + } + }, + Run: func(cmd *cobra.Command, args []string) { + app := internal.ValidateApp(args) + + if len(args) <= 1 { + log.Fatal(i18n.G("no server provided?")) + } + newServer := internal.ValidateServer([]string{args[1]}) + + if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil { + log.Fatal(err) + } + + currentServerClient, err := client.New(app.Server) + if err != nil { + log.Fatal(err) + } + + deployMeta, err := stack.IsDeployed(context.Background(), currentServerClient, app.StackName()) + if err != nil { + log.Fatal(err) + } + + if !deployMeta.IsDeployed { + log.Fatal(i18n.G("%s must first be deployed on %s before moving", app.Name, app.Server)) + } + + resources, err := getAppResources(currentServerClient, app) + if err != nil { + log.Fatal(i18n.G("unable to retrieve %s resources on %s: %s", app.Name, app.Server, err)) + } + + internal.MoveOverview(app, newServer, resources.SecretNames(), resources.VolumeNames()) + if err := internal.PromptProcced(); err != nil { + log.Fatal(i18n.G("bailing out: %s", err)) + } + + log.Info(i18n.G("undeploying %s on %s", app.Name, app.Server)) + rmOpts := stack.Remove{ + Namespaces: []string{app.StackName()}, + Detach: false, + } + if err := stack.RunRemove(context.Background(), currentServerClient, rmOpts); err != nil { + log.Fatal(i18n.G("failed to remove app from %s: %s", err, app.Server)) + } + + newServerClient, err := client.New(newServer) + if err != nil { + log.Fatal(err) + } + + for _, s := range resources.SecretList { + sname := strings.Split(strings.TrimPrefix(s.Spec.Name, app.StackName()+"_"), "_") + secretName := strings.Join(sname[:len(sname)-1], "_") + data := resources.Secrets[secretName] + if err := client.StoreSecret(newServerClient, s.Spec.Name, data); err != nil { + log.Fatal(i18n.G("failed to store secret on %s: %s", err, newServer)) + } + log.Info(i18n.G("created secret on %s: %s", s.Spec.Name, newServer)) + } + + for _, v := range resources.Volumes { + log.Info(i18n.G("moving volume %s from %s to %s", v.Name, app.Server, newServer)) + + // NOTE(p4u1): Need to create the volume before copying the data, because + // when docker creates a new volume it set the folder permissions to + // root, which might be wrong. This ensures we always have the correct + // folder permissions inside the volume. + log.Debug(i18n.G("creating volume %s on %s", v.Name, newServer)) + _, err := newServerClient.VolumeCreate(context.Background(), volume.CreateOptions{ + Name: v.Name, + Driver: v.Driver, + }) + if err != nil { + log.Fatal(i18n.G("failed to create volume %s on %s: %s", v.Name, newServer, err)) + } + + outgoingFilename := fmt.Sprintf("%s_outgoing.tar.gz", v.Name) + log.Debug(i18n.G("creating %s on %s", outgoingFilename, app.Server)) + tarCmd := fmt.Sprintf("sudo tar --same-owner -czhpf %s -C /var/lib/docker/volumes %s", outgoingFilename, v.Name) + cmd := exec.Command("ssh", app.Server, "-tt", tarCmd) + if out, err := cmd.CombinedOutput(); err != nil { + log.Fatal(i18n.G("%s failed on %s: output:%s err:%s", tarCmd, app.Server, string(out), err)) + } + + log.Debug(i18n.G("rsyncing %s from %s to local machine", outgoingFilename, app.Server)) + cmd = exec.Command("rsync", "-a", "-v", fmt.Sprintf("%s:%s", app.Server, outgoingFilename), outgoingFilename) + if out, err := cmd.CombinedOutput(); err != nil { + log.Fatal(i18n.G("failed to copy %s from %s to local machine: output:%s err:%s", outgoingFilename, app.Server, string(out), err)) + } + + incomingFilename := fmt.Sprintf("%s_incoming.tar.gz", v.Name) + log.Debug(i18n.G("rsyncing %s (renaming to %s) to %s from local machine", outgoingFilename, incomingFilename, newServer)) + cmd = exec.Command("rsync", "-a", "-v", outgoingFilename, fmt.Sprintf("%s:%s", newServer, incomingFilename)) + if out, err := cmd.CombinedOutput(); err != nil { + log.Fatal(i18n.G("failed to copy %s from local machine to %s: output:%s err:%s", outgoingFilename, newServer, string(out), err)) + } + + log.Debug(i18n.G("extracting %s on %s", incomingFilename, newServer)) + tarExtractCmd := fmt.Sprintf("sudo tar --same-owner -xzpf %s -C /var/lib/docker/volumes", outgoingFilename) + cmd = exec.Command("ssh", newServer, "-tt", tarExtractCmd) + if out, err := cmd.CombinedOutput(); err != nil { + log.Fatal(i18n.G("%s failed to extract %s on %s: output:%s err:%s", tarExtractCmd, outgoingFilename, newServer, string(out), err)) + } + + // Remove tar files + log.Debug(i18n.G("removing %s from %s", incomingFilename, newServer)) + cmd = exec.Command("ssh", newServer, "-tt", fmt.Sprintf("sudo rm %s", incomingFilename)) + if out, err := cmd.CombinedOutput(); err != nil { + log.Fatal(i18n.G("failed to remove %s from %s: output:%s err:%s", incomingFilename, newServer, string(out), err)) + } + + log.Debug(i18n.G("removing %s from %s", outgoingFilename, app.Server)) + cmd = exec.Command("ssh", app.Server, "-tt", fmt.Sprintf("sudo rm %s", outgoingFilename)) + if out, err := cmd.CombinedOutput(); err != nil { + log.Fatal(i18n.G("failed to remove %s from %s: output:%s err:%s", outgoingFilename, app.Server, string(out), err)) + } + + log.Debug(i18n.G("removing %s from local machine", outgoingFilename)) + cmd = exec.Command("rm", outgoingFilename) + if out, err := cmd.CombinedOutput(); err != nil { + log.Fatal(i18n.G("failed to remove %s on local machine: output:%s err:%s", outgoingFilename, string(out), err)) + } + } + + newServerPath := fmt.Sprintf("%s/servers/%s/%s.env", config.ABRA_DIR, newServer, app.Name) + log.Info(i18n.G("migrating app config from %s to %s", app.Server, newServerPath)) + if err := copyFile(app.Path, newServerPath); err != nil { + log.Fatal(i18n.G("failed to migrate app config: %s", err)) + } + + if err := os.Remove(app.Path); err != nil { + log.Fatal(i18n.G("unable to remove %s: %s", app.Path, err)) + } + + log.Info(i18n.G("%s was successfully moved from %s to %s 🎉", app.Name, app.Server, newServer)) + }, +} + +type AppResources struct { + Secrets map[string]string + SecretList []swarm.Secret + Volumes map[string]containertypes.MountPoint +} + +func (a *AppResources) SecretNames() []string { + secrets := []string{} + for name := range a.Secrets { + secrets = append(secrets, name) + } + return secrets +} + +func (a *AppResources) VolumeNames() []string { + volumes := []string{} + for name := range a.Volumes { + volumes = append(volumes, name) + } + return volumes +} + +func getAppResources(cl *dockerclient.Client, app app.App) (*AppResources, error) { + filter, err := app.Filters(false, false) + if err != nil { + return nil, err + } + + services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter}) + if err != nil { + return nil, err + } + + composeFiles, err := app.Recipe.GetComposeFiles(app.Env) + if err != nil { + return nil, err + } + + secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filter}) + if err != nil { + return nil, err + } + + secretConfigs, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName()) + if err != nil { + return nil, err + } + + opts := stack.Deploy{Composefiles: composeFiles, Namespace: app.StackName()} + compose, err := appPkg.GetAppComposeConfig(app.Name, opts, app.Env) + if err != nil { + return nil, err + } + + resources := &AppResources{ + Secrets: make(map[string]string), + SecretList: secretList, + Volumes: make(map[string]containertypes.MountPoint), + } + + for _, s := range services { + secretNames := map[string]string{} + for _, serviceCompose := range compose.Services { + stackService := fmt.Sprintf("%s_%s", app.StackName(), serviceCompose.Name) + if stackService != s.Spec.Name { + log.Debug(i18n.G("skipping %s as it does not match %s", stackService, s.Spec.Name)) + continue + } + + for _, secret := range serviceCompose.Secrets { + for _, s := range secretList { + stackSecret := fmt.Sprintf("%s_%s_%s", app.StackName(), secret.Source, secretConfigs[secret.Source].Version) + if s.Spec.Name == stackSecret { + secretNames[secret.Source] = s.ID + break + } + } + } + } + + f := filters.NewArgs() + f.Add("name", s.Spec.Name) + targetContainer, err := containerPkg.GetContainer(context.Background(), cl, f, true) + if err != nil { + return nil, errors.New(i18n.G("unable to get container matching %s: %s", s.Spec.Name, err)) + } + + for _, m := range targetContainer.Mounts { + if m.Type == mount.TypeVolume { + resources.Volumes[m.Name] = m + } + } + + for secretName, secretID := range secretNames { + if _, ok := resources.Secrets[secretName]; ok { + continue + } + + log.Debug(i18n.G("extracting secret %s on %s", secretName, app.Server)) + + cmd := fmt.Sprintf("sudo cat /var/lib/docker/containers/%s/mounts/secrets/%s", targetContainer.ID, secretID) + out, err := exec.Command("ssh", app.Server, "-tt", cmd).Output() + if err != nil { + return nil, errors.New(i18n.G("%s failed on %s: output:%s err:%s", cmd, app.Server, string(out), err)) + } + + resources.Secrets[secretName] = string(out) + } + } + + return resources, nil +} + +func copyFile(src string, dst string) error { + // Read all content of src to data, may cause OOM for a large file. + data, err := os.ReadFile(src) + if err != nil { + return err + } + + // Write data to dst + err = os.WriteFile(dst, data, 0o644) + if err != nil { + return err + } + + return nil +} + +func init() { + AppMoveCommand.Flags().BoolVarP( + &internal.Dry, + i18n.G("dry-run"), + i18n.G("r"), + false, + i18n.G("report changes that would be made"), + ) +} diff --git a/cli/app/secret.go b/cli/app/secret.go index 45dbcbaa..b3f5c0f7 100644 --- a/cli/app/secret.go +++ b/cli/app/secret.go @@ -247,7 +247,7 @@ environment. Typically, you can let Abra generate them for you on app creation } secretName := fmt.Sprintf("%s_%s_%s", app.StackName(), name, version) - if err := client.StoreSecret(cl, secretName, data, app.Server); err != nil { + if err := client.StoreSecret(cl, secretName, data); err != nil { log.Fatal(err) } diff --git a/cli/internal/deploy.go b/cli/internal/deploy.go index 37406128..4f816804 100644 --- a/cli/internal/deploy.go +++ b/cli/internal/deploy.go @@ -142,6 +142,69 @@ func getDeployType(currentVersion, newVersion string) string { return i18n.G("DOWNGRADE") } +// MoveOverview shows a overview before moving an app to a different server +func MoveOverview( + app appPkg.App, + newServer string, + secrets []string, + volumes []string, +) { + server := app.Server + if app.Server == "default" { + server = "local" + } + + domain := app.Domain + if domain == "" { + domain = config.NO_DOMAIN_DEFAULT + } + + secretsOverview := strings.Join(secrets, "\n") + if len(secrets) == 0 { + secretsOverview = config.NO_SECRETS_DEFAULT + } + + volumesOverview := strings.Join(volumes, "\n") + if len(volumes) == 0 { + volumesOverview = config.NO_VOLUMES_DEFAULT + } + + rows := [][]string{ + {i18n.G("DOMAIN"), domain}, + {i18n.G("RECIPE"), app.Recipe.Name}, + {i18n.G("OLD SERVER"), server}, + {i18n.G("NEW SERVER"), newServer}, + {i18n.G("SECRETS"), secretsOverview}, + {i18n.G("VOLUMES"), volumesOverview}, + } + + overview := formatter.CreateOverview(i18n.G("MOVE OVERVIEW"), rows) + + fmt.Println(overview) +} + +func PromptProcced() error { + if NoInput { + return nil + } + + if Dry { + return errors.New(i18n.G("dry run")) + } + + response := false + prompt := &survey.Confirm{Message: i18n.G("proceed?")} + if err := survey.AskOne(prompt, &response); err != nil { + return err + } + + if !response { + return errors.New(i18n.G("cancelled")) + } + + return nil +} + // PostCmds parses a string of commands and executes them inside of the respective services // the commands string must have the following format: // " | |... " diff --git a/cli/run.go b/cli/run.go index aafc86e4..a2540f9e 100644 --- a/cli/run.go +++ b/cli/run.go @@ -258,6 +258,7 @@ Config: app.AppRestartCommand, app.AppRestoreCommand, app.AppRollbackCommand, + app.AppMoveCommand, app.AppRunCommand, app.AppSecretCommand, app.AppServicesCommand, diff --git a/pkg/client/client.go b/pkg/client/client.go index b73a2a2c..69cc53a6 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -6,6 +6,7 @@ import ( "errors" "net/http" "os" + "strings" "time" contextPkg "coopcloud.tech/abra/pkg/context" @@ -38,18 +39,24 @@ func WithTimeout(timeout int) Opt { func New(serverName string, opts ...Opt) (*client.Client, error) { var clientOpts []client.Opt - if serverName != "default" { - context, err := GetContext(serverName) - if err != nil { - return nil, errors.New(i18n.G("unknown server, run \"abra server add %s\"?", serverName)) - } + ctx, err := GetContext(serverName) + if err != nil { + return nil, errors.New(i18n.G("unknown server, run \"abra server add %s\"?", serverName)) + } - ctxEndpoint, err := contextPkg.GetContextEndpoint(context) - if err != nil { - return nil, err - } + ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx) + if err != nil { + return nil, err + } + var isUnix bool + if strings.Contains(ctxEndpoint, "unix://") { + isUnix = true + } + + if serverName != "default" && !isUnix { conf := &Conf{} + for _, opt := range opts { opt(conf) } @@ -93,7 +100,7 @@ func New(serverName string, opts ...Opt) (*client.Client, error) { } if info.Swarm.LocalNodeState == "inactive" { - if serverName != "default" { + if serverName != "default" && !isUnix { return cl, errors.New(i18n.G("swarm mode not enabled on %s?", serverName)) } diff --git a/pkg/client/secret.go b/pkg/client/secret.go index f8419425..1d095467 100644 --- a/pkg/client/secret.go +++ b/pkg/client/secret.go @@ -7,7 +7,7 @@ import ( "github.com/docker/docker/client" ) -func StoreSecret(cl *client.Client, secretName, secretValue, server string) error { +func StoreSecret(cl *client.Client, secretName, secretValue string) error { ann := swarm.Annotations{Name: secretName} spec := swarm.SecretSpec{Annotations: ann, Data: []byte(secretValue)} diff --git a/pkg/config/abra.go b/pkg/config/abra.go index 48781b00..9a0718cc 100644 --- a/pkg/config/abra.go +++ b/pkg/config/abra.go @@ -118,6 +118,8 @@ var ( NO_DOMAIN_DEFAULT = "N/A" NO_VERSION_DEFAULT = "N/A" + NO_SECRETS_DEFAULT = "N/A" + NO_VOLUMES_DEFAULT = "N/A" UNKNOWN_DEFAULT = "unknown" ) diff --git a/pkg/i18n/locales/abra.pot b/pkg/i18n/locales/abra.pot index 1b2f494b..1022f84d 100644 --- a/pkg/i18n/locales/abra.pot +++ b/pkg/i18n/locales/abra.pot @@ -7,7 +7,7 @@ msgid "" msgstr "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" - "POT-Creation-Date: 2025-08-30 12:43+0200\n" + "POT-Creation-Date: 2025-09-01 11:17+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -76,6 +76,11 @@ msgid " # list apps of all servers without live status\n" " abra app ls -r gitea" msgstr "" +#: ./cli/app/move.go:60 +msgid " # move an app\n" + " abra app move nextcloud.1312.net myserver.com" +msgstr "" + #: ./cli/app/cmd.go:41 msgid " # pass args/flags without \"--\"\n" " abra app cmd 1312.net app my_cmd_arg foo --user bar\n" @@ -221,7 +226,7 @@ msgstr "" msgid "%s does not exist for %s, use /bin/sh as fallback" msgstr "" -#: ./cli/app/cmd.go:114 ./cli/internal/deploy.go:151 +#: ./cli/app/cmd.go:114 ./cli/internal/deploy.go:214 #, c-format msgid "%s does not exist for %s?" msgstr "" @@ -246,6 +251,16 @@ msgstr "" msgid "%s doesn't have a %s function" msgstr "" +#: ./cli/app/move.go:158 ./cli/app/move.go:318 +#, c-format +msgid "%s failed on %s: output:%s err:%s" +msgstr "" + +#: ./cli/app/move.go:178 +#, c-format +msgid "%s failed to extract %s on %s: output:%s err:%s" +msgstr "" + #: ./pkg/upstream/stack/stack.go:169 #, c-format msgid "%s has been detected as deployed: %v" @@ -351,6 +366,11 @@ msgstr "" msgid "%s missing from %s.env" msgstr "" +#: ./cli/app/move.go:100 +#, c-format +msgid "%s must first be deployed on %s before moving" +msgstr "" + #: ./cli/recipe/upgrade.go:151 #, c-format msgid "%s not considered semver-like" @@ -406,6 +426,11 @@ msgstr "" msgid "%s successfully stored on server" msgstr "" +#: ./cli/app/move.go:211 +#, c-format +msgid "%s was successfully moved from %s to %s 🎉" +msgstr "" + #: ./cli/recipe/new.go:60 #, c-format msgid "%s/example.git" @@ -711,7 +736,7 @@ msgstr "" msgid "DEPLOYED LABELS" msgstr "" -#: ./cli/app/list.go:222 ./cli/internal/deploy.go:75 +#: ./cli/app/list.go:222 ./cli/internal/deploy.go:75 ./cli/internal/deploy.go:173 msgid "DOMAIN" msgstr "" @@ -925,6 +950,10 @@ msgstr "" msgid "List volumes associated with an app" msgstr "" +#: ./cli/internal/deploy.go:181 +msgid "MOVE OVERVIEW" +msgstr "" + #. translators: Short description for `app backup` command group #: ./cli/app/backup.go:246 msgid "Manage app backups" @@ -959,6 +988,32 @@ msgstr "" msgid "Manage the recipe catalogue" msgstr "" +#: ./cli/app/move.go:42 +msgid "Move an app to a differnt server.\n" + "\n" + "This command will migrate an app config and copy secrets and volumes from the\n" + "old server to the new one. The app MUST be deployed on the old server before\n" + "doing the move. The app will be undeployed from the current server but not\n" + "deployed on the new server.\n" + "\n" + "This command requires the \"cat\" command to be present on the app containers\n" + "when retrieving secrets. Some \"distroless\" images will not support this. Not\n" + "all apps are therefore moveable. Rsync is required on your local machine for\n" + "transferring volumes.\n" + "\n" + "Do not forget to update your DNS records. Don't panic, it might take a while\n" + "for the dust to settle after you move an app. If anything goes wrong, you can\n" + "always move the app config file to the original server and deploy it there\n" + "again. No data is removed from the old server.\n" + "\n" + "Use \"--dry-run/-r\" to see which secrets and volumes will be moved." +msgstr "" + +#. translators: Short description for `app move` command +#: ./cli/app/move.go:41 +msgid "Moves an app to a different server" +msgstr "" + #: ./cli/recipe/new.go:123 msgid "N" msgstr "" @@ -975,6 +1030,10 @@ msgstr "" msgid "NEW DEPLOYMENT" msgstr "" +#: ./cli/internal/deploy.go:176 +msgid "NEW SERVER" +msgstr "" + #: ./cli/app/secret.go:147 msgid "NOT" msgstr "" @@ -991,6 +1050,10 @@ msgid "Notify on new versions for deployed apps.\n" "Use \"--major/-m\" to include new major versions." msgstr "" +#: ./cli/internal/deploy.go:175 +msgid "OLD SERVER" +msgstr "" + #: ./cli/app/volume.go:54 msgid "ON SERVER" msgstr "" @@ -1015,7 +1078,7 @@ msgstr "" msgid "README.md metadata filled in" msgstr "" -#: ./cli/app/list.go:222 ./cli/internal/deploy.go:76 +#: ./cli/app/list.go:222 ./cli/internal/deploy.go:76 ./cli/internal/deploy.go:174 msgid "RECIPE" msgstr "" @@ -1135,6 +1198,10 @@ msgstr "" msgid "S" msgstr "" +#: ./cli/internal/deploy.go:177 +msgid "SECRETS" +msgstr "" + #: ./cli/app/new.go:204 msgid "SECRETS OVERVIEW" msgstr "" @@ -1481,6 +1548,10 @@ msgstr "" msgid "VERSION" msgstr "" +#: ./cli/internal/deploy.go:178 +msgid "VOLUMES" +msgstr "" + #: ./cli/recipe/reset.go:24 msgid "WARNING: this will delete your changes. Be Careful." msgstr "" @@ -1756,6 +1827,11 @@ msgstr "" msgid "bad status: %s" msgstr "" +#: ./cli/app/move.go:110 +#, c-format +msgid "bailing out: %s" +msgstr "" + #: ./pkg/upstream/convert/volume.go:113 msgid "bind options are incompatible with type tmpfs" msgstr "" @@ -1810,6 +1886,10 @@ msgstr "" msgid "can't separate key from value: %s (this variable is probably unset)" msgstr "" +#: ./cli/internal/deploy.go:202 +msgid "cancelled" +msgstr "" + #: ./pkg/catalogue/catalogue.go:59 ./pkg/recipe/git.go:245 #, c-format msgid "cannot ensure %s is up-to-date, no git remotes configured" @@ -2139,11 +2219,16 @@ msgstr "" msgid "create remote directory: %s" msgstr "" -#: ./pkg/client/client.go:88 +#: ./pkg/client/client.go:95 #, c-format msgid "created client for %s" msgstr "" +#: ./cli/app/move.go:134 +#, c-format +msgid "created secret on %s: %s" +msgstr "" + #: ./cli/recipe/release.go:428 #, c-format msgid "created tag %s at %s" @@ -2159,6 +2244,11 @@ msgstr "" msgid "creating %s" msgstr "" +#: ./cli/app/move.go:154 +#, c-format +msgid "creating %s on %s" +msgstr "" + #: ./cli/server/add.go:175 #, c-format msgid "creating context with domain %s" @@ -2174,6 +2264,11 @@ msgstr "" msgid "creating secret %s" msgstr "" +#: ./cli/app/move.go:144 +#, c-format +msgid "creating volume %s on %s" +msgstr "" + #: ./pkg/lint/recipe.go:22 msgid "critical" msgstr "" @@ -2373,6 +2468,10 @@ msgstr "" msgid "download [flags]" msgstr "" +#: ./cli/internal/deploy.go:192 +msgid "dry run" +msgstr "" + #: ./pkg/git/add.go:22 #, c-format msgid "dry run: adding %s" @@ -2415,7 +2514,7 @@ msgstr "" msgid "dry run: remote %s (%s) not created" msgstr "" -#: ./cli/catalogue/catalogue.go:301 ./cli/recipe/release.go:639 ./cli/recipe/sync.go:269 +#: ./cli/app/move.go:347 ./cli/catalogue/catalogue.go:301 ./cli/recipe/release.go:639 ./cli/recipe/sync.go:269 msgid "dry-run" msgstr "" @@ -2537,6 +2636,16 @@ msgstr "" msgid "expected 1 service but found %v: %s" msgstr "" +#: ./cli/app/move.go:174 +#, c-format +msgid "extracting %s on %s" +msgstr "" + +#: ./cli/app/move.go:313 +#, c-format +msgid "extracting secret %s on %s" +msgstr "" + #. translators: `abra recipe fetch` aliases. use a comma separated list of aliases #. with no spaces in between #: ./cli/app/deploy.go:347 ./cli/app/remove.go:163 ./cli/app/rollback.go:329 ./cli/app/secret.go:593 ./cli/app/upgrade.go:439 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138 @@ -2563,6 +2672,16 @@ msgstr "" msgid "failed to commit changes: %s" msgstr "" +#: ./cli/app/move.go:164 +#, c-format +msgid "failed to copy %s from %s to local machine: output:%s err:%s" +msgstr "" + +#: ./cli/app/move.go:171 +#, c-format +msgid "failed to copy %s from local machine to %s: output:%s err:%s" +msgstr "" + #: ./pkg/upstream/stack/stack.go:531 #, c-format msgid "failed to create %s" @@ -2583,6 +2702,11 @@ msgstr "" msgid "failed to create secret %s" msgstr "" +#: ./cli/app/move.go:150 +#, c-format +msgid "failed to create volume %s on %s: %s" +msgstr "" + #: ./cli/updater/updater.go:263 #, c-format msgid "failed to determine deployed version of %s" @@ -2611,6 +2735,11 @@ msgstr "" msgid "failed to match chosen service" msgstr "" +#: ./cli/app/move.go:204 +#, c-format +msgid "failed to migrate app config: %s" +msgstr "" + #: ./pkg/client/registry.go:20 #, c-format msgid "failed to parse image %s, saw: %s" @@ -2621,6 +2750,21 @@ msgstr "" msgid "failed to publish new release: %s" msgstr "" +#: ./cli/app/move.go:185 ./cli/app/move.go:191 +#, c-format +msgid "failed to remove %s from %s: output:%s err:%s" +msgstr "" + +#: ./cli/app/move.go:197 +#, c-format +msgid "failed to remove %s on local machine: output:%s err:%s" +msgstr "" + +#: ./cli/app/move.go:119 +#, c-format +msgid "failed to remove app from %s: %s" +msgstr "" + #: ./pkg/upstream/stack/remove.go:183 #, c-format msgid "failed to remove config %s: %s" @@ -2665,6 +2809,11 @@ msgstr "" msgid "failed to select default branch in %s" msgstr "" +#: ./cli/app/move.go:132 +#, c-format +msgid "failed to store secret on %s: %s" +msgstr "" + #: ./cli/recipe/release.go:269 ./cli/recipe/release.go:554 #, c-format msgid "failed to tag release: %s" @@ -3259,9 +3408,11 @@ msgstr "" msgid "ls" msgstr "" +#. translators: `abra app move` aliases. use a comma separated list of aliases +#. with no spaces in between #. translators: `abra man` aliases. use a comma separated list of aliases #. with no spaces in between -#: ./cli/app/list.go:318 ./cli/app/ps.go:205 ./cli/app/secret.go:553 ./cli/app/secret.go:649 ./cli/recipe/list.go:104 ./cli/recipe/upgrade.go:376 ./cli/recipe/version.go:139 ./cli/run.go:128 ./cli/server/list.go:106 ./cli/updater/updater.go:560 +#: ./cli/app/list.go:318 ./cli/app/move.go:34 ./cli/app/ps.go:205 ./cli/app/secret.go:553 ./cli/app/secret.go:649 ./cli/recipe/list.go:104 ./cli/recipe/upgrade.go:376 ./cli/recipe/version.go:139 ./cli/run.go:128 ./cli/server/list.go:106 ./cli/updater/updater.go:560 msgid "m" msgstr "" @@ -3293,6 +3444,11 @@ msgstr "" msgid "man [flags]" msgstr "" +#: ./cli/app/move.go:202 +#, c-format +msgid "migrating app config from %s to %s" +msgstr "" + #: ./cli/internal/recipe.go:48 ./cli/internal/recipe.go:68 ./cli/internal/recipe.go:82 ./cli/recipe/release.go:655 ./cli/recipe/sync.go:285 ./cli/recipe/upgrade.go:359 msgid "minor" msgstr "" @@ -3322,6 +3478,16 @@ msgstr "" msgid "missing version for secret? (%s)" msgstr "" +#. translators: `app move` command +#: ./cli/app/move.go:38 +msgid "move [flags]" +msgstr "" + +#: ./cli/app/move.go:138 +#, c-format +msgid "moving volume %s from %s to %s" +msgstr "" + #: ./cli/app/secret.go:292 msgid "must provide argument if --no-input is passed" msgstr "" @@ -3527,6 +3693,10 @@ msgstr "" msgid "no server provided" msgstr "" +#: ./cli/app/move.go:81 +msgid "no server provided?" +msgstr "" + #: ./cli/app/cmd.go:174 #, c-format msgid "no service %s for %s?" @@ -3588,7 +3758,7 @@ msgstr "" msgid "no-tty" msgstr "" -#: ./cli/internal/deploy.go:159 +#: ./cli/internal/deploy.go:222 #, c-format msgid "not enough arguments: %s" msgstr "" @@ -3761,6 +3931,10 @@ msgstr "" msgid "print machine-readable output" msgstr "" +#: ./cli/internal/deploy.go:196 +msgid "proceed?" +msgstr "" + #: ./pkg/recipe/git.go:398 #, c-format msgid "processing %s for %s" @@ -3808,7 +3982,7 @@ msgstr "" #. with no spaces in between #. translators: `abra recipe` aliases. use a comma separated list of aliases #. with no spaces in between -#: ./cli/app/backup.go:327 ./cli/app/list.go:303 ./cli/app/run.go:23 ./cli/app/upgrade.go:463 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:640 ./cli/recipe/sync.go:270 +#: ./cli/app/backup.go:327 ./cli/app/list.go:303 ./cli/app/move.go:348 ./cli/app/run.go:23 ./cli/app/upgrade.go:463 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:640 ./cli/recipe/sync.go:270 msgid "r" msgstr "" @@ -3988,6 +4162,16 @@ msgstr "" msgid "removed freshly created tag %s" msgstr "" +#: ./cli/app/move.go:182 ./cli/app/move.go:188 +#, c-format +msgid "removing %s from %s" +msgstr "" + +#: ./cli/app/move.go:194 +#, c-format +msgid "removing %s from local machine" +msgstr "" + #: ./cli/server/prune.go:64 msgid "removing all images, not only dangling ones" msgstr "" @@ -4045,7 +4229,7 @@ msgstr "" msgid "repo set config: %s" msgstr "" -#: ./cli/catalogue/catalogue.go:304 ./cli/recipe/release.go:642 ./cli/recipe/sync.go:272 +#: ./cli/app/move.go:350 ./cli/catalogue/catalogue.go:304 ./cli/recipe/release.go:642 ./cli/recipe/sync.go:272 msgid "report changes that would be made" msgstr "" @@ -4186,6 +4370,16 @@ msgstr "" msgid "rs" msgstr "" +#: ./cli/app/move.go:168 +#, c-format +msgid "rsyncing %s (renaming to %s) to %s from local machine" +msgstr "" + +#: ./cli/app/move.go:161 +#, c-format +msgid "rsyncing %s from %s to local machine" +msgstr "" + #: ./cli/recipe/lint.go:41 msgid "rule" msgstr "" @@ -4213,7 +4407,7 @@ msgstr "" msgid "running backup %s on %s with exec config %v" msgstr "" -#: ./cli/internal/deploy.go:189 +#: ./cli/internal/deploy.go:252 #, c-format msgid "running command %s %s within the context of %s_%s" msgstr "" @@ -4233,7 +4427,7 @@ msgstr "" msgid "running command: %s" msgstr "" -#: ./cli/internal/deploy.go:167 +#: ./cli/internal/deploy.go:230 #, c-format msgid "running post-command '%s %s' in container %s" msgstr "" @@ -4444,6 +4638,11 @@ msgstr "" msgid "skipped" msgstr "" +#: ./cli/app/move.go:280 +#, c-format +msgid "skipping %s as it does not match %s" +msgstr "" + #: ./pkg/lint/recipe.go:55 #, c-format msgid "skipping %s based on skip condition" @@ -4588,12 +4787,12 @@ msgstr "" msgid "successfully created %s" msgstr "" -#: ./pkg/client/client.go:97 +#: ./pkg/client/client.go:104 #, c-format msgid "swarm mode not enabled on %s?" msgstr "" -#: ./pkg/client/client.go:100 +#: ./pkg/client/client.go:107 msgid "swarm mode not enabled on local server?" msgstr "" @@ -4814,6 +5013,11 @@ msgstr "" msgid "unable to fetch tags in %s: %s" msgstr "" +#: ./cli/app/move.go:299 +#, c-format +msgid "unable to get container matching %s: %s" +msgstr "" + #: ./pkg/recipe/git.go:274 #, c-format msgid "unable to git pull in %s: %s" @@ -4893,6 +5097,11 @@ msgstr "" msgid "unable to read version for %s from synced label. Did you try running \"abra recipe sync %s\" already?" msgstr "" +#: ./cli/app/move.go:208 +#, c-format +msgid "unable to remove %s: %s" +msgstr "" + #: ./cli/recipe/fetch.go:81 #, c-format msgid "unable to remove default remote in %s: %s" @@ -4918,6 +5127,11 @@ msgstr "" msgid "unable to resolve IPv4 for %s" msgstr "" +#: ./cli/app/move.go:105 +#, c-format +msgid "unable to retrieve %s resources on %s: %s" +msgstr "" + #: ./cli/app/list.go:159 #, c-format msgid "unable to retrieve tags for %s: %s" @@ -4977,6 +5191,11 @@ msgstr "" msgid "undeploy succeeded 🟢" msgstr "" +#: ./cli/app/move.go:113 +#, c-format +msgid "undeploying %s on %s" +msgstr "" + #: ./pkg/upstream/stack/loader.go:108 #, c-format msgid "unexpected environment %q" diff --git a/pkg/i18n/locales/es.po b/pkg/i18n/locales/es.po index 11c2327f..3a97bdbf 100644 --- a/pkg/i18n/locales/es.po +++ b/pkg/i18n/locales/es.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: EMAIL\n" -"POT-Creation-Date: 2025-08-30 12:43+0200\n" +"POT-Creation-Date: 2025-09-01 11:17+0200\n" "PO-Revision-Date: 2025-08-29 21:45+0000\n" "Last-Translator: chasqui \n" "Language-Team: Spanish args/flags without \"--\"\n" @@ -231,7 +237,7 @@ msgstr "" msgid "%s does not exist for %s, use /bin/sh as fallback" msgstr "" -#: cli/app/cmd.go:114 cli/internal/deploy.go:151 +#: cli/app/cmd.go:114 cli/internal/deploy.go:214 #, c-format msgid "%s does not exist for %s?" msgstr "" @@ -256,6 +262,16 @@ msgstr "" msgid "%s doesn't have a %s function" msgstr "" +#: cli/app/move.go:158 cli/app/move.go:318 +#, c-format +msgid "%s failed on %s: output:%s err:%s" +msgstr "" + +#: cli/app/move.go:178 +#, c-format +msgid "%s failed to extract %s on %s: output:%s err:%s" +msgstr "" + #: pkg/upstream/stack/stack.go:169 #, c-format msgid "%s has been detected as deployed: %v" @@ -365,6 +381,11 @@ msgstr "" msgid "%s missing from %s.env" msgstr "" +#: cli/app/move.go:100 +#, c-format +msgid "%s must first be deployed on %s before moving" +msgstr "" + #: cli/recipe/upgrade.go:151 #, c-format msgid "%s not considered semver-like" @@ -422,6 +443,11 @@ msgstr "" msgid "%s successfully stored on server" msgstr "" +#: cli/app/move.go:211 +#, c-format +msgid "%s was successfully moved from %s to %s 🎉" +msgstr "" + #: cli/recipe/new.go:60 #, c-format msgid "%s/example.git" @@ -764,7 +790,7 @@ msgstr "" msgid "DEPLOYED LABELS" msgstr "" -#: cli/app/list.go:222 cli/internal/deploy.go:75 +#: cli/app/list.go:222 cli/internal/deploy.go:75 cli/internal/deploy.go:173 msgid "DOMAIN" msgstr "" @@ -993,6 +1019,10 @@ msgstr "📋 Listar los contenidos de una captura o instantánea 📸" msgid "List volumes associated with an app" msgstr "📋 Listar volúmenes 📦 asociados a una plataforma 🚀" +#: cli/internal/deploy.go:181 +msgid "MOVE OVERVIEW" +msgstr "" + #. translators: Short description for `app backup` command group #: cli/app/backup.go:246 msgid "Manage app backups" @@ -1027,6 +1057,40 @@ msgstr "⚙️ Administrar servidores (huertas digitales) 🕋" msgid "Manage the recipe catalogue" msgstr "⚙️ Administrar catálogo 📋 de recetas 🧑‍🍳" +#: cli/app/move.go:42 +msgid "" +"Move an app to a differnt server.\n" +"\n" +"This command will migrate an app config and copy secrets and volumes from " +"the\n" +"old server to the new one. The app MUST be deployed on the old server " +"before\n" +"doing the move. The app will be undeployed from the current server but not\n" +"deployed on the new server.\n" +"\n" +"This command requires the \"cat\" command to be present on the app " +"containers\n" +"when retrieving secrets. Some \"distroless\" images will not support this. " +"Not\n" +"all apps are therefore moveable. Rsync is required on your local machine " +"for\n" +"transferring volumes.\n" +"\n" +"Do not forget to update your DNS records. Don't panic, it might take a " +"while\n" +"for the dust to settle after you move an app. If anything goes wrong, you " +"can\n" +"always move the app config file to the original server and deploy it there\n" +"again. No data is removed from the old server.\n" +"\n" +"Use \"--dry-run/-r\" to see which secrets and volumes will be moved." +msgstr "" + +#. translators: Short description for `app move` command +#: cli/app/move.go:41 +msgid "Moves an app to a different server" +msgstr "" + #: cli/recipe/new.go:123 msgid "N" msgstr "" @@ -1044,6 +1108,10 @@ msgstr "" msgid "NEW DEPLOYMENT" msgstr "" +#: cli/internal/deploy.go:176 +msgid "NEW SERVER" +msgstr "" + #: cli/app/secret.go:147 msgid "NOT" msgstr "" @@ -1061,6 +1129,10 @@ msgid "" "Use \"--major/-m\" to include new major versions." msgstr "" +#: cli/internal/deploy.go:175 +msgid "OLD SERVER" +msgstr "" + #: cli/app/volume.go:54 msgid "ON SERVER" msgstr "" @@ -1087,7 +1159,7 @@ msgstr "" msgid "README.md metadata filled in" msgstr "" -#: cli/app/list.go:222 cli/internal/deploy.go:76 +#: cli/app/list.go:222 cli/internal/deploy.go:76 cli/internal/deploy.go:174 msgid "RECIPE" msgstr "" @@ -1227,6 +1299,10 @@ msgstr "💻 Ejecutar comandos en una plataforma 🚀" msgid "S" msgstr "" +#: cli/internal/deploy.go:177 +msgid "SECRETS" +msgstr "" + #: cli/app/new.go:204 msgid "SECRETS OVERVIEW" msgstr "" @@ -1618,6 +1694,10 @@ msgstr "" msgid "VERSION" msgstr "" +#: cli/internal/deploy.go:178 +msgid "VOLUMES" +msgstr "" + #: cli/recipe/reset.go:24 msgid "WARNING: this will delete your changes. Be Careful." msgstr "" @@ -1919,6 +1999,11 @@ msgstr "" msgid "bad status: %s" msgstr "" +#: cli/app/move.go:110 +#, c-format +msgid "bailing out: %s" +msgstr "" + #: pkg/upstream/convert/volume.go:113 msgid "bind options are incompatible with type tmpfs" msgstr "" @@ -1974,6 +2059,10 @@ msgstr "" msgid "can't separate key from value: %s (this variable is probably unset)" msgstr "" +#: cli/internal/deploy.go:202 +msgid "cancelled" +msgstr "" + #: pkg/catalogue/catalogue.go:59 pkg/recipe/git.go:245 #, c-format msgid "cannot ensure %s is up-to-date, no git remotes configured" @@ -2315,11 +2404,16 @@ msgstr "" msgid "create remote directory: %s" msgstr "" -#: pkg/client/client.go:88 +#: pkg/client/client.go:95 #, c-format msgid "created client for %s" msgstr "" +#: cli/app/move.go:134 +#, fuzzy, c-format +msgid "created secret on %s: %s" +msgstr "🥷 Genera secretos (contraseñas) automáticamente 🤖" + #: cli/recipe/release.go:428 #, c-format msgid "created tag %s at %s" @@ -2335,6 +2429,11 @@ msgstr "" msgid "creating %s" msgstr "" +#: cli/app/move.go:154 +#, c-format +msgid "creating %s on %s" +msgstr "" + #: cli/server/add.go:175 #, c-format msgid "creating context with domain %s" @@ -2350,6 +2449,11 @@ msgstr "" msgid "creating secret %s" msgstr "" +#: cli/app/move.go:144 +#, c-format +msgid "creating volume %s on %s" +msgstr "" + #: pkg/lint/recipe.go:22 msgid "critical" msgstr "" @@ -2553,6 +2657,10 @@ msgstr "" msgid "download [flags]" msgstr "descargar [flags]" +#: cli/internal/deploy.go:192 +msgid "dry run" +msgstr "" + #: pkg/git/add.go:22 #, c-format msgid "dry run: adding %s" @@ -2595,7 +2703,7 @@ msgstr "" msgid "dry run: remote %s (%s) not created" msgstr "" -#: cli/catalogue/catalogue.go:301 cli/recipe/release.go:639 +#: cli/app/move.go:347 cli/catalogue/catalogue.go:301 cli/recipe/release.go:639 #: cli/recipe/sync.go:269 msgid "dry-run" msgstr "" @@ -2722,6 +2830,16 @@ msgstr "" msgid "expected 1 service but found %v: %s" msgstr "" +#: cli/app/move.go:174 +#, c-format +msgid "extracting %s on %s" +msgstr "" + +#: cli/app/move.go:313 +#, c-format +msgid "extracting secret %s on %s" +msgstr "" + #. translators: `abra recipe fetch` aliases. use a comma separated list of aliases #. with no spaces in between #: cli/app/deploy.go:347 cli/app/remove.go:163 cli/app/rollback.go:329 @@ -2750,6 +2868,16 @@ msgstr "" msgid "failed to commit changes: %s" msgstr "" +#: cli/app/move.go:164 +#, c-format +msgid "failed to copy %s from %s to local machine: output:%s err:%s" +msgstr "" + +#: cli/app/move.go:171 +#, c-format +msgid "failed to copy %s from local machine to %s: output:%s err:%s" +msgstr "" + #: pkg/upstream/stack/stack.go:531 #, c-format msgid "failed to create %s" @@ -2770,6 +2898,11 @@ msgstr "" msgid "failed to create secret %s" msgstr "" +#: cli/app/move.go:150 +#, fuzzy, c-format +msgid "failed to create volume %s on %s: %s" +msgstr "🥷 Genera secretos (contraseñas) automáticamente 🤖" + #: cli/updater/updater.go:263 #, c-format msgid "failed to determine deployed version of %s" @@ -2798,6 +2931,11 @@ msgstr "" msgid "failed to match chosen service" msgstr "" +#: cli/app/move.go:204 +#, c-format +msgid "failed to migrate app config: %s" +msgstr "" + #: pkg/client/registry.go:20 #, c-format msgid "failed to parse image %s, saw: %s" @@ -2808,6 +2946,21 @@ msgstr "" msgid "failed to publish new release: %s" msgstr "" +#: cli/app/move.go:185 cli/app/move.go:191 +#, c-format +msgid "failed to remove %s from %s: output:%s err:%s" +msgstr "" + +#: cli/app/move.go:197 +#, c-format +msgid "failed to remove %s on local machine: output:%s err:%s" +msgstr "" + +#: cli/app/move.go:119 +#, c-format +msgid "failed to remove app from %s: %s" +msgstr "" + #: pkg/upstream/stack/remove.go:183 #, c-format msgid "failed to remove config %s: %s" @@ -2852,6 +3005,11 @@ msgstr "" msgid "failed to select default branch in %s" msgstr "" +#: cli/app/move.go:132 +#, fuzzy, c-format +msgid "failed to store secret on %s: %s" +msgstr "🥷 Genera secretos (contraseñas) automáticamente 🤖" + #: cli/recipe/release.go:269 cli/recipe/release.go:554 #, c-format msgid "failed to tag release: %s" @@ -3458,12 +3616,14 @@ msgstr "" msgid "ls" msgstr "plataformas" +#. translators: `abra app move` aliases. use a comma separated list of aliases +#. with no spaces in between #. translators: `abra man` aliases. use a comma separated list of aliases #. with no spaces in between -#: cli/app/list.go:318 cli/app/ps.go:205 cli/app/secret.go:553 -#: cli/app/secret.go:649 cli/recipe/list.go:104 cli/recipe/upgrade.go:376 -#: cli/recipe/version.go:139 cli/run.go:128 cli/server/list.go:106 -#: cli/updater/updater.go:560 +#: cli/app/list.go:318 cli/app/move.go:34 cli/app/ps.go:205 +#: cli/app/secret.go:553 cli/app/secret.go:649 cli/recipe/list.go:104 +#: cli/recipe/upgrade.go:376 cli/recipe/version.go:139 cli/run.go:128 +#: cli/server/list.go:106 cli/updater/updater.go:560 msgid "m" msgstr "" @@ -3499,6 +3659,11 @@ msgstr "" msgid "man [flags]" msgstr "manual [flags]" +#: cli/app/move.go:202 +#, c-format +msgid "migrating app config from %s to %s" +msgstr "" + #: cli/internal/recipe.go:48 cli/internal/recipe.go:68 #: cli/internal/recipe.go:82 cli/recipe/release.go:655 cli/recipe/sync.go:285 #: cli/recipe/upgrade.go:359 @@ -3530,6 +3695,17 @@ msgstr "" msgid "missing version for secret? (%s)" msgstr "" +#. translators: `app move` command +#: cli/app/move.go:38 +#, fuzzy +msgid "move [flags]" +msgstr "borrar [flags]" + +#: cli/app/move.go:138 +#, c-format +msgid "moving volume %s from %s to %s" +msgstr "" + #: cli/app/secret.go:292 msgid "must provide argument if --no-input is passed" msgstr "" @@ -3743,6 +3919,10 @@ msgstr "" msgid "no server provided" msgstr "" +#: cli/app/move.go:81 +msgid "no server provided?" +msgstr "" + #: cli/app/cmd.go:174 #, c-format msgid "no service %s for %s?" @@ -3806,7 +3986,7 @@ msgstr "" msgid "no-tty" msgstr "" -#: cli/internal/deploy.go:159 +#: cli/internal/deploy.go:222 #, c-format msgid "not enough arguments: %s" msgstr "" @@ -3973,8 +4153,9 @@ msgid "polling deployment status" msgstr "" #: pkg/upstream/stack/remove.go:87 +#, fuzzy msgid "polling undeploy status" -msgstr "" +msgstr "📋 Revisar el estado de una plataforma" #: cli/run.go:185 msgid "prefer offline & filesystem access" @@ -3990,6 +4171,10 @@ msgstr "" msgid "print machine-readable output" msgstr "" +#: cli/internal/deploy.go:196 +msgid "proceed?" +msgstr "" + #: pkg/recipe/git.go:398 #, c-format msgid "processing %s for %s" @@ -4037,8 +4222,8 @@ msgstr "" #. with no spaces in between #. translators: `abra recipe` aliases. use a comma separated list of aliases #. with no spaces in between -#: cli/app/backup.go:327 cli/app/list.go:303 cli/app/run.go:23 -#: cli/app/upgrade.go:463 cli/catalogue/catalogue.go:302 +#: cli/app/backup.go:327 cli/app/list.go:303 cli/app/move.go:348 +#: cli/app/run.go:23 cli/app/upgrade.go:463 cli/catalogue/catalogue.go:302 #: cli/recipe/recipe.go:12 cli/recipe/release.go:640 cli/recipe/sync.go:270 msgid "r" msgstr "" @@ -4221,6 +4406,16 @@ msgstr "" msgid "removed freshly created tag %s" msgstr "" +#: cli/app/move.go:182 cli/app/move.go:188 +#, c-format +msgid "removing %s from %s" +msgstr "" + +#: cli/app/move.go:194 +#, c-format +msgid "removing %s from local machine" +msgstr "" + #: cli/server/prune.go:64 msgid "removing all images, not only dangling ones" msgstr "" @@ -4278,7 +4473,7 @@ msgstr "" msgid "repo set config: %s" msgstr "" -#: cli/catalogue/catalogue.go:304 cli/recipe/release.go:642 +#: cli/app/move.go:350 cli/catalogue/catalogue.go:304 cli/recipe/release.go:642 #: cli/recipe/sync.go:272 msgid "report changes that would be made" msgstr "" @@ -4421,6 +4616,16 @@ msgstr "" msgid "rs" msgstr "" +#: cli/app/move.go:168 +#, c-format +msgid "rsyncing %s (renaming to %s) to %s from local machine" +msgstr "" + +#: cli/app/move.go:161 +#, c-format +msgid "rsyncing %s from %s to local machine" +msgstr "" + #: cli/recipe/lint.go:41 msgid "rule" msgstr "" @@ -4448,7 +4653,7 @@ msgstr "" msgid "running backup %s on %s with exec config %v" msgstr "" -#: cli/internal/deploy.go:189 +#: cli/internal/deploy.go:252 #, c-format msgid "running command %s %s within the context of %s_%s" msgstr "" @@ -4468,7 +4673,7 @@ msgstr "" msgid "running command: %s" msgstr "" -#: cli/internal/deploy.go:167 +#: cli/internal/deploy.go:230 #, c-format msgid "running post-command '%s %s' in container %s" msgstr "" @@ -4686,6 +4891,11 @@ msgstr "" msgid "skipped" msgstr "" +#: cli/app/move.go:280 +#, c-format +msgid "skipping %s as it does not match %s" +msgstr "" + #: pkg/lint/recipe.go:55 #, c-format msgid "skipping %s based on skip condition" @@ -4832,12 +5042,12 @@ msgstr "" msgid "successfully created %s" msgstr "" -#: pkg/client/client.go:97 +#: pkg/client/client.go:104 #, c-format msgid "swarm mode not enabled on %s?" msgstr "" -#: pkg/client/client.go:100 +#: pkg/client/client.go:107 msgid "swarm mode not enabled on local server?" msgstr "" @@ -5060,6 +5270,11 @@ msgstr "" msgid "unable to fetch tags in %s: %s" msgstr "" +#: cli/app/move.go:299 +#, c-format +msgid "unable to get container matching %s: %s" +msgstr "" + #: pkg/recipe/git.go:274 #, c-format msgid "unable to git pull in %s: %s" @@ -5142,6 +5357,11 @@ msgid "" "recipe sync %s\" already?" msgstr "" +#: cli/app/move.go:208 +#, c-format +msgid "unable to remove %s: %s" +msgstr "" + #: cli/recipe/fetch.go:81 #, c-format msgid "unable to remove default remote in %s: %s" @@ -5168,6 +5388,11 @@ msgstr "" msgid "unable to resolve IPv4 for %s" msgstr "" +#: cli/app/move.go:105 +#, c-format +msgid "unable to retrieve %s resources on %s: %s" +msgstr "" + #: cli/app/list.go:159 #, c-format msgid "unable to retrieve tags for %s: %s" @@ -5227,6 +5452,11 @@ msgstr "desarmar [flags]" msgid "undeploy succeeded 🟢" msgstr "" +#: cli/app/move.go:113 +#, c-format +msgid "undeploying %s on %s" +msgstr "" + #: pkg/upstream/stack/loader.go:108 #, c-format msgid "unexpected environment %q" diff --git a/pkg/secret/secret.go b/pkg/secret/secret.go index 2f9c9529..ffec2915 100644 --- a/pkg/secret/secret.go +++ b/pkg/secret/secret.go @@ -218,7 +218,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server return } - if err := client.StoreSecret(cl, secret.RemoteName, password, server); err != nil { + if err := client.StoreSecret(cl, secret.RemoteName, password); err != nil { if strings.Contains(err.Error(), "AlreadyExists") { log.Warnf(i18n.G("%s already exists", secret.RemoteName)) ch <- nil @@ -238,7 +238,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server return } - if err := client.StoreSecret(cl, secret.RemoteName, passphrase, server); err != nil { + if err := client.StoreSecret(cl, secret.RemoteName, passphrase); err != nil { if strings.Contains(err.Error(), "AlreadyExists") { log.Warnf(i18n.G("%s already exists", secret.RemoteName)) ch <- nil diff --git a/tests/integration/app_move.bats b/tests/integration/app_move.bats new file mode 100644 index 00000000..28673242 --- /dev/null +++ b/tests/integration/app_move.bats @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +setup_file(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _add_server + _add_move_server + _new_app +} + +teardown_file(){ + _rm_app + _rm_server + _rm_move_server +} + +setup(){ + load "$PWD/tests/integration/helpers/common" + _common_setup + _ensure_catalogue +} + +teardown(){ + _undeploy_app +} + +@test "validate app argument" { + run $ABRA app move + assert_failure + + run $ABRA app move DOESNTEXIST + assert_failure + + run $ABRA app move "$TEST_APP_DOMAIN" DOESNTEXIST + assert_failure +} + +@test "move app fails if not deployed" { + run $ABRA app move "$TEST_APP_DOMAIN" "$TEST_MOVE_SERVER" + assert_failure + assert_output --partial 'must first be deployed' +} diff --git a/tests/integration/helpers/common.bash b/tests/integration/helpers/common.bash index 892be7b2..0ad6ebaf 100644 --- a/tests/integration/helpers/common.bash +++ b/tests/integration/helpers/common.bash @@ -17,6 +17,7 @@ _common_setup() { export TEST_APP_NAME="$(basename "${BATS_TEST_FILENAME//./_}")" export TEST_APP_DOMAIN="$TEST_APP_NAME.$TEST_SERVER" + export TEST_MOVE_SERVER="default2" export TEST_RECIPE="abra-test-recipe" _ensure_swarm diff --git a/tests/integration/helpers/server.bash b/tests/integration/helpers/server.bash index e3bcb320..76d6164d 100644 --- a/tests/integration/helpers/server.bash +++ b/tests/integration/helpers/server.bash @@ -10,6 +10,15 @@ _add_server() { assert_exists "$ABRA_DIR/servers/$TEST_SERVER" } +_add_move_server() { + run docker context create default2 --docker "host=unix:///var/run/docker.sock" + assert_success + + run mkdir -p "$ABRA_DIR/servers/default2" + assert_success + assert_exists "$ABRA_DIR/servers/default2" +} + _rm_server() { if [[ "$TEST_SERVER" == "default" ]]; then run rm -rf "$ABRA_DIR/servers/default" @@ -20,6 +29,15 @@ _rm_server() { assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER" } +_rm_move_server() { + run docker context rm default2 + assert_success + + run rm -rf "$ABRA_DIR/servers/default2" + assert_success + assert_not_exists "$ABRA_DIR/servers/default2" +} + _rm_default_server(){ run rm -rf "$ABRA_DIR/servers/default" assert_success