From 2de6934322a6430e2634162668b4ce95489212e9 Mon Sep 17 00:00:00 2001 From: p4u1 Date: Tue, 14 Nov 2023 15:29:53 +0100 Subject: [PATCH] feat: abra app cp enhancements --- cli/app/cp.go | 372 +++++++++++++++++++++++++++------- cli/app/cp_test.go | 113 +++++++++++ go.sum | 1 + pkg/container/container.go | 12 ++ tests/integration/app_cp.bats | 224 ++++++++++++++++---- 5 files changed, 609 insertions(+), 113 deletions(-) create mode 100644 cli/app/cp_test.go diff --git a/cli/app/cp.go b/cli/app/cp.go index 937c7975..bfc2c789 100644 --- a/cli/app/cp.go +++ b/cli/app/cp.go @@ -2,19 +2,24 @@ package app import ( "context" + "errors" "fmt" + "io" "os" + "path" + "path/filepath" "strings" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/client" - "coopcloud.tech/abra/pkg/config" - "coopcloud.tech/abra/pkg/container" + containerPkg "coopcloud.tech/abra/pkg/container" "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/filters" dockerClient "github.com/docker/docker/client" + "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/archive" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -49,46 +54,14 @@ And if you want to copy that file back to your current working directory locally dst := c.Args().Get(2) if src == "" { logrus.Fatal("missing argument") - } else if dst == "" { + } + if dst == "" { logrus.Fatal("missing argument") } - parsedSrc := strings.SplitN(src, ":", 2) - parsedDst := strings.SplitN(dst, ":", 2) - errorMsg := "one of / 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) - } - } - - var service string - var srcPath string - var dstPath string - isToContainer := false // - if len(parsedSrc) == 2 { - service = parsedSrc[0] - srcPath = parsedSrc[1] - dstPath = dst - logrus.Debugf("assuming transfer is coming FROM the container") - } else if len(parsedDst) == 2 { - service = parsedDst[0] - dstPath = parsedDst[1] - srcPath = src - isToContainer = true // - logrus.Debugf("assuming transfer is going TO the container") - } - - if isToContainer { - if _, err := os.Stat(srcPath); os.IsNotExist(err) { - logrus.Fatalf("%s does not exist locally?", srcPath) - } + srcPath, dstPath, service, toContainer, err := parseSrcAndDst(src, dst) + if err != nil { + logrus.Fatal(err) } cl, err := client.New(app.Server) @@ -96,7 +69,18 @@ And if you want to copy that file back to your current working directory locally logrus.Fatal(err) } - if err := configureAndCp(c, cl, app, srcPath, dstPath, service, isToContainer); err != nil { + container, err := containerPkg.GetContainerFromStackAndService(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 toContainer { + err = copyToContainer(cl, container.ID, srcPath, dstPath) + } else { + err = copyFromContainer(cl, container.ID, srcPath, dstPath) + } + if err != nil { logrus.Fatal(err) } @@ -104,46 +88,292 @@ And if you want to copy that file back to your current working directory locally }, } -func configureAndCp( - c *cli.Context, - cl *dockerClient.Client, - app config.App, - srcPath string, - dstPath string, - service string, - isToContainer bool) error { - filters := filters.NewArgs() - filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service)) +var errServiceMissing = errors.New("one of / arguments must take $SERVICE:$PATH form") - container, err := container.GetContainer(context.Background(), cl, filters, internal.NoInput) +// 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 +} + +// copyToContainer copies a file or directory from the local file system to the container. +// See the possible copy modes and their documentation. +func copyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath string) error { + srcStat, err := os.Stat(srcPath) if err != nil { - logrus.Fatal(err) + return fmt.Errorf("local %s ", err) } - logrus.Debugf("retrieved %s as target container on %s", formatter.ShortenID(container.ID), app.Server) + dstStat, err := cl.ContainerStatPath(context.Background(), containerID, dstPath) + dstExists := true + if err != nil { + if errdefs.IsNotFound(err) { + dstExists = false + } else { + return fmt.Errorf("remote path: %s", err) + } + } - if isToContainer { - toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip} - content, err := archive.TarWithOptions(srcPath, toTarOpts) - if err != nil { - logrus.Fatal(err) - } + mode, err := copyMode(srcPath, dstPath, srcStat.Mode(), dstStat.Mode, dstExists) + if err != nil { + return err + } + movePath := "" + switch mode { + case CopyModeDirToDir: + // Add the src directory to the destination path + _, srcDir := path.Split(srcPath) + dstPath = path.Join(dstPath, srcDir) - copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false} - if err := cl.CopyToContainer(context.Background(), container.ID, dstPath, content, copyOpts); err != nil { - logrus.Fatal(err) - } - } else { - content, _, err := cl.CopyFromContainer(context.Background(), container.ID, srcPath) + // Make sure the dst directory exits. + dcli, err := command.NewDockerCli() if err != nil { - logrus.Fatal(err) + return err } - defer content.Close() - fromTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip} - if err := archive.Untar(content, dstPath, fromTarOpts); err != nil { - logrus.Fatal(err) + if err := container.RunExec(dcli, cl, containerID, &types.ExecConfig{ + AttachStderr: true, + AttachStdin: true, + AttachStdout: true, + Cmd: []string{"mkdir", "-p", dstPath}, + Detach: false, + Tty: true, + }); err != nil { + return fmt.Errorf("create remote directory: %s", err) + } + case CopyModeFileToFile: + // Remove the file component from the path, since docker can only copy + // to a directory. + dstPath, _ = path.Split(dstPath) + case CopyModeFileToFileRename: + // Copy the file to the temp directory and move it to its dstPath + // afterwards. + movePath = dstPath + dstPath = "/tmp" + } + + toTarOpts := &archive.TarOptions{IncludeSourceDir: true, NoOverwriteDirNonDir: true, Compression: archive.Gzip} + content, err := archive.TarWithOptions(srcPath, toTarOpts) + if err != nil { + return err + } + + logrus.Debugf("copy %s from local to %s on container", srcPath, dstPath) + copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false} + if err := cl.CopyToContainer(context.Background(), containerID, dstPath, content, copyOpts); err != nil { + return err + } + + if movePath != "" { + _, srcFile := path.Split(srcPath) + dcli, err := command.NewDockerCli() + if err != nil { + return err + } + if err := container.RunExec(dcli, cl, containerID, &types.ExecConfig{ + AttachStderr: true, + AttachStdin: true, + AttachStdout: true, + Cmd: []string{"mv", path.Join("/tmp", srcFile), movePath}, + Detach: false, + Tty: true, + }); err != nil { + return fmt.Errorf("create remote directory: %s", err) } } return nil } + +// copyFromContainer copies a file or directory from the given container to the local file system. +// See the possible copy modes and their documentation. +func copyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath string) error { + srcStat, err := cl.ContainerStatPath(context.Background(), containerID, srcPath) + if err != nil { + if errdefs.IsNotFound(err) { + return fmt.Errorf("remote: %s does not exist", srcPath) + } else { + return fmt.Errorf("remote path: %s", err) + } + } + + dstStat, err := os.Stat(dstPath) + dstExists := true + var dstMode os.FileMode + if err != nil { + if os.IsNotExist(err) { + dstExists = false + } else { + return fmt.Errorf("remote path: %s", err) + } + } else { + dstMode = dstStat.Mode() + } + + mode, err := copyMode(srcPath, dstPath, srcStat.Mode, dstMode, dstExists) + if err != nil { + return err + } + + moveDstDir := "" + moveDstFile := "" + switch mode { + case CopyModeFileToFile: + // Remove the file component from the path, since docker can only copy + // to a directory. + dstPath, _ = path.Split(dstPath) + case CopyModeFileToFileRename: + // Copy the file to the temp directory and move it to its dstPath + // afterwards. + moveDstFile = dstPath + dstPath = "/tmp" + case CopyModeFilesToDir: + // Copy the directory to the temp directory and move it to its + // dstPath afterwards. + moveDstDir = path.Join(dstPath, "/") + dstPath = "/tmp" + + // Make sure the temp directory always gets removed + defer os.Remove(path.Join("/tmp")) + } + + content, _, err := cl.CopyFromContainer(context.Background(), containerID, srcPath) + if err != nil { + return fmt.Errorf("copy: %s", err) + } + defer content.Close() + if err := archive.Untar(content, dstPath, &archive.TarOptions{ + NoOverwriteDirNonDir: true, + Compression: archive.Gzip, + NoLchown: true, + }); err != nil { + return fmt.Errorf("untar: %s", err) + } + + if moveDstFile != "" { + _, srcFile := path.Split(strings.TrimSuffix(srcPath, "/")) + if err := moveFile(path.Join("/tmp", srcFile), moveDstFile); err != nil { + return err + } + } + if moveDstDir != "" { + _, srcDir := path.Split(strings.TrimSuffix(srcPath, "/")) + if err := moveDir(path.Join("/tmp", srcDir), moveDstDir); err != nil { + return err + } + } + return nil +} + +var ( + ErrCopyDirToFile = fmt.Errorf("can't copy dir to file") + ErrDstDirNotExist = fmt.Errorf("destination directory does not exist") +) + +type CopyMode int + +const ( + // Copy a src file to a dest file. The src and dest file names are the same. + // / + / -> / + CopyModeFileToFile = CopyMode(iota) + // Copy a src file to a dest file. The src and dest file names are not the same. + // / + / -> / + CopyModeFileToFileRename + // Copy a src file to dest directory. The dest file gets created in the dest + // folder with the src filename. + // / + -> / + CopyModeFileToDir + // Copy a src directory to dest directory. + // + -> / + CopyModeDirToDir + // Copy all files in the src directory to the dest directory. This works recursively. + // / + -> / + CopyModeFilesToDir +) + +// copyMode takes a src and dest path and file mode to determine the copy mode. +// See the possible copy modes and their documentation. +func copyMode(srcPath, dstPath string, srcMode os.FileMode, dstMode os.FileMode, dstExists bool) (CopyMode, error) { + _, srcFile := path.Split(srcPath) + _, dstFile := path.Split(dstPath) + if srcMode.IsDir() { + if !dstExists { + return -1, ErrDstDirNotExist + } + if dstMode.IsDir() { + if strings.HasSuffix(srcPath, "/") { + return CopyModeFilesToDir, nil + } + return CopyModeDirToDir, nil + } + return -1, ErrCopyDirToFile + } + + if dstMode.IsDir() { + return CopyModeFileToDir, nil + } + + if srcFile != dstFile { + return CopyModeFileToFileRename, nil + } + + return CopyModeFileToFile, nil +} + +// moveDir moves all files from a source path to the destination path recursively. +func moveDir(sourcePath, destPath string) error { + return filepath.Walk(sourcePath, func(p string, info os.FileInfo, err error) error { + if err != nil { + return err + } + newPath := path.Join(destPath, strings.TrimPrefix(p, sourcePath)) + if info.IsDir() { + err := os.Mkdir(newPath, info.Mode()) + if err != nil { + if os.IsExist(err) { + return nil + } + return err + } + } + if info.Mode().IsRegular() { + return moveFile(p, newPath) + } + return nil + }) +} + +// moveFile moves a file from a source path to a destination path. +func moveFile(sourcePath, destPath string) error { + inputFile, err := os.Open(sourcePath) + if err != nil { + return err + } + outputFile, err := os.Create(destPath) + if err != nil { + inputFile.Close() + return err + } + defer outputFile.Close() + _, err = io.Copy(outputFile, inputFile) + inputFile.Close() + if err != nil { + return err + } + + // Remove file after succesfull copy. + err = os.Remove(sourcePath) + if err != nil { + return err + } + return nil +} diff --git a/cli/app/cp_test.go b/cli/app/cp_test.go new file mode 100644 index 00000000..e1fef30f --- /dev/null +++ b/cli/app/cp_test.go @@ -0,0 +1,113 @@ +package app + +import ( + "os" + "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) + } + } +} + +func TestCopyMode(t *testing.T) { + tests := []struct { + srcPath string + dstPath string + srcMode os.FileMode + dstMode os.FileMode + dstExists bool + mode CopyMode + err error + }{ + { + srcPath: "foo.txt", + dstPath: "foo.txt", + srcMode: os.ModePerm, + dstMode: os.ModePerm, + dstExists: true, + mode: CopyModeFileToFile, + }, + { + srcPath: "foo.txt", + dstPath: "bar.txt", + srcMode: os.ModePerm, + dstExists: true, + mode: CopyModeFileToFileRename, + }, + { + srcPath: "foo", + dstPath: "foo", + srcMode: os.ModeDir, + dstMode: os.ModeDir, + dstExists: true, + mode: CopyModeDirToDir, + }, + { + srcPath: "foo/", + dstPath: "foo", + srcMode: os.ModeDir, + dstMode: os.ModeDir, + dstExists: true, + mode: CopyModeFilesToDir, + }, + { + srcPath: "foo", + dstPath: "foo", + srcMode: os.ModeDir, + dstExists: false, + mode: -1, + err: ErrDstDirNotExist, + }, + { + srcPath: "foo", + dstPath: "foo", + srcMode: os.ModeDir, + dstMode: os.ModePerm, + dstExists: true, + mode: -1, + err: ErrCopyDirToFile, + }, + } + + for i, tc := range tests { + mode, err := copyMode(tc.srcPath, tc.dstPath, tc.srcMode, tc.dstMode, tc.dstExists) + if mode != tc.mode { + t.Errorf("[%d] mode: want (%d), got(%d)", i, tc.mode, mode) + } + if err != tc.err { + t.Errorf("[%d] err: want (%s), got(%s)", i, tc.err, err) + } + } +} diff --git a/go.sum b/go.sum index d767d61e..a891fa08 100644 --- a/go.sum +++ b/go.sum @@ -1315,6 +1315,7 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= diff --git a/pkg/container/container.go b/pkg/container/container.go index 3bdf46f2..09d5703b 100644 --- a/pkg/container/container.go +++ b/pkg/container/container.go @@ -68,3 +68,15 @@ func GetContainer(c context.Context, cl *client.Client, filters filters.Args, no return containers[0], nil } + +// GetContainerFromStackAndService retrieves the container for the given stack and service. +func GetContainerFromStackAndService(cl *client.Client, stack, service string) (types.Container, error) { + filters := filters.NewArgs() + filters.Add("name", fmt.Sprintf("^%s_%s", stack, service)) + + container, err := GetContainer(context.Background(), cl, filters, true) + if err != nil { + return types.Container{}, err + } + return container, nil +} diff --git a/tests/integration/app_cp.bats b/tests/integration/app_cp.bats index 2687e9d4..4c1f52a0 100644 --- a/tests/integration/app_cp.bats +++ b/tests/integration/app_cp.bats @@ -5,9 +5,11 @@ setup_file(){ _common_setup _add_server _new_app + _deploy_app } teardown_file(){ + _undeploy_app _rm_app _rm_server } @@ -17,11 +19,29 @@ setup(){ _common_setup } -teardown(){ - # https://github.com/bats-core/bats-core/issues/383#issuecomment-738628888 - if [[ -z "${BATS_TEST_COMPLETED}" ]]; then - _undeploy_app - fi +_mkfile() { + run bash -c "echo $2 > $1" + assert_success +} + +_mkfile_remote() { + run $ABRA app run "$TEST_APP_DOMAIN" app "bash -c \"echo $2 > $1\"" + assert_success +} + +_mkdir() { + run bash -c "mkdir -p $1" + assert_success +} + +_rm() { + run rm -rf "$1" + assert_success +} + +_rm_remote() { + run "$ABRA" app run "$TEST_APP_DOMAIN" app rm -rf "$1" + assert_success } @test "validate app argument" { @@ -54,68 +74,120 @@ teardown(){ assert_output --partial 'arguments must take $SERVICE:$PATH form' } -@test "detect 'coming FROM' syntax" { - run $ABRA app cp "$TEST_APP_DOMAIN" app:/myfile.txt . --debug - assert_failure - assert_output --partial 'coming FROM the container' -} - -@test "detect 'going TO' syntax" { - run $ABRA app cp "$TEST_APP_DOMAIN" myfile.txt app:/somewhere --debug - assert_failure - assert_output --partial 'going TO the container' -} - @test "error if local file missing" { - run $ABRA app cp "$TEST_APP_DOMAIN" myfile.txt app:/somewhere + run $ABRA app cp "$TEST_APP_DOMAIN" thisfileshouldnotexist.txt app:/somewhere assert_failure - assert_output --partial 'myfile.txt does not exist locally?' + assert_output --partial 'local stat thisfileshouldnotexist.txt: no such file or directory' } # bats test_tags=slow @test "error if service doesn't exist" { - _deploy_app + _mkfile "$BATS_TMPDIR/myfile.txt" "foo" - run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt" - 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:/ --debug assert_failure assert_output --partial 'no containers matching' - run rm -rf "$BATS_TMPDIR/myfile.txt" - assert_success - - _undeploy_app + _rm "$BATS_TMPDIR/myfile.txt" } # bats test_tags=slow -@test "copy to container" { - _deploy_app - - run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt" - assert_success +@test "copy local file to container directory" { + _mkfile "$BATS_TMPDIR/myfile.txt" "foo" run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc assert_success - run rm -rf "$BATS_TMPDIR/myfile.txt" + run $ABRA app run "$TEST_APP_DOMAIN" app cat /etc/myfile.txt assert_success + assert_output --partial "foo" - _undeploy_app + _rm "$BATS_TMPDIR/myfile.txt" + _rm_remote "/etc/myfile.txt" } # bats test_tags=slow -@test "copy from container" { - _deploy_app +@test "copy local file to container file (and override on remote)" { + _mkfile "$BATS_TMPDIR/myfile.txt" "foo" - run bash -c "echo foo >> $BATS_TMPDIR/myfile.txt" + # create + run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc/myfile.txt assert_success - run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc + run $ABRA app run "$TEST_APP_DOMAIN" app cat /etc/myfile.txt + assert_success + assert_output --partial "foo" + + _mkfile "$BATS_TMPDIR/myfile.txt" "bar" + + # override + run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc/myfile.txt assert_success - run rm -rf "$BATS_TMPDIR/myfile.txt" + run $ABRA app run "$TEST_APP_DOMAIN" app cat /etc/myfile.txt + assert_success + assert_output --partial "bar" + + _rm "$BATS_TMPDIR/myfile.txt" + _rm_remote "/etc/myfile.txt" +} + +# bats test_tags=slow +@test "copy local file to container file (and rename)" { + _mkfile "$BATS_TMPDIR/myfile.txt" "foo" + + # rename + run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/myfile.txt" app:/etc/myfile2.txt + assert_success + + run $ABRA app run "$TEST_APP_DOMAIN" app cat /etc/myfile2.txt + assert_success + assert_output --partial "foo" + + _rm "$BATS_TMPDIR/myfile.txt" + _rm_remote "/etc/myfile2.txt" +} + +# bats test_tags=slow +@test "copy local directory to container directory (and creates missing directory)" { + _mkdir "$BATS_TMPDIR/mydir" + _mkfile "$BATS_TMPDIR/mydir/myfile.txt" "foo" + + run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/mydir" app:/etc + assert_success + + run $ABRA app run "$TEST_APP_DOMAIN" app ls /etc/mydir + assert_success + assert_output --partial "myfile.txt" + + _rm "$BATS_TMPDIR/mydir" + _rm_remote "/etc/mydir" +} + +# bats test_tags=slow +@test "copy local files to container directory" { + _mkdir "$BATS_TMPDIR/mydir" + _mkfile "$BATS_TMPDIR/mydir/myfile.txt" "foo" + _mkfile "$BATS_TMPDIR/mydir/myfile2.txt" "foo" + + run $ABRA app cp "$TEST_APP_DOMAIN" "$BATS_TMPDIR/mydir/" app:/etc + assert_success + + run $ABRA app run "$TEST_APP_DOMAIN" app ls /etc/myfile.txt + assert_success + assert_output --partial "myfile.txt" + + run $ABRA app run "$TEST_APP_DOMAIN" app ls /etc/myfile2.txt + assert_success + assert_output --partial "myfile2.txt" + + _rm "$BATS_TMPDIR/mydir" + _rm_remote "/etc/myfile*" +} + +# bats test_tags=slow +@test "copy container file to local directory" { + run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt" assert_success run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR" @@ -123,8 +195,76 @@ teardown(){ assert_exists "$BATS_TMPDIR/myfile.txt" assert bash -c "cat $BATS_TMPDIR/myfile.txt | grep -q foo" - run rm -rf "$BATS_TMPDIR/myfile.txt" + _rm "$BATS_TMPDIR/myfile.txt" + _rm_remote "/etc/myfile.txt" +} + +# bats test_tags=slow +@test "copy container file to local file" { + run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt" assert_success - _undeploy_app + run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR/myfile.txt" + assert_success + assert_exists "$BATS_TMPDIR/myfile.txt" + assert bash -c "cat $BATS_TMPDIR/myfile.txt | grep -q foo" + + _rm "$BATS_TMPDIR/myfile.txt" + _rm_remote "/etc/myfile.txt" +} + +# bats test_tags=slow +@test "copy container file to local file and rename" { + run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt" + assert_success + + run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR/myfile2.txt" + assert_success + assert_exists "$BATS_TMPDIR/myfile2.txt" + assert bash -c "cat $BATS_TMPDIR/myfile2.txt | grep -q foo" + + _rm "$BATS_TMPDIR/myfile2.txt" + _rm_remote "/etc/myfile.txt" +} + +# bats test_tags=slow +@test "copy container directory to local directory" { + run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt" + assert_success + + run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo bar > /etc/myfile2.txt" + assert_success + + mkdir "$BATS_TMPDIR/mydir" + + run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc "$BATS_TMPDIR/mydir" + assert_success + assert_exists "$BATS_TMPDIR/mydir/etc/myfile.txt" + assert_success + assert_exists "$BATS_TMPDIR/mydir/etc/myfile2.txt" + + _rm "$BATS_TMPDIR/mydir" + _rm_remote "/etc/myfile.txt" + _rm_remote "/etc/myfile2.txt" +} + +# bats test_tags=slow +@test "copy container files to local directory" { + run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt" + assert_success + + run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo bar > /etc/myfile2.txt" + assert_success + + mkdir "$BATS_TMPDIR/mydir" + + run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/ "$BATS_TMPDIR/mydir" + assert_success + assert_exists "$BATS_TMPDIR/mydir/myfile.txt" + assert_success + assert_exists "$BATS_TMPDIR/mydir/myfile2.txt" + + _rm "$BATS_TMPDIR/mydir" + _rm_remote "/etc/myfile.txt" + _rm_remote "/etc/myfile2.txt" }