client: refactor `ContainerExport` to wrap options/result structs
| ... | ... |
@@ -59,7 +59,7 @@ type ContainerAPIClient interface {
|
| 59 | 59 |
ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) |
| 60 | 60 |
ContainerDiff(ctx context.Context, container string, options ContainerDiffOptions) (ContainerDiffResult, error) |
| 61 | 61 |
ExecAPIClient |
| 62 |
- ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) |
|
| 62 |
+ ContainerExport(ctx context.Context, container string, options ContainerExportOptions) (ContainerExportResult, error) |
|
| 63 | 63 |
ContainerInspect(ctx context.Context, container string, options ContainerInspectOptions) (ContainerInspectResult, error) |
| 64 | 64 |
ContainerKill(ctx context.Context, container string, options ContainerKillOptions) (ContainerKillResult, error) |
| 65 | 65 |
ContainerList(ctx context.Context, options ContainerListOptions) (ContainerListResult, error) |
| ... | ... |
@@ -4,21 +4,59 @@ import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"io" |
| 6 | 6 |
"net/url" |
| 7 |
+ "sync" |
|
| 7 | 8 |
) |
| 8 | 9 |
|
| 10 |
+// ContainerExportOptions specifies options for container export operations. |
|
| 11 |
+type ContainerExportOptions struct {
|
|
| 12 |
+ // Currently no options are defined for ContainerExport |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+// ContainerExportResult represents the result of a container export operation. |
|
| 16 |
+type ContainerExportResult struct {
|
|
| 17 |
+ rc io.ReadCloser |
|
| 18 |
+ close func() error |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 9 | 21 |
// ContainerExport retrieves the raw contents of a container |
| 10 | 22 |
// and returns them as an [io.ReadCloser]. It's up to the caller |
| 11 | 23 |
// to close the stream. |
| 12 |
-func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) {
|
|
| 24 |
+func (cli *Client) ContainerExport(ctx context.Context, containerID string, options ContainerExportOptions) (ContainerExportResult, error) {
|
|
| 13 | 25 |
containerID, err := trimID("container", containerID)
|
| 14 | 26 |
if err != nil {
|
| 15 |
- return nil, err |
|
| 27 |
+ return ContainerExportResult{}, err
|
|
| 16 | 28 |
} |
| 17 | 29 |
|
| 18 | 30 |
resp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
|
| 19 | 31 |
if err != nil {
|
| 20 |
- return nil, err |
|
| 32 |
+ return ContainerExportResult{}, err
|
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ return newContainerExportResult(resp.Body), nil |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+func newContainerExportResult(rc io.ReadCloser) ContainerExportResult {
|
|
| 39 |
+ if rc == nil {
|
|
| 40 |
+ panic("nil io.ReadCloser")
|
|
| 41 |
+ } |
|
| 42 |
+ return ContainerExportResult{
|
|
| 43 |
+ rc: rc, |
|
| 44 |
+ close: sync.OnceValue(rc.Close), |
|
| 21 | 45 |
} |
| 46 |
+} |
|
| 22 | 47 |
|
| 23 |
- return resp.Body, nil |
|
| 48 |
+// Read implements io.ReadCloser |
|
| 49 |
+func (r ContainerExportResult) Read(p []byte) (n int, err error) {
|
|
| 50 |
+ if r.rc == nil {
|
|
| 51 |
+ return 0, io.EOF |
|
| 52 |
+ } |
|
| 53 |
+ return r.rc.Read(p) |
|
| 54 |
+} |
|
| 55 |
+ |
|
| 56 |
+// Close implements io.ReadCloser |
|
| 57 |
+func (r ContainerExportResult) Close() error {
|
|
| 58 |
+ if r.close == nil {
|
|
| 59 |
+ return nil |
|
| 60 |
+ } |
|
| 61 |
+ return r.close() |
|
| 24 | 62 |
} |
| ... | ... |
@@ -17,14 +17,14 @@ func TestContainerExportError(t *testing.T) {
|
| 17 | 17 |
) |
| 18 | 18 |
assert.NilError(t, err) |
| 19 | 19 |
|
| 20 |
- _, err = client.ContainerExport(context.Background(), "nothing") |
|
| 20 |
+ _, err = client.ContainerExport(context.Background(), "nothing", ContainerExportOptions{})
|
|
| 21 | 21 |
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) |
| 22 | 22 |
|
| 23 |
- _, err = client.ContainerExport(context.Background(), "") |
|
| 23 |
+ _, err = client.ContainerExport(context.Background(), "", ContainerExportOptions{})
|
|
| 24 | 24 |
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) |
| 25 | 25 |
assert.Check(t, is.ErrorContains(err, "value is empty")) |
| 26 | 26 |
|
| 27 |
- _, err = client.ContainerExport(context.Background(), " ") |
|
| 27 |
+ _, err = client.ContainerExport(context.Background(), " ", ContainerExportOptions{})
|
|
| 28 | 28 |
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) |
| 29 | 29 |
assert.Check(t, is.ErrorContains(err, "value is empty")) |
| 30 | 30 |
} |
| ... | ... |
@@ -40,7 +40,7 @@ func TestContainerExport(t *testing.T) {
|
| 40 | 40 |
}), |
| 41 | 41 |
) |
| 42 | 42 |
assert.NilError(t, err) |
| 43 |
- body, err := client.ContainerExport(context.Background(), "container_id") |
|
| 43 |
+ body, err := client.ContainerExport(context.Background(), "container_id", ContainerExportOptions{})
|
|
| 44 | 44 |
assert.NilError(t, err) |
| 45 | 45 |
defer body.Close() |
| 46 | 46 |
content, err := io.ReadAll(body) |
| ... | ... |
@@ -103,7 +103,7 @@ func (s *DockerAPISuite) TestContainerAPIGetExport(c *testing.T) {
|
| 103 | 103 |
assert.NilError(c, err) |
| 104 | 104 |
defer apiClient.Close() |
| 105 | 105 |
|
| 106 |
- body, err := apiClient.ContainerExport(testutil.GetContext(c), name) |
|
| 106 |
+ body, err := apiClient.ContainerExport(testutil.GetContext(c), name, client.ContainerExportOptions{})
|
|
| 107 | 107 |
assert.NilError(c, err) |
| 108 | 108 |
defer body.Close() |
| 109 | 109 |
found := false |
| ... | ... |
@@ -27,7 +27,7 @@ func TestExportContainerAndImportImage(t *testing.T) {
|
| 27 | 27 |
poll.WaitOn(t, container.IsStopped(ctx, apiClient, cID)) |
| 28 | 28 |
|
| 29 | 29 |
reference := "repo/" + strings.ToLower(t.Name()) + ":v1" |
| 30 |
- exportRes, err := apiClient.ContainerExport(ctx, cID) |
|
| 30 |
+ exportRes, err := apiClient.ContainerExport(ctx, cID, client.ContainerExportOptions{})
|
|
| 31 | 31 |
assert.NilError(t, err) |
| 32 | 32 |
importRes, err := apiClient.ImageImport(ctx, client.ImageImportSource{
|
| 33 | 33 |
Source: exportRes, |
| ... | ... |
@@ -70,6 +70,6 @@ func TestExportContainerAfterDaemonRestart(t *testing.T) {
|
| 70 | 70 |
|
| 71 | 71 |
d.Restart(t) |
| 72 | 72 |
|
| 73 |
- _, err := c.ContainerExport(ctx, ctrID) |
|
| 73 |
+ _, err := c.ContainerExport(ctx, ctrID, client.ContainerExportOptions{})
|
|
| 74 | 74 |
assert.NilError(t, err) |
| 75 | 75 |
} |
| ... | ... |
@@ -32,7 +32,7 @@ func TestNoOverlayfsWarningsAboutUndefinedBehaviors(t *testing.T) {
|
| 32 | 32 |
return err |
| 33 | 33 |
}}, |
| 34 | 34 |
{name: "export", operation: func(*testing.T) error {
|
| 35 |
- rc, err := apiClient.ContainerExport(ctx, cID) |
|
| 35 |
+ rc, err := apiClient.ContainerExport(ctx, cID, client.ContainerExportOptions{})
|
|
| 36 | 36 |
if err == nil {
|
| 37 | 37 |
defer rc.Close() |
| 38 | 38 |
_, err = io.Copy(io.Discard, rc) |
| ... | ... |
@@ -362,7 +362,7 @@ func TestAuthZPluginEnsureLoadImportWorking(t *testing.T) {
|
| 362 | 362 |
|
| 363 | 363 |
cID := container.Run(ctx, t, c) |
| 364 | 364 |
|
| 365 |
- responseReader, err := c.ContainerExport(ctx, cID) |
|
| 365 |
+ responseReader, err := c.ContainerExport(ctx, cID, client.ContainerExportOptions{})
|
|
| 366 | 366 |
assert.NilError(t, err) |
| 367 | 367 |
defer responseReader.Close() |
| 368 | 368 |
file, err := os.Create(exportedImagePath) |
| ... | ... |
@@ -59,7 +59,7 @@ type ContainerAPIClient interface {
|
| 59 | 59 |
ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) |
| 60 | 60 |
ContainerDiff(ctx context.Context, container string, options ContainerDiffOptions) (ContainerDiffResult, error) |
| 61 | 61 |
ExecAPIClient |
| 62 |
- ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) |
|
| 62 |
+ ContainerExport(ctx context.Context, container string, options ContainerExportOptions) (ContainerExportResult, error) |
|
| 63 | 63 |
ContainerInspect(ctx context.Context, container string, options ContainerInspectOptions) (ContainerInspectResult, error) |
| 64 | 64 |
ContainerKill(ctx context.Context, container string, options ContainerKillOptions) (ContainerKillResult, error) |
| 65 | 65 |
ContainerList(ctx context.Context, options ContainerListOptions) (ContainerListResult, error) |
| ... | ... |
@@ -4,21 +4,59 @@ import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"io" |
| 6 | 6 |
"net/url" |
| 7 |
+ "sync" |
|
| 7 | 8 |
) |
| 8 | 9 |
|
| 10 |
+// ContainerExportOptions specifies options for container export operations. |
|
| 11 |
+type ContainerExportOptions struct {
|
|
| 12 |
+ // Currently no options are defined for ContainerExport |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+// ContainerExportResult represents the result of a container export operation. |
|
| 16 |
+type ContainerExportResult struct {
|
|
| 17 |
+ rc io.ReadCloser |
|
| 18 |
+ close func() error |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 9 | 21 |
// ContainerExport retrieves the raw contents of a container |
| 10 | 22 |
// and returns them as an [io.ReadCloser]. It's up to the caller |
| 11 | 23 |
// to close the stream. |
| 12 |
-func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) {
|
|
| 24 |
+func (cli *Client) ContainerExport(ctx context.Context, containerID string, options ContainerExportOptions) (ContainerExportResult, error) {
|
|
| 13 | 25 |
containerID, err := trimID("container", containerID)
|
| 14 | 26 |
if err != nil {
|
| 15 |
- return nil, err |
|
| 27 |
+ return ContainerExportResult{}, err
|
|
| 16 | 28 |
} |
| 17 | 29 |
|
| 18 | 30 |
resp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
|
| 19 | 31 |
if err != nil {
|
| 20 |
- return nil, err |
|
| 32 |
+ return ContainerExportResult{}, err
|
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ return newContainerExportResult(resp.Body), nil |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+func newContainerExportResult(rc io.ReadCloser) ContainerExportResult {
|
|
| 39 |
+ if rc == nil {
|
|
| 40 |
+ panic("nil io.ReadCloser")
|
|
| 41 |
+ } |
|
| 42 |
+ return ContainerExportResult{
|
|
| 43 |
+ rc: rc, |
|
| 44 |
+ close: sync.OnceValue(rc.Close), |
|
| 21 | 45 |
} |
| 46 |
+} |
|
| 22 | 47 |
|
| 23 |
- return resp.Body, nil |
|
| 48 |
+// Read implements io.ReadCloser |
|
| 49 |
+func (r ContainerExportResult) Read(p []byte) (n int, err error) {
|
|
| 50 |
+ if r.rc == nil {
|
|
| 51 |
+ return 0, io.EOF |
|
| 52 |
+ } |
|
| 53 |
+ return r.rc.Read(p) |
|
| 54 |
+} |
|
| 55 |
+ |
|
| 56 |
+// Close implements io.ReadCloser |
|
| 57 |
+func (r ContainerExportResult) Close() error {
|
|
| 58 |
+ if r.close == nil {
|
|
| 59 |
+ return nil |
|
| 60 |
+ } |
|
| 61 |
+ return r.close() |
|
| 24 | 62 |
} |