Compare commits

..

No commits in common. "27fb892208132227afcdf22e5f49af3d8ec6cab7" and "5aad497ed0af31578c4fdbf4d2fc22d345ae49ad" have entirely different histories.

3 changed files with 68 additions and 200 deletions

View File

@ -2,20 +2,16 @@ package app
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log"
"os" "os"
"path"
"strings" "strings"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
containerPkg "coopcloud.tech/abra/pkg/container" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/container"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/upstream/container"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
@ -57,14 +53,36 @@ And if you want to copy that file back to your current working directory locally
logrus.Fatal("missing <dest> argument") logrus.Fatal("missing <dest> argument")
} }
srcPath, dstPath, service, isToContainer, err := parseSrcAndDst(src, dst) parsedSrc := strings.SplitN(src, ":", 2)
if err != nil { parsedDst := strings.SplitN(dst, ":", 2)
log.Fatal(err) errorMsg := "one of <src>/<dest> arguments must take $SERVICE:$PATH form"
if len(parsedSrc) == 2 && len(parsedDst) == 2 {
logrus.Fatal(errorMsg)
} else if len(parsedSrc) != 2 {
if len(parsedDst) != 2 {
logrus.Fatal(errorMsg)
}
} else if len(parsedDst) != 2 {
if len(parsedSrc) != 2 {
logrus.Fatal(errorMsg)
}
} }
if isToContainer {
logrus.Debugf("assuming transfer is going TO the container") var service string
} else { var srcPath string
var dstPath string
isToContainer := false // <container:src> <dst>
if len(parsedSrc) == 2 {
service = parsedSrc[0]
srcPath = parsedSrc[1]
dstPath = dst
logrus.Debugf("assuming transfer is coming FROM the container") logrus.Debugf("assuming transfer is coming FROM the container")
} else if len(parsedDst) == 2 {
service = parsedDst[0]
dstPath = parsedDst[1]
srcPath = src
isToContainer = true // <src> <container:dst>
logrus.Debugf("assuming transfer is going TO the container")
} }
if isToContainer { if isToContainer {
@ -78,18 +96,7 @@ And if you want to copy that file back to your current working directory locally
logrus.Fatal(err) logrus.Fatal(err)
} }
container, err := findContainer(cl, app.StackName(), service) if err := configureAndCp(c, cl, app, srcPath, dstPath, service, isToContainer); err != nil {
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) logrus.Fatal(err)
} }
@ -97,88 +104,46 @@ And if you want to copy that file back to your current working directory locally
}, },
} }
var errServiceMissing = errors.New("one of <src>/<dest> arguments must take $SERVICE:$PATH form") func configureAndCp(
c *cli.Context,
// parseSrcAndDst parses src and dest string. One of src or dst must be of the form $SERVICE:$PATH cl *dockerClient.Client,
func parseSrcAndDst(src, dst string) (srcPath string, dstPath string, service string, toContainer bool, err error) { app config.App,
parsedSrc := strings.SplitN(src, ":", 2) srcPath string,
parsedDst := strings.SplitN(dst, ":", 2) dstPath string,
if len(parsedSrc)+len(parsedDst) != 3 { service string,
return "", "", "", false, errServiceMissing isToContainer bool) error {
}
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 := filters.NewArgs()
filters.Add("name", fmt.Sprintf("^%s_%s", stack, service)) filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service))
container, err := containerPkg.GetContainer(context.Background(), cl, filters, internal.NoInput) container, err := container.GetContainer(context.Background(), cl, filters, internal.NoInput)
if err != nil { if err != nil {
return types.Container{}, err logrus.Fatal(err)
}
return container, nil
}
// copyToContainer works with one of the following:
// <src_dir>/<file> -> <dst_dir> = <dst_dir>/<file>
// <src_dir>/<file> -> <dst_dir>/<file> = <dst_dir>/<file> (overrides the file)
// <src_dir> -> <dst_dir> = <dst_dir>/<contents_of_src_dir> (creates the dst_dir if it does not exist)
func copyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath string) error {
toTarOpts := &archive.TarOptions{IncludeSourceDir: true, NoOverwriteDirNonDir: true, Compression: archive.Gzip}
content, err := archive.TarWithOptions(srcPath, toTarOpts)
if err != nil {
return err
} }
srcStat, _ := os.Stat(srcPath) logrus.Debugf("retrieved %s as target container on %s", formatter.ShortenID(container.ID), app.Server)
stat, _ := cl.ContainerStatPath(context.Background(), containerID, dstPath)
if !stat.Mode.IsDir() { if isToContainer {
if srcStat.IsDir() { toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip}
// Make sure the dst directory exits, when copying a directory. content, err := archive.TarWithOptions(srcPath, toTarOpts)
dcli, err := command.NewDockerCli() if err != nil {
if err != nil { logrus.Fatal(err)
return err }
}
if err := container.RunExec(dcli, cl, containerID, &types.ExecConfig{ copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false}
AttachStderr: true, if err := cl.CopyToContainer(context.Background(), container.ID, dstPath, content, copyOpts); err != nil {
AttachStdin: true, logrus.Fatal(err)
AttachStdout: true, }
Cmd: []string{"mkdir", "-p", dstPath}, } else {
Detach: false, content, _, err := cl.CopyFromContainer(context.Background(), container.ID, srcPath)
Tty: true, if err != nil {
}); err != nil { logrus.Fatal(err)
return fmt.Errorf("create remote directory: %s", err) }
} defer content.Close()
} else { fromTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip}
// Remove the file component from the path, since docker can only copy to a directoy. if err := archive.Untar(content, dstPath, fromTarOpts); err != nil {
dstPath, _ = path.Split(dstPath) logrus.Fatal(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 return nil
} }

View File

@ -1,39 +0,0 @@
package app
import "testing"
func TestParse(t *testing.T) {
tests := []struct {
src string
dst string
srcPath string
dstPath string
service string
toContainer bool
err error
}{
{src: "foo", dst: "bar", err: errServiceMissing},
{src: "app:foo", dst: "app:bar", err: errServiceMissing},
{src: "app:foo", dst: "bar", srcPath: "foo", dstPath: "bar", service: "app", toContainer: false},
{src: "foo", dst: "app:bar", srcPath: "foo", dstPath: "bar", service: "app", toContainer: true},
}
for i, tc := range tests {
srcPath, dstPath, service, toContainer, err := parseSrcAndDst(tc.src, tc.dst)
if srcPath != tc.srcPath {
t.Errorf("[%d] srcPath: want (%s), got(%s)", i, tc.srcPath, srcPath)
}
if dstPath != tc.dstPath {
t.Errorf("[%d] dstPath: want (%s), got(%s)", i, tc.dstPath, dstPath)
}
if service != tc.service {
t.Errorf("[%d] service: want (%s), got(%s)", i, tc.service, service)
}
if toContainer != tc.toContainer {
t.Errorf("[%d] toConainer: want (%t), got(%t)", i, tc.toContainer, toContainer)
}
if err == nil && tc.err != nil && err.Error() != tc.err.Error() {
t.Errorf("[%d] err: want (%s), got(%s)", i, tc.err, err)
}
}
}

View File

@ -76,7 +76,7 @@ teardown(){
@test "error if service doesn't exist" { @test "error if service doesn't exist" {
_deploy_app _deploy_app
run bash -c "echo foo > $BATS_TMPDIR/myfile.txt" run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt"
assert_success assert_success
run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" doesnt_exist:/ run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" doesnt_exist:/
@ -90,19 +90,15 @@ teardown(){
} }
# bats test_tags=slow # bats test_tags=slow
@test "copy local file to container directory" { @test "copy to container" {
_deploy_app _deploy_app
run bash -c "echo foo > $BATS_TMPDIR/myfile.txt" run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt"
assert_success assert_success
run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc
assert_success assert_success
run $ABRA app run "$TEST_APP_DOMAIN" app cat /etc/myfile.txt
assert_success
assert_output --partial "foo"
run rm -rf "$BATS_TMPDIR/myfile.txt" run rm -rf "$BATS_TMPDIR/myfile.txt"
assert_success assert_success
@ -110,61 +106,7 @@ teardown(){
} }
# bats test_tags=slow # bats test_tags=slow
@test "copy local file to container file (override on remote)" { @test "copy from container" {
_deploy_app
run bash -c "echo 'foo' > $BATS_TMPDIR/myfile.txt"
assert_success
run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc
assert_success
run $ABRA app run "$TEST_APP_DOMAIN" app cat /etc/myfile.txt
assert_success
assert_output --partial "foo"
run bash -c "echo 'bar' > $BATS_TMPDIR/myfile.txt"
assert_success
run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc/myfile.txt
assert_success
run $ABRA app run "$TEST_APP_DOMAIN" app cat /etc/myfile.txt
assert_success
assert_output --partial "bar"
run rm -rf "$BATS_TMPDIR/myfile.txt"
assert_success
_undeploy_app
}
# bats test_tags=slow
@test "copy local directory to container directory (and creates missing directory)" {
_deploy_app
run bash -c "mkdir -p $BATS_TMPDIR/mydir"
assert_success
run bash -c "echo 'foo' > $BATS_TMPDIR/mydir/myfile.txt"
assert_success
run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/mydir" app:/etc/mydir
assert_success
run $ABRA app run "$TEST_APP_DOMAIN" app ls /etc/mydir
assert_success
assert_output --partial "myfile.txt"
run rm -rf "$BATS_TMPDIR/mydir"
assert_success
_undeploy_app
}
# bats test_tags=slow
@test "copy container file to local directory" {
_deploy_app _deploy_app
run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt" run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt"