Accept platform spec on container create

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

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
This commit is contained in:
Brian Goff 2020-03-19 13:54:48 -07:00
parent 30d54e64f6
commit 7a9cb29fb9
28 changed files with 188 additions and 62 deletions

View File

@ -9,6 +9,7 @@ import (
"strconv"
"syscall"
"github.com/containerd/containerd/platforms"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend"
@ -19,6 +20,7 @@ import (
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/signal"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/websocket"
@ -497,6 +499,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 {
// Don't set a limit if either no limit was specified, or "unlimited" was
// explicitly set.
@ -511,6 +535,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
AdjustCPUShares: adjustCPUShares,
Platform: platform,
})
if err != nil {
return err

View File

@ -3,6 +3,7 @@ package types // import "github.com/docker/docker/api/types"
import (
"github.com/docker/docker/api/types/container"
"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
@ -15,6 +16,7 @@ type ContainerCreateConfig struct {
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
Platform *specs.Platform
AdjustCPUShares bool
}

View File

@ -5,20 +5,23 @@ import (
"encoding/json"
"net/url"
"github.com/containerd/containerd/platforms"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/versions"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
type configWrapper struct {
*container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
Platform *specs.Platform
}
// ContainerCreate creates a new container based in the given configuration.
// 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
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
}
if err := cli.NewVersionError("1.41", "specify container image platform"); platform != nil && err != nil {
return response, err
}
query := url.Values{}
if platform != nil {
query.Set("platform", platforms.Format(*platform))
}
if containerName != "" {
query.Set("name", containerName)
}

View File

@ -18,7 +18,7 @@ func TestContainerCreateError(t *testing.T) {
client := &Client{
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) {
t.Fatalf("expected a Server Error while testing StatusInternalServerError, got %T", err)
}
@ -27,7 +27,7 @@ func TestContainerCreateError(t *testing.T) {
client = &Client{
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) {
t.Fatalf("expected a Server Error while testing StatusNotFound, got %T", err)
}
@ -37,7 +37,7 @@ func TestContainerCreateImageNotFound(t *testing.T) {
client := &Client{
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) {
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 {
t.Fatal(err)
}
@ -106,14 +106,14 @@ func TestContainerCreateAutoRemove(t *testing.T) {
client: newMockClient(autoRemoveValidator(false)),
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)
}
client = &Client{
client: newMockClient(autoRemoveValidator(true)),
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)
}
}

View File

@ -16,6 +16,7 @@ import (
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm"
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.
@ -47,7 +48,7 @@ type CommonAPIClient interface {
type ContainerAPIClient interface {
ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, 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)
ContainerExecAttach(ctx context.Context, execID string, config types.ExecStartCheck) (types.HijackedResponse, error)
ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error)

View File

@ -60,7 +60,7 @@ func (daemon *Daemon) containerCreate(opts createOpts) (containertypes.Container
os := runtime.GOOS
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 {
os = img.OS
}
@ -114,7 +114,7 @@ func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr
os := runtime.GOOS
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 {
return nil, err
}

View File

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

View File

@ -3,9 +3,12 @@ package images // import "github.com/docker/docker/daemon/images"
import (
"fmt"
"github.com/pkg/errors"
"github.com/docker/distribution/reference"
"github.com/docker/docker/errdefs"
"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.
@ -25,7 +28,39 @@ func (e ErrImageDoesNotExist) Error() string {
func (e ErrImageDoesNotExist) NotFound() {}
// 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)
if err != nil {
return nil, errdefs.InvalidParameter(err)

View File

@ -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 {
return nil, err
}
return i.GetImage(name)
return i.GetImage(name, platform)
}
// 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 {
image, err := i.GetImage(refOrID)
image, err := i.GetImage(refOrID, opts.Platform)
if err != nil && opts.PullOption == backend.PullOptionNoPull {
return nil, nil, err
}

View File

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

View File

@ -11,7 +11,7 @@ func (i *ImageService) LogImageEvent(imageID, refName, action string) {
// LogImageEventWithAttributes generates an event related to an image with specific given attributes.
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 {
// image has not been removed yet.
// it could be missing if the event is `delete`.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ const (
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 {
return nil, err
}

View File

@ -523,7 +523,7 @@ func (s *DockerSuite) TestContainerAPIBadPort(c *testing.T) {
assert.NilError(c, err)
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"`)
}
@ -537,7 +537,7 @@ func (s *DockerSuite) TestContainerAPICreate(c *testing.T) {
assert.NilError(c, err)
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)
out, _ := dockerCmd(c, "start", "-a", container.ID)
@ -550,7 +550,7 @@ func (s *DockerSuite) TestContainerAPICreateEmptyConfig(c *testing.T) {
assert.NilError(c, err)
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"
assert.ErrorContains(c, err, expected)
@ -574,7 +574,7 @@ func (s *DockerSuite) TestContainerAPICreateMultipleNetworksConfig(c *testing.T)
assert.NilError(c, err)
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()
// network name order in error message is not deterministic
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)
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)
containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@ -636,7 +636,7 @@ func (s *DockerSuite) TestContainerAPICreateWithCpuSharesCpuset(c *testing.T) {
assert.NilError(c, err)
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)
containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@ -948,7 +948,7 @@ func (s *DockerSuite) TestContainerAPIStart(c *testing.T) {
assert.NilError(c, err)
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)
err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
@ -1272,7 +1272,7 @@ func (s *DockerSuite) TestPostContainerAPICreateWithStringOrSliceEntrypoint(c *t
assert.NilError(c, err)
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)
out, _ := dockerCmd(c, "start", "-a", "echotest")
assert.Equal(c, strings.TrimSpace(out), "hello world")
@ -1299,7 +1299,7 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCmd(c *testing.T)
assert.NilError(c, err)
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)
out, _ := dockerCmd(c, "start", "-a", "echotest")
assert.Equal(c, strings.TrimSpace(out), "hello world")
@ -1342,7 +1342,7 @@ func (s *DockerSuite) TestPostContainersCreateWithStringOrSliceCapAddDrop(c *tes
assert.NilError(c, err)
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)
}
@ -1356,7 +1356,7 @@ func (s *DockerSuite) TestContainerAPICreateNoHostConfig118(c *testing.T) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("v1.18"))
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)
}
@ -1407,7 +1407,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T
}
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"
assert.ErrorContains(c, err, expected)
@ -1417,7 +1417,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *testing.T
},
}
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"
assert.ErrorContains(c, err, expected)
}
@ -1436,7 +1436,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *testing.T) {
assert.NilError(c, err)
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")
}
@ -1453,7 +1453,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeHostConfigOmitted(c *testin
assert.NilError(c, err)
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)
containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@ -1480,7 +1480,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeOmitted(c *testing.T) {
assert.NilError(c, err)
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)
containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@ -1511,7 +1511,7 @@ func (s *DockerSuite) TestPostContainersCreateWithShmSize(c *testing.T) {
assert.NilError(c, err)
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)
containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@ -1537,7 +1537,7 @@ func (s *DockerSuite) TestPostContainersCreateMemorySwappinessHostConfigOmitted(
assert.NilError(c, err)
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)
containerJSON, err := cli.ContainerInspect(context.Background(), container.ID)
@ -1568,7 +1568,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *tes
defer cli.Close()
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]"
assert.ErrorContains(c, err, expected)
@ -1578,7 +1578,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *tes
}
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]"
assert.ErrorContains(c, err, expected)
@ -1610,7 +1610,7 @@ func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T) {
assert.NilError(c, err)
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)
err = cli.ContainerStart(context.Background(), name, types.ContainerStartOptions{})
@ -1926,7 +1926,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *testing.T) {
for i, x := range cases {
x := x
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 {
assert.ErrorContains(c, err, x.msg, "%v", cases[i].config)
} else {
@ -1959,7 +1959,7 @@ func (s *DockerSuite) TestContainerAPICreateMountsBindRead(c *testing.T) {
assert.NilError(c, err)
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)
out, _ := dockerCmd(c, "start", "-a", "test")
@ -2106,6 +2106,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *testing.T) {
&containertypes.Config{Image: testImg},
&containertypes.HostConfig{Mounts: []mounttypes.Mount{x.spec}},
&networktypes.NetworkingConfig{},
nil,
"")
assert.NilError(c, err)
@ -2213,7 +2214,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *testing.T) {
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)
out, _ := dockerCmd(c, "start", "-a", cName)
for _, option := range x.expectedOptions {

View File

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

View File

@ -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)

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/docker/api/types"
"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/versions"
"github.com/docker/docker/client"
@ -17,6 +18,7 @@ import (
ctr "github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/oci"
"github.com/docker/docker/testutil/request"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/poll"
@ -57,6 +59,7 @@ func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
&container.Config{Image: tc.image},
&container.HostConfig{},
&network.NetworkingConfig{},
nil,
"",
)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
@ -81,6 +84,7 @@ func TestCreateLinkToNonExistingContainer(t *testing.T) {
Links: []string{"no-such-container"},
},
&network.NetworkingConfig{},
nil,
"",
)
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{},
&network.NetworkingConfig{},
nil,
"",
)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
@ -166,6 +171,7 @@ func TestCreateTmpfsMountsTarget(t *testing.T) {
Tmpfs: map[string]string{tc.target: ""},
},
&network.NetworkingConfig{},
nil,
"",
)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
@ -235,6 +241,7 @@ func TestCreateWithCustomMaskedPaths(t *testing.T) {
&config,
&hc,
&network.NetworkingConfig{},
nil,
name,
)
assert.NilError(t, err)
@ -361,6 +368,7 @@ func TestCreateWithCapabilities(t *testing.T) {
&container.Config{Image: "busybox"},
&tc.hostConfig,
&network.NetworkingConfig{},
nil,
"",
)
if tc.expectedError == "" {
@ -439,6 +447,7 @@ func TestCreateWithCustomReadonlyPaths(t *testing.T) {
&config,
&hc,
&network.NetworkingConfig{},
nil,
name,
)
assert.NilError(t, err)
@ -522,7 +531,7 @@ func TestCreateWithInvalidHealthcheckParams(t *testing.T) {
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))
if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
@ -581,3 +590,34 @@ func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) {
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)
})
}

View File

@ -66,7 +66,7 @@ func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool,
client := testEnv.APIClient()
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.Check(t, is.Equal(len(resp.Warnings), 0))
@ -138,7 +138,7 @@ func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
client := testEnv.APIClient()
// 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.Check(t, is.Equal(len(resp.Warnings), 0))
name1 := resp.ID
@ -148,7 +148,7 @@ func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
// create and start the second container
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.Check(t, is.Equal(len(resp.Warnings), 0))
name2 := resp.ID
@ -204,7 +204,7 @@ func TestAPIIpcModeHost(t *testing.T) {
ctx := context.Background()
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.Check(t, is.Equal(len(resp.Warnings), 0))
name := resp.ID
@ -241,7 +241,7 @@ func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...strin
}
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.Check(t, is.Equal(len(resp.Warnings), 0))

View File

@ -63,7 +63,7 @@ func TestContainerNetworkMountsNoChown(t *testing.T) {
assert.NilError(t, err)
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)
// 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{})
@ -174,7 +174,7 @@ func TestMountDaemonRoot(t *testing.T) {
c, err := client.ContainerCreate(ctx, &containertypes.Config{
Image: "busybox",
Cmd: []string{"true"},
}, hc, nil, "")
}, hc, nil, nil, "")
if err != nil {
if test.expected != "" {

View File

@ -76,7 +76,7 @@ func TestDaemonRestartKillContainers(t *testing.T) {
defer d.Stop(t)
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)
defer client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})

View File

@ -9,6 +9,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/v3/assert"
)
@ -19,6 +20,7 @@ type TestContainerConfig struct {
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
Platform *specs.Platform
}
// 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)
}
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

View File

@ -9,6 +9,7 @@ import (
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/go-connections/nat"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
// 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)
}
}
// WithPlatform specifies the desired platform the image should have.
func WithPlatform(p *specs.Platform) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.Platform = p
}
}

View File

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

View File

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