Skip to content

Commit

Permalink
Merge pull request #40725 from cpuguy83/check_img_platform
Browse files Browse the repository at this point in the history
Accept platform spec on container create
  • Loading branch information
tiborvass authored May 21, 2020
2 parents e5a679c + 7a9cb29 commit 5c10ea6
Show file tree
Hide file tree
Showing 28 changed files with 188 additions and 62 deletions.
25 changes: 25 additions & 0 deletions api/server/router/container/container_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions api/types/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,6 +16,7 @@ type ContainerCreateConfig struct {
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
Platform *specs.Platform
AdjustCPUShares bool
}

Expand Down
13 changes: 12 additions & 1 deletion client/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down
12 changes: 6 additions & 6 deletions client/container_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
}
3 changes: 2 additions & 1 deletion client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions daemon/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/images/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 36 additions & 1 deletion daemon/images/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions daemon/images/image_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/images/image_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/images/image_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
4 changes: 2 additions & 2 deletions daemon/images/image_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/images/image_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/images/image_tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions daemon/images/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ 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 {
return nil, err
}

err = imageFilters.WalkValues("since", func(value string) error {
sinceFilter, err = i.GetImage(value)
sinceFilter, err = i.GetImage(value, nil)
return err
})
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions daemon/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/oci_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit 5c10ea6

Please sign in to comment.