diff --git a/client/image_build.go b/client/image_build.go index 67c7e160a6..5062ec5de1 100644 --- a/client/image_build.go +++ b/client/image_build.go @@ -8,8 +8,8 @@ import ( "net/http" "net/url" "strconv" - "strings" + cerrdefs "github.com/containerd/errdefs" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/network" ) @@ -154,8 +154,12 @@ func (cli *Client) imageBuildOptionsToQuery(_ context.Context, options ImageBuil if options.SessionID != "" { query.Set("session", options.SessionID) } - if options.Platform != "" { - query.Set("platform", strings.ToLower(options.Platform)) + if len(options.Platforms) > 0 { + if len(options.Platforms) > 1 { + // TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it. + return query, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported") + } + query.Set("platform", formatPlatform(options.Platforms[0])) } if options.BuildID != "" { query.Set("buildid", options.BuildID) diff --git a/client/image_build_opts.go b/client/image_build_opts.go index effb259e36..f65ad0f2bf 100644 --- a/client/image_build_opts.go +++ b/client/image_build_opts.go @@ -6,6 +6,7 @@ import ( "github.com/moby/moby/api/types/build" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/registry" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImageBuildOptions holds the information @@ -50,7 +51,9 @@ type ImageBuildOptions struct { ExtraHosts []string // List of extra hosts Target string SessionID string - Platform string + // Platforms selects the platforms to build the image for. Multiple platforms + // can be provided if the daemon supports multi-platform builds. + Platforms []ocispec.Platform // Version specifies the version of the underlying builder to use Version build.BuilderVersion // BuildID is an optional identifier that can be passed together with the diff --git a/client/image_create.go b/client/image_create.go index 25e8a57ecc..4d429570c1 100644 --- a/client/image_create.go +++ b/client/image_create.go @@ -4,8 +4,8 @@ import ( "context" "net/http" "net/url" - "strings" + cerrdefs "github.com/containerd/errdefs" "github.com/distribution/reference" "github.com/moby/moby/api/types/registry" ) @@ -21,8 +21,12 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti query := url.Values{} query.Set("fromImage", ref.Name()) query.Set("tag", getAPITagFromNamedRef(ref)) - if options.Platform != "" { - query.Set("platform", strings.ToLower(options.Platform)) + if len(options.Platforms) > 0 { + if len(options.Platforms) > 1 { + // TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it. + return ImageCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported") + } + query.Set("platform", formatPlatform(options.Platforms[0])) } resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth)) if err != nil { diff --git a/client/image_create_opts.go b/client/image_create_opts.go index 301cf0bb81..eb4c486c77 100644 --- a/client/image_create_opts.go +++ b/client/image_create_opts.go @@ -1,11 +1,18 @@ package client -import "io" +import ( + "io" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) // ImageCreateOptions holds information to create images. type ImageCreateOptions struct { RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry. - Platform string // Platform is the target platform of the image if it needs to be pulled from the registry. + // Platforms specifies the platforms to platform of the image if it needs + // to be pulled from the registry. Multiple platforms can be provided + // if the daemon supports multi-platform pulls. + Platforms []ocispec.Platform } // ImageCreateResult holds the response body returned by the daemon for image create. diff --git a/client/image_import.go b/client/image_import.go index b8f1ccb500..3718ab50cc 100644 --- a/client/image_import.go +++ b/client/image_import.go @@ -3,7 +3,6 @@ package client import ( "context" "net/url" - "strings" "github.com/distribution/reference" ) @@ -31,8 +30,9 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re if options.Message != "" { query.Set("message", options.Message) } - if options.Platform != "" { - query.Set("platform", strings.ToLower(options.Platform)) + if p := formatPlatform(options.Platform); p != "unknown" { + // TODO(thaJeztah): would we ever support mutiple platforms here? (would require multiple rootfs tars as well?) + query.Set("platform", p) } for _, change := range options.Changes { query.Add("changes", change) diff --git a/client/image_import_opts.go b/client/image_import_opts.go index 2ba5b593ec..513548cc34 100644 --- a/client/image_import_opts.go +++ b/client/image_import_opts.go @@ -2,6 +2,8 @@ package client import ( "io" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImageImportSource holds source information for ImageImport @@ -12,10 +14,10 @@ type ImageImportSource struct { // ImageImportOptions holds information to import images from the client host. type ImageImportOptions struct { - Tag string // Tag is the name to tag this image with. This attribute is deprecated. - Message string // Message is the message to tag the image with - Changes []string // Changes are the raw changes to apply to this image - Platform string // Platform is the target platform of the image + Tag string // Tag is the name to tag this image with. This attribute is deprecated. + Message string // Message is the message to tag the image with + Changes []string // Changes are the raw changes to apply to this image + Platform ocispec.Platform // Platform is the target platform of the image } // ImageImportResult holds the response body returned by the daemon for image import. diff --git a/client/image_import_test.go b/client/image_import_test.go index 4227a8f600..8f9ae367f4 100644 --- a/client/image_import_test.go +++ b/client/image_import_test.go @@ -9,6 +9,7 @@ import ( "testing" cerrdefs "github.com/containerd/errdefs" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -55,7 +56,10 @@ func TestImageImport(t *testing.T) { { doc: "with platform", options: ImageImportOptions{ - Platform: "linux/amd64", + Platform: ocispec.Platform{ + Architecture: "amd64", + OS: "linux", + }, }, expectedQueryParams: url.Values{ "fromSrc": {"image_source"}, diff --git a/client/image_pull.go b/client/image_pull.go index f111adc102..b195762df1 100644 --- a/client/image_pull.go +++ b/client/image_pull.go @@ -5,7 +5,6 @@ import ( "io" "iter" "net/url" - "strings" cerrdefs "github.com/containerd/errdefs" "github.com/distribution/reference" @@ -44,10 +43,13 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePu if !options.All { query.Set("tag", getAPITagFromNamedRef(ref)) } - if options.Platform != "" { - query.Set("platform", strings.ToLower(options.Platform)) + if len(options.Platforms) > 0 { + if len(options.Platforms) > 1 { + // TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it. + return nil, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported") + } + query.Set("platform", formatPlatform(options.Platforms[0])) } - resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth)) if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { resp, err = cli.tryImageCreate(ctx, query, options.PrivilegeFunc) diff --git a/client/image_pull_opts.go b/client/image_pull_opts.go index 3f1042a888..1b78185dda 100644 --- a/client/image_pull_opts.go +++ b/client/image_pull_opts.go @@ -2,6 +2,8 @@ package client import ( "context" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImagePullOptions holds information to pull images. @@ -16,5 +18,8 @@ type ImagePullOptions struct { // // For details, refer to [github.com/moby/moby/api/types/registry.RequestAuthConfig]. PrivilegeFunc func(context.Context) (string, error) - Platform string + + // Platforms selects the platforms to pull. Multiple platforms can be + // specified if the image ia a multi-platform image. + Platforms []ocispec.Platform } diff --git a/integration/build/build_test.go b/integration/build/build_test.go index 7539471588..b538b6b851 100644 --- a/integration/build/build_test.go +++ b/integration/build/build_test.go @@ -22,6 +22,7 @@ import ( "github.com/moby/moby/client/pkg/jsonmessage" "github.com/moby/moby/v2/internal/testutil" "github.com/moby/moby/v2/internal/testutil/fakecontext" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/skip" @@ -662,7 +663,7 @@ func TestBuildPlatformInvalid(t *testing.T) { _, err = testEnv.APIClient().ImageBuild(ctx, buf, client.ImageBuildOptions{ Remove: true, ForceRemove: true, - Platform: "foobar", + Platforms: []ocispec.Platform{{OS: "foobar"}}, }) assert.Check(t, is.ErrorContains(err, "unknown operating system or architecture")) diff --git a/integration/image/history_test.go b/integration/image/history_test.go index d65b165a3e..e78b5782e5 100644 --- a/integration/image/history_test.go +++ b/integration/image/history_test.go @@ -5,7 +5,6 @@ import ( "io" "testing" - "github.com/containerd/platforms" buildtypes "github.com/moby/moby/api/types/build" "github.com/moby/moby/client" build "github.com/moby/moby/v2/integration/internal/build" @@ -19,13 +18,13 @@ import ( func TestAPIImagesHistory(t *testing.T) { ctx := setupTest(t) - client := testEnv.APIClient() + apiClient := testEnv.APIClient() dockerfile := "FROM busybox\nENV FOO bar" - imgID := build.Do(ctx, t, client, fakecontext.New(t, t.TempDir(), fakecontext.WithDockerfile(dockerfile))) + imgID := build.Do(ctx, t, apiClient, fakecontext.New(t, t.TempDir(), fakecontext.WithDockerfile(dockerfile))) - res, err := client.ImageHistory(ctx, imgID) + res, err := apiClient.ImageHistory(ctx, imgID) assert.NilError(t, err) assert.Assert(t, len(res.Items) != 0) @@ -69,9 +68,9 @@ func TestAPIImageHistoryCrossPlatform(t *testing.T) { // Build the image for a non-native platform resp, err := apiClient.ImageBuild(ctx, buildCtx.AsTarReader(t), client.ImageBuildOptions{ - Version: buildtypes.BuilderBuildKit, - Tags: []string{"cross-platform-test"}, - Platform: platforms.FormatAll(nonNativePlatform), + Version: buildtypes.BuilderBuildKit, + Tags: []string{"cross-platform-test"}, + Platforms: []ocispec.Platform{nonNativePlatform}, }) assert.NilError(t, err) defer resp.Body.Close() @@ -128,7 +127,9 @@ func TestAPIImageHistoryCrossPlatform(t *testing.T) { } func pullImageForPlatform(t *testing.T, ctx context.Context, apiClient client.APIClient, ref string, platform ocispec.Platform) { - pullResp, err := apiClient.ImagePull(ctx, ref, client.ImagePullOptions{Platform: platforms.FormatAll(platform)}) + pullResp, err := apiClient.ImagePull(ctx, ref, client.ImagePullOptions{ + Platforms: []ocispec.Platform{platform}, + }) assert.NilError(t, err) _, _ = io.Copy(io.Discard, pullResp) diff --git a/integration/image/import_test.go b/integration/image/import_test.go index fed7ace5d1..babcb89ae6 100644 --- a/integration/image/import_test.go +++ b/integration/image/import_test.go @@ -14,6 +14,7 @@ import ( "github.com/moby/moby/client" "github.com/moby/moby/v2/internal/testutil" "github.com/moby/moby/v2/internal/testutil/daemon" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/skip" @@ -70,33 +71,30 @@ func TestImportWithCustomPlatform(t *testing.T) { tests := []struct { name string - platform string - expected platforms.Platform + platform ocispec.Platform + expected ocispec.Platform }{ { - platform: "", - expected: platforms.Platform{ + expected: ocispec.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, // this may fail on armhf due to normalization? }, }, { - platform: runtime.GOOS, - expected: platforms.Platform{ + platform: ocispec.Platform{ + OS: runtime.GOOS, + }, + expected: ocispec.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, // this may fail on armhf due to normalization? }, }, { - platform: strings.ToUpper(runtime.GOOS), - expected: platforms.Platform{ + platform: ocispec.Platform{ OS: runtime.GOOS, - Architecture: runtime.GOARCH, // this may fail on armhf due to normalization? + Architecture: "sparc64", }, - }, - { - platform: runtime.GOOS + "/sparc64", - expected: platforms.Platform{ + expected: ocispec.Platform{ OS: runtime.GOOS, Architecture: "sparc64", }, @@ -104,7 +102,7 @@ func TestImportWithCustomPlatform(t *testing.T) { } for i, tc := range tests { - t.Run(tc.platform, func(t *testing.T) { + t.Run(platforms.Format(tc.platform), func(t *testing.T) { ctx := testutil.StartSpan(ctx, t) reference := "import-with-platform:tc-" + strconv.Itoa(i) @@ -140,36 +138,45 @@ func TestImportWithCustomPlatformReject(t *testing.T) { tests := []struct { name string - platform string + platform ocispec.Platform expectedErr string }{ { - platform: " ", + name: "whitespace-only platform", + platform: ocispec.Platform{ + OS: " ", + }, expectedErr: "is an invalid OS component", }, { - platform: "/", - expectedErr: "is an invalid OS component", - }, - { - platform: "macos", + name: "valid, but unsupported os", + platform: ocispec.Platform{ + OS: "macos", + }, expectedErr: "operating system is not supported", }, { - platform: "macos/arm64", + name: "valid, but unsupported os/arch", + platform: ocispec.Platform{ + OS: "macos", + Architecture: "arm64", + }, expectedErr: "operating system is not supported", }, { + name: "valid, but unsupported os", // TODO: platforms.Normalize() only validates os or arch if a single component is passed, // but ignores unknown os/arch in other cases. See: // https://github.com/containerd/containerd/blob/7d4891783aac5adf6cd83f657852574a71875631/platforms/platforms.go#L183-L209 - platform: "nintendo64", + platform: ocispec.Platform{ + OS: "nintendo64", + }, expectedErr: "unknown operating system or architecture", }, } for i, tc := range tests { - t.Run(tc.platform, func(t *testing.T) { + t.Run(tc.name, func(t *testing.T) { ctx := testutil.StartSpan(ctx, t) reference := "import-with-platform:tc-" + strconv.Itoa(i) _, err = apiClient.ImageImport(ctx, diff --git a/integration/image/pull_test.go b/integration/image/pull_test.go index 30333c3bf4..562ca36b25 100644 --- a/integration/image/pull_test.go +++ b/integration/image/pull_test.go @@ -33,7 +33,9 @@ func TestImagePullPlatformInvalid(t *testing.T) { apiClient := testEnv.APIClient() - _, err := apiClient.ImagePull(ctx, "docker.io/library/hello-world:latest", client.ImagePullOptions{Platform: "foobar"}) + _, err := apiClient.ImagePull(ctx, "docker.io/library/hello-world:latest", client.ImagePullOptions{ + Platforms: []ocispec.Platform{{OS: "foobar"}}, + }) assert.Assert(t, err != nil) assert.Check(t, is.ErrorContains(err, "unknown operating system or architecture")) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) diff --git a/integration/image/save_test.go b/integration/image/save_test.go index e386b72341..0bbd8e4ce8 100644 --- a/integration/image/save_test.go +++ b/integration/image/save_test.go @@ -219,7 +219,7 @@ func TestSaveAndLoadPlatform(t *testing.T) { type testCase struct { testName string containerdStoreOnly bool - pullPlatforms []string + pullPlatforms []ocispec.Platform savePlatforms []ocispec.Platform loadPlatforms []ocispec.Platform expectedSavedPlatforms []ocispec.Platform @@ -230,9 +230,13 @@ func TestSaveAndLoadPlatform(t *testing.T) { { testName: "With no platforms specified", containerdStoreOnly: true, - pullPlatforms: []string{"linux/amd64", "linux/riscv64", "linux/arm64/v8"}, - savePlatforms: nil, - loadPlatforms: nil, + pullPlatforms: []ocispec.Platform{ + {OS: "linux", Architecture: "amd64"}, + {OS: "linux", Architecture: "riscv64"}, + {OS: "linux", Architecture: "arm64", Variant: "v8"}, + }, + savePlatforms: nil, + loadPlatforms: nil, expectedSavedPlatforms: []ocispec.Platform{ {OS: "linux", Architecture: "amd64"}, {OS: "linux", Architecture: "riscv64"}, @@ -246,16 +250,20 @@ func TestSaveAndLoadPlatform(t *testing.T) { }, { testName: "With single pulled platform", - pullPlatforms: []string{"linux/amd64"}, + pullPlatforms: []ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, savePlatforms: []ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, loadPlatforms: []ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, expectedSavedPlatforms: []ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, expectedLoadedPlatforms: []ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, }, { - testName: "With single platform save and load", - containerdStoreOnly: true, - pullPlatforms: []string{"linux/amd64", "linux/riscv64", "linux/arm64/v8"}, + testName: "With single platform save and load", + containerdStoreOnly: true, + pullPlatforms: []ocispec.Platform{ + {OS: "linux", Architecture: "amd64"}, + {OS: "linux", Architecture: "riscv64"}, + {OS: "linux", Architecture: "arm64", Variant: "v8"}, + }, savePlatforms: []ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, loadPlatforms: []ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, expectedSavedPlatforms: []ocispec.Platform{{OS: "linux", Architecture: "amd64"}}, @@ -264,7 +272,11 @@ func TestSaveAndLoadPlatform(t *testing.T) { { testName: "With multiple platforms save and load", containerdStoreOnly: true, - pullPlatforms: []string{"linux/amd64", "linux/riscv64", "linux/arm64/v8"}, + pullPlatforms: []ocispec.Platform{ + {OS: "linux", Architecture: "amd64"}, + {OS: "linux", Architecture: "riscv64"}, + {OS: "linux", Architecture: "arm64", Variant: "v8"}, + }, savePlatforms: []ocispec.Platform{ {OS: "linux", Architecture: "arm64", Variant: "v8"}, {OS: "linux", Architecture: "riscv64"}, @@ -285,7 +297,11 @@ func TestSaveAndLoadPlatform(t *testing.T) { { testName: "With mixed platform save and load", containerdStoreOnly: true, - pullPlatforms: []string{"linux/amd64", "linux/riscv64", "linux/arm64/v8"}, + pullPlatforms: []ocispec.Platform{ + {OS: "linux", Architecture: "amd64"}, + {OS: "linux", Architecture: "riscv64"}, + {OS: "linux", Architecture: "arm64", Variant: "v8"}, + }, savePlatforms: []ocispec.Platform{ {OS: "linux", Architecture: "arm64", Variant: "v8"}, {OS: "linux", Architecture: "riscv64"}, @@ -310,7 +326,7 @@ func TestSaveAndLoadPlatform(t *testing.T) { t.Run(tc.testName, func(t *testing.T) { // pull the image for _, p := range tc.pullPlatforms { - resp, err := apiClient.ImagePull(ctx, repoName, client.ImagePullOptions{Platform: p}) + resp, err := apiClient.ImagePull(ctx, repoName, client.ImagePullOptions{Platforms: []ocispec.Platform{p}}) assert.NilError(t, err) _, err = io.ReadAll(resp) resp.Close() @@ -348,10 +364,10 @@ func TestSaveAndLoadPlatform(t *testing.T) { // pull the image again (start fresh) for _, p := range tc.pullPlatforms { - resp, err := apiClient.ImagePull(ctx, repoName, client.ImagePullOptions{Platform: p}) + pullRes, err := apiClient.ImagePull(ctx, repoName, client.ImagePullOptions{Platforms: []ocispec.Platform{p}}) assert.NilError(t, err) - _, err = io.ReadAll(resp) - resp.Close() + _, err = io.ReadAll(pullRes) + _ = pullRes.Close() assert.NilError(t, err) } diff --git a/vendor/github.com/moby/moby/client/image_build.go b/vendor/github.com/moby/moby/client/image_build.go index 67c7e160a6..5062ec5de1 100644 --- a/vendor/github.com/moby/moby/client/image_build.go +++ b/vendor/github.com/moby/moby/client/image_build.go @@ -8,8 +8,8 @@ import ( "net/http" "net/url" "strconv" - "strings" + cerrdefs "github.com/containerd/errdefs" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/network" ) @@ -154,8 +154,12 @@ func (cli *Client) imageBuildOptionsToQuery(_ context.Context, options ImageBuil if options.SessionID != "" { query.Set("session", options.SessionID) } - if options.Platform != "" { - query.Set("platform", strings.ToLower(options.Platform)) + if len(options.Platforms) > 0 { + if len(options.Platforms) > 1 { + // TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it. + return query, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported") + } + query.Set("platform", formatPlatform(options.Platforms[0])) } if options.BuildID != "" { query.Set("buildid", options.BuildID) diff --git a/vendor/github.com/moby/moby/client/image_build_opts.go b/vendor/github.com/moby/moby/client/image_build_opts.go index effb259e36..f65ad0f2bf 100644 --- a/vendor/github.com/moby/moby/client/image_build_opts.go +++ b/vendor/github.com/moby/moby/client/image_build_opts.go @@ -6,6 +6,7 @@ import ( "github.com/moby/moby/api/types/build" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/registry" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImageBuildOptions holds the information @@ -50,7 +51,9 @@ type ImageBuildOptions struct { ExtraHosts []string // List of extra hosts Target string SessionID string - Platform string + // Platforms selects the platforms to build the image for. Multiple platforms + // can be provided if the daemon supports multi-platform builds. + Platforms []ocispec.Platform // Version specifies the version of the underlying builder to use Version build.BuilderVersion // BuildID is an optional identifier that can be passed together with the diff --git a/vendor/github.com/moby/moby/client/image_create.go b/vendor/github.com/moby/moby/client/image_create.go index 25e8a57ecc..4d429570c1 100644 --- a/vendor/github.com/moby/moby/client/image_create.go +++ b/vendor/github.com/moby/moby/client/image_create.go @@ -4,8 +4,8 @@ import ( "context" "net/http" "net/url" - "strings" + cerrdefs "github.com/containerd/errdefs" "github.com/distribution/reference" "github.com/moby/moby/api/types/registry" ) @@ -21,8 +21,12 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti query := url.Values{} query.Set("fromImage", ref.Name()) query.Set("tag", getAPITagFromNamedRef(ref)) - if options.Platform != "" { - query.Set("platform", strings.ToLower(options.Platform)) + if len(options.Platforms) > 0 { + if len(options.Platforms) > 1 { + // TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it. + return ImageCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported") + } + query.Set("platform", formatPlatform(options.Platforms[0])) } resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth)) if err != nil { diff --git a/vendor/github.com/moby/moby/client/image_create_opts.go b/vendor/github.com/moby/moby/client/image_create_opts.go index 301cf0bb81..eb4c486c77 100644 --- a/vendor/github.com/moby/moby/client/image_create_opts.go +++ b/vendor/github.com/moby/moby/client/image_create_opts.go @@ -1,11 +1,18 @@ package client -import "io" +import ( + "io" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) // ImageCreateOptions holds information to create images. type ImageCreateOptions struct { RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry. - Platform string // Platform is the target platform of the image if it needs to be pulled from the registry. + // Platforms specifies the platforms to platform of the image if it needs + // to be pulled from the registry. Multiple platforms can be provided + // if the daemon supports multi-platform pulls. + Platforms []ocispec.Platform } // ImageCreateResult holds the response body returned by the daemon for image create. diff --git a/vendor/github.com/moby/moby/client/image_import.go b/vendor/github.com/moby/moby/client/image_import.go index b8f1ccb500..3718ab50cc 100644 --- a/vendor/github.com/moby/moby/client/image_import.go +++ b/vendor/github.com/moby/moby/client/image_import.go @@ -3,7 +3,6 @@ package client import ( "context" "net/url" - "strings" "github.com/distribution/reference" ) @@ -31,8 +30,9 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re if options.Message != "" { query.Set("message", options.Message) } - if options.Platform != "" { - query.Set("platform", strings.ToLower(options.Platform)) + if p := formatPlatform(options.Platform); p != "unknown" { + // TODO(thaJeztah): would we ever support mutiple platforms here? (would require multiple rootfs tars as well?) + query.Set("platform", p) } for _, change := range options.Changes { query.Add("changes", change) diff --git a/vendor/github.com/moby/moby/client/image_import_opts.go b/vendor/github.com/moby/moby/client/image_import_opts.go index 2ba5b593ec..513548cc34 100644 --- a/vendor/github.com/moby/moby/client/image_import_opts.go +++ b/vendor/github.com/moby/moby/client/image_import_opts.go @@ -2,6 +2,8 @@ package client import ( "io" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImageImportSource holds source information for ImageImport @@ -12,10 +14,10 @@ type ImageImportSource struct { // ImageImportOptions holds information to import images from the client host. type ImageImportOptions struct { - Tag string // Tag is the name to tag this image with. This attribute is deprecated. - Message string // Message is the message to tag the image with - Changes []string // Changes are the raw changes to apply to this image - Platform string // Platform is the target platform of the image + Tag string // Tag is the name to tag this image with. This attribute is deprecated. + Message string // Message is the message to tag the image with + Changes []string // Changes are the raw changes to apply to this image + Platform ocispec.Platform // Platform is the target platform of the image } // ImageImportResult holds the response body returned by the daemon for image import. diff --git a/vendor/github.com/moby/moby/client/image_pull.go b/vendor/github.com/moby/moby/client/image_pull.go index f111adc102..b195762df1 100644 --- a/vendor/github.com/moby/moby/client/image_pull.go +++ b/vendor/github.com/moby/moby/client/image_pull.go @@ -5,7 +5,6 @@ import ( "io" "iter" "net/url" - "strings" cerrdefs "github.com/containerd/errdefs" "github.com/distribution/reference" @@ -44,10 +43,13 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePu if !options.All { query.Set("tag", getAPITagFromNamedRef(ref)) } - if options.Platform != "" { - query.Set("platform", strings.ToLower(options.Platform)) + if len(options.Platforms) > 0 { + if len(options.Platforms) > 1 { + // TODO(thaJeztah): update API spec and add equivalent check on the daemon. We need this still for older daemons, which would ignore it. + return nil, cerrdefs.ErrInvalidArgument.WithMessage("specifying multiple platforms is not yet supported") + } + query.Set("platform", formatPlatform(options.Platforms[0])) } - resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth)) if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { resp, err = cli.tryImageCreate(ctx, query, options.PrivilegeFunc) diff --git a/vendor/github.com/moby/moby/client/image_pull_opts.go b/vendor/github.com/moby/moby/client/image_pull_opts.go index 3f1042a888..1b78185dda 100644 --- a/vendor/github.com/moby/moby/client/image_pull_opts.go +++ b/vendor/github.com/moby/moby/client/image_pull_opts.go @@ -2,6 +2,8 @@ package client import ( "context" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImagePullOptions holds information to pull images. @@ -16,5 +18,8 @@ type ImagePullOptions struct { // // For details, refer to [github.com/moby/moby/api/types/registry.RequestAuthConfig]. PrivilegeFunc func(context.Context) (string, error) - Platform string + + // Platforms selects the platforms to pull. Multiple platforms can be + // specified if the image ia a multi-platform image. + Platforms []ocispec.Platform }