Browse code

Merge pull request #40725 from cpuguy83/check_img_platform

Accept platform spec on container create

Tibor Vass authored on 2020/05/22 03:33:27
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"
... ...
@@ -502,6 +504,28 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
502 502
 		}
503 503
 	}
504 504
 
505
+	var platform *specs.Platform
506
+	if versions.GreaterThanOrEqualTo(version, "1.41") {
507
+		if v := r.Form.Get("platform"); v != "" {
508
+			p, err := platforms.Parse(v)
509
+			if err != nil {
510
+				return errdefs.InvalidParameter(err)
511
+			}
512
+			platform = &p
513
+		}
514
+		defaultPlatform := platforms.DefaultSpec()
515
+		if platform == nil {
516
+			platform = &defaultPlatform
517
+		}
518
+		if platform.OS == "" {
519
+			platform.OS = defaultPlatform.OS
520
+		}
521
+		if platform.Architecture == "" {
522
+			platform.Architecture = defaultPlatform.Architecture
523
+			platform.Variant = defaultPlatform.Variant
524
+		}
525
+	}
526
+
505 527
 	if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
506 528
 		// Don't set a limit if either no limit was specified, or "unlimited" was
507 529
 		// explicitly set.
... ...
@@ -516,6 +540,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
516 516
 		HostConfig:       hostConfig,
517 517
 		NetworkingConfig: networkingConfig,
518 518
 		AdjustCPUShares:  adjustCPUShares,
519
+		Platform:         platform,
519 520
 	})
520 521
 	if err != nil {
521 522
 		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
... ...
@@ -581,7 +581,7 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty
581 581
 	c := s.Container
582 582
 	image := s.Image // keep the original ref if still valid (hasn't changed)
583 583
 	if image != s.ImageID {
584
-		img, err := daemon.imageService.GetImage(image)
584
+		img, err := daemon.imageService.GetImage(image, nil)
585 585
 		if _, isDNE := err.(images.ErrImageDoesNotExist); err != nil && !isDNE {
586 586
 			return nil, err
587 587
 		}
... ...
@@ -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 != "" {
... ...
@@ -77,7 +77,7 @@ func TestDaemonRestartKillContainers(t *testing.T) {
77 77
 					defer d.Stop(t)
78 78
 					ctx := context.Background()
79 79
 
80
-					resp, err := client.ContainerCreate(ctx, c.config, c.hostConfig, nil, "")
80
+					resp, err := client.ContainerCreate(ctx, c.config, c.hostConfig, nil, nil, "")
81 81
 					assert.NilError(t, err)
82 82
 					defer client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})
83 83
 
... ...
@@ -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)