diff --git a/client/client_interfaces.go b/client/client_interfaces.go index 7276e147a1..9cee2d8e96 100644 --- a/client/client_interfaces.go +++ b/client/client_interfaces.go @@ -59,7 +59,7 @@ type ContainerAPIClient interface { ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) ContainerDiff(ctx context.Context, container string, options ContainerDiffOptions) (ContainerDiffResult, error) ExecAPIClient - ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) + ContainerExport(ctx context.Context, container string, options ContainerExportOptions) (ContainerExportResult, error) ContainerInspect(ctx context.Context, container string, options ContainerInspectOptions) (ContainerInspectResult, error) ContainerKill(ctx context.Context, container string, options ContainerKillOptions) (ContainerKillResult, error) ContainerList(ctx context.Context, options ContainerListOptions) (ContainerListResult, error) diff --git a/client/container_export.go b/client/container_export.go index 211d92ed75..ca40fe78ad 100644 --- a/client/container_export.go +++ b/client/container_export.go @@ -4,21 +4,59 @@ import ( "context" "io" "net/url" + "sync" ) +// ContainerExportOptions specifies options for container export operations. +type ContainerExportOptions struct { + // Currently no options are defined for ContainerExport +} + +// ContainerExportResult represents the result of a container export operation. +type ContainerExportResult struct { + rc io.ReadCloser + close func() error +} + // ContainerExport retrieves the raw contents of a container // and returns them as an [io.ReadCloser]. It's up to the caller // to close the stream. -func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) { +func (cli *Client) ContainerExport(ctx context.Context, containerID string, options ContainerExportOptions) (ContainerExportResult, error) { containerID, err := trimID("container", containerID) if err != nil { - return nil, err + return ContainerExportResult{}, err } resp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil) if err != nil { - return nil, err + return ContainerExportResult{}, err } - return resp.Body, nil + return newContainerExportResult(resp.Body), nil +} + +func newContainerExportResult(rc io.ReadCloser) ContainerExportResult { + if rc == nil { + panic("nil io.ReadCloser") + } + return ContainerExportResult{ + rc: rc, + close: sync.OnceValue(rc.Close), + } +} + +// Read implements io.ReadCloser +func (r ContainerExportResult) Read(p []byte) (n int, err error) { + if r.rc == nil { + return 0, io.EOF + } + return r.rc.Read(p) +} + +// Close implements io.ReadCloser +func (r ContainerExportResult) Close() error { + if r.close == nil { + return nil + } + return r.close() } diff --git a/client/container_export_test.go b/client/container_export_test.go index 03d79b22e3..1a0da96555 100644 --- a/client/container_export_test.go +++ b/client/container_export_test.go @@ -17,14 +17,14 @@ func TestContainerExportError(t *testing.T) { ) assert.NilError(t, err) - _, err = client.ContainerExport(context.Background(), "nothing") + _, err = client.ContainerExport(context.Background(), "nothing", ContainerExportOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) - _, err = client.ContainerExport(context.Background(), "") + _, err = client.ContainerExport(context.Background(), "", ContainerExportOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) - _, err = client.ContainerExport(context.Background(), " ") + _, err = client.ContainerExport(context.Background(), " ", ContainerExportOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorContains(err, "value is empty")) } @@ -40,7 +40,7 @@ func TestContainerExport(t *testing.T) { }), ) assert.NilError(t, err) - body, err := client.ContainerExport(context.Background(), "container_id") + body, err := client.ContainerExport(context.Background(), "container_id", ContainerExportOptions{}) assert.NilError(t, err) defer body.Close() content, err := io.ReadAll(body) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index f722a4b947..14448081c5 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -103,7 +103,7 @@ func (s *DockerAPISuite) TestContainerAPIGetExport(c *testing.T) { assert.NilError(c, err) defer apiClient.Close() - body, err := apiClient.ContainerExport(testutil.GetContext(c), name) + body, err := apiClient.ContainerExport(testutil.GetContext(c), name, client.ContainerExportOptions{}) assert.NilError(c, err) defer body.Close() found := false diff --git a/integration/container/export_test.go b/integration/container/export_test.go index aa0af50528..ac4d2a4323 100644 --- a/integration/container/export_test.go +++ b/integration/container/export_test.go @@ -27,7 +27,7 @@ func TestExportContainerAndImportImage(t *testing.T) { poll.WaitOn(t, container.IsStopped(ctx, apiClient, cID)) reference := "repo/" + strings.ToLower(t.Name()) + ":v1" - exportRes, err := apiClient.ContainerExport(ctx, cID) + exportRes, err := apiClient.ContainerExport(ctx, cID, client.ContainerExportOptions{}) assert.NilError(t, err) importRes, err := apiClient.ImageImport(ctx, client.ImageImportSource{ Source: exportRes, @@ -70,6 +70,6 @@ func TestExportContainerAfterDaemonRestart(t *testing.T) { d.Restart(t) - _, err := c.ContainerExport(ctx, ctrID) + _, err := c.ContainerExport(ctx, ctrID, client.ContainerExportOptions{}) assert.NilError(t, err) } diff --git a/integration/container/overlayfs_linux_test.go b/integration/container/overlayfs_linux_test.go index b7769e103c..70e8592fb9 100644 --- a/integration/container/overlayfs_linux_test.go +++ b/integration/container/overlayfs_linux_test.go @@ -32,7 +32,7 @@ func TestNoOverlayfsWarningsAboutUndefinedBehaviors(t *testing.T) { return err }}, {name: "export", operation: func(*testing.T) error { - rc, err := apiClient.ContainerExport(ctx, cID) + rc, err := apiClient.ContainerExport(ctx, cID, client.ContainerExportOptions{}) if err == nil { defer rc.Close() _, err = io.Copy(io.Discard, rc) diff --git a/integration/plugin/authz/authz_plugin_test.go b/integration/plugin/authz/authz_plugin_test.go index 26eaffafaf..3cea2d91fa 100644 --- a/integration/plugin/authz/authz_plugin_test.go +++ b/integration/plugin/authz/authz_plugin_test.go @@ -362,7 +362,7 @@ func TestAuthZPluginEnsureLoadImportWorking(t *testing.T) { cID := container.Run(ctx, t, c) - responseReader, err := c.ContainerExport(ctx, cID) + responseReader, err := c.ContainerExport(ctx, cID, client.ContainerExportOptions{}) assert.NilError(t, err) defer responseReader.Close() file, err := os.Create(exportedImagePath) diff --git a/vendor/github.com/moby/moby/client/client_interfaces.go b/vendor/github.com/moby/moby/client/client_interfaces.go index 7276e147a1..9cee2d8e96 100644 --- a/vendor/github.com/moby/moby/client/client_interfaces.go +++ b/vendor/github.com/moby/moby/client/client_interfaces.go @@ -59,7 +59,7 @@ type ContainerAPIClient interface { ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) ContainerDiff(ctx context.Context, container string, options ContainerDiffOptions) (ContainerDiffResult, error) ExecAPIClient - ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) + ContainerExport(ctx context.Context, container string, options ContainerExportOptions) (ContainerExportResult, error) ContainerInspect(ctx context.Context, container string, options ContainerInspectOptions) (ContainerInspectResult, error) ContainerKill(ctx context.Context, container string, options ContainerKillOptions) (ContainerKillResult, error) ContainerList(ctx context.Context, options ContainerListOptions) (ContainerListResult, error) diff --git a/vendor/github.com/moby/moby/client/container_export.go b/vendor/github.com/moby/moby/client/container_export.go index 211d92ed75..ca40fe78ad 100644 --- a/vendor/github.com/moby/moby/client/container_export.go +++ b/vendor/github.com/moby/moby/client/container_export.go @@ -4,21 +4,59 @@ import ( "context" "io" "net/url" + "sync" ) +// ContainerExportOptions specifies options for container export operations. +type ContainerExportOptions struct { + // Currently no options are defined for ContainerExport +} + +// ContainerExportResult represents the result of a container export operation. +type ContainerExportResult struct { + rc io.ReadCloser + close func() error +} + // ContainerExport retrieves the raw contents of a container // and returns them as an [io.ReadCloser]. It's up to the caller // to close the stream. -func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) { +func (cli *Client) ContainerExport(ctx context.Context, containerID string, options ContainerExportOptions) (ContainerExportResult, error) { containerID, err := trimID("container", containerID) if err != nil { - return nil, err + return ContainerExportResult{}, err } resp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil) if err != nil { - return nil, err + return ContainerExportResult{}, err } - return resp.Body, nil + return newContainerExportResult(resp.Body), nil +} + +func newContainerExportResult(rc io.ReadCloser) ContainerExportResult { + if rc == nil { + panic("nil io.ReadCloser") + } + return ContainerExportResult{ + rc: rc, + close: sync.OnceValue(rc.Close), + } +} + +// Read implements io.ReadCloser +func (r ContainerExportResult) Read(p []byte) (n int, err error) { + if r.rc == nil { + return 0, io.EOF + } + return r.rc.Read(p) +} + +// Close implements io.ReadCloser +func (r ContainerExportResult) Close() error { + if r.close == nil { + return nil + } + return r.close() }