152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
"coopcloud.tech/abra/cli/internal"
|
|
"coopcloud.tech/abra/pkg/autocomplete"
|
|
"coopcloud.tech/abra/pkg/client"
|
|
"coopcloud.tech/abra/pkg/container"
|
|
"coopcloud.tech/abra/pkg/formatter"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
dockerClient "github.com/docker/docker/client"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
var appCpCommand = cli.Command{
|
|
Name: "cp",
|
|
Aliases: []string{"c"},
|
|
ArgsUsage: "<domain> <src> <dst>",
|
|
Flags: []cli.Flag{
|
|
internal.DebugFlag,
|
|
internal.NoInputFlag,
|
|
},
|
|
Before: internal.SubCommandBefore,
|
|
Usage: "Copy files to/from a deployed app service",
|
|
Description: `
|
|
Copy files to and from any app service file system.
|
|
|
|
If you want to copy a myfile.txt to the root of the app service:
|
|
|
|
abra app cp <domain> myfile.txt app:/
|
|
|
|
And if you want to copy that file back to your current working directory locally:
|
|
|
|
abra app cp <domain> app:/myfile.txt .
|
|
`,
|
|
BashComplete: autocomplete.AppNameComplete,
|
|
Action: func(c *cli.Context) error {
|
|
app := internal.ValidateApp(c)
|
|
|
|
src := c.Args().Get(1)
|
|
dst := c.Args().Get(2)
|
|
if src == "" {
|
|
logrus.Fatal("missing <src> argument")
|
|
} else if dst == "" {
|
|
logrus.Fatal("missing <dest> argument")
|
|
}
|
|
|
|
srcPath, dstPath, service, isToContainer, err := parseSrcAndDst(src, dst)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if isToContainer {
|
|
logrus.Debugf("assuming transfer is going TO the container")
|
|
} else {
|
|
logrus.Debugf("assuming transfer is coming FROM the container")
|
|
}
|
|
|
|
if isToContainer {
|
|
if _, err := os.Stat(srcPath); os.IsNotExist(err) {
|
|
logrus.Fatalf("%s does not exist locally?", srcPath)
|
|
}
|
|
}
|
|
|
|
cl, err := client.New(app.Server)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
container, err := findContainer(cl, app.StackName(), service)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
logrus.Debugf("retrieved %s as target container on %s", formatter.ShortenID(container.ID), app.Server)
|
|
|
|
if isToContainer {
|
|
err = copyToContainer(cl, container.ID, srcPath, dstPath)
|
|
} else {
|
|
err = copyFromContainer(cl, container.ID, srcPath, dstPath)
|
|
}
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var errServiceMissing = errors.New("one of <src>/<dest> arguments must take $SERVICE:$PATH form")
|
|
|
|
// parseSrcAndDst parses src and dest string. One of src or dst must be of the form $SERVICE:$PATH
|
|
func parseSrcAndDst(src, dst string) (srcPath string, dstPath string, service string, toContainer bool, err error) {
|
|
parsedSrc := strings.SplitN(src, ":", 2)
|
|
parsedDst := strings.SplitN(dst, ":", 2)
|
|
if len(parsedSrc)+len(parsedDst) != 3 {
|
|
return "", "", "", false, errServiceMissing
|
|
}
|
|
if len(parsedSrc) == 2 {
|
|
return parsedSrc[1], dst, parsedSrc[0], false, nil
|
|
}
|
|
if len(parsedDst) == 2 {
|
|
return src, parsedDst[1], parsedDst[0], true, nil
|
|
}
|
|
return "", "", "", false, errServiceMissing
|
|
}
|
|
|
|
func findContainer(cl *dockerClient.Client, stack, service string) (types.Container, error) {
|
|
filters := filters.NewArgs()
|
|
filters.Add("name", fmt.Sprintf("^%s_%s", stack, service))
|
|
|
|
container, err := container.GetContainer(context.Background(), cl, filters, internal.NoInput)
|
|
if err != nil {
|
|
return types.Container{}, err
|
|
}
|
|
return container, nil
|
|
}
|
|
|
|
func copyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath string) error {
|
|
toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip}
|
|
content, err := archive.TarWithOptions(srcPath, toTarOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false}
|
|
if err := cl.CopyToContainer(context.Background(), containerID, dstPath, content, copyOpts); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func copyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath string) error {
|
|
content, _, err := cl.CopyFromContainer(context.Background(), containerID, srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer content.Close()
|
|
fromTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip}
|
|
if err := archive.Untar(content, dstPath, fromTarOpts); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|