diff --git a/client/client_interfaces.go b/client/client_interfaces.go index 4ab5adb409..24fb1dc3c6 100644 --- a/client/client_interfaces.go +++ b/client/client_interfaces.go @@ -67,7 +67,7 @@ type ContainerAPIClient interface { ContainerRename(ctx context.Context, container string, options ContainerRenameOptions) (ContainerRenameResult, error) ContainerResize(ctx context.Context, container string, options ContainerResizeOptions) (ContainerResizeResult, error) ContainerRestart(ctx context.Context, container string, options ContainerRestartOptions) (ContainerRestartResult, error) - ContainerStatPath(ctx context.Context, container, path string) (container.PathStat, error) + ContainerStatPath(ctx context.Context, container string, options ContainerStatPathOptions) (ContainerStatPathResult, error) ContainerStats(ctx context.Context, container string, options ContainerStatsOptions) (ContainerStatsResult, error) ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error) ContainerStop(ctx context.Context, container string, options ContainerStopOptions) (ContainerStopResult, error) @@ -75,8 +75,8 @@ type ContainerAPIClient interface { ContainerUnpause(ctx context.Context, container string, options ContainerUnpauseOptions) (ContainerUnpauseResult, error) ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error) ContainerWait(ctx context.Context, container string, options ContainerWaitOptions) ContainerWaitResult - CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error) - CopyToContainer(ctx context.Context, container, path string, content io.Reader, options CopyToContainerOptions) error + CopyFromContainer(ctx context.Context, container string, options CopyFromContainerOptions) (CopyFromContainerResult, error) + CopyToContainer(ctx context.Context, container string, options CopyToContainerOptions) (CopyToContainerResult, error) ContainersPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error) } diff --git a/client/container_copy.go b/client/container_copy.go index 48bff5d986..f76511246c 100644 --- a/client/container_copy.go +++ b/client/container_copy.go @@ -14,41 +14,57 @@ import ( "github.com/moby/moby/api/types/container" ) +type ContainerStatPathOptions struct { + Path string +} + +type ContainerStatPathResult struct { + Stat container.PathStat +} + // ContainerStatPath returns stat information about a path inside the container filesystem. -func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (container.PathStat, error) { +func (cli *Client) ContainerStatPath(ctx context.Context, containerID string, options ContainerStatPathOptions) (ContainerStatPathResult, error) { containerID, err := trimID("container", containerID) if err != nil { - return container.PathStat{}, err + return ContainerStatPathResult{}, err } query := url.Values{} - query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. + query.Set("path", filepath.ToSlash(options.Path)) // Normalize the paths used in the API. resp, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil) defer ensureReaderClosed(resp) if err != nil { - return container.PathStat{}, err + return ContainerStatPathResult{}, err } - return getContainerPathStatFromHeader(resp.Header) + stat, err := getContainerPathStatFromHeader(resp.Header) + if err != nil { + return ContainerStatPathResult{}, err + } + return ContainerStatPathResult{Stat: stat}, nil } // CopyToContainerOptions holds information // about files to copy into a container type CopyToContainerOptions struct { + DestinationPath string + Content io.Reader AllowOverwriteDirWithFile bool CopyUIDGID bool } +type CopyToContainerResult struct{} + // CopyToContainer copies content into the container filesystem. // Note that `content` must be a Reader for a TAR archive -func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options CopyToContainerOptions) error { +func (cli *Client) CopyToContainer(ctx context.Context, containerID string, options CopyToContainerOptions) (CopyToContainerResult, error) { containerID, err := trimID("container", containerID) if err != nil { - return err + return CopyToContainerResult{}, err } query := url.Values{} - query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API. + query.Set("path", filepath.ToSlash(options.DestinationPath)) // Normalize the paths used in the API. // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. if !options.AllowOverwriteDirWithFile { query.Set("noOverwriteDirNonDir", "true") @@ -58,29 +74,38 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str query.Set("copyUIDGID", "true") } - response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, content, nil) + response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, options.Content, nil) defer ensureReaderClosed(response) if err != nil { - return err + return CopyToContainerResult{}, err } - return nil + return CopyToContainerResult{}, nil +} + +type CopyFromContainerOptions struct { + SourcePath string +} + +type CopyFromContainerResult struct { + Content io.ReadCloser + Stat container.PathStat } // CopyFromContainer gets the content from the container and returns it as a Reader // for a TAR archive to manipulate it in the host. It's up to the caller to close the reader. -func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) { +func (cli *Client) CopyFromContainer(ctx context.Context, containerID string, options CopyFromContainerOptions) (CopyFromContainerResult, error) { containerID, err := trimID("container", containerID) if err != nil { - return nil, container.PathStat{}, err + return CopyFromContainerResult{}, err } query := make(url.Values, 1) - query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. + query.Set("path", filepath.ToSlash(options.SourcePath)) // Normalize the paths used in the API. resp, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil) if err != nil { - return nil, container.PathStat{}, err + return CopyFromContainerResult{}, err } // In order to get the copy behavior right, we need to know information @@ -91,9 +116,10 @@ func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath s // can be when copying a file/dir from one location to another file/dir. stat, err := getContainerPathStatFromHeader(resp.Header) if err != nil { - return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err) + ensureReaderClosed(resp) + return CopyFromContainerResult{Stat: stat}, fmt.Errorf("unable to get resource stat from response: %s", err) } - return resp.Body, stat, err + return CopyFromContainerResult{Content: resp.Body, Stat: stat}, nil } func getContainerPathStatFromHeader(header http.Header) (container.PathStat, error) { diff --git a/client/container_copy_test.go b/client/container_copy_test.go index 4066e707b7..88e63afa9a 100644 --- a/client/container_copy_test.go +++ b/client/container_copy_test.go @@ -24,14 +24,14 @@ func TestContainerStatPathError(t *testing.T) { ) assert.NilError(t, err) - _, err = client.ContainerStatPath(context.Background(), "container_id", "path") + _, err = client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: "path"}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - _, err = client.ContainerStatPath(context.Background(), "", "path") + _, err = client.ContainerStatPath(context.Background(), "", ContainerStatPathOptions{Path: "path"}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - _, err = client.ContainerStatPath(context.Background(), " ", "path") + _, err = client.ContainerStatPath(context.Background(), " ", ContainerStatPathOptions{Path: "path"}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -42,7 +42,7 @@ func TestContainerStatPathNotFoundError(t *testing.T) { ) assert.NilError(t, err) - _, err = client.ContainerStatPath(context.Background(), "container_id", "path") + _, err = client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: "path"}) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) } @@ -52,7 +52,7 @@ func TestContainerStatPathNoHeaderError(t *testing.T) { ) assert.NilError(t, err) - _, err = client.ContainerStatPath(context.Background(), "container_id", "path/to/file") + _, err = client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: "path/to/file"}) assert.Check(t, err != nil, "expected an error, got nothing") } @@ -86,10 +86,10 @@ func TestContainerStatPath(t *testing.T) { }), ) assert.NilError(t, err) - stat, err := client.ContainerStatPath(context.Background(), "container_id", expectedPath) + res, err := client.ContainerStatPath(context.Background(), "container_id", ContainerStatPathOptions{Path: expectedPath}) assert.NilError(t, err) - assert.Check(t, is.Equal(stat.Name, "name")) - assert.Check(t, is.Equal(stat.Mode, os.FileMode(0o700))) + assert.Check(t, is.Equal(res.Stat.Name, "name")) + assert.Check(t, is.Equal(res.Stat.Mode, os.FileMode(0o700))) } func TestCopyToContainerError(t *testing.T) { @@ -98,14 +98,23 @@ func TestCopyToContainerError(t *testing.T) { ) assert.NilError(t, err) - err = client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{}) + _, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{ + DestinationPath: "path/to/file", + Content: bytes.NewReader([]byte("")), + }) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - err = client.CopyToContainer(context.Background(), "", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{}) + _, err = client.CopyToContainer(context.Background(), "", CopyToContainerOptions{ + DestinationPath: "path/to/file", + Content: bytes.NewReader([]byte("")), + }) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - err = client.CopyToContainer(context.Background(), " ", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{}) + _, err = client.CopyToContainer(context.Background(), " ", CopyToContainerOptions{ + DestinationPath: "path/to/file", + Content: bytes.NewReader([]byte("")), + }) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -116,7 +125,10 @@ func TestCopyToContainerNotFoundError(t *testing.T) { ) assert.NilError(t, err) - err = client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{}) + _, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{ + DestinationPath: "path/to/file", + Content: bytes.NewReader([]byte("")), + }) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) } @@ -128,7 +140,10 @@ func TestCopyToContainerEmptyResponse(t *testing.T) { ) assert.NilError(t, err) - err = client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), CopyToContainerOptions{}) + _, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{ + DestinationPath: "path/to/file", + Content: bytes.NewReader([]byte("")), + }) assert.NilError(t, err) } @@ -168,7 +183,9 @@ func TestCopyToContainer(t *testing.T) { ) assert.NilError(t, err) - err = client.CopyToContainer(context.Background(), "container_id", expectedPath, bytes.NewReader([]byte("content")), CopyToContainerOptions{ + _, err = client.CopyToContainer(context.Background(), "container_id", CopyToContainerOptions{ + DestinationPath: expectedPath, + Content: bytes.NewReader([]byte("content")), AllowOverwriteDirWithFile: false, }) assert.NilError(t, err) @@ -180,14 +197,14 @@ func TestCopyFromContainerError(t *testing.T) { ) assert.NilError(t, err) - _, _, err = client.CopyFromContainer(context.Background(), "container_id", "path/to/file") + _, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - _, _, err = client.CopyFromContainer(context.Background(), "", "path/to/file") + _, err = client.CopyFromContainer(context.Background(), "", CopyFromContainerOptions{SourcePath: "path/to/file"}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - _, _, err = client.CopyFromContainer(context.Background(), " ", "path/to/file") + _, err = client.CopyFromContainer(context.Background(), " ", CopyFromContainerOptions{SourcePath: "path/to/file"}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -198,7 +215,7 @@ func TestCopyFromContainerNotFoundError(t *testing.T) { ) assert.NilError(t, err) - _, _, err = client.CopyFromContainer(context.Background(), "container_id", "path/to/file") + _, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"}) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) } @@ -223,7 +240,7 @@ func TestCopyFromContainerEmptyResponse(t *testing.T) { ) assert.NilError(t, err) - _, _, err = client.CopyFromContainer(context.Background(), "container_id", "path/to/file") + _, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"}) assert.NilError(t, err) } @@ -233,7 +250,7 @@ func TestCopyFromContainerNoHeaderError(t *testing.T) { ) assert.NilError(t, err) - _, _, err = client.CopyFromContainer(context.Background(), "container_id", "path/to/file") + _, err = client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: "path/to/file"}) assert.Check(t, err != nil, "expected an error, got nothing") } @@ -268,13 +285,13 @@ func TestCopyFromContainer(t *testing.T) { }), ) assert.NilError(t, err) - r, stat, err := client.CopyFromContainer(context.Background(), "container_id", expectedPath) + res2, err := client.CopyFromContainer(context.Background(), "container_id", CopyFromContainerOptions{SourcePath: expectedPath}) assert.NilError(t, err) - assert.Check(t, is.Equal(stat.Name, "name")) - assert.Check(t, is.Equal(stat.Mode, os.FileMode(0o700))) + assert.Check(t, is.Equal(res2.Stat.Name, "name")) + assert.Check(t, is.Equal(res2.Stat.Mode, os.FileMode(0o700))) - content, err := io.ReadAll(r) + content, err := io.ReadAll(res2.Content) assert.NilError(t, err) assert.Check(t, is.Equal(string(content), "content")) - assert.NilError(t, r.Close()) + assert.NilError(t, res2.Content.Close()) } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index cb4ff5ec1d..db7a10454d 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -991,7 +991,7 @@ func (s *DockerAPISuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRoot apiClient, err := client.NewClientWithOpts(client.FromEnv) assert.NilError(c, err) - err = apiClient.CopyToContainer(testutil.GetContext(c), cID, "/vol2/symlinkToAbsDir", nil, client.CopyToContainerOptions{}) + _, err = apiClient.CopyToContainer(testutil.GetContext(c), cID, client.CopyToContainerOptions{DestinationPath: "/vol2/symlinkToAbsDir"}) assert.ErrorContains(c, err, "container rootfs is marked read-only") } diff --git a/integration/container/copy_test.go b/integration/container/copy_test.go index e82117c3b8..48a8cb7060 100644 --- a/integration/container/copy_test.go +++ b/integration/container/copy_test.go @@ -30,7 +30,7 @@ func TestCopyFromContainerPathDoesNotExist(t *testing.T) { apiClient := testEnv.APIClient() cid := container.Create(ctx, t, apiClient) - _, _, err := apiClient.CopyFromContainer(ctx, cid, "/dne") + _, err := apiClient.CopyFromContainer(ctx, cid, client.CopyFromContainerOptions{SourcePath: "/dne"}) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) assert.Check(t, is.ErrorContains(err, "Could not find the file /dne in container "+cid)) } @@ -58,7 +58,7 @@ func TestCopyFromContainerPathIsNotDir(t *testing.T) { "The filename, directory name, or volume label syntax is incorrect.", // ERROR_INVALID_NAME } } - _, _, err := apiClient.CopyFromContainer(ctx, cid, existingFile) + _, err := apiClient.CopyFromContainer(ctx, cid, client.CopyFromContainerOptions{SourcePath: existingFile}) var found bool for _, expErr := range expected { if err != nil && strings.Contains(err.Error(), expErr) { @@ -75,7 +75,7 @@ func TestCopyToContainerPathDoesNotExist(t *testing.T) { apiClient := testEnv.APIClient() cid := container.Create(ctx, t, apiClient) - err := apiClient.CopyToContainer(ctx, cid, "/dne", nil, client.CopyToContainerOptions{}) + _, err := apiClient.CopyToContainer(ctx, cid, client.CopyToContainerOptions{DestinationPath: "/dne", Content: bytes.NewReader([]byte(""))}) assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound)) assert.Check(t, is.ErrorContains(err, "Could not find the file /dne in container "+cid)) } @@ -88,23 +88,22 @@ func TestCopyEmptyFile(t *testing.T) { // empty content dstDir, _ := makeEmptyArchive(t) - err := apiClient.CopyToContainer(ctx, cid, dstDir, bytes.NewReader([]byte("")), client.CopyToContainerOptions{}) + _, err := apiClient.CopyToContainer(ctx, cid, client.CopyToContainerOptions{DestinationPath: dstDir, Content: bytes.NewReader([]byte(""))}) assert.NilError(t, err) // tar with empty file dstDir, preparedArchive := makeEmptyArchive(t) - err = apiClient.CopyToContainer(ctx, cid, dstDir, preparedArchive, client.CopyToContainerOptions{}) + _, err = apiClient.CopyToContainer(ctx, cid, client.CopyToContainerOptions{DestinationPath: dstDir, Content: preparedArchive}) assert.NilError(t, err) // tar with empty file archive mode dstDir, preparedArchive = makeEmptyArchive(t) - err = apiClient.CopyToContainer(ctx, cid, dstDir, preparedArchive, client.CopyToContainerOptions{ - CopyUIDGID: true, - }) + _, err = apiClient.CopyToContainer(ctx, cid, client.CopyToContainerOptions{DestinationPath: dstDir, Content: preparedArchive, CopyUIDGID: true}) assert.NilError(t, err) // copy from empty file - rdr, _, err := apiClient.CopyFromContainer(ctx, cid, dstDir) + res, err := apiClient.CopyFromContainer(ctx, cid, client.CopyFromContainerOptions{SourcePath: dstDir}) + rdr := res.Content assert.NilError(t, err) defer rdr.Close() } @@ -189,9 +188,7 @@ func TestCopyToContainerCopyUIDGID(t *testing.T) { // tar with empty file dstDir, preparedArchive := makeEmptyArchive(t) - err := apiClient.CopyToContainer(ctx, cID, dstDir, preparedArchive, client.CopyToContainerOptions{ - CopyUIDGID: true, - }) + _, err := apiClient.CopyToContainer(ctx, cID, client.CopyToContainerOptions{DestinationPath: dstDir, Content: preparedArchive, CopyUIDGID: true}) assert.NilError(t, err) res, err := container.Exec(ctx, apiClient, cID, []string{"stat", "-c", "%u:%g", "/empty-file.txt"}) @@ -264,7 +261,7 @@ func TestCopyToContainerPathIsNotDir(t *testing.T) { if testEnv.DaemonInfo.OSType == "windows" { path = "c:/windows/system32/drivers/etc/hosts/" } - err := apiClient.CopyToContainer(ctx, cid, path, nil, client.CopyToContainerOptions{}) + _, err := apiClient.CopyToContainer(ctx, cid, client.CopyToContainerOptions{DestinationPath: path}) assert.Check(t, is.ErrorContains(err, "not a directory")) } @@ -327,7 +324,8 @@ func TestCopyFromContainer(t *testing.T) { {"bar/notarget", map[string]string{"notarget": ""}}, } { t.Run(x.src, func(t *testing.T) { - rdr, _, err := apiClient.CopyFromContainer(ctx, cid, x.src) + res, err := apiClient.CopyFromContainer(ctx, cid, client.CopyFromContainerOptions{SourcePath: x.src}) + rdr := res.Content assert.NilError(t, err) defer rdr.Close() diff --git a/integration/container/mounts_linux_test.go b/integration/container/mounts_linux_test.go index 8d43ed7d1a..b9fa3ba8bf 100644 --- a/integration/container/mounts_linux_test.go +++ b/integration/container/mounts_linux_test.go @@ -480,7 +480,7 @@ func TestContainerCopyLeaksMounts(t *testing.T) { mountsBefore := getMounts() - _, _, err := apiClient.CopyFromContainer(ctx, cid, "/etc/passwd") + _, err := apiClient.CopyFromContainer(ctx, cid, client.CopyFromContainerOptions{SourcePath: "/etc/passwd"}) assert.NilError(t, err) mountsAfter := getMounts() diff --git a/integration/container/overlayfs_linux_test.go b/integration/container/overlayfs_linux_test.go index 70e8592fb9..802f04a5ca 100644 --- a/integration/container/overlayfs_linux_test.go +++ b/integration/container/overlayfs_linux_test.go @@ -42,13 +42,14 @@ func TestNoOverlayfsWarningsAboutUndefinedBehaviors(t *testing.T) { {name: "cp to container", operation: func(t *testing.T) error { archiveReader, err := archive.Generate("new-file", "hello-world") assert.NilError(t, err, "failed to create a temporary archive") - return apiClient.CopyToContainer(ctx, cID, "/", archiveReader, client.CopyToContainerOptions{}) + _, err = apiClient.CopyToContainer(ctx, cID, client.CopyToContainerOptions{DestinationPath: "/", Content: archiveReader}) + return err }}, {name: "cp from container", operation: func(*testing.T) error { - rc, _, err := apiClient.CopyFromContainer(ctx, cID, "/file") + res, err := apiClient.CopyFromContainer(ctx, cID, client.CopyFromContainerOptions{SourcePath: "/file"}) if err == nil { - defer rc.Close() - _, err = io.Copy(io.Discard, rc) + defer res.Content.Close() + _, err = io.Copy(io.Discard, res.Content) } return err diff --git a/integration/daemon/daemon_test.go b/integration/daemon/daemon_test.go index d1358e8e5f..40bfe607d6 100644 --- a/integration/daemon/daemon_test.go +++ b/integration/daemon/daemon_test.go @@ -607,7 +607,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) { // Wait until container creates a file in the volume. poll.WaitOn(t, func(t poll.LogT) poll.Result { - stat, err := c.ContainerStatPath(ctx, cID, "/foo/test.txt") + res, err := c.ContainerStatPath(ctx, cID, client.ContainerStatPathOptions{Path: "/foo/test.txt"}) if err != nil { if cerrdefs.IsNotFound(err) { return poll.Continue("file doesn't yet exist") @@ -615,8 +615,8 @@ func testLiveRestoreVolumeReferences(t *testing.T) { return poll.Error(err) } - if int(stat.Size) != len(testContent)+1 { - return poll.Error(fmt.Errorf("unexpected test file size: %d", stat.Size)) + if int(res.Stat.Size) != len(testContent)+1 { + return poll.Error(fmt.Errorf("unexpected test file size: %d", res.Stat.Size)) } return poll.Success() @@ -680,7 +680,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) { defer c.ContainerRemove(ctx, cID, client.ContainerRemoveOptions{Force: true}) waitFn := func(t poll.LogT) poll.Result { - _, err := c.ContainerStatPath(ctx, cID, "/image/hello") + _, err := c.ContainerStatPath(ctx, cID, client.ContainerStatPathOptions{Path: "/image/hello"}) if err != nil { if cerrdefs.IsNotFound(err) { return poll.Continue("file doesn't yet exist") diff --git a/integration/plugin/authz/authz_plugin_test.go b/integration/plugin/authz/authz_plugin_test.go index a146cb420c..2787093930 100644 --- a/integration/plugin/authz/authz_plugin_test.go +++ b/integration/plugin/authz/authz_plugin_test.go @@ -415,12 +415,12 @@ func TestAuthzPluginEnsureContainerCopyToFrom(t *testing.T) { dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, archive.CopyInfo{Path: "/test"}) assert.NilError(t, err) - err = c.CopyToContainer(ctx, cID, dstDir, preparedArchive, client.CopyToContainerOptions{}) + _, err = c.CopyToContainer(ctx, cID, client.CopyToContainerOptions{DestinationPath: dstDir, Content: preparedArchive}) assert.NilError(t, err) - rdr, _, err := c.CopyFromContainer(ctx, cID, "/test") + res, err := c.CopyFromContainer(ctx, cID, client.CopyFromContainerOptions{SourcePath: "/test"}) assert.NilError(t, err) - _, err = io.Copy(io.Discard, rdr) + _, err = io.Copy(io.Discard, res.Content) assert.NilError(t, err) } diff --git a/vendor/github.com/moby/moby/client/client_interfaces.go b/vendor/github.com/moby/moby/client/client_interfaces.go index 4ab5adb409..24fb1dc3c6 100644 --- a/vendor/github.com/moby/moby/client/client_interfaces.go +++ b/vendor/github.com/moby/moby/client/client_interfaces.go @@ -67,7 +67,7 @@ type ContainerAPIClient interface { ContainerRename(ctx context.Context, container string, options ContainerRenameOptions) (ContainerRenameResult, error) ContainerResize(ctx context.Context, container string, options ContainerResizeOptions) (ContainerResizeResult, error) ContainerRestart(ctx context.Context, container string, options ContainerRestartOptions) (ContainerRestartResult, error) - ContainerStatPath(ctx context.Context, container, path string) (container.PathStat, error) + ContainerStatPath(ctx context.Context, container string, options ContainerStatPathOptions) (ContainerStatPathResult, error) ContainerStats(ctx context.Context, container string, options ContainerStatsOptions) (ContainerStatsResult, error) ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error) ContainerStop(ctx context.Context, container string, options ContainerStopOptions) (ContainerStopResult, error) @@ -75,8 +75,8 @@ type ContainerAPIClient interface { ContainerUnpause(ctx context.Context, container string, options ContainerUnpauseOptions) (ContainerUnpauseResult, error) ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error) ContainerWait(ctx context.Context, container string, options ContainerWaitOptions) ContainerWaitResult - CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error) - CopyToContainer(ctx context.Context, container, path string, content io.Reader, options CopyToContainerOptions) error + CopyFromContainer(ctx context.Context, container string, options CopyFromContainerOptions) (CopyFromContainerResult, error) + CopyToContainer(ctx context.Context, container string, options CopyToContainerOptions) (CopyToContainerResult, error) ContainersPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error) } diff --git a/vendor/github.com/moby/moby/client/container_copy.go b/vendor/github.com/moby/moby/client/container_copy.go index 48bff5d986..f76511246c 100644 --- a/vendor/github.com/moby/moby/client/container_copy.go +++ b/vendor/github.com/moby/moby/client/container_copy.go @@ -14,41 +14,57 @@ import ( "github.com/moby/moby/api/types/container" ) +type ContainerStatPathOptions struct { + Path string +} + +type ContainerStatPathResult struct { + Stat container.PathStat +} + // ContainerStatPath returns stat information about a path inside the container filesystem. -func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (container.PathStat, error) { +func (cli *Client) ContainerStatPath(ctx context.Context, containerID string, options ContainerStatPathOptions) (ContainerStatPathResult, error) { containerID, err := trimID("container", containerID) if err != nil { - return container.PathStat{}, err + return ContainerStatPathResult{}, err } query := url.Values{} - query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. + query.Set("path", filepath.ToSlash(options.Path)) // Normalize the paths used in the API. resp, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil) defer ensureReaderClosed(resp) if err != nil { - return container.PathStat{}, err + return ContainerStatPathResult{}, err } - return getContainerPathStatFromHeader(resp.Header) + stat, err := getContainerPathStatFromHeader(resp.Header) + if err != nil { + return ContainerStatPathResult{}, err + } + return ContainerStatPathResult{Stat: stat}, nil } // CopyToContainerOptions holds information // about files to copy into a container type CopyToContainerOptions struct { + DestinationPath string + Content io.Reader AllowOverwriteDirWithFile bool CopyUIDGID bool } +type CopyToContainerResult struct{} + // CopyToContainer copies content into the container filesystem. // Note that `content` must be a Reader for a TAR archive -func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options CopyToContainerOptions) error { +func (cli *Client) CopyToContainer(ctx context.Context, containerID string, options CopyToContainerOptions) (CopyToContainerResult, error) { containerID, err := trimID("container", containerID) if err != nil { - return err + return CopyToContainerResult{}, err } query := url.Values{} - query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API. + query.Set("path", filepath.ToSlash(options.DestinationPath)) // Normalize the paths used in the API. // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. if !options.AllowOverwriteDirWithFile { query.Set("noOverwriteDirNonDir", "true") @@ -58,29 +74,38 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str query.Set("copyUIDGID", "true") } - response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, content, nil) + response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, options.Content, nil) defer ensureReaderClosed(response) if err != nil { - return err + return CopyToContainerResult{}, err } - return nil + return CopyToContainerResult{}, nil +} + +type CopyFromContainerOptions struct { + SourcePath string +} + +type CopyFromContainerResult struct { + Content io.ReadCloser + Stat container.PathStat } // CopyFromContainer gets the content from the container and returns it as a Reader // for a TAR archive to manipulate it in the host. It's up to the caller to close the reader. -func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) { +func (cli *Client) CopyFromContainer(ctx context.Context, containerID string, options CopyFromContainerOptions) (CopyFromContainerResult, error) { containerID, err := trimID("container", containerID) if err != nil { - return nil, container.PathStat{}, err + return CopyFromContainerResult{}, err } query := make(url.Values, 1) - query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. + query.Set("path", filepath.ToSlash(options.SourcePath)) // Normalize the paths used in the API. resp, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil) if err != nil { - return nil, container.PathStat{}, err + return CopyFromContainerResult{}, err } // In order to get the copy behavior right, we need to know information @@ -91,9 +116,10 @@ func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath s // can be when copying a file/dir from one location to another file/dir. stat, err := getContainerPathStatFromHeader(resp.Header) if err != nil { - return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err) + ensureReaderClosed(resp) + return CopyFromContainerResult{Stat: stat}, fmt.Errorf("unable to get resource stat from response: %s", err) } - return resp.Body, stat, err + return CopyFromContainerResult{Content: resp.Body, Stat: stat}, nil } func getContainerPathStatFromHeader(header http.Header) (container.PathStat, error) {