client: Client.ImageImport: close reader on context cancellation

Use a cancelReadCloser to automatically close the reader when the context
is cancelled. Consumers are still recommended to manually close the reader,
but the cancelReadCloser makes the Close idempotent.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-10-27 16:50:14 +01:00
parent 849239cedf
commit 08cd02cab6
4 changed files with 20 additions and 41 deletions

View File

@@ -13,8 +13,10 @@ type ImageImportResult interface {
io.ReadCloser
}
// ImageImport creates a new image based on the source options.
// It returns the JSON content in the response body.
// ImageImport creates a new image based on the source options. It returns the
// JSON content in the [ImageImportResult].
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (ImageImportResult, error) {
if ref != "" {
// Check if the given image name can be resolved
@@ -48,30 +50,17 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re
if err != nil {
return nil, err
}
return &imageImportResult{body: resp.Body}, nil
return &imageImportResult{
ReadCloser: newCancelReadCloser(ctx, resp.Body),
}, nil
}
// ImageImportResult holds the response body returned by the daemon for image import.
type imageImportResult struct {
// body must be closed to avoid a resource leak
body io.ReadCloser
io.ReadCloser
}
var (
_ io.ReadCloser = (*imageImportResult)(nil)
_ ImageImportResult = (*imageImportResult)(nil)
)
func (r *imageImportResult) Read(p []byte) (int, error) {
if r == nil || r.body == nil {
return 0, io.EOF
}
return r.body.Read(p)
}
func (r *imageImportResult) Close() error {
if r == nil || r.body == nil {
return nil
}
return r.body.Close()
}

View File

@@ -34,6 +34,7 @@ func TestExportContainerAndImportImage(t *testing.T) {
SourceName: "-",
}, reference, client.ImageImportOptions{})
assert.NilError(t, err)
defer func() { _ = importRes.Close() }()
// If the import is successfully, then the message output should contain
// the image ID and match with the output from `docker images`.

View File

@@ -460,17 +460,17 @@ func imageImport(ctx context.Context, apiClient client.APIClient, path string) e
return err
}
defer file.Close()
options := client.ImageImportOptions{}
ref := ""
source := client.ImageImportSource{
Source: file,
SourceName: "-",
}
responseReader, err := apiClient.ImageImport(ctx, source, ref, options)
resp, err := apiClient.ImageImport(ctx, source, ref, client.ImageImportOptions{})
if err != nil {
return err
}
defer responseReader.Close()
_, _ = io.Copy(io.Discard, resp)
_ = resp.Close()
return nil
}

View File

@@ -13,8 +13,10 @@ type ImageImportResult interface {
io.ReadCloser
}
// ImageImport creates a new image based on the source options.
// It returns the JSON content in the response body.
// ImageImport creates a new image based on the source options. It returns the
// JSON content in the [ImageImportResult].
//
// The underlying [io.ReadCloser] is automatically closed if the context is canceled,
func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (ImageImportResult, error) {
if ref != "" {
// Check if the given image name can be resolved
@@ -48,30 +50,17 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re
if err != nil {
return nil, err
}
return &imageImportResult{body: resp.Body}, nil
return &imageImportResult{
ReadCloser: newCancelReadCloser(ctx, resp.Body),
}, nil
}
// ImageImportResult holds the response body returned by the daemon for image import.
type imageImportResult struct {
// body must be closed to avoid a resource leak
body io.ReadCloser
io.ReadCloser
}
var (
_ io.ReadCloser = (*imageImportResult)(nil)
_ ImageImportResult = (*imageImportResult)(nil)
)
func (r *imageImportResult) Read(p []byte) (int, error) {
if r == nil || r.body == nil {
return 0, io.EOF
}
return r.body.Read(p)
}
func (r *imageImportResult) Close() error {
if r == nil || r.body == nil {
return nil
}
return r.body.Close()
}