diff --git a/api/server/router/image/backend.go b/api/server/router/image/backend.go index f943816ed5..167d03af2c 100644 --- a/api/server/router/image/backend.go +++ b/api/server/router/image/backend.go @@ -32,9 +32,9 @@ type imageBackend interface { } type importExportBackend interface { - LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error + LoadImage(ctx context.Context, inTar io.ReadCloser, platform *ocispec.Platform, outStream io.Writer, quiet bool) error ImportImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, msg string, layerReader io.Reader, changes []string) (dockerimage.ID, error) - ExportImage(ctx context.Context, names []string, outStream io.Writer) error + ExportImage(ctx context.Context, names []string, platform *ocispec.Platform, outStream io.Writer) error } type registryBackend interface { diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index 5601164ea9..bf5b865ed0 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -246,7 +246,18 @@ func (ir *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, names = r.Form["names"] } - if err := ir.backend.ExportImage(ctx, names, output); err != nil { + var platform *ocispec.Platform + if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.48") { + if formPlatform := r.Form.Get("platform"); formPlatform != "" { + p, err := httputils.DecodePlatform(formPlatform) + if err != nil { + return err + } + platform = p + } + } + + if err := ir.backend.ExportImage(ctx, names, platform, output); err != nil { if !output.Flushed() { return err } @@ -259,13 +270,24 @@ func (ir *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter if err := httputils.ParseForm(r); err != nil { return err } + + var platform *ocispec.Platform + if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.47") { + if formPlatform := r.Form.Get("platform"); formPlatform != "" { + p, err := httputils.DecodePlatform(formPlatform) + if err != nil { + return err + } + platform = p + } + } quiet := httputils.BoolValueOrDefault(r, "quiet", true) w.Header().Set("Content-Type", "application/json") output := ioutils.NewWriteFlusher(w) defer output.Close() - if err := ir.backend.LoadImage(ctx, r.Body, output, quiet); err != nil { + if err := ir.backend.LoadImage(ctx, r.Body, platform, output, quiet); err != nil { _, _ = output.Write(streamformatter.FormatError(err)) } return nil diff --git a/api/swagger.yaml b/api/swagger.yaml index 26cf84e9f4..e57f104091 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -9956,7 +9956,16 @@ paths: description: "Image name or ID" type: "string" required: true - tags: ["Image"] + - name: "platform" + type: "string" + in: "query" + description: | + JSON encoded OCI platform describing a platform which will be used + to select a platform-specific image to be saved if the image is + multi-platform. + If not provided, the full multi-platform image will be saved. + + Example: `{"os": "linux", "architecture": "arm", "variant": "v5"}` /images/get: get: summary: "Export several images" @@ -10023,6 +10032,16 @@ paths: description: "Suppress progress details during load." type: "boolean" default: false + - name: "platform" + type: "string" + in: "query" + description: | + JSON encoded OCI platform describing a platform which will be used + to select a platform-specific image to be load if the image is + multi-platform. + If not provided, the full multi-platform image will be loaded. + + Example: `{"os": "linux", "architecture": "arm", "variant": "v5"}` tags: ["Image"] /containers/{id}/exec: post: diff --git a/api/types/image/opts.go b/api/types/image/opts.go index e99756eeb1..29aa7c5e79 100644 --- a/api/types/image/opts.go +++ b/api/types/image/opts.go @@ -92,3 +92,18 @@ type HistoryOptions struct { // Platform from the manifest list to use for history. Platform *ocispec.Platform } + +// LoadOptions holds parameters to load images. +type LoadOptions struct { + // Quiet suppresses progress output + Quiet bool + + // Platform is a specific platform to load when the image is a multi-platform + Platform *ocispec.Platform +} + +// SaveOptions holds parameters to save images. +type SaveOptions struct { + // Platform is a specific platform to save if the image is a multi-platform image. + Platform *ocispec.Platform +} diff --git a/client/image_load.go b/client/image_load.go index c68f0013e6..38e024ba19 100644 --- a/client/image_load.go +++ b/client/image_load.go @@ -2,6 +2,7 @@ package client // import "github.com/docker/docker/client" import ( "context" + "encoding/json" "io" "net/http" "net/url" @@ -12,12 +13,28 @@ import ( // ImageLoad loads an image in the docker host from the client host. // It's up to the caller to close the io.ReadCloser in the // ImageLoadResponse returned by this function. -func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (image.LoadResponse, error) { +// +// Platform is an optional parameter that specifies the platform to load from +// the provided multi-platform image. This is only has effect if the input image +// is a multi-platform image. +func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error) { v := url.Values{} v.Set("quiet", "0") - if quiet { + if opts.Quiet { v.Set("quiet", "1") } + if opts.Platform != nil { + if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil { + return image.LoadResponse{}, err + } + + p, err := json.Marshal(*opts.Platform) + if err != nil { + return image.LoadResponse{}, err + } + v.Set("platform", string(p)) + } + resp, err := cli.postRaw(ctx, "/images/load", v, input, http.Header{ "Content-Type": {"application/x-tar"}, }) diff --git a/client/image_load_test.go b/client/image_load_test.go index 41dfbe59b2..b184f8cf3a 100644 --- a/client/image_load_test.go +++ b/client/image_load_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" @@ -19,7 +20,7 @@ func TestImageLoadError(t *testing.T) { client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), } - _, err := client.ImageLoad(context.Background(), nil, true) + _, err := client.ImageLoad(context.Background(), nil, image.LoadOptions{Quiet: true}) assert.Check(t, is.ErrorType(err, errdefs.IsSystem)) } @@ -78,7 +79,7 @@ func TestImageLoad(t *testing.T) { } input := bytes.NewReader([]byte(expectedInput)) - imageLoadResponse, err := client.ImageLoad(context.Background(), input, loadCase.quiet) + imageLoadResponse, err := client.ImageLoad(context.Background(), input, image.LoadOptions{Quiet: loadCase.quiet}) if err != nil { t.Fatal(err) } diff --git a/client/image_save.go b/client/image_save.go index d1314e4b22..847d1dd8b3 100644 --- a/client/image_save.go +++ b/client/image_save.go @@ -2,17 +2,33 @@ package client // import "github.com/docker/docker/client" import ( "context" + "encoding/json" + "fmt" "io" "net/url" + + "github.com/docker/docker/api/types/image" ) // ImageSave retrieves one or more images from the docker host as an io.ReadCloser. // It's up to the caller to store the images and close the stream. -func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) { +func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, opts image.SaveOptions) (io.ReadCloser, error) { query := url.Values{ "names": imageIDs, } + if opts.Platform != nil { + if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil { + return nil, err + } + + p, err := json.Marshal(*opts.Platform) + if err != nil { + return nil, fmt.Errorf("invalid platform: %v", err) + } + query.Set("platform", string(p)) + } + resp, err := cli.get(ctx, "/images/get", query, nil) if err != nil { return nil, err diff --git a/client/image_save_test.go b/client/image_save_test.go index 022355fad7..935fae598e 100644 --- a/client/image_save_test.go +++ b/client/image_save_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" @@ -19,7 +20,7 @@ func TestImageSaveError(t *testing.T) { client := &Client{ client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), } - _, err := client.ImageSave(context.Background(), []string{"nothing"}) + _, err := client.ImageSave(context.Background(), []string{"nothing"}, image.SaveOptions{}) assert.Check(t, is.ErrorType(err, errdefs.IsSystem)) } @@ -43,7 +44,7 @@ func TestImageSave(t *testing.T) { }, nil }), } - saveResponse, err := client.ImageSave(context.Background(), []string{"image_id1", "image_id2"}) + saveResponse, err := client.ImageSave(context.Background(), []string{"image_id1", "image_id2"}, image.SaveOptions{}) if err != nil { t.Fatal(err) } diff --git a/client/interface.go b/client/interface.go index 142b79c347..470923a243 100644 --- a/client/interface.go +++ b/client/interface.go @@ -95,12 +95,12 @@ type ImageAPIClient interface { ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) ImageInspectWithRaw(ctx context.Context, image string) (image.InspectResponse, []byte, error) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) - ImageLoad(ctx context.Context, input io.Reader, quiet bool) (image.LoadResponse, error) + ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error) ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error) ImagePush(ctx context.Context, ref string, options image.PushOptions) (io.ReadCloser, error) ImageRemove(ctx context.Context, image string, options image.RemoveOptions) ([]image.DeleteResponse, error) + ImageSave(ctx context.Context, images []string, opts image.SaveOptions) (io.ReadCloser, error) ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error) - ImageSave(ctx context.Context, images []string) (io.ReadCloser, error) ImageTag(ctx context.Context, image, ref string) error ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, error) } diff --git a/daemon/containerd/image_exporter.go b/daemon/containerd/image_exporter.go index 344ec2b09d..ebd30e2183 100644 --- a/daemon/containerd/image_exporter.go +++ b/daemon/containerd/image_exporter.go @@ -47,10 +47,8 @@ func (i *ImageService) PerformWithBaseFS(ctx context.Context, c *container.Conta // outStream is the writer which the images are written to. // // TODO(thaJeztah): produce JSON stream progress response and image events; see https://github.com/moby/moby/issues/43910 -func (i *ImageService) ExportImage(ctx context.Context, names []string, outStream io.Writer) error { - // TODO: Pass as argument - var requestedPlatform *ocispec.Platform - pm := i.matchRequestedOrDefault(platforms.OnlyStrict, requestedPlatform) +func (i *ImageService) ExportImage(ctx context.Context, names []string, platform *ocispec.Platform, outStream io.Writer) error { + pm := i.matchRequestedOrDefault(platforms.OnlyStrict, platform) opts := []archive.ExportOpt{ archive.WithSkipNonDistributableBlobs(), @@ -85,7 +83,17 @@ func (i *ImageService) ExportImage(ctx context.Context, names []string, outStrea return leaseContent(ctx, i.content, leasesManager, lease, target) } - exportImage := func(ctx context.Context, target ocispec.Descriptor, ref reference.Named) error { + exportImage := func(ctx context.Context, img containerdimages.Image, ref reference.Named) error { + target := img.Target + + if platform != nil { + newTarget, err := i.getPushDescriptor(ctx, img, platform) + if err != nil { + return errors.Wrap(err, "no suitable export target found for platform "+platforms.FormatAll(*platform)) + } + target = newTarget + } + if err := addLease(ctx, target); err != nil { return err } @@ -142,7 +150,7 @@ func (i *ImageService) ExportImage(ctx context.Context, names []string, outStrea continue } - if err := exportImage(ctx, img.Target, ref); err != nil { + if err := exportImage(ctx, img, ref); err != nil { return err } } @@ -151,19 +159,20 @@ func (i *ImageService) ExportImage(ctx context.Context, names []string, outStrea } for _, name := range names { - target, resolveErr := i.resolveDescriptor(ctx, name) + img, resolveErr := i.resolveImage(ctx, name) // Check if the requested name is a truncated digest of the resolved descriptor. // If yes, that means that the user specified a specific image ID so // it's not referencing a repository. specificDigestResolved := false if resolveErr == nil { - nameWithoutDigestAlgorithm := strings.TrimPrefix(name, target.Digest.Algorithm().String()+":") - specificDigestResolved = strings.HasPrefix(target.Digest.Encoded(), nameWithoutDigestAlgorithm) + nameWithoutDigestAlgorithm := strings.TrimPrefix(name, img.Target.Digest.Algorithm().String()+":") + specificDigestResolved = strings.HasPrefix(img.Target.Digest.Encoded(), nameWithoutDigestAlgorithm) } log.G(ctx).WithFields(log.Fields{ "name": name, + "img": img, "resolveErr": resolveErr, "specificDigestResolved": specificDigestResolved, }).Debug("export requested") @@ -199,7 +208,8 @@ func (i *ImageService) ExportImage(ctx context.Context, names []string, outStrea if specificDigestResolved { ref = nil } - if err := exportImage(ctx, target, ref); err != nil { + + if err := exportImage(ctx, img, ref); err != nil { return err } } @@ -234,16 +244,17 @@ func leaseContent(ctx context.Context, store content.Store, leasesManager leases // LoadImage uploads a set of images into the repository. This is the // complement of ExportImage. The input stream is an uncompressed tar // ball containing images and metadata. -func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error { +func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, platform *ocispec.Platform, outStream io.Writer, quiet bool) error { decompressed, err := dockerarchive.DecompressStream(inTar) if err != nil { return errors.Wrap(err, "failed to decompress input tar archive") } defer decompressed.Close() + pm := i.matchRequestedOrDefault(platforms.OnlyStrict, platform) + opts := []containerd.ImportOpt{ - // TODO(vvoland): Allow user to pass platform - containerd.WithImportPlatform(platforms.All), + containerd.WithImportPlatform(pm), containerd.WithSkipMissing(), diff --git a/daemon/containerd/image_push.go b/daemon/containerd/image_push.go index 8fb19a3c7d..60604e4760 100644 --- a/daemon/containerd/image_push.go +++ b/daemon/containerd/image_push.go @@ -256,7 +256,7 @@ func (i *ImageService) getPushDescriptor(ctx context.Context, img containerdimag return nil }) if err != nil { - return ocispec.Descriptor{}, err + return ocispec.Descriptor{}, errdefs.System(err) } switch len(presentMatchingManifests) { diff --git a/daemon/image_service.go b/daemon/image_service.go index c3580fcdfa..94606b3aaf 100644 --- a/daemon/image_service.go +++ b/daemon/image_service.go @@ -30,9 +30,9 @@ type ImageService interface { PushImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error CreateImage(ctx context.Context, config []byte, parent string, contentStoreDigest digest.Digest) (builder.Image, error) ImageDelete(ctx context.Context, imageRef string, force, prune bool) ([]imagetype.DeleteResponse, error) - ExportImage(ctx context.Context, names []string, outStream io.Writer) error + ExportImage(ctx context.Context, names []string, platform *ocispec.Platform, outStream io.Writer) error PerformWithBaseFS(ctx context.Context, c *container.Container, fn func(string) error) error - LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error + LoadImage(ctx context.Context, inTar io.ReadCloser, platform *ocispec.Platform, outStream io.Writer, quiet bool) error Images(ctx context.Context, opts imagetype.ListOptions) ([]*imagetype.Summary, error) LogImageEvent(imageID, refName string, action events.Action) CountImages(ctx context.Context) int diff --git a/daemon/images/image_exporter.go b/daemon/images/image_exporter.go index 0c41d80e61..fd10be26a7 100644 --- a/daemon/images/image_exporter.go +++ b/daemon/images/image_exporter.go @@ -6,7 +6,10 @@ import ( "github.com/containerd/log" "github.com/docker/docker/container" + "github.com/docker/docker/errdefs" "github.com/docker/docker/image/tarexport" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" ) // ExportImage exports a list of images to the given output stream. The @@ -14,7 +17,10 @@ import ( // stream. All images with the given tag and all versions containing // the same tag are exported. names is the set of tags to export, and // outStream is the writer which the images are written to. -func (i *ImageService) ExportImage(ctx context.Context, names []string, outStream io.Writer) error { +func (i *ImageService) ExportImage(ctx context.Context, names []string, platform *ocispec.Platform, outStream io.Writer) error { + if platform != nil { + return errdefs.NotImplemented(errors.New("platform parameter is not supported with the graphdriver image store")) + } imageExporter := tarexport.NewTarExporter(i.imageStore, i.layerStore, i.referenceStore, i) return imageExporter.Save(ctx, names, outStream) } @@ -44,7 +50,10 @@ func (i *ImageService) PerformWithBaseFS(ctx context.Context, c *container.Conta // LoadImage uploads a set of images into the repository. This is the // complement of ExportImage. The input stream is an uncompressed tar // ball containing images and metadata. -func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error { +func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, platform *ocispec.Platform, outStream io.Writer, quiet bool) error { + if platform != nil { + return errdefs.NotImplemented(errors.New("platform parameter is not supported with the graphdriver image store")) + } imageExporter := tarexport.NewTarExporter(i.imageStore, i.layerStore, i.referenceStore, i) return imageExporter.Load(ctx, inTar, outStream, quiet) } diff --git a/docs/api/version-history.md b/docs/api/version-history.md index a1b184f9de..5053b9dd4e 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -20,6 +20,10 @@ keywords: "API, Docker, rcli, REST, documentation" * `GET /images/{name}/history` now supports a `platform` parameter (JSON encoded OCI Platform type) that allows to specify a platform to show the history of. +* `POST /images/{name}/load` and `GET /images/{name}/get` now support a + `platform` parameter (JSON encoded OCI Platform type) that allows to specify + a platform to load/save. Not passing this parameter will result in + loading/saving the full multi-platform image. ## v1.47 API changes diff --git a/integration/build/build_userns_linux_test.go b/integration/build/build_userns_linux_test.go index e4d56a6302..e295624d51 100644 --- a/integration/build/build_userns_linux_test.go +++ b/integration/build/build_userns_linux_test.go @@ -10,6 +10,7 @@ import ( "github.com/docker/docker/api/types" containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/integration/internal/container" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/stdcopy" @@ -76,7 +77,7 @@ func TestBuildUserNamespaceValidateCapabilitiesAreV2(t *testing.T) { err = jsonmessage.DisplayJSONMessagesStream(resp.Body, buf, 0, false, nil) assert.NilError(t, err) - reader, err := clientUserRemap.ImageSave(ctx, []string{imageTag}) + reader, err := clientUserRemap.ImageSave(ctx, []string{imageTag}, image.SaveOptions{}) assert.NilError(t, err, "failed to download capabilities image") defer reader.Close() @@ -106,7 +107,7 @@ func TestBuildUserNamespaceValidateCapabilitiesAreV2(t *testing.T) { defer tarFile.Close() tarReader := bufio.NewReader(tarFile) - loadResp, err := clientNoUserRemap.ImageLoad(ctx, tarReader, false) + loadResp, err := clientNoUserRemap.ImageLoad(ctx, tarReader, image.LoadOptions{}) assert.NilError(t, err, "failed to load image tar file") defer loadResp.Body.Close() buf = bytes.NewBuffer(nil) diff --git a/integration/image/save_test.go b/integration/image/save_test.go index 7f76996861..f1c5af4a9a 100644 --- a/integration/image/save_test.go +++ b/integration/image/save_test.go @@ -15,6 +15,7 @@ import ( "github.com/cpuguy83/tar2go" containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/integration/internal/build" "github.com/docker/docker/integration/internal/container" @@ -63,7 +64,7 @@ func TestSaveCheckTimes(t *testing.T) { img, _, err := client.ImageInspectWithRaw(ctx, repoName) assert.NilError(t, err) - rdr, err := client.ImageSave(ctx, []string{repoName}) + rdr, err := client.ImageSave(ctx, []string{repoName}, image.SaveOptions{}) assert.NilError(t, err) tarfs := tarIndexFS(t, rdr) @@ -138,7 +139,7 @@ func TestSaveOCI(t *testing.T) { inspect, _, err := client.ImageInspectWithRaw(ctx, tc.image) assert.NilError(t, err) - rdr, err := client.ImageSave(ctx, []string{tc.image}) + rdr, err := client.ImageSave(ctx, []string{tc.image}, image.SaveOptions{}) assert.NilError(t, err) defer rdr.Close() @@ -242,7 +243,7 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { idBar := makeImage("busybox:latest", tagBar) idBusybox := busyboxImg.ID - rdr, err := client.ImageSave(ctx, []string{repoName, "busybox:latest"}) + rdr, err := client.ImageSave(ctx, []string{repoName, "busybox:latest"}, image.SaveOptions{}) assert.NilError(t, err) defer rdr.Close() @@ -299,7 +300,7 @@ RUN touch /opt/a/b/c && chown user:user /opt/a/b/c` imgID := build.Do(ctx, t, client, fakecontext.New(t, t.TempDir(), fakecontext.WithDockerfile(dockerfile))) - rdr, err := client.ImageSave(ctx, []string{imgID}) + rdr, err := client.ImageSave(ctx, []string{imgID}, image.SaveOptions{}) assert.NilError(t, err) defer rdr.Close() diff --git a/integration/plugin/authz/authz_plugin_test.go b/integration/plugin/authz/authz_plugin_test.go index 109cdb0d4b..1b266773c4 100644 --- a/integration/plugin/authz/authz_plugin_test.go +++ b/integration/plugin/authz/authz_plugin_test.go @@ -424,8 +424,8 @@ func TestAuthzPluginEnsureContainerCopyToFrom(t *testing.T) { assert.NilError(t, err) } -func imageSave(ctx context.Context, client client.APIClient, path, image string) error { - responseReader, err := client.ImageSave(ctx, []string{image}) +func imageSave(ctx context.Context, client client.APIClient, path, imgRef string) error { + responseReader, err := client.ImageSave(ctx, []string{imgRef}, image.SaveOptions{}) if err != nil { return err } @@ -445,8 +445,7 @@ func imageLoad(ctx context.Context, client client.APIClient, path string) error return err } defer file.Close() - quiet := true - response, err := client.ImageLoad(ctx, file, quiet) + response, err := client.ImageLoad(ctx, file, image.LoadOptions{Quiet: true}) if err != nil { return err } diff --git a/internal/testutils/specialimage/load.go b/internal/testutils/specialimage/load.go index 21f588e69a..db175e525b 100644 --- a/internal/testutils/specialimage/load.go +++ b/internal/testutils/specialimage/load.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/jsonmessage" @@ -29,7 +30,7 @@ func Load(ctx context.Context, t *testing.T, apiClient client.APIClient, imageFu defer rc.Close() - resp, err := apiClient.ImageLoad(ctx, rc, true) + resp, err := apiClient.ImageLoad(ctx, rc, image.LoadOptions{Quiet: true}) assert.NilError(t, err, "Failed to load dangling image") defer resp.Body.Close() diff --git a/testutil/daemon/daemon.go b/testutil/daemon/daemon.go index a98fe1f253..e93ab23fe2 100644 --- a/testutil/daemon/daemon.go +++ b/testutil/daemon/daemon.go @@ -17,6 +17,7 @@ import ( "time" "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/system" "github.com/docker/docker/client" "github.com/docker/docker/container" @@ -836,14 +837,14 @@ func (d *Daemon) LoadBusybox(ctx context.Context, t testing.TB) { assert.NilError(t, err, "[%s] failed to create client", d.id) defer clientHost.Close() - reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"}) + reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"}, image.SaveOptions{}) assert.NilError(t, err, "[%s] failed to download busybox", d.id) defer reader.Close() c := d.NewClientT(t) defer c.Close() - resp, err := c.ImageLoad(ctx, reader, true) + resp, err := c.ImageLoad(ctx, reader, image.LoadOptions{Quiet: true}) assert.NilError(t, err, "[%s] failed to load busybox", d.id) defer resp.Body.Close() } diff --git a/testutil/fixtures/load/frozen.go b/testutil/fixtures/load/frozen.go index 40b7d7a619..9d369aa001 100644 --- a/testutil/fixtures/load/frozen.go +++ b/testutil/fixtures/load/frozen.go @@ -111,7 +111,7 @@ func loadFrozenImages(ctx context.Context, client client.APIClient) error { tarCmd.Start() defer tarCmd.Wait() - resp, err := client.ImageLoad(ctx, out, true) + resp, err := client.ImageLoad(ctx, out, image.LoadOptions{Quiet: true}) if err != nil { return errors.Wrap(err, "failed to load frozen images") }