Currently the cp will tar from the same directory it will untar into
simultaneously. There is a race between reading the file and truncating
the file for write, however, the race will not show up with a large
enough buffer on the tar side if buffered before the copy begins.
Also removes the unnecessary deferred removal, the removal is handled by
cleanup and respects the no cleanup env.
Signed-off-by: Derek McGowan <derek@mcg.dev>
(cherry picked from commit 8c0cb30515)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
204 lines
5.7 KiB
Go
204 lines
5.7 KiB
Go
package container
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/docker/cli/internal/test"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
"gotest.tools/v3/fs"
|
|
"gotest.tools/v3/skip"
|
|
)
|
|
|
|
func TestRunCopyWithInvalidArguments(t *testing.T) {
|
|
testcases := []struct {
|
|
doc string
|
|
options copyOptions
|
|
expectedErr string
|
|
}{
|
|
{
|
|
doc: "copy between container",
|
|
options: copyOptions{
|
|
source: "first:/path",
|
|
destination: "second:/path",
|
|
},
|
|
expectedErr: "copying between containers is not supported",
|
|
},
|
|
{
|
|
doc: "copy without a container",
|
|
options: copyOptions{
|
|
source: "./source",
|
|
destination: "./dest",
|
|
},
|
|
expectedErr: "must specify at least one container source",
|
|
},
|
|
}
|
|
for _, testcase := range testcases {
|
|
t.Run(testcase.doc, func(t *testing.T) {
|
|
err := runCopy(context.TODO(), test.NewFakeCli(nil), testcase.options)
|
|
assert.Error(t, err, testcase.expectedErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRunCopyFromContainerToStdout(t *testing.T) {
|
|
tarContent := "the tar content"
|
|
|
|
cli := test.NewFakeCli(&fakeClient{
|
|
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
|
|
assert.Check(t, is.Equal("container", ctr))
|
|
return io.NopCloser(strings.NewReader(tarContent)), container.PathStat{}, nil
|
|
},
|
|
})
|
|
err := runCopy(context.TODO(), cli, copyOptions{
|
|
source: "container:/path",
|
|
destination: "-",
|
|
})
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal(tarContent, cli.OutBuffer().String()))
|
|
assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
|
|
}
|
|
|
|
func TestRunCopyFromContainerToFilesystem(t *testing.T) {
|
|
srcDir := fs.NewDir(t, "cp-test",
|
|
fs.WithFile("file1", "content\n"))
|
|
|
|
destDir := fs.NewDir(t, "cp-test")
|
|
|
|
cli := test.NewFakeCli(&fakeClient{
|
|
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
|
|
assert.Check(t, is.Equal("container", ctr))
|
|
readCloser, err := archive.Tar(srcDir.Path(), archive.Uncompressed)
|
|
return readCloser, container.PathStat{}, err
|
|
},
|
|
})
|
|
err := runCopy(context.TODO(), cli, copyOptions{
|
|
source: "container:/path",
|
|
destination: destDir.Path(),
|
|
quiet: true,
|
|
})
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal("", cli.OutBuffer().String()))
|
|
assert.Check(t, is.Equal("", cli.ErrBuffer().String()))
|
|
|
|
content, err := os.ReadFile(destDir.Join("file1"))
|
|
assert.NilError(t, err)
|
|
assert.Check(t, is.Equal("content\n", string(content)))
|
|
}
|
|
|
|
func TestRunCopyFromContainerToFilesystemMissingDestinationDirectory(t *testing.T) {
|
|
destDir := fs.NewDir(t, "cp-test",
|
|
fs.WithFile("file1", "content\n"))
|
|
defer destDir.Remove()
|
|
|
|
cli := test.NewFakeCli(&fakeClient{
|
|
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
|
|
assert.Check(t, is.Equal("container", ctr))
|
|
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
|
|
return readCloser, container.PathStat{}, err
|
|
},
|
|
})
|
|
err := runCopy(context.TODO(), cli, copyOptions{
|
|
source: "container:/path",
|
|
destination: destDir.Join("missing", "foo"),
|
|
})
|
|
assert.ErrorContains(t, err, destDir.Join("missing"))
|
|
}
|
|
|
|
func TestRunCopyToContainerFromFileWithTrailingSlash(t *testing.T) {
|
|
srcFile := fs.NewFile(t, t.Name())
|
|
defer srcFile.Remove()
|
|
|
|
cli := test.NewFakeCli(&fakeClient{})
|
|
err := runCopy(context.TODO(), cli, copyOptions{
|
|
source: srcFile.Path() + string(os.PathSeparator),
|
|
destination: "container:/path",
|
|
})
|
|
|
|
expectedError := "not a directory"
|
|
if runtime.GOOS == "windows" {
|
|
expectedError = "The filename, directory name, or volume label syntax is incorrect"
|
|
}
|
|
assert.ErrorContains(t, err, expectedError)
|
|
}
|
|
|
|
func TestRunCopyToContainerSourceDoesNotExist(t *testing.T) {
|
|
cli := test.NewFakeCli(&fakeClient{})
|
|
err := runCopy(context.TODO(), cli, copyOptions{
|
|
source: "/does/not/exist",
|
|
destination: "container:/path",
|
|
})
|
|
expected := "no such file or directory"
|
|
if runtime.GOOS == "windows" {
|
|
expected = "cannot find the file specified"
|
|
}
|
|
assert.ErrorContains(t, err, expected)
|
|
}
|
|
|
|
func TestSplitCpArg(t *testing.T) {
|
|
testcases := []struct {
|
|
doc string
|
|
path string
|
|
os string
|
|
expectedContainer string
|
|
expectedPath string
|
|
}{
|
|
{
|
|
doc: "absolute path with colon",
|
|
os: "linux",
|
|
path: "/abs/path:withcolon",
|
|
expectedPath: "/abs/path:withcolon",
|
|
},
|
|
{
|
|
doc: "relative path with colon",
|
|
path: "./relative:path",
|
|
expectedPath: "./relative:path",
|
|
},
|
|
{
|
|
doc: "absolute path with drive",
|
|
os: "windows",
|
|
path: `d:\abs\path`,
|
|
expectedPath: `d:\abs\path`,
|
|
},
|
|
{
|
|
doc: "no separator",
|
|
path: "relative/path",
|
|
expectedPath: "relative/path",
|
|
},
|
|
{
|
|
doc: "with separator",
|
|
path: "container:/opt/foo",
|
|
expectedPath: "/opt/foo",
|
|
expectedContainer: "container",
|
|
},
|
|
}
|
|
for _, tc := range testcases {
|
|
tc := tc
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
skip.If(t, tc.os == "windows" && runtime.GOOS != "windows" || tc.os == "linux" && runtime.GOOS == "windows")
|
|
|
|
ctr, path := splitCpArg(tc.path)
|
|
assert.Check(t, is.Equal(tc.expectedContainer, ctr))
|
|
assert.Check(t, is.Equal(tc.expectedPath, path))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRunCopyFromContainerToFilesystemIrregularDestination(t *testing.T) {
|
|
cli := test.NewFakeCli(nil)
|
|
err := runCopy(context.TODO(), cli, copyOptions{
|
|
source: "container:/dev/null",
|
|
destination: "/dev/random",
|
|
})
|
|
assert.Assert(t, err != nil)
|
|
expected := `"/dev/random" must be a directory or a regular file`
|
|
assert.ErrorContains(t, err, expected)
|
|
}
|