Browse code

Accept platform spec on container create

This enables image lookup when creating a container to fail when the
reference exists but it is for the wrong platform. This prevents trying
to run an image for the wrong platform, as can be the case with, for
example binfmt_misc+qemu.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2020/03/20 05:54:48
Showing 28 changed files
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"strconv"
10 10
 	"syscall"
11 11
 
12
+	"github.com/containerd/containerd/platforms"
12 13
 	"github.com/docker/docker/api/server/httputils"
13 14
 	"github.com/docker/docker/api/types"
14 15
 	"github.com/docker/docker/api/types/backend"
... ...
@@ -19,6 +20,7 @@ import (
19 19
 	"github.com/docker/docker/errdefs"
20 20
 	"github.com/docker/docker/pkg/ioutils"
21 21
 	"github.com/docker/docker/pkg/signal"
22
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
22 23
 	"github.com/pkg/errors"
23 24
 	"github.com/sirupsen/logrus"
24 25
 	"golang.org/x/net/websocket"
... ...
@@ -497,6 +499,28 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
497 497
 		}
498 498
 	}
499 499
 
500
+	var platform *specs.Platform
501
+	if versions.GreaterThanOrEqualTo(version, "1.41") {
502
+		if v := r.Form.Get("platform"); v != "" {
503
+			p, err := platforms.Parse(v)
504
+			if err != nil {
505
+				return errdefs.InvalidParameter(err)
506
+			}
507
+			platform = &p
508
+		}
509
+		defaultPlatform := platforms.DefaultSpec()
510
+		if platform == nil {
511
+			platform = &defaultPlatform
512
+		}
513
+		if platform.OS == "" {
514
+			platform.OS = defaultPlatform.OS
515
+		}
516
+		if platform.Architecture == "" {
517
+			platform.Architecture = defaultPlatform.Architecture
518
+			platform.Variant = defaultPlatform.Variant
519
+		}
520
+	}
521
+
500 522
 	if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
501 523
 		// Don't set a limit if either no limit was specified, or "unlimited" was
502 524
 		// explicitly set.
... ...
@@ -511,6 +535,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
511 511
 		HostConfig:       hostConfig,
512 512
 		NetworkingConfig: networkingConfig,
513 513
 		AdjustCPUShares:  adjustCPUShares,
514
+		Platform:         platform,
514 515
 	})
515 516
 	if err != nil {
516 517
 		return err
... ...
@@ -3,6 +3,7 @@ package types // import "github.com/docker/docker/api/types"
3 3
 import (
4 4
 	"github.com/docker/docker/api/types/container"
5 5
 	"github.com/docker/docker/api/types/network"
6
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
6 7
 )
7 8
 
8 9
 // configs holds structs used for internal communication between the
... ...
@@ -15,6 +16,7 @@ type ContainerCreateConfig struct {
15 15
 	Config           *container.Config
16 16
 	HostConfig       *container.HostConfig
17 17
 	NetworkingConfig *network.NetworkingConfig
18
+	Platform         *specs.Platform
18 19
 	AdjustCPUShares  bool
19 20
 }
20 21
 
... ...
@@ -5,20 +5,23 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
+	"github.com/containerd/containerd/platforms"
8 9
 	"github.com/docker/docker/api/types/container"
9 10
 	"github.com/docker/docker/api/types/network"
10 11
 	"github.com/docker/docker/api/types/versions"
12
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
11 13
 )
12 14
 
13 15
 type configWrapper struct {
14 16
 	*container.Config
15 17
 	HostConfig       *container.HostConfig
16 18
 	NetworkingConfig *network.NetworkingConfig
19
+	Platform         *specs.Platform
17 20
 }
18 21
 
19 22
 // ContainerCreate creates a new container based in the given configuration.
20 23
 // It can be associated with a name, but it's not mandatory.
21
-func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
24
+func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) {
22 25
 	var response container.ContainerCreateCreatedBody
23 26
 
24 27
 	if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
... ...
@@ -30,7 +33,15 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
30 30
 		hostConfig.AutoRemove = false
31 31
 	}
32 32
 
33
+	if err := cli.NewVersionError("1.41", "specify container image platform"); platform != nil && err != nil {
34
+		return response, err
35
+	}
36
+
33 37
 	query := url.Values{}
38
+	if platform != nil {
39
+		query.Set("platform", platforms.Format(*platform))
40
+	}
41
+
34 42
 	if containerName != "" {
35 43
 		query.Set("name", containerName)
36 44
 	}
... ...
@@ -18,7 +18,7 @@ func TestContainerCreateError(t *testing.T) {
18 18
 	client := &Client{
19 19
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
20 20
 	}
21
-	_, err := client.ContainerCreate(context.Background(), nil, nil, nil, "nothing")
21
+	_, err := client.ContainerCreate(context.Background(), nil, nil, nil, nil, "nothing")
22 22
 	if !errdefs.IsSystem(err) {
23 23
 		t.Fatalf("expected a Server Error while testing StatusInternalServerError, got %T", err)
24 24
 	}
... ...
@@ -27,7 +27,7 @@ func TestContainerCreateError(t *testing.T) {
27 27
 	client = &Client{
28 28
 		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
29 29
 	}
30
-	_, err = client.ContainerCreate(context.Background(), nil, nil, nil, "nothing")
30
+	_, err = client.ContainerCreate(context.Background(), nil, nil, nil, nil, "nothing")
31 31
 	if err == nil || !IsErrNotFound(err) {
32 32
 		t.Fatalf("expected a Server Error while testing StatusNotFound, got %T", err)
33 33
 	}
... ...
@@ -37,7 +37,7 @@ func TestContainerCreateImageNotFound(t *testing.T) {
37 37
 	client := &Client{
38 38
 		client: newMockClient(errorMock(http.StatusNotFound, "No such image")),
39 39
 	}
40
-	_, err := client.ContainerCreate(context.Background(), &container.Config{Image: "unknown_image"}, nil, nil, "unknown")
40
+	_, err := client.ContainerCreate(context.Background(), &container.Config{Image: "unknown_image"}, nil, nil, nil, "unknown")
41 41
 	if err == nil || !IsErrNotFound(err) {
42 42
 		t.Fatalf("expected an imageNotFound error, got %v, %T", err, err)
43 43
 	}
... ...
@@ -67,7 +67,7 @@ func TestContainerCreateWithName(t *testing.T) {
67 67
 		}),
68 68
 	}
69 69
 
70
-	r, err := client.ContainerCreate(context.Background(), nil, nil, nil, "container_name")
70
+	r, err := client.ContainerCreate(context.Background(), nil, nil, nil, nil, "container_name")
71 71
 	if err != nil {
72 72
 		t.Fatal(err)
73 73
 	}
... ...
@@ -106,14 +106,14 @@ func TestContainerCreateAutoRemove(t *testing.T) {
106 106
 		client:  newMockClient(autoRemoveValidator(false)),
107 107
 		version: "1.24",
108 108
 	}
109
-	if _, err := client.ContainerCreate(context.Background(), nil, &container.HostConfig{AutoRemove: true}, nil, ""); err != nil {
109
+	if _, err := client.ContainerCreate(context.Background(), nil, &container.HostConfig{AutoRemove: true}, nil, nil, ""); err != nil {
110 110
 		t.Fatal(err)
111 111
 	}
112 112
 	client = &Client{
113 113
 		client:  newMockClient(autoRemoveValidator(true)),
114 114
 		version: "1.25",
115 115
 	}
116
-	if _, err := client.ContainerCreate(context.Background(), nil, &container.HostConfig{AutoRemove: true}, nil, ""); err != nil {
116
+	if _, err := client.ContainerCreate(context.Background(), nil, &container.HostConfig{AutoRemove: true}, nil, nil, ""); err != nil {
117 117
 		t.Fatal(err)
118 118
 	}
119 119
 }
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"github.com/docker/docker/api/types/registry"
17 17
 	"github.com/docker/docker/api/types/swarm"
18 18
 	volumetypes "github.com/docker/docker/api/types/volume"
19
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
19 20
 )
20 21
 
21 22
 // CommonAPIClient is the common methods between stable and experimental versions of APIClient.
... ...
@@ -47,7 +48,7 @@ type CommonAPIClient interface {
47 47
 type ContainerAPIClient interface {
48 48
 	ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error)
49 49
 	ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.IDResponse, error)
50
-	ContainerCreate(ctx context.Context, config *containertypes.Config, hostConfig *containertypes.HostConfig, networkingConfig *networktypes.NetworkingConfig, containerName string) (containertypes.ContainerCreateCreatedBody, error)
50
+	ContainerCreate(ctx context.Context, config *containertypes.Config, hostConfig *containertypes.HostConfig, networkingConfig *networktypes.NetworkingConfig, platform *specs.Platform, containerName string) (containertypes.ContainerCreateCreatedBody, error)
51 51
 	ContainerDiff(ctx context.Context, container string) ([]containertypes.ContainerChangeResponseItem, error)
52 52
 	ContainerExecAttach(ctx context.Context, execID string, config types.ExecStartCheck) (types.HijackedResponse, error)
53 53
 	ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error)
... ...
@@ -60,7 +60,7 @@ func (daemon *Daemon) containerCreate(opts createOpts) (containertypes.Container
60 60
 
61 61
 	os := runtime.GOOS
62 62
 	if opts.params.Config.Image != "" {
63
-		img, err := daemon.imageService.GetImage(opts.params.Config.Image)
63
+		img, err := daemon.imageService.GetImage(opts.params.Config.Image, opts.params.Platform)
64 64
 		if err == nil {
65 65
 			os = img.OS
66 66
 		}
... ...
@@ -114,7 +114,7 @@ func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr
114 114
 
115 115
 	os := runtime.GOOS
116 116
 	if opts.params.Config.Image != "" {
117
-		img, err = daemon.imageService.GetImage(opts.params.Config.Image)
117
+		img, err = daemon.imageService.GetImage(opts.params.Config.Image, opts.params.Platform)
118 118
 		if err != nil {
119 119
 			return nil, err
120 120
 		}
... ...
@@ -15,7 +15,7 @@ func (i *ImageService) MakeImageCache(sourceRefs []string) builder.ImageCache {
15 15
 	cache := cache.New(i.imageStore)
16 16
 
17 17
 	for _, ref := range sourceRefs {
18
-		img, err := i.GetImage(ref)
18
+		img, err := i.GetImage(ref, nil)
19 19
 		if err != nil {
20 20
 			logrus.Warnf("Could not look up %s for cache resolution, skipping: %+v", ref, err)
21 21
 			continue
... ...
@@ -3,9 +3,12 @@ package images // import "github.com/docker/docker/daemon/images"
3 3
 import (
4 4
 	"fmt"
5 5
 
6
+	"github.com/pkg/errors"
7
+
6 8
 	"github.com/docker/distribution/reference"
7 9
 	"github.com/docker/docker/errdefs"
8 10
 	"github.com/docker/docker/image"
11
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
9 12
 )
10 13
 
11 14
 // ErrImageDoesNotExist is error returned when no image can be found for a reference.
... ...
@@ -25,7 +28,39 @@ func (e ErrImageDoesNotExist) Error() string {
25 25
 func (e ErrImageDoesNotExist) NotFound() {}
26 26
 
27 27
 // GetImage returns an image corresponding to the image referred to by refOrID.
28
-func (i *ImageService) GetImage(refOrID string) (*image.Image, error) {
28
+func (i *ImageService) GetImage(refOrID string, platform *specs.Platform) (retImg *image.Image, retErr error) {
29
+	defer func() {
30
+		if retErr != nil || retImg == nil || platform == nil {
31
+			return
32
+		}
33
+
34
+		// This allows us to tell clients that we don't have the image they asked for
35
+		// Where this gets hairy is the image store does not currently support multi-arch images, e.g.:
36
+		//   An image `foo` may have a multi-arch manifest, but the image store only fetches the image for a specific platform
37
+		//   The image store does not store the manifest list and image tags are assigned to architecture specific images.
38
+		//   So we can have a `foo` image that is amd64 but the user requested armv7. If the user looks at the list of images.
39
+		//   This may be confusing.
40
+		//   The alternative to this is to return a errdefs.Conflict error with a helpful message, but clients will not be
41
+		//   able to automatically tell what causes the conflict.
42
+		if retImg.OS != platform.OS {
43
+			retErr = errdefs.NotFound(errors.Errorf("image with reference %s was found but does not match the specified OS platform: wanted: %s, actual: %s", refOrID, platform.OS, retImg.OS))
44
+			retImg = nil
45
+			return
46
+		}
47
+		if retImg.Architecture != platform.Architecture {
48
+			retErr = errdefs.NotFound(errors.Errorf("image with reference %s was found but does not match the specified platform cpu architecture: wanted: %s, actual: %s", refOrID, platform.Architecture, retImg.Architecture))
49
+			retImg = nil
50
+			return
51
+		}
52
+
53
+		// Only validate variant if retImg has a variant set.
54
+		// The image variant may not be set since it's a newer field.
55
+		if platform.Variant != "" && retImg.Variant != "" && retImg.Variant != platform.Variant {
56
+			retErr = errdefs.NotFound(errors.Errorf("image with reference %s was found but does not match the specified platform cpu architecture variant: wanted: %s, actual: %s", refOrID, platform.Variant, retImg.Variant))
57
+			retImg = nil
58
+			return
59
+		}
60
+	}()
29 61
 	ref, err := reference.ParseAnyReference(refOrID)
30 62
 	if err != nil {
31 63
 		return nil, errdefs.InvalidParameter(err)
... ...
@@ -161,7 +161,7 @@ func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConf
161 161
 	if err := i.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil {
162 162
 		return nil, err
163 163
 	}
164
-	return i.GetImage(name)
164
+	return i.GetImage(name, platform)
165 165
 }
166 166
 
167 167
 // GetImageAndReleasableLayer returns an image and releaseable layer for a reference or ID.
... ...
@@ -184,7 +184,7 @@ func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID s
184 184
 	}
185 185
 
186 186
 	if opts.PullOption != backend.PullOptionForcePull {
187
-		image, err := i.GetImage(refOrID)
187
+		image, err := i.GetImage(refOrID, opts.Platform)
188 188
 		if err != nil && opts.PullOption == backend.PullOptionNoPull {
189 189
 			return nil, nil, err
190 190
 		}
... ...
@@ -64,7 +64,7 @@ func (i *ImageService) ImageDelete(imageRef string, force, prune bool) ([]types.
64 64
 	start := time.Now()
65 65
 	records := []types.ImageDeleteResponseItem{}
66 66
 
67
-	img, err := i.GetImage(imageRef)
67
+	img, err := i.GetImage(imageRef, nil)
68 68
 	if err != nil {
69 69
 		return nil, err
70 70
 	}
... ...
@@ -11,7 +11,7 @@ func (i *ImageService) LogImageEvent(imageID, refName, action string) {
11 11
 
12 12
 // LogImageEventWithAttributes generates an event related to an image with specific given attributes.
13 13
 func (i *ImageService) LogImageEventWithAttributes(imageID, refName, action string, attributes map[string]string) {
14
-	img, err := i.GetImage(imageID)
14
+	img, err := i.GetImage(imageID, nil)
15 15
 	if err == nil && img.Config != nil {
16 16
 		// image has not been removed yet.
17 17
 		// it could be missing if the event is `delete`.
... ...
@@ -14,7 +14,7 @@ import (
14 14
 // name by walking the image lineage.
15 15
 func (i *ImageService) ImageHistory(name string) ([]*image.HistoryResponseItem, error) {
16 16
 	start := time.Now()
17
-	img, err := i.GetImage(name)
17
+	img, err := i.GetImage(name, nil)
18 18
 	if err != nil {
19 19
 		return nil, err
20 20
 	}
... ...
@@ -77,7 +77,7 @@ func (i *ImageService) ImageHistory(name string) ([]*image.HistoryResponseItem,
77 77
 		if id == "" {
78 78
 			break
79 79
 		}
80
-		histImg, err = i.GetImage(id.String())
80
+		histImg, err = i.GetImage(id.String(), nil)
81 81
 		if err != nil {
82 82
 			break
83 83
 		}
... ...
@@ -14,7 +14,7 @@ import (
14 14
 // LookupImage looks up an image by name and returns it as an ImageInspect
15 15
 // structure.
16 16
 func (i *ImageService) LookupImage(name string) (*types.ImageInspect, error) {
17
-	img, err := i.GetImage(name)
17
+	img, err := i.GetImage(name, nil)
18 18
 	if err != nil {
19 19
 		return nil, errors.Wrapf(err, "no such image: %s", name)
20 20
 	}
... ...
@@ -8,7 +8,7 @@ import (
8 8
 // TagImage creates the tag specified by newTag, pointing to the image named
9 9
 // imageName (alternatively, imageName can also be an image ID).
10 10
 func (i *ImageService) TagImage(imageName, repository, tag string) (string, error) {
11
-	img, err := i.GetImage(imageName)
11
+	img, err := i.GetImage(imageName, nil)
12 12
 	if err != nil {
13 13
 		return "", err
14 14
 	}
... ...
@@ -69,7 +69,7 @@ func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttr
69 69
 
70 70
 	var beforeFilter, sinceFilter *image.Image
71 71
 	err = imageFilters.WalkValues("before", func(value string) error {
72
-		beforeFilter, err = i.GetImage(value)
72
+		beforeFilter, err = i.GetImage(value, nil)
73 73
 		return err
74 74
 	})
75 75
 	if err != nil {
... ...
@@ -77,7 +77,7 @@ func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttr
77 77
 	}
78 78
 
79 79
 	err = imageFilters.WalkValues("since", func(value string) error {
80
-		sinceFilter, err = i.GetImage(value)
80
+		sinceFilter, err = i.GetImage(value, nil)
81 81
 		return err
82 82
 	})
83 83
 	if err != nil {
... ...
@@ -317,7 +317,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
317 317
 	if psFilters.Contains("ancestor") {
318 318
 		ancestorFilter = true
319 319
 		psFilters.WalkValues("ancestor", func(ancestor string) error {
320
-			img, err := daemon.imageService.GetImage(ancestor)
320
+			img, err := daemon.imageService.GetImage(ancestor, nil)
321 321
 			if err != nil {
322 322
 				logrus.Warnf("Error while looking up for image %v", ancestor)
323 323
 				return nil
... ...
@@ -585,7 +585,7 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty
585 585
 	c := s.Container
586 586
 	image := s.Image // keep the original ref if still valid (hasn't changed)
587 587
 	if image != s.ImageID {
588
-		img, err := daemon.imageService.GetImage(image)
588
+		img, err := daemon.imageService.GetImage(image, nil)
589 589
 		if _, isDNE := err.(images.ErrImageDoesNotExist); err != nil && !isDNE {
590 590
 			return nil, err
591 591
 		}
... ...
@@ -29,7 +29,7 @@ const (
29 29
 
30 30
 func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
31 31
 
32
-	img, err := daemon.imageService.GetImage(string(c.ImageID))
32
+	img, err := daemon.imageService.GetImage(string(c.ImageID), nil)
33 33
 	if err != nil {
34 34
 		return nil, err
35 35
 	}
... ...
@@ -523,7 +523,7 @@ func (s *DockerSuite) TestContainerAPIBadPort(c *testing.T) {
523 523
 	assert.NilError(c, err)
524 524
 	defer cli.Close()
525 525
 
526
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
526
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
527 527
 	assert.ErrorContains(c, err, `invalid port specification: "aa80"`)
528 528
 }
529 529
 
... ...
@@ -537,7 +537,7 @@ func (s *DockerSuite) TestContainerAPICreate(c *testing.T) {
537 537
 	assert.NilError(c, err)
538 538
 	defer cli.Close()
539 539
 
540
-	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
540
+	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
541 541
 	assert.NilError(c, err)
542 542
 
543 543
 	out, _ := dockerCmd(c, "start", "-a", container.ID)
... ...
@@ -550,7 +550,7 @@ func (s *DockerSuite) TestContainerAPICreateEmptyConfig(c *testing.T) {
550 550
 	assert.NilError(c, err)
551 551
 	defer cli.Close()
552 552
 
553
-	_, err = cli.ContainerCreate(context.Background(), &containertypes.Config{}, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
553
+	_, err = cli.ContainerCreate(context.Background(), &containertypes.Config{}, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
554 554
 
555 555
 	expected := "No command specified"
556 556
 	assert.ErrorContains(c, err, expected)
... ...
@@ -574,7 +574,7 @@ func (s *DockerSuite) TestContainerAPICreateMultipleNetworksConfig(c *testing.T)
574 574
 	assert.NilError(c, err)
575 575
 	defer cli.Close()
576 576
 
577
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networkingConfig, "")
577
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networkingConfig, nil, "")
578 578
 	msg := err.Error()
579 579
 	// network name order in error message is not deterministic
580 580
 	assert.Assert(c, strings.Contains(msg, "Container cannot be connected to network endpoints"))
... ...
@@ -609,7 +609,7 @@ func UtilCreateNetworkMode(c *testing.T, networkMode containertypes.NetworkMode)
609 609
 	assert.NilError(c, err)
610 610
 	defer cli.Close()
611 611
 
612
-	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
612
+	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
613 613
 	assert.NilError(c, err)
614 614
 
615 615
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
... ...
@@ -636,7 +636,7 @@ func (s *DockerSuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) {
636 636
 	assert.NilError(c, err)
637 637
 	defer cli.Close()
638 638
 
639
-	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
639
+	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
640 640
 	assert.NilError(c, err)
641 641
 
642 642
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
... ...
@@ -948,7 +948,7 @@ func (s *DockerSuite) TestContainerAPIStart(c *testing.T) {
948 948
 	assert.NilError(c, err)
949 949
 	defer cli.Close()
950 950
 
951
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, name)
951
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, name)
952 952
 	assert.NilError(c, err)
953 953
 
954 954
 	err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
... ...
@@ -1272,7 +1272,7 @@ func (s *DockerSuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *t
1272 1272
 	assert.NilError(c, err)
1273 1273
 	defer cli.Close()
1274 1274
 
1275
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "echotest")
1275
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "echotest")
1276 1276
 	assert.NilError(c, err)
1277 1277
 	out, _ := dockerCmd(c, "start", "-a", "echotest")
1278 1278
 	assert.Equal(c, strings.TrimSpace(out), "hello world")
... ...
@@ -1299,7 +1299,7 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T)
1299 1299
 	assert.NilError(c, err)
1300 1300
 	defer cli.Close()
1301 1301
 
1302
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "echotest")
1302
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "echotest")
1303 1303
 	assert.NilError(c, err)
1304 1304
 	out, _ := dockerCmd(c, "start", "-a", "echotest")
1305 1305
 	assert.Equal(c, strings.TrimSpace(out), "hello world")
... ...
@@ -1342,7 +1342,7 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *tes
1342 1342
 	assert.NilError(c, err)
1343 1343
 	defer cli.Close()
1344 1344
 
1345
-	_, err = cli.ContainerCreate(context.Background(), &config2, &hostConfig, &networktypes.NetworkingConfig{}, "capaddtest1")
1345
+	_, err = cli.ContainerCreate(context.Background(), &config2, &hostConfig, &networktypes.NetworkingConfig{}, nil, "capaddtest1")
1346 1346
 	assert.NilError(c, err)
1347 1347
 }
1348 1348
 
... ...
@@ -1356,7 +1356,7 @@ func (s *DockerSuite) TestContainerAPICreateNoHostConfig118(c *testing.T) {
1356 1356
 	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18"))
1357 1357
 	assert.NilError(c, err)
1358 1358
 
1359
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
1359
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
1360 1360
 	assert.NilError(c, err)
1361 1361
 }
1362 1362
 
... ...
@@ -1407,7 +1407,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T
1407 1407
 	}
1408 1408
 	name := "wrong-cpuset-cpus"
1409 1409
 
1410
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig1, &networktypes.NetworkingConfig{}, name)
1410
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig1, &networktypes.NetworkingConfig{}, nil, name)
1411 1411
 	expected := "Invalid value 1-42,, for cpuset cpus"
1412 1412
 	assert.ErrorContains(c, err, expected)
1413 1413
 
... ...
@@ -1417,7 +1417,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T
1417 1417
 		},
1418 1418
 	}
1419 1419
 	name = "wrong-cpuset-mems"
1420
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig2, &networktypes.NetworkingConfig{}, name)
1420
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig2, &networktypes.NetworkingConfig{}, nil, name)
1421 1421
 	expected = "Invalid value 42-3,1-- for cpuset mems"
1422 1422
 	assert.ErrorContains(c, err, expected)
1423 1423
 }
... ...
@@ -1436,7 +1436,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *testing.T) {
1436 1436
 	assert.NilError(c, err)
1437 1437
 	defer cli.Close()
1438 1438
 
1439
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
1439
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
1440 1440
 	assert.ErrorContains(c, err, "SHM size can not be less than 0")
1441 1441
 }
1442 1442
 
... ...
@@ -1453,7 +1453,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testin
1453 1453
 	assert.NilError(c, err)
1454 1454
 	defer cli.Close()
1455 1455
 
1456
-	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
1456
+	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
1457 1457
 	assert.NilError(c, err)
1458 1458
 
1459 1459
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
... ...
@@ -1480,7 +1480,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) {
1480 1480
 	assert.NilError(c, err)
1481 1481
 	defer cli.Close()
1482 1482
 
1483
-	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
1483
+	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
1484 1484
 	assert.NilError(c, err)
1485 1485
 
1486 1486
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
... ...
@@ -1511,7 +1511,7 @@ func (s *DockerSuite) TestPostContainersCreateWithShmSize(c *testing.T) {
1511 1511
 	assert.NilError(c, err)
1512 1512
 	defer cli.Close()
1513 1513
 
1514
-	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
1514
+	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
1515 1515
 	assert.NilError(c, err)
1516 1516
 
1517 1517
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
... ...
@@ -1537,7 +1537,7 @@ func (s *DockerSuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(
1537 1537
 	assert.NilError(c, err)
1538 1538
 	defer cli.Close()
1539 1539
 
1540
-	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
1540
+	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
1541 1541
 	assert.NilError(c, err)
1542 1542
 
1543 1543
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
... ...
@@ -1568,7 +1568,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *tes
1568 1568
 	defer cli.Close()
1569 1569
 
1570 1570
 	name := "oomscoreadj-over"
1571
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, name)
1571
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, name)
1572 1572
 
1573 1573
 	expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
1574 1574
 	assert.ErrorContains(c, err, expected)
... ...
@@ -1578,7 +1578,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *tes
1578 1578
 	}
1579 1579
 
1580 1580
 	name = "oomscoreadj-low"
1581
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, name)
1581
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, name)
1582 1582
 
1583 1583
 	expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
1584 1584
 	assert.ErrorContains(c, err, expected)
... ...
@@ -1610,7 +1610,7 @@ func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) {
1610 1610
 	assert.NilError(c, err)
1611 1611
 	defer cli.Close()
1612 1612
 
1613
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, name)
1613
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, name)
1614 1614
 	assert.NilError(c, err)
1615 1615
 
1616 1616
 	err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
... ...
@@ -1926,7 +1926,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *testing.T) {
1926 1926
 	for i, x := range cases {
1927 1927
 		x := x
1928 1928
 		c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) {
1929
-			_, err = apiClient.ContainerCreate(context.Background(), &x.config, &x.hostConfig, &networktypes.NetworkingConfig{}, "")
1929
+			_, err = apiClient.ContainerCreate(context.Background(), &x.config, &x.hostConfig, &networktypes.NetworkingConfig{}, nil, "")
1930 1930
 			if len(x.msg) > 0 {
1931 1931
 				assert.ErrorContains(c, err, x.msg, "%v", cases[i].config)
1932 1932
 			} else {
... ...
@@ -1959,7 +1959,7 @@ func (s *DockerSuite) TestContainerAPICreateMountsBindRead(c *testing.T) {
1959 1959
 	assert.NilError(c, err)
1960 1960
 	defer cli.Close()
1961 1961
 
1962
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "test")
1962
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "test")
1963 1963
 	assert.NilError(c, err)
1964 1964
 
1965 1965
 	out, _ := dockerCmd(c, "start", "-a", "test")
... ...
@@ -2106,6 +2106,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *testing.T) {
2106 2106
 				&containertypes.Config{Image: testImg},
2107 2107
 				&containertypes.HostConfig{Mounts: []mounttypes.Mount{x.spec}},
2108 2108
 				&networktypes.NetworkingConfig{},
2109
+				nil,
2109 2110
 				"")
2110 2111
 			assert.NilError(c, err)
2111 2112
 
... ...
@@ -2213,7 +2214,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
2213 2213
 			Mounts: []mounttypes.Mount{x.cfg},
2214 2214
 		}
2215 2215
 
2216
-		_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, cName)
2216
+		_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, cName)
2217 2217
 		assert.NilError(c, err)
2218 2218
 		out, _ := dockerCmd(c, "start", "-a", cName)
2219 2219
 		for _, option := range x.expectedOptions {
... ...
@@ -65,7 +65,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsBindNamedPipe(c *testing.T) {
65 65
 				},
66 66
 			},
67 67
 		},
68
-		nil, name)
68
+		nil, nil, name)
69 69
 	assert.NilError(c, err)
70 70
 
71 71
 	err = client.ContainerStart(ctx, name, types.ContainerStartOptions{})
... ...
@@ -578,7 +578,7 @@ func (s *DockerSuite) TestDuplicateMountpointsForVolumesFromAndMounts(c *testing
578 578
 			},
579 579
 		},
580 580
 	}
581
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, "app")
581
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "app")
582 582
 
583 583
 	assert.NilError(c, err)
584 584
 
... ...
@@ -10,6 +10,7 @@ import (
10 10
 
11 11
 	"github.com/docker/docker/api/types"
12 12
 	"github.com/docker/docker/api/types/container"
13
+	containertypes "github.com/docker/docker/api/types/container"
13 14
 	"github.com/docker/docker/api/types/network"
14 15
 	"github.com/docker/docker/api/types/versions"
15 16
 	"github.com/docker/docker/client"
... ...
@@ -17,6 +18,7 @@ import (
17 17
 	ctr "github.com/docker/docker/integration/internal/container"
18 18
 	"github.com/docker/docker/oci"
19 19
 	"github.com/docker/docker/testutil/request"
20
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
20 21
 	"gotest.tools/v3/assert"
21 22
 	is "gotest.tools/v3/assert/cmp"
22 23
 	"gotest.tools/v3/poll"
... ...
@@ -57,6 +59,7 @@ func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
57 57
 				&container.Config{Image: tc.image},
58 58
 				&container.HostConfig{},
59 59
 				&network.NetworkingConfig{},
60
+				nil,
60 61
 				"",
61 62
 			)
62 63
 			assert.Check(t, is.ErrorContains(err, tc.expectedError))
... ...
@@ -81,6 +84,7 @@ func TestCreateLinkToNonExistingContainer(t *testing.T) {
81 81
 			Links: []string{"no-such-container"},
82 82
 		},
83 83
 		&network.NetworkingConfig{},
84
+		nil,
84 85
 		"",
85 86
 	)
86 87
 	assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container"))
... ...
@@ -120,6 +124,7 @@ func TestCreateWithInvalidEnv(t *testing.T) {
120 120
 				},
121 121
 				&container.HostConfig{},
122 122
 				&network.NetworkingConfig{},
123
+				nil,
123 124
 				"",
124 125
 			)
125 126
 			assert.Check(t, is.ErrorContains(err, tc.expectedError))
... ...
@@ -166,6 +171,7 @@ func TestCreateTmpfsMountsTarget(t *testing.T) {
166 166
 				Tmpfs: map[string]string{tc.target: ""},
167 167
 			},
168 168
 			&network.NetworkingConfig{},
169
+			nil,
169 170
 			"",
170 171
 		)
171 172
 		assert.Check(t, is.ErrorContains(err, tc.expectedError))
... ...
@@ -235,6 +241,7 @@ func TestCreateWithCustomMaskedPaths(t *testing.T) {
235 235
 			&config,
236 236
 			&hc,
237 237
 			&network.NetworkingConfig{},
238
+			nil,
238 239
 			name,
239 240
 		)
240 241
 		assert.NilError(t, err)
... ...
@@ -361,6 +368,7 @@ func TestCreateWithCapabilities(t *testing.T) {
361 361
 				&container.Config{Image: "busybox"},
362 362
 				&tc.hostConfig,
363 363
 				&network.NetworkingConfig{},
364
+				nil,
364 365
 				"",
365 366
 			)
366 367
 			if tc.expectedError == "" {
... ...
@@ -439,6 +447,7 @@ func TestCreateWithCustomReadonlyPaths(t *testing.T) {
439 439
 			&config,
440 440
 			&hc,
441 441
 			&network.NetworkingConfig{},
442
+			nil,
442 443
 			name,
443 444
 		)
444 445
 		assert.NilError(t, err)
... ...
@@ -522,7 +531,7 @@ func TestCreateWithInvalidHealthcheckParams(t *testing.T) {
522 522
 				cfg.Healthcheck.StartPeriod = tc.startPeriod
523 523
 			}
524 524
 
525
-			resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, "")
525
+			resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, nil, "")
526 526
 			assert.Check(t, is.Equal(len(resp.Warnings), 0))
527 527
 
528 528
 			if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
... ...
@@ -581,3 +590,34 @@ func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) {
581 581
 		assert.NilError(t, err)
582 582
 	}
583 583
 }
584
+
585
+// Test that if the referenced image platform does not match the requested platform on container create that we get an
586
+// error.
587
+func TestCreateDifferentPlatform(t *testing.T) {
588
+	defer setupTest(t)()
589
+	c := testEnv.APIClient()
590
+	ctx := context.Background()
591
+
592
+	img, _, err := c.ImageInspectWithRaw(ctx, "busybox:latest")
593
+	assert.NilError(t, err)
594
+	assert.Assert(t, img.Architecture != "")
595
+
596
+	t.Run("different os", func(t *testing.T) {
597
+		p := specs.Platform{
598
+			OS:           img.Os + "DifferentOS",
599
+			Architecture: img.Architecture,
600
+			Variant:      img.Variant,
601
+		}
602
+		_, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "")
603
+		assert.Assert(t, client.IsErrNotFound(err), err)
604
+	})
605
+	t.Run("different cpu arch", func(t *testing.T) {
606
+		p := specs.Platform{
607
+			OS:           img.Os,
608
+			Architecture: img.Architecture + "DifferentArch",
609
+			Variant:      img.Variant,
610
+		}
611
+		_, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "")
612
+		assert.Assert(t, client.IsErrNotFound(err), err)
613
+	})
614
+}
... ...
@@ -66,7 +66,7 @@ func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool,
66 66
 	client := testEnv.APIClient()
67 67
 	ctx := context.Background()
68 68
 
69
-	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
69
+	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
70 70
 	assert.NilError(t, err)
71 71
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
72 72
 
... ...
@@ -138,7 +138,7 @@ func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
138 138
 	client := testEnv.APIClient()
139 139
 
140 140
 	// create and start the "donor" container
141
-	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
141
+	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
142 142
 	assert.NilError(t, err)
143 143
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
144 144
 	name1 := resp.ID
... ...
@@ -148,7 +148,7 @@ func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
148 148
 
149 149
 	// create and start the second container
150 150
 	hostCfg.IpcMode = containertypes.IpcMode("container:" + name1)
151
-	resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
151
+	resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
152 152
 	assert.NilError(t, err)
153 153
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
154 154
 	name2 := resp.ID
... ...
@@ -204,7 +204,7 @@ func TestAPIIpcModeHost(t *testing.T) {
204 204
 	ctx := context.Background()
205 205
 
206 206
 	client := testEnv.APIClient()
207
-	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
207
+	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
208 208
 	assert.NilError(t, err)
209 209
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
210 210
 	name := resp.ID
... ...
@@ -241,7 +241,7 @@ func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...strin
241 241
 	}
242 242
 	ctx := context.Background()
243 243
 
244
-	resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, "")
244
+	resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, nil, "")
245 245
 	assert.NilError(t, err)
246 246
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
247 247
 
... ...
@@ -63,7 +63,7 @@ func TestContainerNetworkMountsNoChown(t *testing.T) {
63 63
 	assert.NilError(t, err)
64 64
 	defer cli.Close()
65 65
 
66
-	ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, "")
66
+	ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
67 67
 	assert.NilError(t, err)
68 68
 	// container will exit immediately because of no tty, but we only need the start sequence to test the condition
69 69
 	err = cli.ContainerStart(ctx, ctrCreate.ID, types.ContainerStartOptions{})
... ...
@@ -174,7 +174,7 @@ func TestMountDaemonRoot(t *testing.T) {
174 174
 					c, err := client.ContainerCreate(ctx, &containertypes.Config{
175 175
 						Image: "busybox",
176 176
 						Cmd:   []string{"true"},
177
-					}, hc, nil, "")
177
+					}, hc, nil, nil, "")
178 178
 
179 179
 					if err != nil {
180 180
 						if test.expected != "" {
... ...
@@ -76,7 +76,7 @@ func TestDaemonRestartKillContainers(t *testing.T) {
76 76
 					defer d.Stop(t)
77 77
 					ctx := context.Background()
78 78
 
79
-					resp, err := client.ContainerCreate(ctx, c.config, c.hostConfig, nil, "")
79
+					resp, err := client.ContainerCreate(ctx, c.config, c.hostConfig, nil, nil, "")
80 80
 					assert.NilError(t, err)
81 81
 					defer client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})
82 82
 
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"github.com/docker/docker/api/types/container"
10 10
 	"github.com/docker/docker/api/types/network"
11 11
 	"github.com/docker/docker/client"
12
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
12 13
 	"gotest.tools/v3/assert"
13 14
 )
14 15
 
... ...
@@ -19,6 +20,7 @@ type TestContainerConfig struct {
19 19
 	Config           *container.Config
20 20
 	HostConfig       *container.HostConfig
21 21
 	NetworkingConfig *network.NetworkingConfig
22
+	Platform         *specs.Platform
22 23
 }
23 24
 
24 25
 // create creates a container with the specified options
... ...
@@ -41,7 +43,7 @@ func create(ctx context.Context, t *testing.T, client client.APIClient, ops ...f
41 41
 		op(config)
42 42
 	}
43 43
 
44
-	return client.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Name)
44
+	return client.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Platform, config.Name)
45 45
 }
46 46
 
47 47
 // Create creates a container with the specified options, asserting that there was no error
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	networktypes "github.com/docker/docker/api/types/network"
10 10
 	"github.com/docker/docker/api/types/strslice"
11 11
 	"github.com/docker/go-connections/nat"
12
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
12 13
 )
13 14
 
14 15
 // WithName sets the name of the container
... ...
@@ -205,3 +206,10 @@ func WithExtraHost(extraHost string) func(*TestContainerConfig) {
205 205
 		c.HostConfig.ExtraHosts = append(c.HostConfig.ExtraHosts, extraHost)
206 206
 	}
207 207
 }
208
+
209
+// WithPlatform specifies the desired platform the image should have.
210
+func WithPlatform(p *specs.Platform) func(*TestContainerConfig) {
211
+	return func(c *TestContainerConfig) {
212
+		c.Platform = p
213
+	}
214
+}
... ...
@@ -54,6 +54,7 @@ func TestReadPluginNoRead(t *testing.T) {
54 54
 				cfg,
55 55
 				&container.HostConfig{LogConfig: container.LogConfig{Type: "test"}},
56 56
 				nil,
57
+				nil,
57 58
 				"",
58 59
 			)
59 60
 			assert.Assert(t, err)
... ...
@@ -155,7 +155,7 @@ COPY . /static`); err != nil {
155 155
 	// Start the container
156 156
 	b, err := c.ContainerCreate(context.Background(), &containertypes.Config{
157 157
 		Image: image,
158
-	}, &containertypes.HostConfig{}, nil, container)
158
+	}, &containertypes.HostConfig{}, nil, nil, container)
159 159
 	assert.NilError(t, err)
160 160
 	err = c.ContainerStart(context.Background(), b.ID, types.ContainerStartOptions{})
161 161
 	assert.NilError(t, err)