feat: Adds abra app move
All checks were successful
continuous-integration/drone/pr Build is passing
All checks were successful
continuous-integration/drone/pr Build is passing
This commit is contained in:
@ -141,7 +141,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
|
||||
Cmd: []string{"mkdir", "-p", dstPath},
|
||||
Detach: false,
|
||||
Tty: true,
|
||||
}); err != nil {
|
||||
}, true); err != nil {
|
||||
return fmt.Errorf("create remote directory: %s", err)
|
||||
}
|
||||
case CopyModeFileToFile:
|
||||
@ -180,7 +180,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
|
||||
Cmd: []string{"mv", path.Join("/tmp", srcFile), movePath},
|
||||
Detach: false,
|
||||
Tty: true,
|
||||
}); err != nil {
|
||||
}, true); err != nil {
|
||||
return fmt.Errorf("create remote directory: %s", err)
|
||||
}
|
||||
}
|
||||
|
292
cli/app/move.go
Normal file
292
cli/app/move.go
Normal file
@ -0,0 +1,292 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"coopcloud.tech/abra/pkg/upstream/container"
|
||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"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/spf13/cobra"
|
||||
)
|
||||
|
||||
var AppMoveCommand = &cobra.Command{
|
||||
Use: "move <domain> <server> [flags]",
|
||||
Aliases: []string{"d"},
|
||||
Short: "Moves an app to a different server",
|
||||
Long: `Deploy an app.
|
||||
|
||||
This command supports chaos operations. Use "--chaos/-C" to deploy your recipe
|
||||
checkout as-is. Recipe commit hashes are also supported as values for
|
||||
"[version]". Please note, "upgrade"/"rollback" do not support chaos operations.`,
|
||||
Example: ` # standard deployment
|
||||
abra app deploy 1312.net
|
||||
|
||||
# chaos deployment
|
||||
abra app deploy 1312.net --chaos
|
||||
|
||||
# deploy specific version
|
||||
abra app deploy 1312.net 2.0.0+1.2.3
|
||||
|
||||
# deploy a specific git hash
|
||||
abra app deploy 1312.net 886db76d`,
|
||||
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
|
||||
}
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, app.StackName()))
|
||||
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dcli, err := command.NewDockerCli()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretsToStore := map[string]string{}
|
||||
volumes := map[string]containertypes.MountPoint{}
|
||||
for _, s := range services {
|
||||
log.Info("service", s.Spec.Name)
|
||||
// stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), s.Spec.Name)
|
||||
f := filters.NewArgs()
|
||||
f.Add("name", s.Spec.Name)
|
||||
targetContainer, err := containerPkg.GetContainer(context.Background(), cl, f, true)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
continue
|
||||
}
|
||||
for _, m := range targetContainer.Mounts {
|
||||
if m.Type == mount.TypeVolume {
|
||||
volumes[m.Name] = m
|
||||
}
|
||||
}
|
||||
|
||||
execCreateOpts := containertypes.ExecOptions{
|
||||
AttachStderr: false,
|
||||
AttachStdin: false,
|
||||
AttachStdout: true,
|
||||
Cmd: []string{"ls", "/run/secrets"},
|
||||
Detach: false,
|
||||
Tty: false,
|
||||
}
|
||||
out, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts, false)
|
||||
if err != nil {
|
||||
log.Error(out)
|
||||
continue
|
||||
}
|
||||
for _, secret := range strings.Split(strings.TrimSpace(out), "\n") {
|
||||
if _, ok := secretsToStore[secret]; ok {
|
||||
continue
|
||||
}
|
||||
log.Debugf("extracting secret %s", secret)
|
||||
|
||||
execCreateOpts := containertypes.ExecOptions{
|
||||
AttachStderr: false,
|
||||
AttachStdin: false,
|
||||
AttachStdout: true,
|
||||
Cmd: []string{"cat", fmt.Sprintf("/run/secrets/%s", secret)},
|
||||
Detach: false,
|
||||
Tty: false,
|
||||
}
|
||||
out, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretsToStore[secret] = out
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
opts := stack.Deploy{Composefiles: composeFiles, Namespace: app.StackName()}
|
||||
compose, err := appPkg.GetAppComposeConfig(app.Name, opts, app.Env)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
stack.WaitTimeout, err = appPkg.GetTimeoutFromLabel(compose, app.StackName())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
rmOpts := stack.Remove{
|
||||
Namespaces: []string{app.StackName()},
|
||||
Detach: false,
|
||||
}
|
||||
if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cl2, err := client.New(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filters, err := app.Filters(false, false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filters})
|
||||
for _, s := range secretList {
|
||||
sname := strings.Split(strings.TrimPrefix(s.Spec.Name, app.StackName()+"_"), "_")
|
||||
secretName := strings.Join(sname[:len(sname)-1], "_")
|
||||
data := secretsToStore[secretName]
|
||||
fmt.Println(s.Spec.Name)
|
||||
fmt.Println(secretName)
|
||||
fmt.Println(data)
|
||||
if err := client.StoreSecret(cl2, s.Spec.Name, data); err != nil {
|
||||
log.Info(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, volume := range volumes {
|
||||
fileName := fmt.Sprintf("%s.tar.gz", volume.Name)
|
||||
|
||||
log.Infof("moving volume: %s", volume.Name)
|
||||
log.Debug("creating %s", fileName)
|
||||
cmd := exec.Command("ssh", app.Server, "-tt", fmt.Sprintf("sudo tar --same-owner -czhpf %s -C /var/lib/docker/volumes %s", fileName, volume.Name))
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
fmt.Println(string(out))
|
||||
fmt.Println(err)
|
||||
}
|
||||
log.Debug("copying %s to local machine", fileName)
|
||||
cmd = exec.Command("scp", fmt.Sprintf("%s:%s", app.Server, fileName), fileName)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
fmt.Println(string(out))
|
||||
fmt.Println(err)
|
||||
}
|
||||
log.Debug("copying %s to %s", fileName, args[1])
|
||||
cmd = exec.Command("scp", fileName, fmt.Sprintf("%s:%s", args[1], fileName))
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
fmt.Println(string(out))
|
||||
fmt.Println(err)
|
||||
}
|
||||
log.Debug("extracting %s on %s", fileName, args[1])
|
||||
cmd = exec.Command("ssh", args[1], "-tt", fmt.Sprintf("sudo tar --same-owner -xzpf %s -C /var/lib/docker/volumes", fileName))
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
fmt.Println(string(out))
|
||||
fmt.Println(err)
|
||||
}
|
||||
cmd = exec.Command("ssh", args[1], "-tt", fmt.Sprintf("sudo rm %s", fileName))
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
fmt.Println(string(out))
|
||||
fmt.Println(err)
|
||||
}
|
||||
cmd = exec.Command("ssh", app.Server, "-tt", fmt.Sprintf("sudo rm %s", fileName))
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
fmt.Println(string(out))
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(strings.ReplaceAll(app.Path, app.Server, args[1]))
|
||||
if err := copyFile(app.Path, strings.ReplaceAll(app.Path, app.Server, args[1])); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(app.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// AppDeployCommand.Run(cmd, args)
|
||||
|
||||
// app = internal.ValidateApp(args)
|
||||
// cl, err := client.New(app.Server)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// stackName := app.StackName()
|
||||
// deployOpts := stack.Deploy{
|
||||
// Composefiles: composeFiles,
|
||||
// Namespace: stackName,
|
||||
// Prune: false,
|
||||
// ResolveImage: stack.ResolveImageAlways,
|
||||
// Detach: false,
|
||||
// }
|
||||
// compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// f, err := app.Filters(true, false, serviceNames...)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// if err := stack.RunDeploy(
|
||||
// cl,
|
||||
// deployOpts,
|
||||
// compose,
|
||||
// app.Name,
|
||||
// app.Server,
|
||||
// internal.DontWaitConverge,
|
||||
// f,
|
||||
// ); err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
||||
return 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
|
||||
}
|
@ -85,7 +85,7 @@ var AppRunCommand = &cobra.Command{
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts, true); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
|
@ -196,7 +196,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)
|
||||
}
|
||||
|
||||
|
@ -66,10 +66,10 @@ func RunBackupCmdRemote(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err := container.RunExec(dcli, cl, containerID, &execBackupListOpts)
|
||||
_, err = container.RunExec(dcli, cl, containerID, &execBackupListOpts, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ func RunCmdRemote(
|
||||
Tty: false,
|
||||
}
|
||||
|
||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts, true); err != nil {
|
||||
log.Infof("%s does not exist for %s, use /bin/sh as fallback", shell, app.Name)
|
||||
shell = "/bin/sh"
|
||||
}
|
||||
@ -91,7 +91,7 @@ func RunCmdRemote(
|
||||
log.Debugf("not requesting a remote TTY")
|
||||
}
|
||||
|
||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -203,6 +203,7 @@ func Run(version, commit string) {
|
||||
app.AppRestartCommand,
|
||||
app.AppRestoreCommand,
|
||||
app.AppRollbackCommand,
|
||||
app.AppMoveCommand,
|
||||
app.AppRunCommand,
|
||||
app.AppSecretCommand,
|
||||
app.AppServicesCommand,
|
||||
|
@ -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)}
|
||||
|
||||
|
@ -201,7 +201,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("%s already exists", secret.RemoteName)
|
||||
ch <- nil
|
||||
@ -221,7 +221,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("%s already exists", secret.RemoteName)
|
||||
ch <- nil
|
||||
|
@ -1,6 +1,7 @@
|
||||
package container // https://github.com/docker/cli/blob/master/cli/command/container/exec.go
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -16,7 +17,7 @@ import (
|
||||
// RunExec runs a command on a remote container. io.Writer corresponds to the
|
||||
// command output.
|
||||
func RunExec(dockerCli command.Cli, client *apiclient.Client, containerID string,
|
||||
execOptions *container.ExecOptions) (io.Writer, error) {
|
||||
execOptions *container.ExecOptions, stdout bool) (string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// We need to check the tty _before_ we do the ContainerExecCreate, because
|
||||
@ -24,22 +25,22 @@ func RunExec(dockerCli command.Cli, client *apiclient.Client, containerID string
|
||||
// there's no easy way to clean those up). But also in order to make "not
|
||||
// exist" errors take precedence we do a dummy inspect first.
|
||||
if _, err := client.ContainerInspect(ctx, containerID); err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
if !execOptions.Detach {
|
||||
if err := dockerCli.In().CheckTty(execOptions.AttachStdin, execOptions.Tty); err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
response, err := client.ContainerExecCreate(ctx, containerID, *execOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
execID := response.ID
|
||||
if execID == "" {
|
||||
return nil, errors.New("exec ID empty")
|
||||
return "", errors.New("exec ID empty")
|
||||
}
|
||||
|
||||
if execOptions.Detach {
|
||||
@ -47,16 +48,17 @@ func RunExec(dockerCli command.Cli, client *apiclient.Client, containerID string
|
||||
Detach: execOptions.Detach,
|
||||
Tty: execOptions.Tty,
|
||||
}
|
||||
return nil, client.ContainerExecStart(ctx, execID, execStartCheck)
|
||||
return "", client.ContainerExecStart(ctx, execID, execStartCheck)
|
||||
}
|
||||
return interactiveExec(ctx, dockerCli, client, execOptions, execID)
|
||||
return interactiveExec(ctx, dockerCli, client, execOptions, execID, stdout)
|
||||
}
|
||||
|
||||
func interactiveExec(ctx context.Context, dockerCli command.Cli, client *apiclient.Client,
|
||||
execOpts *container.ExecOptions, execID string) (io.Writer, error) {
|
||||
execOpts *container.ExecOptions, execID string, stdout bool) (string, error) {
|
||||
// Interactive exec requested.
|
||||
var (
|
||||
out, stderr io.Writer
|
||||
outBuffer *bytes.Buffer
|
||||
in io.ReadCloser
|
||||
)
|
||||
|
||||
@ -64,7 +66,12 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, client *apiclie
|
||||
in = dockerCli.In()
|
||||
}
|
||||
if execOpts.AttachStdout {
|
||||
out = dockerCli.Out()
|
||||
if stdout {
|
||||
out = dockerCli.Out()
|
||||
} else {
|
||||
outBuffer = new(bytes.Buffer)
|
||||
out = outBuffer
|
||||
}
|
||||
}
|
||||
if execOpts.AttachStderr {
|
||||
if execOpts.Tty {
|
||||
@ -79,7 +86,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, client *apiclie
|
||||
}
|
||||
resp, err := client.ContainerExecAttach(ctx, execID, execStartCheck)
|
||||
if err != nil {
|
||||
return out, err
|
||||
return "", err
|
||||
}
|
||||
defer resp.Close()
|
||||
|
||||
@ -110,10 +117,14 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, client *apiclie
|
||||
|
||||
if err := <-errCh; err != nil {
|
||||
log.Debugf("Error hijack: %s", err)
|
||||
return out, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
return out, getExecExitStatus(ctx, client, execID)
|
||||
if execOpts.AttachStdout {
|
||||
return outBuffer.String(), getExecExitStatus(ctx, client, execID)
|
||||
}
|
||||
|
||||
return "", getExecExitStatus(ctx, client, execID)
|
||||
}
|
||||
|
||||
func getExecExitStatus(ctx context.Context, client apiclient.ContainerAPIClient, execID string) error {
|
||||
|
Reference in New Issue
Block a user