diff --git a/client/client_interfaces.go b/client/client_interfaces.go index d6e6e855f0..62b67b642b 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) ([]container.Summary, 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 5441e28e78..9c3fa04357 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 d6e6e855f0..62b67b642b 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) ([]container.Summary, 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() }