Browse code

client: Change ImageLoad to use functional options

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Paweł Gronowski authored on 2025/02/14 21:54:11
Showing 8 changed files
... ...
@@ -125,7 +125,7 @@ type ImageAPIClient interface {
125 125
 
126 126
 	ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (image.InspectResponse, error)
127 127
 	ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) ([]image.HistoryResponseItem, error)
128
-	ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error)
128
+	ImageLoad(ctx context.Context, input io.Reader, _ ...ImageLoadOption) (image.LoadResponse, error)
129 129
 	ImageSave(ctx context.Context, images []string, opts image.SaveOptions) (io.ReadCloser, error)
130 130
 
131 131
 	ImageAPIClientDeprecated
... ...
@@ -2,13 +2,48 @@ package client // import "github.com/docker/docker/client"
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"fmt"
5 6
 	"io"
6 7
 	"net/http"
7 8
 	"net/url"
8 9
 
9 10
 	"github.com/docker/docker/api/types/image"
11
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
10 12
 )
11 13
 
14
+// ImageLoadOption is a type representing functional options for the image load operation.
15
+type ImageLoadOption interface {
16
+	Apply(*imageLoadOpts) error
17
+}
18
+type imageLoadOptionFunc func(opt *imageLoadOpts) error
19
+
20
+func (f imageLoadOptionFunc) Apply(o *imageLoadOpts) error {
21
+	return f(o)
22
+}
23
+
24
+type imageLoadOpts struct {
25
+	apiOptions image.LoadOptions
26
+}
27
+
28
+// ImageLoadWithQuiet sets the quiet option for the image load operation.
29
+func ImageLoadWithQuiet(quiet bool) ImageLoadOption {
30
+	return imageLoadOptionFunc(func(opt *imageLoadOpts) error {
31
+		opt.apiOptions.Quiet = quiet
32
+		return nil
33
+	})
34
+}
35
+
36
+// ImageLoadWithPlatforms sets the platforms to be loaded from the image.
37
+func ImageLoadWithPlatforms(platforms ...ocispec.Platform) ImageLoadOption {
38
+	return imageLoadOptionFunc(func(opt *imageLoadOpts) error {
39
+		if opt.apiOptions.Platforms != nil {
40
+			return fmt.Errorf("platforms already set to %v", opt.apiOptions.Platforms)
41
+		}
42
+		opt.apiOptions.Platforms = platforms
43
+		return nil
44
+	})
45
+}
46
+
12 47
 // ImageLoad loads an image in the docker host from the client host.
13 48
 // It's up to the caller to close the io.ReadCloser in the
14 49
 // ImageLoadResponse returned by this function.
... ...
@@ -16,18 +51,25 @@ import (
16 16
 // Platform is an optional parameter that specifies the platform to load from
17 17
 // the provided multi-platform image. This is only has effect if the input image
18 18
 // is a multi-platform image.
19
-func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error) {
19
+func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...ImageLoadOption) (image.LoadResponse, error) {
20
+	var opts imageLoadOpts
21
+	for _, opt := range loadOpts {
22
+		if err := opt.Apply(&opts); err != nil {
23
+			return image.LoadResponse{}, err
24
+		}
25
+	}
26
+
20 27
 	query := url.Values{}
21 28
 	query.Set("quiet", "0")
22
-	if opts.Quiet {
29
+	if opts.apiOptions.Quiet {
23 30
 		query.Set("quiet", "1")
24 31
 	}
25
-	if len(opts.Platforms) > 0 {
32
+	if len(opts.apiOptions.Platforms) > 0 {
26 33
 		if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
27 34
 			return image.LoadResponse{}, err
28 35
 		}
29 36
 
30
-		p, err := encodePlatforms(opts.Platforms...)
37
+		p, err := encodePlatforms(opts.apiOptions.Platforms...)
31 38
 		if err != nil {
32 39
 			return image.LoadResponse{}, err
33 40
 		}
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"net/url"
9 9
 	"testing"
10 10
 
11
-	"github.com/docker/docker/api/types/image"
12 11
 	"github.com/docker/docker/errdefs"
13 12
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
14 13
 	"gotest.tools/v3/assert"
... ...
@@ -20,7 +19,7 @@ func TestImageLoadError(t *testing.T) {
20 20
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
21 21
 	}
22 22
 
23
-	_, err := client.ImageLoad(context.Background(), nil, image.LoadOptions{Quiet: true})
23
+	_, err := client.ImageLoad(context.Background(), nil, ImageLoadWithQuiet(true))
24 24
 	assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
25 25
 }
26 26
 
... ...
@@ -97,10 +96,10 @@ func TestImageLoad(t *testing.T) {
97 97
 			}
98 98
 
99 99
 			input := bytes.NewReader([]byte(expectedInput))
100
-			imageLoadResponse, err := client.ImageLoad(context.Background(), input, image.LoadOptions{
101
-				Quiet:     tc.quiet,
102
-				Platforms: tc.platforms,
103
-			})
100
+			imageLoadResponse, err := client.ImageLoad(context.Background(), input,
101
+				ImageLoadWithQuiet(tc.quiet),
102
+				ImageLoadWithPlatforms(tc.platforms...),
103
+			)
104 104
 			assert.NilError(t, err)
105 105
 			assert.Check(t, is.Equal(imageLoadResponse.JSON, tc.expectedResponseJSON))
106 106
 
... ...
@@ -108,7 +108,7 @@ func TestBuildUserNamespaceValidateCapabilitiesAreV2(t *testing.T) {
108 108
 	defer tarFile.Close()
109 109
 
110 110
 	tarReader := bufio.NewReader(tarFile)
111
-	loadResp, err := clientNoUserRemap.ImageLoad(ctx, tarReader, image.LoadOptions{})
111
+	loadResp, err := clientNoUserRemap.ImageLoad(ctx, tarReader)
112 112
 	assert.NilError(t, err, "failed to load image tar file")
113 113
 	defer loadResp.Body.Close()
114 114
 	buf = bytes.NewBuffer(nil)
... ...
@@ -439,13 +439,13 @@ func imageSave(ctx context.Context, client client.APIClient, path, imgRef string
439 439
 	return err
440 440
 }
441 441
 
442
-func imageLoad(ctx context.Context, client client.APIClient, path string) error {
442
+func imageLoad(ctx context.Context, apiClient client.APIClient, path string) error {
443 443
 	file, err := os.Open(path)
444 444
 	if err != nil {
445 445
 		return err
446 446
 	}
447 447
 	defer file.Close()
448
-	response, err := client.ImageLoad(ctx, file, image.LoadOptions{Quiet: true})
448
+	response, err := apiClient.ImageLoad(ctx, file, client.ImageLoadWithQuiet(true))
449 449
 	if err != nil {
450 450
 		return err
451 451
 	}
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"strings"
10 10
 	"testing"
11 11
 
12
-	"github.com/docker/docker/api/types/image"
13 12
 	"github.com/docker/docker/client"
14 13
 	"github.com/docker/docker/pkg/archive"
15 14
 	"github.com/docker/docker/pkg/jsonmessage"
... ...
@@ -30,7 +29,7 @@ func Load(ctx context.Context, t *testing.T, apiClient client.APIClient, imageFu
30 30
 
31 31
 	defer rc.Close()
32 32
 
33
-	resp, err := apiClient.ImageLoad(ctx, rc, image.LoadOptions{Quiet: true})
33
+	resp, err := apiClient.ImageLoad(ctx, rc, client.ImageLoadWithQuiet(true))
34 34
 	assert.NilError(t, err, "Failed to load dangling image")
35 35
 
36 36
 	defer resp.Body.Close()
... ...
@@ -875,7 +875,7 @@ func (d *Daemon) LoadImage(ctx context.Context, t testing.TB, img string) {
875 875
 	c := d.NewClientT(t)
876 876
 	defer c.Close()
877 877
 
878
-	resp, err := c.ImageLoad(ctx, reader, image.LoadOptions{Quiet: true})
878
+	resp, err := c.ImageLoad(ctx, reader, client.ImageLoadWithQuiet(true))
879 879
 	assert.NilError(t, err, "[%s] failed to load %s", d.id, img)
880 880
 	defer resp.Body.Close()
881 881
 }
... ...
@@ -92,7 +92,7 @@ func imageExists(ctx context.Context, client client.APIClient, name string) bool
92 92
 	return err == nil
93 93
 }
94 94
 
95
-func loadFrozenImages(ctx context.Context, client client.APIClient) error {
95
+func loadFrozenImages(ctx context.Context, apiClient client.APIClient) error {
96 96
 	ctx, span := otel.Tracer("").Start(ctx, "load frozen images")
97 97
 	defer span.End()
98 98
 
... ...
@@ -111,7 +111,7 @@ func loadFrozenImages(ctx context.Context, client client.APIClient) error {
111 111
 	tarCmd.Start()
112 112
 	defer tarCmd.Wait()
113 113
 
114
-	resp, err := client.ImageLoad(ctx, out, image.LoadOptions{Quiet: true})
114
+	resp, err := apiClient.ImageLoad(ctx, out, client.ImageLoadWithQuiet(true))
115 115
 	if err != nil {
116 116
 		return errors.Wrap(err, "failed to load frozen images")
117 117
 	}