Merge pull request #40725 from cpuguy83/check_img_platform

Accept platform spec on container create
This commit is contained in:
Tibor Vass 2020-05-21 11:33:27 -07:00 committed by GitHub
commit 5c10ea6ae8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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"
@ -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 {
// Don't set a limit if either no limit was specified, or "unlimited" was
// explicitly set.
@ -516,6 +540,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
@ -581,7 +581,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

@ -77,7 +77,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)