浏览代码

Merge pull request #40725 from cpuguy83/check_img_platform

Accept platform spec on container create
Tibor Vass 5 年之前
父节点
当前提交
5c10ea6ae8

+ 25 - 0
api/server/router/container/container_routes.go

@@ -9,6 +9,7 @@ import (
 	"strconv"
 	"strconv"
 	"syscall"
 	"syscall"
 
 
+	"github.com/containerd/containerd/platforms"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
@@ -19,6 +20,7 @@ import (
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/websocket"
 	"golang.org/x/net/websocket"
@@ -502,6 +504,28 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
 		}
 		}
 	}
 	}
 
 
+	var platform *specs.Platform
+	if versions.GreaterThanOrEqualTo(version, "1.41") {
+		if v := r.Form.Get("platform"); v != "" {
+			p, err := platforms.Parse(v)
+			if err != nil {
+				return errdefs.InvalidParameter(err)
+			}
+			platform = &p
+		}
+		defaultPlatform := platforms.DefaultSpec()
+		if platform == nil {
+			platform = &defaultPlatform
+		}
+		if platform.OS == "" {
+			platform.OS = defaultPlatform.OS
+		}
+		if platform.Architecture == "" {
+			platform.Architecture = defaultPlatform.Architecture
+			platform.Variant = defaultPlatform.Variant
+		}
+	}
+
 	if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
 	if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
 		// Don't set a limit if either no limit was specified, or "unlimited" was
 		// Don't set a limit if either no limit was specified, or "unlimited" was
 		// explicitly set.
 		// explicitly set.
@@ -516,6 +540,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
 		HostConfig:       hostConfig,
 		HostConfig:       hostConfig,
 		NetworkingConfig: networkingConfig,
 		NetworkingConfig: networkingConfig,
 		AdjustCPUShares:  adjustCPUShares,
 		AdjustCPUShares:  adjustCPUShares,
+		Platform:         platform,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		return err
 		return err

+ 2 - 0
api/types/configs.go

@@ -3,6 +3,7 @@ package types // import "github.com/docker/docker/api/types"
 import (
 import (
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 )
 
 
 // configs holds structs used for internal communication between the
 // configs holds structs used for internal communication between the
@@ -15,6 +16,7 @@ type ContainerCreateConfig struct {
 	Config           *container.Config
 	Config           *container.Config
 	HostConfig       *container.HostConfig
 	HostConfig       *container.HostConfig
 	NetworkingConfig *network.NetworkingConfig
 	NetworkingConfig *network.NetworkingConfig
+	Platform         *specs.Platform
 	AdjustCPUShares  bool
 	AdjustCPUShares  bool
 }
 }
 
 

+ 12 - 1
client/container_create.go

@@ -5,20 +5,23 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"net/url"
 	"net/url"
 
 
+	"github.com/containerd/containerd/platforms"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 )
 
 
 type configWrapper struct {
 type configWrapper struct {
 	*container.Config
 	*container.Config
 	HostConfig       *container.HostConfig
 	HostConfig       *container.HostConfig
 	NetworkingConfig *network.NetworkingConfig
 	NetworkingConfig *network.NetworkingConfig
+	Platform         *specs.Platform
 }
 }
 
 
 // ContainerCreate creates a new container based in the given configuration.
 // ContainerCreate creates a new container based in the given configuration.
 // It can be associated with a name, but it's not mandatory.
 // It can be associated with a name, but it's not mandatory.
-func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
+func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) {
 	var response container.ContainerCreateCreatedBody
 	var response container.ContainerCreateCreatedBody
 
 
 	if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
 	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
 		hostConfig.AutoRemove = false
 		hostConfig.AutoRemove = false
 	}
 	}
 
 
+	if err := cli.NewVersionError("1.41", "specify container image platform"); platform != nil && err != nil {
+		return response, err
+	}
+
 	query := url.Values{}
 	query := url.Values{}
+	if platform != nil {
+		query.Set("platform", platforms.Format(*platform))
+	}
+
 	if containerName != "" {
 	if containerName != "" {
 		query.Set("name", containerName)
 		query.Set("name", containerName)
 	}
 	}

+ 6 - 6
client/container_create_test.go

@@ -18,7 +18,7 @@ func TestContainerCreateError(t *testing.T) {
 	client := &Client{
 	client := &Client{
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
 	}
 	}
-	_, err := client.ContainerCreate(context.Background(), nil, nil, nil, "nothing")
+	_, err := client.ContainerCreate(context.Background(), nil, nil, nil, nil, "nothing")
 	if !errdefs.IsSystem(err) {
 	if !errdefs.IsSystem(err) {
 		t.Fatalf("expected a Server Error while testing StatusInternalServerError, got %T", err)
 		t.Fatalf("expected a Server Error while testing StatusInternalServerError, got %T", err)
 	}
 	}
@@ -27,7 +27,7 @@ func TestContainerCreateError(t *testing.T) {
 	client = &Client{
 	client = &Client{
 		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
 		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
 	}
 	}
-	_, err = client.ContainerCreate(context.Background(), nil, nil, nil, "nothing")
+	_, err = client.ContainerCreate(context.Background(), nil, nil, nil, nil, "nothing")
 	if err == nil || !IsErrNotFound(err) {
 	if err == nil || !IsErrNotFound(err) {
 		t.Fatalf("expected a Server Error while testing StatusNotFound, got %T", err)
 		t.Fatalf("expected a Server Error while testing StatusNotFound, got %T", err)
 	}
 	}
@@ -37,7 +37,7 @@ func TestContainerCreateImageNotFound(t *testing.T) {
 	client := &Client{
 	client := &Client{
 		client: newMockClient(errorMock(http.StatusNotFound, "No such image")),
 		client: newMockClient(errorMock(http.StatusNotFound, "No such image")),
 	}
 	}
-	_, err := client.ContainerCreate(context.Background(), &container.Config{Image: "unknown_image"}, nil, nil, "unknown")
+	_, err := client.ContainerCreate(context.Background(), &container.Config{Image: "unknown_image"}, nil, nil, nil, "unknown")
 	if err == nil || !IsErrNotFound(err) {
 	if err == nil || !IsErrNotFound(err) {
 		t.Fatalf("expected an imageNotFound error, got %v, %T", err, err)
 		t.Fatalf("expected an imageNotFound error, got %v, %T", err, err)
 	}
 	}
@@ -67,7 +67,7 @@ func TestContainerCreateWithName(t *testing.T) {
 		}),
 		}),
 	}
 	}
 
 
-	r, err := client.ContainerCreate(context.Background(), nil, nil, nil, "container_name")
+	r, err := client.ContainerCreate(context.Background(), nil, nil, nil, nil, "container_name")
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -106,14 +106,14 @@ func TestContainerCreateAutoRemove(t *testing.T) {
 		client:  newMockClient(autoRemoveValidator(false)),
 		client:  newMockClient(autoRemoveValidator(false)),
 		version: "1.24",
 		version: "1.24",
 	}
 	}
-	if _, err := client.ContainerCreate(context.Background(), nil, &container.HostConfig{AutoRemove: true}, nil, ""); err != nil {
+	if _, err := client.ContainerCreate(context.Background(), nil, &container.HostConfig{AutoRemove: true}, nil, nil, ""); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	client = &Client{
 	client = &Client{
 		client:  newMockClient(autoRemoveValidator(true)),
 		client:  newMockClient(autoRemoveValidator(true)),
 		version: "1.25",
 		version: "1.25",
 	}
 	}
-	if _, err := client.ContainerCreate(context.Background(), nil, &container.HostConfig{AutoRemove: true}, nil, ""); err != nil {
+	if _, err := client.ContainerCreate(context.Background(), nil, &container.HostConfig{AutoRemove: true}, nil, nil, ""); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 }
 }

+ 2 - 1
client/interface.go

@@ -16,6 +16,7 @@ import (
 	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
 	volumetypes "github.com/docker/docker/api/types/volume"
 	volumetypes "github.com/docker/docker/api/types/volume"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 )
 
 
 // CommonAPIClient is the common methods between stable and experimental versions of APIClient.
 // CommonAPIClient is the common methods between stable and experimental versions of APIClient.
@@ -47,7 +48,7 @@ type CommonAPIClient interface {
 type ContainerAPIClient interface {
 type ContainerAPIClient interface {
 	ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error)
 	ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error)
 	ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.IDResponse, error)
 	ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.IDResponse, error)
-	ContainerCreate(ctx context.Context, config *containertypes.Config, hostConfig *containertypes.HostConfig, networkingConfig *networktypes.NetworkingConfig, containerName string) (containertypes.ContainerCreateCreatedBody, error)
+	ContainerCreate(ctx context.Context, config *containertypes.Config, hostConfig *containertypes.HostConfig, networkingConfig *networktypes.NetworkingConfig, platform *specs.Platform, containerName string) (containertypes.ContainerCreateCreatedBody, error)
 	ContainerDiff(ctx context.Context, container string) ([]containertypes.ContainerChangeResponseItem, error)
 	ContainerDiff(ctx context.Context, container string) ([]containertypes.ContainerChangeResponseItem, error)
 	ContainerExecAttach(ctx context.Context, execID string, config types.ExecStartCheck) (types.HijackedResponse, error)
 	ContainerExecAttach(ctx context.Context, execID string, config types.ExecStartCheck) (types.HijackedResponse, error)
 	ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error)
 	ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error)

+ 2 - 2
daemon/create.go

@@ -60,7 +60,7 @@ func (daemon *Daemon) containerCreate(opts createOpts) (containertypes.Container
 
 
 	os := runtime.GOOS
 	os := runtime.GOOS
 	if opts.params.Config.Image != "" {
 	if opts.params.Config.Image != "" {
-		img, err := daemon.imageService.GetImage(opts.params.Config.Image)
+		img, err := daemon.imageService.GetImage(opts.params.Config.Image, opts.params.Platform)
 		if err == nil {
 		if err == nil {
 			os = img.OS
 			os = img.OS
 		}
 		}
@@ -114,7 +114,7 @@ func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr
 
 
 	os := runtime.GOOS
 	os := runtime.GOOS
 	if opts.params.Config.Image != "" {
 	if opts.params.Config.Image != "" {
-		img, err = daemon.imageService.GetImage(opts.params.Config.Image)
+		img, err = daemon.imageService.GetImage(opts.params.Config.Image, opts.params.Platform)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}

+ 1 - 1
daemon/images/cache.go

@@ -15,7 +15,7 @@ func (i *ImageService) MakeImageCache(sourceRefs []string) builder.ImageCache {
 	cache := cache.New(i.imageStore)
 	cache := cache.New(i.imageStore)
 
 
 	for _, ref := range sourceRefs {
 	for _, ref := range sourceRefs {
-		img, err := i.GetImage(ref)
+		img, err := i.GetImage(ref, nil)
 		if err != nil {
 		if err != nil {
 			logrus.Warnf("Could not look up %s for cache resolution, skipping: %+v", ref, err)
 			logrus.Warnf("Could not look up %s for cache resolution, skipping: %+v", ref, err)
 			continue
 			continue

+ 36 - 1
daemon/images/image.go

@@ -3,9 +3,12 @@ package images // import "github.com/docker/docker/daemon/images"
 import (
 import (
 	"fmt"
 	"fmt"
 
 
+	"github.com/pkg/errors"
+
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 )
 
 
 // ErrImageDoesNotExist is error returned when no image can be found for a reference.
 // ErrImageDoesNotExist is error returned when no image can be found for a reference.
@@ -25,7 +28,39 @@ func (e ErrImageDoesNotExist) Error() string {
 func (e ErrImageDoesNotExist) NotFound() {}
 func (e ErrImageDoesNotExist) NotFound() {}
 
 
 // GetImage returns an image corresponding to the image referred to by refOrID.
 // GetImage returns an image corresponding to the image referred to by refOrID.
-func (i *ImageService) GetImage(refOrID string) (*image.Image, error) {
+func (i *ImageService) GetImage(refOrID string, platform *specs.Platform) (retImg *image.Image, retErr error) {
+	defer func() {
+		if retErr != nil || retImg == nil || platform == nil {
+			return
+		}
+
+		// This allows us to tell clients that we don't have the image they asked for
+		// Where this gets hairy is the image store does not currently support multi-arch images, e.g.:
+		//   An image `foo` may have a multi-arch manifest, but the image store only fetches the image for a specific platform
+		//   The image store does not store the manifest list and image tags are assigned to architecture specific images.
+		//   So we can have a `foo` image that is amd64 but the user requested armv7. If the user looks at the list of images.
+		//   This may be confusing.
+		//   The alternative to this is to return a errdefs.Conflict error with a helpful message, but clients will not be
+		//   able to automatically tell what causes the conflict.
+		if retImg.OS != platform.OS {
+			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))
+			retImg = nil
+			return
+		}
+		if retImg.Architecture != platform.Architecture {
+			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))
+			retImg = nil
+			return
+		}
+
+		// Only validate variant if retImg has a variant set.
+		// The image variant may not be set since it's a newer field.
+		if platform.Variant != "" && retImg.Variant != "" && retImg.Variant != platform.Variant {
+			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))
+			retImg = nil
+			return
+		}
+	}()
 	ref, err := reference.ParseAnyReference(refOrID)
 	ref, err := reference.ParseAnyReference(refOrID)
 	if err != nil {
 	if err != nil {
 		return nil, errdefs.InvalidParameter(err)
 		return nil, errdefs.InvalidParameter(err)

+ 2 - 2
daemon/images/image_builder.go

@@ -161,7 +161,7 @@ func (i *ImageService) pullForBuilder(ctx context.Context, name string, authConf
 	if err := i.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil {
 	if err := i.pullImageWithReference(ctx, ref, platform, nil, pullRegistryAuth, output); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	return i.GetImage(name)
+	return i.GetImage(name, platform)
 }
 }
 
 
 // GetImageAndReleasableLayer returns an image and releaseable layer for a reference or ID.
 // 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
 	}
 	}
 
 
 	if opts.PullOption != backend.PullOptionForcePull {
 	if opts.PullOption != backend.PullOptionForcePull {
-		image, err := i.GetImage(refOrID)
+		image, err := i.GetImage(refOrID, opts.Platform)
 		if err != nil && opts.PullOption == backend.PullOptionNoPull {
 		if err != nil && opts.PullOption == backend.PullOptionNoPull {
 			return nil, nil, err
 			return nil, nil, err
 		}
 		}

+ 1 - 1
daemon/images/image_delete.go

@@ -64,7 +64,7 @@ func (i *ImageService) ImageDelete(imageRef string, force, prune bool) ([]types.
 	start := time.Now()
 	start := time.Now()
 	records := []types.ImageDeleteResponseItem{}
 	records := []types.ImageDeleteResponseItem{}
 
 
-	img, err := i.GetImage(imageRef)
+	img, err := i.GetImage(imageRef, nil)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 1 - 1
daemon/images/image_events.go

@@ -11,7 +11,7 @@ func (i *ImageService) LogImageEvent(imageID, refName, action string) {
 
 
 // LogImageEventWithAttributes generates an event related to an image with specific given attributes.
 // LogImageEventWithAttributes generates an event related to an image with specific given attributes.
 func (i *ImageService) LogImageEventWithAttributes(imageID, refName, action string, attributes map[string]string) {
 func (i *ImageService) LogImageEventWithAttributes(imageID, refName, action string, attributes map[string]string) {
-	img, err := i.GetImage(imageID)
+	img, err := i.GetImage(imageID, nil)
 	if err == nil && img.Config != nil {
 	if err == nil && img.Config != nil {
 		// image has not been removed yet.
 		// image has not been removed yet.
 		// it could be missing if the event is `delete`.
 		// it could be missing if the event is `delete`.

+ 2 - 2
daemon/images/image_history.go

@@ -14,7 +14,7 @@ import (
 // name by walking the image lineage.
 // name by walking the image lineage.
 func (i *ImageService) ImageHistory(name string) ([]*image.HistoryResponseItem, error) {
 func (i *ImageService) ImageHistory(name string) ([]*image.HistoryResponseItem, error) {
 	start := time.Now()
 	start := time.Now()
-	img, err := i.GetImage(name)
+	img, err := i.GetImage(name, nil)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -77,7 +77,7 @@ func (i *ImageService) ImageHistory(name string) ([]*image.HistoryResponseItem,
 		if id == "" {
 		if id == "" {
 			break
 			break
 		}
 		}
-		histImg, err = i.GetImage(id.String())
+		histImg, err = i.GetImage(id.String(), nil)
 		if err != nil {
 		if err != nil {
 			break
 			break
 		}
 		}

+ 1 - 1
daemon/images/image_inspect.go

@@ -14,7 +14,7 @@ import (
 // LookupImage looks up an image by name and returns it as an ImageInspect
 // LookupImage looks up an image by name and returns it as an ImageInspect
 // structure.
 // structure.
 func (i *ImageService) LookupImage(name string) (*types.ImageInspect, error) {
 func (i *ImageService) LookupImage(name string) (*types.ImageInspect, error) {
-	img, err := i.GetImage(name)
+	img, err := i.GetImage(name, nil)
 	if err != nil {
 	if err != nil {
 		return nil, errors.Wrapf(err, "no such image: %s", name)
 		return nil, errors.Wrapf(err, "no such image: %s", name)
 	}
 	}

+ 1 - 1
daemon/images/image_tag.go

@@ -8,7 +8,7 @@ import (
 // TagImage creates the tag specified by newTag, pointing to the image named
 // TagImage creates the tag specified by newTag, pointing to the image named
 // imageName (alternatively, imageName can also be an image ID).
 // imageName (alternatively, imageName can also be an image ID).
 func (i *ImageService) TagImage(imageName, repository, tag string) (string, error) {
 func (i *ImageService) TagImage(imageName, repository, tag string) (string, error) {
-	img, err := i.GetImage(imageName)
+	img, err := i.GetImage(imageName, nil)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}

+ 2 - 2
daemon/images/images.go

@@ -69,7 +69,7 @@ func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttr
 
 
 	var beforeFilter, sinceFilter *image.Image
 	var beforeFilter, sinceFilter *image.Image
 	err = imageFilters.WalkValues("before", func(value string) error {
 	err = imageFilters.WalkValues("before", func(value string) error {
-		beforeFilter, err = i.GetImage(value)
+		beforeFilter, err = i.GetImage(value, nil)
 		return err
 		return err
 	})
 	})
 	if err != nil {
 	if err != nil {
@@ -77,7 +77,7 @@ func (i *ImageService) Images(imageFilters filters.Args, all bool, withExtraAttr
 	}
 	}
 
 
 	err = imageFilters.WalkValues("since", func(value string) error {
 	err = imageFilters.WalkValues("since", func(value string) error {
-		sinceFilter, err = i.GetImage(value)
+		sinceFilter, err = i.GetImage(value, nil)
 		return err
 		return err
 	})
 	})
 	if err != nil {
 	if err != nil {

+ 2 - 2
daemon/list.go

@@ -317,7 +317,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
 	if psFilters.Contains("ancestor") {
 	if psFilters.Contains("ancestor") {
 		ancestorFilter = true
 		ancestorFilter = true
 		psFilters.WalkValues("ancestor", func(ancestor string) error {
 		psFilters.WalkValues("ancestor", func(ancestor string) error {
-			img, err := daemon.imageService.GetImage(ancestor)
+			img, err := daemon.imageService.GetImage(ancestor, nil)
 			if err != nil {
 			if err != nil {
 				logrus.Warnf("Error while looking up for image %v", ancestor)
 				logrus.Warnf("Error while looking up for image %v", ancestor)
 				return nil
 				return nil
@@ -581,7 +581,7 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty
 	c := s.Container
 	c := s.Container
 	image := s.Image // keep the original ref if still valid (hasn't changed)
 	image := s.Image // keep the original ref if still valid (hasn't changed)
 	if image != s.ImageID {
 	if image != s.ImageID {
-		img, err := daemon.imageService.GetImage(image)
+		img, err := daemon.imageService.GetImage(image, nil)
 		if _, isDNE := err.(images.ErrImageDoesNotExist); err != nil && !isDNE {
 		if _, isDNE := err.(images.ErrImageDoesNotExist); err != nil && !isDNE {
 			return nil, err
 			return nil, err
 		}
 		}

+ 1 - 1
daemon/oci_windows.go

@@ -29,7 +29,7 @@ const (
 
 
 func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 
 
-	img, err := daemon.imageService.GetImage(string(c.ImageID))
+	img, err := daemon.imageService.GetImage(string(c.ImageID), nil)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 25 - 24
integration-cli/docker_api_containers_test.go

@@ -523,7 +523,7 @@ func (s *DockerSuite) TestContainerAPIBadPort(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
 	assert.ErrorContains(c, err, `invalid port specification: "aa80"`)
 	assert.ErrorContains(c, err, `invalid port specification: "aa80"`)
 }
 }
 
 
@@ -537,7 +537,7 @@ func (s *DockerSuite) TestContainerAPICreate(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	out, _ := dockerCmd(c, "start", "-a", container.ID)
 	out, _ := dockerCmd(c, "start", "-a", container.ID)
@@ -550,7 +550,7 @@ func (s *DockerSuite) TestContainerAPICreateEmptyConfig(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &containertypes.Config{}, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	_, err = cli.ContainerCreate(context.Background(), &containertypes.Config{}, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
 
 
 	expected := "No command specified"
 	expected := "No command specified"
 	assert.ErrorContains(c, err, expected)
 	assert.ErrorContains(c, err, expected)
@@ -574,7 +574,7 @@ func (s *DockerSuite) TestContainerAPICreateMultipleNetworksConfig(c *testing.T)
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networkingConfig, "")
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networkingConfig, nil, "")
 	msg := err.Error()
 	msg := err.Error()
 	// network name order in error message is not deterministic
 	// network name order in error message is not deterministic
 	assert.Assert(c, strings.Contains(msg, "Container cannot be connected to network endpoints"))
 	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)
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
+	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@@ -636,7 +636,7 @@ func (s *DockerSuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
+	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@@ -948,7 +948,7 @@ func (s *DockerSuite) TestContainerAPIStart(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, name)
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, name)
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
 	err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
@@ -1272,7 +1272,7 @@ func (s *DockerSuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *t
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "echotest")
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "echotest")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	out, _ := dockerCmd(c, "start", "-a", "echotest")
 	out, _ := dockerCmd(c, "start", "-a", "echotest")
 	assert.Equal(c, strings.TrimSpace(out), "hello world")
 	assert.Equal(c, strings.TrimSpace(out), "hello world")
@@ -1299,7 +1299,7 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T)
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "echotest")
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "echotest")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	out, _ := dockerCmd(c, "start", "-a", "echotest")
 	out, _ := dockerCmd(c, "start", "-a", "echotest")
 	assert.Equal(c, strings.TrimSpace(out), "hello world")
 	assert.Equal(c, strings.TrimSpace(out), "hello world")
@@ -1342,7 +1342,7 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *tes
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config2, &hostConfig, &networktypes.NetworkingConfig{}, "capaddtest1")
+	_, err = cli.ContainerCreate(context.Background(), &config2, &hostConfig, &networktypes.NetworkingConfig{}, nil, "capaddtest1")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 }
 }
 
 
@@ -1356,7 +1356,7 @@ func (s *DockerSuite) TestContainerAPICreateNoHostConfig118(c *testing.T) {
 	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18"))
 	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18"))
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 }
 }
 
 
@@ -1407,7 +1407,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T
 	}
 	}
 	name := "wrong-cpuset-cpus"
 	name := "wrong-cpuset-cpus"
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig1, &networktypes.NetworkingConfig{}, name)
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig1, &networktypes.NetworkingConfig{}, nil, name)
 	expected := "Invalid value 1-42,, for cpuset cpus"
 	expected := "Invalid value 1-42,, for cpuset cpus"
 	assert.ErrorContains(c, err, expected)
 	assert.ErrorContains(c, err, expected)
 
 
@@ -1417,7 +1417,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T
 		},
 		},
 	}
 	}
 	name = "wrong-cpuset-mems"
 	name = "wrong-cpuset-mems"
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig2, &networktypes.NetworkingConfig{}, name)
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig2, &networktypes.NetworkingConfig{}, nil, name)
 	expected = "Invalid value 42-3,1-- for cpuset mems"
 	expected = "Invalid value 42-3,1-- for cpuset mems"
 	assert.ErrorContains(c, err, expected)
 	assert.ErrorContains(c, err, expected)
 }
 }
@@ -1436,7 +1436,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
 	assert.ErrorContains(c, err, "SHM size can not be less than 0")
 	assert.ErrorContains(c, err, "SHM size can not be less than 0")
 }
 }
 
 
@@ -1453,7 +1453,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testin
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@@ -1480,7 +1480,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@@ -1511,7 +1511,7 @@ func (s *DockerSuite) TestPostContainersCreateWithShmSize(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "")
+	container, err := cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@@ -1537,7 +1537,7 @@ func (s *DockerSuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	container, err := cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, "")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
 	containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@@ -1568,7 +1568,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *tes
 	defer cli.Close()
 	defer cli.Close()
 
 
 	name := "oomscoreadj-over"
 	name := "oomscoreadj-over"
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, name)
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, name)
 
 
 	expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
 	expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
 	assert.ErrorContains(c, err, expected)
 	assert.ErrorContains(c, err, expected)
@@ -1578,7 +1578,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *tes
 	}
 	}
 
 
 	name = "oomscoreadj-low"
 	name = "oomscoreadj-low"
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, name)
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, name)
 
 
 	expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
 	expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
 	assert.ErrorContains(c, err, expected)
 	assert.ErrorContains(c, err, expected)
@@ -1610,7 +1610,7 @@ func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, name)
+	_, err = cli.ContainerCreate(context.Background(), &config, &containertypes.HostConfig{}, &networktypes.NetworkingConfig{}, nil, name)
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
 	err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
@@ -1926,7 +1926,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *testing.T) {
 	for i, x := range cases {
 	for i, x := range cases {
 		x := x
 		x := x
 		c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) {
 		c.Run(fmt.Sprintf("case %d", i), func(c *testing.T) {
-			_, err = apiClient.ContainerCreate(context.Background(), &x.config, &x.hostConfig, &networktypes.NetworkingConfig{}, "")
+			_, err = apiClient.ContainerCreate(context.Background(), &x.config, &x.hostConfig, &networktypes.NetworkingConfig{}, nil, "")
 			if len(x.msg) > 0 {
 			if len(x.msg) > 0 {
 				assert.ErrorContains(c, err, x.msg, "%v", cases[i].config)
 				assert.ErrorContains(c, err, x.msg, "%v", cases[i].config)
 			} else {
 			} else {
@@ -1959,7 +1959,7 @@ func (s *DockerSuite) TestContainerAPICreateMountsBindRead(c *testing.T) {
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, "test")
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, "test")
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	out, _ := dockerCmd(c, "start", "-a", "test")
 	out, _ := dockerCmd(c, "start", "-a", "test")
@@ -2106,6 +2106,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *testing.T) {
 				&containertypes.Config{Image: testImg},
 				&containertypes.Config{Image: testImg},
 				&containertypes.HostConfig{Mounts: []mounttypes.Mount{x.spec}},
 				&containertypes.HostConfig{Mounts: []mounttypes.Mount{x.spec}},
 				&networktypes.NetworkingConfig{},
 				&networktypes.NetworkingConfig{},
+				nil,
 				"")
 				"")
 			assert.NilError(c, err)
 			assert.NilError(c, err)
 
 
@@ -2213,7 +2214,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
 			Mounts: []mounttypes.Mount{x.cfg},
 			Mounts: []mounttypes.Mount{x.cfg},
 		}
 		}
 
 
-		_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, cName)
+		_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &networktypes.NetworkingConfig{}, nil, cName)
 		assert.NilError(c, err)
 		assert.NilError(c, err)
 		out, _ := dockerCmd(c, "start", "-a", cName)
 		out, _ := dockerCmd(c, "start", "-a", cName)
 		for _, option := range x.expectedOptions {
 		for _, option := range x.expectedOptions {

+ 1 - 1
integration-cli/docker_api_containers_windows_test.go

@@ -65,7 +65,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsBindNamedPipe(c *testing.T) {
 				},
 				},
 			},
 			},
 		},
 		},
-		nil, name)
+		nil, nil, name)
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 
 	err = client.ContainerStart(ctx, name, types.ContainerStartOptions{})
 	err = client.ContainerStart(ctx, name, types.ContainerStartOptions{})

+ 1 - 1
integration-cli/docker_cli_volume_test.go

@@ -578,7 +578,7 @@ func (s *DockerSuite) TestDuplicateMountpointsForVolumesFromAndMounts(c *testing
 			},
 			},
 		},
 		},
 	}
 	}
-	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, "app")
+	_, err = cli.ContainerCreate(context.Background(), &config, &hostConfig, &network.NetworkingConfig{}, nil, "app")
 
 
 	assert.NilError(c, err)
 	assert.NilError(c, err)
 
 

+ 41 - 1
integration/container/create_test.go

@@ -10,6 +10,7 @@ import (
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
+	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/client"
@@ -17,6 +18,7 @@ import (
 	ctr "github.com/docker/docker/integration/internal/container"
 	ctr "github.com/docker/docker/integration/internal/container"
 	"github.com/docker/docker/oci"
 	"github.com/docker/docker/oci"
 	"github.com/docker/docker/testutil/request"
 	"github.com/docker/docker/testutil/request"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"gotest.tools/v3/assert"
 	"gotest.tools/v3/assert"
 	is "gotest.tools/v3/assert/cmp"
 	is "gotest.tools/v3/assert/cmp"
 	"gotest.tools/v3/poll"
 	"gotest.tools/v3/poll"
@@ -57,6 +59,7 @@ func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
 				&container.Config{Image: tc.image},
 				&container.Config{Image: tc.image},
 				&container.HostConfig{},
 				&container.HostConfig{},
 				&network.NetworkingConfig{},
 				&network.NetworkingConfig{},
+				nil,
 				"",
 				"",
 			)
 			)
 			assert.Check(t, is.ErrorContains(err, tc.expectedError))
 			assert.Check(t, is.ErrorContains(err, tc.expectedError))
@@ -81,6 +84,7 @@ func TestCreateLinkToNonExistingContainer(t *testing.T) {
 			Links: []string{"no-such-container"},
 			Links: []string{"no-such-container"},
 		},
 		},
 		&network.NetworkingConfig{},
 		&network.NetworkingConfig{},
+		nil,
 		"",
 		"",
 	)
 	)
 	assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container"))
 	assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container"))
@@ -120,6 +124,7 @@ func TestCreateWithInvalidEnv(t *testing.T) {
 				},
 				},
 				&container.HostConfig{},
 				&container.HostConfig{},
 				&network.NetworkingConfig{},
 				&network.NetworkingConfig{},
+				nil,
 				"",
 				"",
 			)
 			)
 			assert.Check(t, is.ErrorContains(err, tc.expectedError))
 			assert.Check(t, is.ErrorContains(err, tc.expectedError))
@@ -166,6 +171,7 @@ func TestCreateTmpfsMountsTarget(t *testing.T) {
 				Tmpfs: map[string]string{tc.target: ""},
 				Tmpfs: map[string]string{tc.target: ""},
 			},
 			},
 			&network.NetworkingConfig{},
 			&network.NetworkingConfig{},
+			nil,
 			"",
 			"",
 		)
 		)
 		assert.Check(t, is.ErrorContains(err, tc.expectedError))
 		assert.Check(t, is.ErrorContains(err, tc.expectedError))
@@ -235,6 +241,7 @@ func TestCreateWithCustomMaskedPaths(t *testing.T) {
 			&config,
 			&config,
 			&hc,
 			&hc,
 			&network.NetworkingConfig{},
 			&network.NetworkingConfig{},
+			nil,
 			name,
 			name,
 		)
 		)
 		assert.NilError(t, err)
 		assert.NilError(t, err)
@@ -361,6 +368,7 @@ func TestCreateWithCapabilities(t *testing.T) {
 				&container.Config{Image: "busybox"},
 				&container.Config{Image: "busybox"},
 				&tc.hostConfig,
 				&tc.hostConfig,
 				&network.NetworkingConfig{},
 				&network.NetworkingConfig{},
+				nil,
 				"",
 				"",
 			)
 			)
 			if tc.expectedError == "" {
 			if tc.expectedError == "" {
@@ -439,6 +447,7 @@ func TestCreateWithCustomReadonlyPaths(t *testing.T) {
 			&config,
 			&config,
 			&hc,
 			&hc,
 			&network.NetworkingConfig{},
 			&network.NetworkingConfig{},
+			nil,
 			name,
 			name,
 		)
 		)
 		assert.NilError(t, err)
 		assert.NilError(t, err)
@@ -522,7 +531,7 @@ func TestCreateWithInvalidHealthcheckParams(t *testing.T) {
 				cfg.Healthcheck.StartPeriod = tc.startPeriod
 				cfg.Healthcheck.StartPeriod = tc.startPeriod
 			}
 			}
 
 
-			resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, "")
+			resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, nil, "")
 			assert.Check(t, is.Equal(len(resp.Warnings), 0))
 			assert.Check(t, is.Equal(len(resp.Warnings), 0))
 
 
 			if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
 			if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
@@ -581,3 +590,34 @@ func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) {
 		assert.NilError(t, err)
 		assert.NilError(t, err)
 	}
 	}
 }
 }
+
+// Test that if the referenced image platform does not match the requested platform on container create that we get an
+// error.
+func TestCreateDifferentPlatform(t *testing.T) {
+	defer setupTest(t)()
+	c := testEnv.APIClient()
+	ctx := context.Background()
+
+	img, _, err := c.ImageInspectWithRaw(ctx, "busybox:latest")
+	assert.NilError(t, err)
+	assert.Assert(t, img.Architecture != "")
+
+	t.Run("different os", func(t *testing.T) {
+		p := specs.Platform{
+			OS:           img.Os + "DifferentOS",
+			Architecture: img.Architecture,
+			Variant:      img.Variant,
+		}
+		_, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "")
+		assert.Assert(t, client.IsErrNotFound(err), err)
+	})
+	t.Run("different cpu arch", func(t *testing.T) {
+		p := specs.Platform{
+			OS:           img.Os,
+			Architecture: img.Architecture + "DifferentArch",
+			Variant:      img.Variant,
+		}
+		_, err := c.ContainerCreate(ctx, &containertypes.Config{Image: "busybox:latest"}, &containertypes.HostConfig{}, nil, &p, "")
+		assert.Assert(t, client.IsErrNotFound(err), err)
+	})
+}

+ 5 - 5
integration/container/ipcmode_linux_test.go

@@ -66,7 +66,7 @@ func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool,
 	client := testEnv.APIClient()
 	client := testEnv.APIClient()
 	ctx := context.Background()
 	ctx := context.Background()
 
 
-	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
+	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 
 
@@ -138,7 +138,7 @@ func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
 	client := testEnv.APIClient()
 	client := testEnv.APIClient()
 
 
 	// create and start the "donor" container
 	// create and start the "donor" container
-	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
+	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 	name1 := resp.ID
 	name1 := resp.ID
@@ -148,7 +148,7 @@ func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
 
 
 	// create and start the second container
 	// create and start the second container
 	hostCfg.IpcMode = containertypes.IpcMode("container:" + name1)
 	hostCfg.IpcMode = containertypes.IpcMode("container:" + name1)
-	resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
+	resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 	name2 := resp.ID
 	name2 := resp.ID
@@ -204,7 +204,7 @@ func TestAPIIpcModeHost(t *testing.T) {
 	ctx := context.Background()
 	ctx := context.Background()
 
 
 	client := testEnv.APIClient()
 	client := testEnv.APIClient()
-	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
+	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 	name := resp.ID
 	name := resp.ID
@@ -241,7 +241,7 @@ func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...strin
 	}
 	}
 	ctx := context.Background()
 	ctx := context.Background()
 
 
-	resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, "")
+	resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, nil, "")
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 	assert.Check(t, is.Equal(len(resp.Warnings), 0))
 
 

+ 2 - 2
integration/container/mounts_linux_test.go

@@ -63,7 +63,7 @@ func TestContainerNetworkMountsNoChown(t *testing.T) {
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, "")
+	ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, nil, "")
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	// container will exit immediately because of no tty, but we only need the start sequence to test the condition
 	// container will exit immediately because of no tty, but we only need the start sequence to test the condition
 	err = cli.ContainerStart(ctx, ctrCreate.ID, types.ContainerStartOptions{})
 	err = cli.ContainerStart(ctx, ctrCreate.ID, types.ContainerStartOptions{})
@@ -174,7 +174,7 @@ func TestMountDaemonRoot(t *testing.T) {
 					c, err := client.ContainerCreate(ctx, &containertypes.Config{
 					c, err := client.ContainerCreate(ctx, &containertypes.Config{
 						Image: "busybox",
 						Image: "busybox",
 						Cmd:   []string{"true"},
 						Cmd:   []string{"true"},
-					}, hc, nil, "")
+					}, hc, nil, nil, "")
 
 
 					if err != nil {
 					if err != nil {
 						if test.expected != "" {
 						if test.expected != "" {

+ 1 - 1
integration/container/restart_test.go

@@ -77,7 +77,7 @@ func TestDaemonRestartKillContainers(t *testing.T) {
 					defer d.Stop(t)
 					defer d.Stop(t)
 					ctx := context.Background()
 					ctx := context.Background()
 
 
-					resp, err := client.ContainerCreate(ctx, c.config, c.hostConfig, nil, "")
+					resp, err := client.ContainerCreate(ctx, c.config, c.hostConfig, nil, nil, "")
 					assert.NilError(t, err)
 					assert.NilError(t, err)
 					defer client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})
 					defer client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})
 
 

+ 3 - 1
integration/internal/container/container.go

@@ -9,6 +9,7 @@ import (
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/client"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"gotest.tools/v3/assert"
 	"gotest.tools/v3/assert"
 )
 )
 
 
@@ -19,6 +20,7 @@ type TestContainerConfig struct {
 	Config           *container.Config
 	Config           *container.Config
 	HostConfig       *container.HostConfig
 	HostConfig       *container.HostConfig
 	NetworkingConfig *network.NetworkingConfig
 	NetworkingConfig *network.NetworkingConfig
+	Platform         *specs.Platform
 }
 }
 
 
 // create creates a container with the specified options
 // 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
 		op(config)
 		op(config)
 	}
 	}
 
 
-	return client.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Name)
+	return client.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Platform, config.Name)
 }
 }
 
 
 // Create creates a container with the specified options, asserting that there was no error
 // Create creates a container with the specified options, asserting that there was no error

+ 8 - 0
integration/internal/container/ops.go

@@ -9,6 +9,7 @@ import (
 	networktypes "github.com/docker/docker/api/types/network"
 	networktypes "github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/strslice"
 	"github.com/docker/docker/api/types/strslice"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 )
 
 
 // WithName sets the name of the container
 // WithName sets the name of the container
@@ -205,3 +206,10 @@ func WithExtraHost(extraHost string) func(*TestContainerConfig) {
 		c.HostConfig.ExtraHosts = append(c.HostConfig.ExtraHosts, extraHost)
 		c.HostConfig.ExtraHosts = append(c.HostConfig.ExtraHosts, extraHost)
 	}
 	}
 }
 }
+
+// WithPlatform specifies the desired platform the image should have.
+func WithPlatform(p *specs.Platform) func(*TestContainerConfig) {
+	return func(c *TestContainerConfig) {
+		c.Platform = p
+	}
+}

+ 1 - 0
integration/plugin/logging/read_test.go

@@ -54,6 +54,7 @@ func TestReadPluginNoRead(t *testing.T) {
 				cfg,
 				cfg,
 				&container.HostConfig{LogConfig: container.LogConfig{Type: "test"}},
 				&container.HostConfig{LogConfig: container.LogConfig{Type: "test"}},
 				nil,
 				nil,
+				nil,
 				"",
 				"",
 			)
 			)
 			assert.Assert(t, err)
 			assert.Assert(t, err)

+ 1 - 1
testutil/fakestorage/storage.go

@@ -155,7 +155,7 @@ COPY . /static`); err != nil {
 	// Start the container
 	// Start the container
 	b, err := c.ContainerCreate(context.Background(), &containertypes.Config{
 	b, err := c.ContainerCreate(context.Background(), &containertypes.Config{
 		Image: image,
 		Image: image,
-	}, &containertypes.HostConfig{}, nil, container)
+	}, &containertypes.HostConfig{}, nil, nil, container)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	err = c.ContainerStart(context.Background(), b.ID, types.ContainerStartOptions{})
 	err = c.ContainerStart(context.Background(), b.ID, types.ContainerStartOptions{})
 	assert.NilError(t, err)
 	assert.NilError(t, err)