mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
client/container_copy: Wrap options and result struct
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
@@ -67,7 +67,7 @@ type ContainerAPIClient interface {
|
|||||||
ContainerRename(ctx context.Context, container string, options ContainerRenameOptions) (ContainerRenameResult, error)
|
ContainerRename(ctx context.Context, container string, options ContainerRenameOptions) (ContainerRenameResult, error)
|
||||||
ContainerResize(ctx context.Context, container string, options ContainerResizeOptions) (ContainerResizeResult, error)
|
ContainerResize(ctx context.Context, container string, options ContainerResizeOptions) (ContainerResizeResult, error)
|
||||||
ContainerRestart(ctx context.Context, container string, options ContainerRestartOptions) (ContainerRestartResult, 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)
|
ContainerStats(ctx context.Context, container string, options ContainerStatsOptions) (ContainerStatsResult, error)
|
||||||
ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error)
|
ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error)
|
||||||
ContainerStop(ctx context.Context, container string, options ContainerStopOptions) (ContainerStopResult, 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)
|
ContainerUnpause(ctx context.Context, container string, options ContainerUnpauseOptions) (ContainerUnpauseResult, error)
|
||||||
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error)
|
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error)
|
||||||
ContainerWait(ctx context.Context, container string, options ContainerWaitOptions) ContainerWaitResult
|
ContainerWait(ctx context.Context, container string, options ContainerWaitOptions) ContainerWaitResult
|
||||||
CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error)
|
CopyFromContainer(ctx context.Context, container string, options CopyFromContainerOptions) (CopyFromContainerResult, error)
|
||||||
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options CopyToContainerOptions) error
|
CopyToContainer(ctx context.Context, container string, options CopyToContainerOptions) (CopyToContainerResult, error)
|
||||||
ContainersPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error)
|
ContainersPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,41 +14,57 @@ import (
|
|||||||
"github.com/moby/moby/api/types/container"
|
"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.
|
// 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)
|
containerID, err := trimID("container", containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return container.PathStat{}, err
|
return ContainerStatPathResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
query := url.Values{}
|
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)
|
resp, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
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
|
// CopyToContainerOptions holds information
|
||||||
// about files to copy into a container
|
// about files to copy into a container
|
||||||
type CopyToContainerOptions struct {
|
type CopyToContainerOptions struct {
|
||||||
|
DestinationPath string
|
||||||
|
Content io.Reader
|
||||||
AllowOverwriteDirWithFile bool
|
AllowOverwriteDirWithFile bool
|
||||||
CopyUIDGID bool
|
CopyUIDGID bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CopyToContainerResult struct{}
|
||||||
|
|
||||||
// CopyToContainer copies content into the container filesystem.
|
// CopyToContainer copies content into the container filesystem.
|
||||||
// Note that `content` must be a Reader for a TAR archive
|
// 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)
|
containerID, err := trimID("container", containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return CopyToContainerResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
query := url.Values{}
|
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.
|
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
|
||||||
if !options.AllowOverwriteDirWithFile {
|
if !options.AllowOverwriteDirWithFile {
|
||||||
query.Set("noOverwriteDirNonDir", "true")
|
query.Set("noOverwriteDirNonDir", "true")
|
||||||
@@ -58,29 +74,38 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
|
|||||||
query.Set("copyUIDGID", "true")
|
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)
|
defer ensureReaderClosed(response)
|
||||||
if err != nil {
|
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
|
// 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.
|
// 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)
|
containerID, err := trimID("container", containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, container.PathStat{}, err
|
return CopyFromContainerResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
query := make(url.Values, 1)
|
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)
|
resp, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
|
||||||
if err != 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
|
// 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.
|
// can be when copying a file/dir from one location to another file/dir.
|
||||||
stat, err := getContainerPathStatFromHeader(resp.Header)
|
stat, err := getContainerPathStatFromHeader(resp.Header)
|
||||||
if err != nil {
|
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) {
|
func getContainerPathStatFromHeader(header http.Header) (container.PathStat, error) {
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ func TestContainerStatPathError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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))
|
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.ErrorType(err, cerrdefs.IsInvalidArgument))
|
||||||
assert.Check(t, is.ErrorContains(err, "value is empty"))
|
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.ErrorType(err, cerrdefs.IsInvalidArgument))
|
||||||
assert.Check(t, is.ErrorContains(err, "value is empty"))
|
assert.Check(t, is.ErrorContains(err, "value is empty"))
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ func TestContainerStatPathNotFoundError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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))
|
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ func TestContainerStatPathNoHeaderError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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")
|
assert.Check(t, err != nil, "expected an error, got nothing")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,10 +86,10 @@ func TestContainerStatPath(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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.NilError(t, err)
|
||||||
assert.Check(t, is.Equal(stat.Name, "name"))
|
assert.Check(t, is.Equal(res.Stat.Name, "name"))
|
||||||
assert.Check(t, is.Equal(stat.Mode, os.FileMode(0o700)))
|
assert.Check(t, is.Equal(res.Stat.Mode, os.FileMode(0o700)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCopyToContainerError(t *testing.T) {
|
func TestCopyToContainerError(t *testing.T) {
|
||||||
@@ -98,14 +98,23 @@ func TestCopyToContainerError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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))
|
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.ErrorType(err, cerrdefs.IsInvalidArgument))
|
||||||
assert.Check(t, is.ErrorContains(err, "value is empty"))
|
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.ErrorType(err, cerrdefs.IsInvalidArgument))
|
||||||
assert.Check(t, is.ErrorContains(err, "value is empty"))
|
assert.Check(t, is.ErrorContains(err, "value is empty"))
|
||||||
}
|
}
|
||||||
@@ -116,7 +125,10 @@ func TestCopyToContainerNotFoundError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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))
|
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +140,10 @@ func TestCopyToContainerEmptyResponse(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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)
|
assert.NilError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +183,9 @@ func TestCopyToContainer(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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,
|
AllowOverwriteDirWithFile: false,
|
||||||
})
|
})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
@@ -180,14 +197,14 @@ func TestCopyFromContainerError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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))
|
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.ErrorType(err, cerrdefs.IsInvalidArgument))
|
||||||
assert.Check(t, is.ErrorContains(err, "value is empty"))
|
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.ErrorType(err, cerrdefs.IsInvalidArgument))
|
||||||
assert.Check(t, is.ErrorContains(err, "value is empty"))
|
assert.Check(t, is.ErrorContains(err, "value is empty"))
|
||||||
}
|
}
|
||||||
@@ -198,7 +215,7 @@ func TestCopyFromContainerNotFoundError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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))
|
assert.Check(t, is.ErrorType(err, cerrdefs.IsNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +240,7 @@ func TestCopyFromContainerEmptyResponse(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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)
|
assert.NilError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +250,7 @@ func TestCopyFromContainerNoHeaderError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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")
|
assert.Check(t, err != nil, "expected an error, got nothing")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,13 +285,13 @@ func TestCopyFromContainer(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
assert.NilError(t, err)
|
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.NilError(t, err)
|
||||||
assert.Check(t, is.Equal(stat.Name, "name"))
|
assert.Check(t, is.Equal(res2.Stat.Name, "name"))
|
||||||
assert.Check(t, is.Equal(stat.Mode, os.FileMode(0o700)))
|
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.NilError(t, err)
|
||||||
assert.Check(t, is.Equal(string(content), "content"))
|
assert.Check(t, is.Equal(string(content), "content"))
|
||||||
assert.NilError(t, r.Close())
|
assert.NilError(t, res2.Content.Close())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -991,7 +991,7 @@ func (s *DockerAPISuite) TestPutContainerArchiveErrSymlinkInVolumeToReadOnlyRoot
|
|||||||
apiClient, err := client.NewClientWithOpts(client.FromEnv)
|
apiClient, err := client.NewClientWithOpts(client.FromEnv)
|
||||||
assert.NilError(c, err)
|
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")
|
assert.ErrorContains(c, err, "container rootfs is marked read-only")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestCopyFromContainerPathDoesNotExist(t *testing.T) {
|
|||||||
apiClient := testEnv.APIClient()
|
apiClient := testEnv.APIClient()
|
||||||
cid := container.Create(ctx, t, 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.ErrorType(err, cerrdefs.IsNotFound))
|
||||||
assert.Check(t, is.ErrorContains(err, "Could not find the file /dne in container "+cid))
|
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
|
"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
|
var found bool
|
||||||
for _, expErr := range expected {
|
for _, expErr := range expected {
|
||||||
if err != nil && strings.Contains(err.Error(), expErr) {
|
if err != nil && strings.Contains(err.Error(), expErr) {
|
||||||
@@ -75,7 +75,7 @@ func TestCopyToContainerPathDoesNotExist(t *testing.T) {
|
|||||||
apiClient := testEnv.APIClient()
|
apiClient := testEnv.APIClient()
|
||||||
cid := container.Create(ctx, t, 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.ErrorType(err, cerrdefs.IsNotFound))
|
||||||
assert.Check(t, is.ErrorContains(err, "Could not find the file /dne in container "+cid))
|
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
|
// empty content
|
||||||
dstDir, _ := makeEmptyArchive(t)
|
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)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
// tar with empty file
|
// tar with empty file
|
||||||
dstDir, preparedArchive := makeEmptyArchive(t)
|
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)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
// tar with empty file archive mode
|
// tar with empty file archive mode
|
||||||
dstDir, preparedArchive = makeEmptyArchive(t)
|
dstDir, preparedArchive = makeEmptyArchive(t)
|
||||||
err = apiClient.CopyToContainer(ctx, cid, dstDir, preparedArchive, client.CopyToContainerOptions{
|
_, err = apiClient.CopyToContainer(ctx, cid, client.CopyToContainerOptions{DestinationPath: dstDir, Content: preparedArchive, CopyUIDGID: true})
|
||||||
CopyUIDGID: true,
|
|
||||||
})
|
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
// copy from empty file
|
// 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)
|
assert.NilError(t, err)
|
||||||
defer rdr.Close()
|
defer rdr.Close()
|
||||||
}
|
}
|
||||||
@@ -189,9 +188,7 @@ func TestCopyToContainerCopyUIDGID(t *testing.T) {
|
|||||||
|
|
||||||
// tar with empty file
|
// tar with empty file
|
||||||
dstDir, preparedArchive := makeEmptyArchive(t)
|
dstDir, preparedArchive := makeEmptyArchive(t)
|
||||||
err := apiClient.CopyToContainer(ctx, cID, dstDir, preparedArchive, client.CopyToContainerOptions{
|
_, err := apiClient.CopyToContainer(ctx, cID, client.CopyToContainerOptions{DestinationPath: dstDir, Content: preparedArchive, CopyUIDGID: true})
|
||||||
CopyUIDGID: true,
|
|
||||||
})
|
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
res, err := container.Exec(ctx, apiClient, cID, []string{"stat", "-c", "%u:%g", "/empty-file.txt"})
|
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" {
|
if testEnv.DaemonInfo.OSType == "windows" {
|
||||||
path = "c:/windows/system32/drivers/etc/hosts/"
|
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"))
|
assert.Check(t, is.ErrorContains(err, "not a directory"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +324,8 @@ func TestCopyFromContainer(t *testing.T) {
|
|||||||
{"bar/notarget", map[string]string{"notarget": ""}},
|
{"bar/notarget", map[string]string{"notarget": ""}},
|
||||||
} {
|
} {
|
||||||
t.Run(x.src, func(t *testing.T) {
|
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)
|
assert.NilError(t, err)
|
||||||
defer rdr.Close()
|
defer rdr.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -480,7 +480,7 @@ func TestContainerCopyLeaksMounts(t *testing.T) {
|
|||||||
|
|
||||||
mountsBefore := getMounts()
|
mountsBefore := getMounts()
|
||||||
|
|
||||||
_, _, err := apiClient.CopyFromContainer(ctx, cid, "/etc/passwd")
|
_, err := apiClient.CopyFromContainer(ctx, cid, client.CopyFromContainerOptions{SourcePath: "/etc/passwd"})
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
mountsAfter := getMounts()
|
mountsAfter := getMounts()
|
||||||
|
|||||||
@@ -42,13 +42,14 @@ func TestNoOverlayfsWarningsAboutUndefinedBehaviors(t *testing.T) {
|
|||||||
{name: "cp to container", operation: func(t *testing.T) error {
|
{name: "cp to container", operation: func(t *testing.T) error {
|
||||||
archiveReader, err := archive.Generate("new-file", "hello-world")
|
archiveReader, err := archive.Generate("new-file", "hello-world")
|
||||||
assert.NilError(t, err, "failed to create a temporary archive")
|
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 {
|
{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 {
|
if err == nil {
|
||||||
defer rc.Close()
|
defer res.Content.Close()
|
||||||
_, err = io.Copy(io.Discard, rc)
|
_, err = io.Copy(io.Discard, res.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -607,7 +607,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) {
|
|||||||
|
|
||||||
// Wait until container creates a file in the volume.
|
// Wait until container creates a file in the volume.
|
||||||
poll.WaitOn(t, func(t poll.LogT) poll.Result {
|
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 err != nil {
|
||||||
if cerrdefs.IsNotFound(err) {
|
if cerrdefs.IsNotFound(err) {
|
||||||
return poll.Continue("file doesn't yet exist")
|
return poll.Continue("file doesn't yet exist")
|
||||||
@@ -615,8 +615,8 @@ func testLiveRestoreVolumeReferences(t *testing.T) {
|
|||||||
return poll.Error(err)
|
return poll.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if int(stat.Size) != len(testContent)+1 {
|
if int(res.Stat.Size) != len(testContent)+1 {
|
||||||
return poll.Error(fmt.Errorf("unexpected test file size: %d", stat.Size))
|
return poll.Error(fmt.Errorf("unexpected test file size: %d", res.Stat.Size))
|
||||||
}
|
}
|
||||||
|
|
||||||
return poll.Success()
|
return poll.Success()
|
||||||
@@ -680,7 +680,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) {
|
|||||||
defer c.ContainerRemove(ctx, cID, client.ContainerRemoveOptions{Force: true})
|
defer c.ContainerRemove(ctx, cID, client.ContainerRemoveOptions{Force: true})
|
||||||
|
|
||||||
waitFn := func(t poll.LogT) poll.Result {
|
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 err != nil {
|
||||||
if cerrdefs.IsNotFound(err) {
|
if cerrdefs.IsNotFound(err) {
|
||||||
return poll.Continue("file doesn't yet exist")
|
return poll.Continue("file doesn't yet exist")
|
||||||
|
|||||||
@@ -415,12 +415,12 @@ func TestAuthzPluginEnsureContainerCopyToFrom(t *testing.T) {
|
|||||||
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, archive.CopyInfo{Path: "/test"})
|
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, archive.CopyInfo{Path: "/test"})
|
||||||
assert.NilError(t, err)
|
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)
|
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)
|
assert.NilError(t, err)
|
||||||
_, err = io.Copy(io.Discard, rdr)
|
_, err = io.Copy(io.Discard, res.Content)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
vendor/github.com/moby/moby/client/client_interfaces.go
generated
vendored
6
vendor/github.com/moby/moby/client/client_interfaces.go
generated
vendored
@@ -67,7 +67,7 @@ type ContainerAPIClient interface {
|
|||||||
ContainerRename(ctx context.Context, container string, options ContainerRenameOptions) (ContainerRenameResult, error)
|
ContainerRename(ctx context.Context, container string, options ContainerRenameOptions) (ContainerRenameResult, error)
|
||||||
ContainerResize(ctx context.Context, container string, options ContainerResizeOptions) (ContainerResizeResult, error)
|
ContainerResize(ctx context.Context, container string, options ContainerResizeOptions) (ContainerResizeResult, error)
|
||||||
ContainerRestart(ctx context.Context, container string, options ContainerRestartOptions) (ContainerRestartResult, 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)
|
ContainerStats(ctx context.Context, container string, options ContainerStatsOptions) (ContainerStatsResult, error)
|
||||||
ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error)
|
ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error)
|
||||||
ContainerStop(ctx context.Context, container string, options ContainerStopOptions) (ContainerStopResult, 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)
|
ContainerUnpause(ctx context.Context, container string, options ContainerUnpauseOptions) (ContainerUnpauseResult, error)
|
||||||
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error)
|
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error)
|
||||||
ContainerWait(ctx context.Context, container string, options ContainerWaitOptions) ContainerWaitResult
|
ContainerWait(ctx context.Context, container string, options ContainerWaitOptions) ContainerWaitResult
|
||||||
CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error)
|
CopyFromContainer(ctx context.Context, container string, options CopyFromContainerOptions) (CopyFromContainerResult, error)
|
||||||
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options CopyToContainerOptions) error
|
CopyToContainer(ctx context.Context, container string, options CopyToContainerOptions) (CopyToContainerResult, error)
|
||||||
ContainersPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error)
|
ContainersPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
60
vendor/github.com/moby/moby/client/container_copy.go
generated
vendored
60
vendor/github.com/moby/moby/client/container_copy.go
generated
vendored
@@ -14,41 +14,57 @@ import (
|
|||||||
"github.com/moby/moby/api/types/container"
|
"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.
|
// 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)
|
containerID, err := trimID("container", containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return container.PathStat{}, err
|
return ContainerStatPathResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
query := url.Values{}
|
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)
|
resp, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
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
|
// CopyToContainerOptions holds information
|
||||||
// about files to copy into a container
|
// about files to copy into a container
|
||||||
type CopyToContainerOptions struct {
|
type CopyToContainerOptions struct {
|
||||||
|
DestinationPath string
|
||||||
|
Content io.Reader
|
||||||
AllowOverwriteDirWithFile bool
|
AllowOverwriteDirWithFile bool
|
||||||
CopyUIDGID bool
|
CopyUIDGID bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CopyToContainerResult struct{}
|
||||||
|
|
||||||
// CopyToContainer copies content into the container filesystem.
|
// CopyToContainer copies content into the container filesystem.
|
||||||
// Note that `content` must be a Reader for a TAR archive
|
// 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)
|
containerID, err := trimID("container", containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return CopyToContainerResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
query := url.Values{}
|
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.
|
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
|
||||||
if !options.AllowOverwriteDirWithFile {
|
if !options.AllowOverwriteDirWithFile {
|
||||||
query.Set("noOverwriteDirNonDir", "true")
|
query.Set("noOverwriteDirNonDir", "true")
|
||||||
@@ -58,29 +74,38 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
|
|||||||
query.Set("copyUIDGID", "true")
|
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)
|
defer ensureReaderClosed(response)
|
||||||
if err != nil {
|
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
|
// 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.
|
// 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)
|
containerID, err := trimID("container", containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, container.PathStat{}, err
|
return CopyFromContainerResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
query := make(url.Values, 1)
|
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)
|
resp, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
|
||||||
if err != 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
|
// 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.
|
// can be when copying a file/dir from one location to another file/dir.
|
||||||
stat, err := getContainerPathStatFromHeader(resp.Header)
|
stat, err := getContainerPathStatFromHeader(resp.Header)
|
||||||
if err != nil {
|
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) {
|
func getContainerPathStatFromHeader(header http.Header) (container.PathStat, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user