Forráskód Böngészése

LCOW: API change JSON header to string POST parameter

Signed-off-by: John Howard <jhoward@microsoft.com>
John Howard 7 éve
szülő
commit
d98ecf2d6c

+ 0 - 30
api/server/httputils/httputils.go

@@ -1,17 +1,11 @@
 package httputils
 
 import (
-	"encoding/json"
-	"fmt"
 	"io"
 	"mime"
 	"net/http"
-	"runtime"
 	"strings"
 
-	"github.com/docker/docker/api/types/versions"
-	"github.com/docker/docker/pkg/system"
-	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
@@ -115,27 +109,3 @@ func matchesContentType(contentType, expectedType string) bool {
 	}
 	return err == nil && mimetype == expectedType
 }
-
-// GetRequestedPlatform extracts an optional platform structure from an HTTP request header
-func GetRequestedPlatform(ctx context.Context, r *http.Request) (*specs.Platform, error) {
-	platform := &specs.Platform{}
-	version := VersionFromContext(ctx)
-	if versions.GreaterThanOrEqualTo(version, "1.32") {
-		requestedPlatform := r.Header.Get("X-Requested-Platform")
-		if requestedPlatform != "" {
-			if err := json.Unmarshal([]byte(requestedPlatform), platform); err != nil {
-				return nil, fmt.Errorf("invalid X-Requested-Platform header: %s", err)
-			}
-		}
-		if err := system.ValidatePlatform(platform); err != nil {
-			return nil, err
-		}
-	}
-	if platform.OS == "" {
-		platform.OS = runtime.GOOS
-	}
-	if platform.Architecture == "" {
-		platform.Architecture = runtime.GOARCH
-	}
-	return platform, nil
-}

+ 20 - 6
api/server/router/build/build_routes.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"os"
 	"runtime"
 	"strconv"
 	"strings"
@@ -20,6 +21,7 @@ import (
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/streamformatter"
+	"github.com/docker/docker/pkg/system"
 	units "github.com/docker/go-units"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
@@ -67,6 +69,24 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
 	options.Squash = httputils.BoolValue(r, "squash")
 	options.Target = r.FormValue("target")
 	options.RemoteContext = r.FormValue("remote")
+	if versions.GreaterThanOrEqualTo(version, "1.32") {
+		// TODO @jhowardmsft. The following environment variable is an interim
+		// measure to allow the daemon to have a default platform if omitted by
+		// the client. This allows LCOW and WCOW to work with a down-level CLI
+		// for a short period of time, as the CLI changes can't be merged
+		// until after the daemon changes have been merged. Once the CLI is
+		// updated, this can be removed. PR for CLI is currently in
+		// https://github.com/docker/cli/pull/474.
+		apiPlatform := r.FormValue("platform")
+		if system.LCOWSupported() && apiPlatform == "" {
+			apiPlatform = os.Getenv("LCOW_API_PLATFORM_IF_OMITTED")
+		}
+		p := system.ParsePlatform(apiPlatform)
+		if err := system.ValidatePlatform(p); err != nil {
+			return nil, validationError{fmt.Errorf("invalid platform: %s", err)}
+		}
+		options.Platform = p.OS
+	}
 
 	if r.Form.Get("shmsize") != "" {
 		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
@@ -87,12 +107,6 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
 		return nil, validationError{fmt.Errorf("The daemon on this platform does not support setting security options on build")}
 	}
 
-	platform, err := httputils.GetRequestedPlatform(ctx, r)
-	if err != nil {
-		return nil, err
-	}
-	options.Platform = *platform
-
 	var buildUlimits = []*units.Ulimit{}
 	ulimitsJSON := r.FormValue("ulimits")
 	if ulimitsJSON != "" {

+ 23 - 1
api/server/router/image/image_routes.go

@@ -3,8 +3,10 @@ package image
 import (
 	"encoding/base64"
 	"encoding/json"
+	"fmt"
 	"io"
 	"net/http"
+	"os"
 	"strconv"
 	"strings"
 
@@ -16,6 +18,7 @@ import (
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/streamformatter"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/registry"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
@@ -70,6 +73,7 @@ func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *
 
 // Creates an image from Pull or from Import
 func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 	}
@@ -87,7 +91,25 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
 
 	w.Header().Set("Content-Type", "application/json")
 
-	platform, err = httputils.GetRequestedPlatform(ctx, r)
+	version := httputils.VersionFromContext(ctx)
+	if versions.GreaterThanOrEqualTo(version, "1.32") {
+		// TODO @jhowardmsft. The following environment variable is an interim
+		// measure to allow the daemon to have a default platform if omitted by
+		// the client. This allows LCOW and WCOW to work with a down-level CLI
+		// for a short period of time, as the CLI changes can't be merged
+		// until after the daemon changes have been merged. Once the CLI is
+		// updated, this can be removed. PR for CLI is currently in
+		// https://github.com/docker/cli/pull/474.
+		apiPlatform := r.FormValue("platform")
+		if system.LCOWSupported() && apiPlatform == "" {
+			apiPlatform = os.Getenv("LCOW_API_PLATFORM_IF_OMITTED")
+		}
+		platform = system.ParsePlatform(apiPlatform)
+		if err = system.ValidatePlatform(platform); err != nil {
+			err = fmt.Errorf("invalid platform: %s", err)
+		}
+	}
+
 	if err == nil {
 		if image != "" { //pull
 			metaHeaders := map[string][]string{}

+ 6 - 22
api/swagger.yaml

@@ -6181,17 +6181,9 @@ paths:
 
             Only the registry domain name (and port if not the default 443) are required. However, for legacy reasons, the Docker Hub registry must be specified with both a `https://` prefix and a `/v1/` suffix even though Docker will prefer to use the v2 registry API.
           type: "string"
-        - name: "X-Requested-Platform"
-          in: "header"
-          description: |
-            This is a JSON object representing an OCI image-spec `Platform` object. It is used to request a platform in the case that the engine supports multiple platforms. For example:
-
-            ```
-            {
-              "architecture": "amd64",
-              "os": "linux"
-            }
-            ```
+        - name: "platform"
+          in: "query"
+          description: "Platform in the format os[/arch[/variant]]"
           type: "string"
           default: ""
       responses:
@@ -6275,17 +6267,9 @@ paths:
           in: "header"
           description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)"
           type: "string"
-        - name: "X-Requested-Platform"
-          in: "header"
-          description: |
-            This is a JSON object representing an OCI image-spec `Platform` object. It is used to request a platform in the case that the engine supports multiple platforms. For example:
-
-            ```
-            {
-              "architecture": "amd64",
-              "os": "linux"
-            }
-            ```
+        - name: "platform"
+          in: "query"
+          description: "Platform in the format os[/arch[/variant]]"
           type: "string"
           default: ""
       tags: ["Image"]

+ 8 - 8
api/types/client.go

@@ -8,7 +8,6 @@ import (
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
 	units "github.com/docker/go-units"
-	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 // CheckpointCreateOptions holds parameters to create a checkpoint from a container
@@ -180,7 +179,7 @@ type ImageBuildOptions struct {
 	ExtraHosts  []string // List of extra hosts
 	Target      string
 	SessionID   string
-	Platform    specs.Platform
+	Platform    string
 }
 
 // ImageBuildResponse holds information
@@ -193,8 +192,8 @@ type ImageBuildResponse struct {
 
 // ImageCreateOptions holds information to create images.
 type ImageCreateOptions struct {
-	RegistryAuth string         // RegistryAuth is the base64 encoded credentials for the registry.
-	Platform     specs.Platform // Platform is the target platform of the image if it needs to be pulled from the registry.
+	RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
+	Platform     string // Platform is the target platform of the image if it needs to be pulled from the registry.
 }
 
 // ImageImportSource holds source information for ImageImport
@@ -205,9 +204,10 @@ type ImageImportSource struct {
 
 // ImageImportOptions holds information to import images from the client host.
 type ImageImportOptions struct {
-	Tag     string   // Tag is the name to tag this image with. This attribute is deprecated.
-	Message string   // Message is the message to tag the image with
-	Changes []string // Changes are the raw changes to apply to this image
+	Tag      string   // Tag is the name to tag this image with. This attribute is deprecated.
+	Message  string   // Message is the message to tag the image with
+	Changes  []string // Changes are the raw changes to apply to this image
+	Platform string   // Platform is the target platform of the image
 }
 
 // ImageListOptions holds parameters to filter the list of images with.
@@ -228,7 +228,7 @@ type ImagePullOptions struct {
 	All           bool
 	RegistryAuth  string // RegistryAuth is the base64 encoded credentials for the registry
 	PrivilegeFunc RequestPrivilegeFunc
-	Platform      specs.Platform
+	Platform      string
 }
 
 // RequestPrivilegeFunc is a function interface that

+ 2 - 1
api/types/types.go

@@ -15,6 +15,7 @@ import (
 	"github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/go-connections/nat"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 // RootFS returns Image's RootFS description including the layer IDs.
@@ -327,7 +328,7 @@ type ContainerJSONBase struct {
 	Name            string
 	RestartCount    int
 	Driver          string
-	OS              string
+	Platform        specs.Platform
 	MountLabel      string
 	ProcessLabel    string
 	AppArmorProfile string

+ 6 - 4
builder/dockerfile/builder.go

@@ -20,6 +20,7 @@ import (
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/pkg/system"
 	"github.com/moby/buildkit/session"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
@@ -102,15 +103,16 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
 	}
 
 	os := runtime.GOOS
+	optionsPlatform := system.ParsePlatform(config.Options.Platform)
 	if dockerfile.OS != "" {
-		if config.Options.Platform.OS != "" && config.Options.Platform.OS != dockerfile.OS {
+		if optionsPlatform.OS != "" && optionsPlatform.OS != dockerfile.OS {
 			return nil, fmt.Errorf("invalid platform")
 		}
 		os = dockerfile.OS
-	} else if config.Options.Platform.OS != "" {
-		os = config.Options.Platform.OS
+	} else if optionsPlatform.OS != "" {
+		os = optionsPlatform.OS
 	}
-	config.Options.Platform.OS = os
+	config.Options.Platform = os
 	dockerfile.OS = os
 
 	builderOptions := builderOptions{

+ 1 - 1
builder/dockerfile/copy.go

@@ -82,7 +82,7 @@ func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, i
 		pathCache:   req.builder.pathCache,
 		download:    download,
 		imageSource: imageSource,
-		platform:    req.builder.options.Platform.OS,
+		platform:    req.builder.options.Platform,
 	}
 }
 

+ 13 - 13
builder/dockerfile/dispatchers.go

@@ -194,11 +194,6 @@ func dispatchTriggeredOnBuild(d dispatchRequest, triggers []string) error {
 	return nil
 }
 
-// scratchImage is used as a token for the empty base image. It uses buildStage
-// as a convenient implementation of builder.Image, but is not actually a
-// buildStage.
-var scratchImage builder.Image = &image.Image{}
-
 func (d *dispatchRequest) getExpandedImageName(shlex *ShellLex, name string) (string, error) {
 	substitutionArgs := []string{}
 	for key, value := range d.state.buildArgs.GetAllMeta() {
@@ -223,8 +218,9 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
 		imageImage := &image.Image{}
 		imageImage.OS = runtime.GOOS
 		if runtime.GOOS == "windows" {
-			switch d.builder.options.Platform.OS {
-			case "windows":
+			optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
+			switch optionsOS {
+			case "windows", "":
 				return nil, errors.New("Windows does not support FROM scratch")
 			case "linux":
 				if !system.LCOWSupported() {
@@ -232,7 +228,7 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
 				}
 				imageImage.OS = "linux"
 			default:
-				return nil, errors.Errorf("operating system %q is not supported", d.builder.options.Platform.OS)
+				return nil, errors.Errorf("operating system %q is not supported", optionsOS)
 			}
 		}
 		return builder.Image(imageImage), nil
@@ -264,7 +260,8 @@ func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error {
 func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
 	runConfig := d.state.runConfig
 	var err error
-	runConfig.WorkingDir, err = normalizeWorkdir(d.builder.options.Platform.OS, runConfig.WorkingDir, c.Path)
+	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
+	runConfig.WorkingDir, err = normalizeWorkdir(optionsOS, runConfig.WorkingDir, c.Path)
 	if err != nil {
 		return err
 	}
@@ -280,7 +277,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
 	}
 
 	comment := "WORKDIR " + runConfig.WorkingDir
-	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.builder.options.Platform.OS))
+	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, optionsOS))
 	containerID, err := d.builder.probeAndCreate(d.state, runConfigWithCommentCmd)
 	if err != nil || containerID == "" {
 		return err
@@ -313,7 +310,8 @@ func resolveCmdLine(cmd instructions.ShellDependantCmdLine, runConfig *container
 func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
 
 	stateRunConfig := d.state.runConfig
-	cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.builder.options.Platform.OS)
+	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
+	cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, optionsOS)
 	buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env)
 
 	saveCmd := cmdFromArgs
@@ -390,7 +388,8 @@ func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.S
 //
 func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
 	runConfig := d.state.runConfig
-	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
+	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS)
 	runConfig.Cmd = cmd
 	// set config as already being escaped, this prevents double escaping on windows
 	runConfig.ArgsEscaped = true
@@ -433,7 +432,8 @@ func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand)
 //
 func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error {
 	runConfig := d.state.runConfig
-	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
+	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS)
 	runConfig.Entrypoint = cmd
 	if !d.state.cmdSet {
 		runConfig.Cmd = nil

+ 2 - 3
builder/dockerfile/dispatchers_test.go

@@ -14,7 +14,6 @@ import (
 	"github.com/docker/docker/builder/dockerfile/instructions"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/go-connections/nat"
-	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -23,13 +22,13 @@ func newBuilderWithMockBackend() *Builder {
 	mockBackend := &MockBackend{}
 	ctx := context.Background()
 	b := &Builder{
-		options:       &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
+		options:       &types.ImageBuildOptions{Platform: runtime.GOOS},
 		docker:        mockBackend,
 		Stdout:        new(bytes.Buffer),
 		clientCtx:     ctx,
 		disableCommit: true,
 		imageSources: newImageSources(ctx, builderOptions{
-			Options: &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
+			Options: &types.ImageBuildOptions{Platform: runtime.GOOS},
 			Backend: mockBackend,
 		}),
 		imageProber:      newImageProber(mockBackend, nil, runtime.GOOS, false),

+ 2 - 1
builder/dockerfile/evaluator.go

@@ -34,7 +34,8 @@ import (
 
 func dispatch(d dispatchRequest, cmd instructions.Command) error {
 	if c, ok := cmd.(instructions.PlatformSpecific); ok {
-		err := c.CheckPlatform(d.builder.options.Platform.OS)
+		optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
+		err := c.CheckPlatform(optionsOS)
 		if err != nil {
 			return validationError{err}
 		}

+ 3 - 1
builder/dockerfile/imagecontext.go

@@ -5,6 +5,7 @@ import (
 	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder/remotecontext"
 	dockerimage "github.com/docker/docker/image"
+	"github.com/docker/docker/pkg/system"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
@@ -30,11 +31,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
 				pullOption = backend.PullOptionPreferLocal
 			}
 		}
+		optionsPlatform := system.ParsePlatform(options.Options.Platform)
 		return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{
 			PullOption: pullOption,
 			AuthConfig: options.Options.AuthConfigs,
 			Output:     options.ProgressWriter.Output,
-			OS:         options.Options.Platform.OS,
+			OS:         optionsPlatform.OS,
 		})
 	}
 

+ 11 - 6
builder/dockerfile/internals.go

@@ -83,7 +83,8 @@ func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
 		return errors.New("Please provide a source image with `from` prior to commit")
 	}
 
-	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, b.options.Platform.OS))
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
+	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, optionsPlatform.OS))
 	hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
 	if err != nil || hit {
 		return err
@@ -122,7 +123,8 @@ func (b *Builder) commitContainer(dispatchState *dispatchState, id string, conta
 }
 
 func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runConfig *container.Config) error {
-	newLayer, err := imageMount.Layer().Commit(b.options.Platform.OS)
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
+	newLayer, err := imageMount.Layer().Commit(optionsPlatform.OS)
 	if err != nil {
 		return err
 	}
@@ -170,9 +172,10 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
 	commentStr := fmt.Sprintf("%s %s%s in %s ", inst.cmdName, chownComment, srcHash, inst.dest)
 
 	// TODO: should this have been using origPaths instead of srcHash in the comment?
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
 	runConfigWithCommentCmd := copyRunConfig(
 		state.runConfig,
-		withCmdCommentString(commentStr, b.options.Platform.OS))
+		withCmdCommentString(commentStr, optionsPlatform.OS))
 	hit, err := b.probeCache(state, runConfigWithCommentCmd)
 	if err != nil || hit {
 		return err
@@ -183,7 +186,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
 		return errors.Wrapf(err, "failed to get destination image %q", state.imageID)
 	}
 
-	destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, imageMount, b.options.Platform.OS)
+	destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, imageMount, b.options.Platform)
 	if err != nil {
 		return err
 	}
@@ -463,13 +466,15 @@ func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *contai
 	}
 	// Set a log config to override any default value set on the daemon
 	hostConfig := &container.HostConfig{LogConfig: defaultLogConfig}
-	container, err := b.containerManager.Create(runConfig, hostConfig, b.options.Platform.OS)
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
+	container, err := b.containerManager.Create(runConfig, hostConfig, optionsPlatform.OS)
 	return container.ID, err
 }
 
 func (b *Builder) create(runConfig *container.Config) (string, error) {
 	hostConfig := hostConfigFromOptions(b.options)
-	container, err := b.containerManager.Create(runConfig, hostConfig, b.options.Platform.OS)
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
+	container, err := b.containerManager.Create(runConfig, hostConfig, optionsPlatform.OS)
 	if err != nil {
 		return "", err
 	}

+ 6 - 10
client/image_build.go

@@ -7,12 +7,12 @@ import (
 	"net/http"
 	"net/url"
 	"strconv"
+	"strings"
 
 	"golang.org/x/net/context"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
-	"github.com/docker/docker/pkg/system"
 )
 
 // ImageBuild sends request to the daemon to build images.
@@ -31,18 +31,11 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
 	}
 	headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
 
-	// TODO @jhowardmsft: system.IsPlatformEmpty is a temporary function. We need to move
-	// (in the reasonably short future) to a package which supports all the platform
-	// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
-	if !system.IsPlatformEmpty(options.Platform) {
+	if options.Platform != "" {
 		if err := cli.NewVersionError("1.32", "platform"); err != nil {
 			return types.ImageBuildResponse{}, err
 		}
-		platformJSON, err := json.Marshal(options.Platform)
-		if err != nil {
-			return types.ImageBuildResponse{}, err
-		}
-		headers.Add("X-Requested-Platform", string(platformJSON[:]))
+		query.Set("platform", options.Platform)
 	}
 	headers.Set("Content-Type", "application/x-tar")
 
@@ -138,5 +131,8 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
 	if options.SessionID != "" {
 		query.Set("session", options.SessionID)
 	}
+	if options.Platform != "" {
+		query.Set("platform", strings.ToLower(options.Platform))
+	}
 	return query, nil
 }

+ 6 - 16
client/image_create.go

@@ -1,16 +1,14 @@
 package client
 
 import (
-	"encoding/json"
 	"io"
 	"net/url"
+	"strings"
 
 	"golang.org/x/net/context"
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
-	"github.com/docker/docker/pkg/system"
-	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 // ImageCreate creates a new image based in the parent options.
@@ -24,25 +22,17 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
 	query := url.Values{}
 	query.Set("fromImage", reference.FamiliarName(ref))
 	query.Set("tag", getAPITagFromNamedRef(ref))
-	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
+	if options.Platform != "" {
+		query.Set("platform", strings.ToLower(options.Platform))
+	}
+	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
 	if err != nil {
 		return nil, err
 	}
 	return resp.body, nil
 }
 
-func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string, platform specs.Platform) (serverResponse, error) {
+func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
 	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
-
-	// TODO @jhowardmsft: system.IsPlatformEmpty is a temporary function. We need to move
-	// (in the reasonably short future) to a package which supports all the platform
-	// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
-	if !system.IsPlatformEmpty(platform) {
-		platformJSON, err := json.Marshal(platform)
-		if err != nil {
-			return serverResponse{}, err
-		}
-		headers["X-Requested-Platform"] = []string{string(platformJSON[:])}
-	}
 	return cli.post(ctx, "/images/create", query, nil, headers)
 }

+ 4 - 0
client/image_import.go

@@ -3,6 +3,7 @@ package client
 import (
 	"io"
 	"net/url"
+	"strings"
 
 	"golang.org/x/net/context"
 
@@ -25,6 +26,9 @@ func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSour
 	query.Set("repo", ref)
 	query.Set("tag", options.Tag)
 	query.Set("message", options.Message)
+	if options.Platform != "" {
+		query.Set("platform", strings.ToLower(options.Platform))
+	}
 	for _, change := range options.Changes {
 		query.Add("changes", change)
 	}

+ 5 - 18
client/image_pull.go

@@ -4,12 +4,12 @@ import (
 	"io"
 	"net/http"
 	"net/url"
+	"strings"
 
 	"golang.org/x/net/context"
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
-	"github.com/docker/docker/pkg/system"
 )
 
 // ImagePull requests the docker host to pull an image from a remote registry.
@@ -31,30 +31,17 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options types.I
 	if !options.All {
 		query.Set("tag", getAPITagFromNamedRef(ref))
 	}
-
-	// TODO 1: Extend to include "and the platform is supported by the daemon".
-	// This is dependent on https://github.com/moby/moby/pull/34628 though,
-	// and the daemon returning the set of platforms it supports via the _ping
-	// API endpoint.
-	//
-	// TODO 2: system.IsPlatformEmpty is a temporary function. We need to move
-	// (in the reasonably short future) to a package which supports all the platform
-	// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
-	//
-	// @jhowardmsft.
-	if !system.IsPlatformEmpty(options.Platform) {
-		if err := cli.NewVersionError("1.32", "platform"); err != nil {
-			return nil, err
-		}
+	if options.Platform != "" {
+		query.Set("platform", strings.ToLower(options.Platform))
 	}
 
-	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
+	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
 	if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
 		newAuthHeader, privilegeErr := options.PrivilegeFunc()
 		if privilegeErr != nil {
 			return nil, privilegeErr
 		}
-		resp, err = cli.tryImageCreate(ctx, query, newAuthHeader, options.Platform)
+		resp, err = cli.tryImageCreate(ctx, query, newAuthHeader)
 	}
 	if err != nil {
 		return nil, err

+ 3 - 3
container/container_unix.go

@@ -66,7 +66,7 @@ func (container *Container) BuildHostnameFile() error {
 func (container *Container) NetworkMounts() []Mount {
 	var mounts []Mount
 	shared := container.HostConfig.NetworkMode.IsContainer()
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 	if container.ResolvConfPath != "" {
 		if _, err := os.Stat(container.ResolvConfPath); err != nil {
 			logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
@@ -195,7 +195,7 @@ func (container *Container) UnmountIpcMount(unmount func(pth string) error) erro
 // IpcMounts returns the list of IPC mounts
 func (container *Container) IpcMounts() []Mount {
 	var mounts []Mount
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 
 	if container.HasMountFor("/dev/shm") {
 		return mounts
@@ -429,7 +429,7 @@ func copyOwnership(source, destination string) error {
 
 // TmpfsMounts returns the list of tmpfs mounts
 func (container *Container) TmpfsMounts() ([]Mount, error) {
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 	var mounts []Mount
 	for dest, data := range container.HostConfig.Tmpfs {
 		mounts = append(mounts, Mount{

+ 1 - 1
daemon/archive_unix.go

@@ -12,7 +12,7 @@ import (
 // cannot be configured with a read-only rootfs.
 func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
 	var toVolume bool
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 	for _, mnt := range container.MountPoints {
 		if toVolume = parser.HasResource(mnt, absPath); toVolume {
 			if mnt.RW {

+ 2 - 1
daemon/inspect.go

@@ -13,6 +13,7 @@ import (
 	"github.com/docker/docker/daemon/network"
 	volumestore "github.com/docker/docker/volume/store"
 	"github.com/docker/go-connections/nat"
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 // ContainerInspect returns low-level information about a
@@ -171,7 +172,7 @@ func (daemon *Daemon) getInspectData(container *container.Container) (*types.Con
 		Name:         container.Name,
 		RestartCount: container.RestartCount,
 		Driver:       container.Driver,
-		OS:           container.OS,
+		Platform:     specs.Platform{OS: container.OS},
 		MountLabel:   container.MountLabel,
 		ProcessLabel: container.ProcessLabel,
 		ExecIDs:      container.GetExecIDs(),

+ 4 - 1
distribution/pull_v1.go

@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/docker/distribution/reference"
+	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/distribution/registry/client/transport"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/xfer"
@@ -68,7 +69,9 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named, platform strin
 	return nil
 }
 
-func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) error {
+// Note use auth.Scope rather than reference.Named due to this warning causing Jenkins CI to fail:
+// warning: ref can be github.com/docker/docker/vendor/github.com/docker/distribution/registry/client/auth.Scope (interfacer)
+func (p *v1Puller) pullRepository(ctx context.Context, ref auth.Scope) error {
 	progress.Message(p.config.ProgressOutput, "", "Pulling repository "+p.repoInfo.Name.Name())
 
 	tagged, isTagged := ref.(reference.NamedTagged)

+ 3 - 3
distribution/pull_v2.go

@@ -510,7 +510,7 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
 
 	// Early bath if the requested OS doesn't match that of the configuration.
 	// This avoids doing the download, only to potentially fail later.
-	if !strings.EqualFold(string(configOS), requestedOS) {
+	if !strings.EqualFold(configOS, requestedOS) {
 		return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
 	}
 
@@ -651,7 +651,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 	}
 
 	if configJSON == nil {
-		configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
+		configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
 		if err == nil && configRootFS == nil {
 			err = errRootFSInvalid
 		}
@@ -723,7 +723,7 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
 
 	logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), os, runtime.GOARCH)
 
-	manifestMatches := filterManifests(mfstList.Manifests)
+	manifestMatches := filterManifests(mfstList.Manifests, os)
 
 	if len(manifestMatches) == 0 {
 		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", os, runtime.GOARCH)

+ 3 - 3
distribution/pull_v2_unix.go

@@ -16,13 +16,13 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
 	return blobs.Open(ctx, ld.digest)
 }
 
-func filterManifests(manifests []manifestlist.ManifestDescriptor) []manifestlist.ManifestDescriptor {
+func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
 	var matches []manifestlist.ManifestDescriptor
 	for _, manifestDescriptor := range manifests {
-		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
+		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
 			matches = append(matches, manifestDescriptor)
 
-			logrus.Debugf("found match for %s/%s with media type %s, digest %s", runtime.GOOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
+			logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
 		}
 	}
 	return matches

+ 13 - 14
distribution/pull_v2_windows.go

@@ -62,29 +62,28 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
 	return rsc, err
 }
 
-func filterManifests(manifests []manifestlist.ManifestDescriptor) []manifestlist.ManifestDescriptor {
-	version := system.GetOSVersion()
-
-	// TODO @jhowardmsft LCOW Support: Need to remove the hard coding in LCOW mode.
-	lookingForOS := runtime.GOOS
-	osVersion := fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
-	if system.LCOWSupported() {
-		lookingForOS = "linux"
-		osVersion = ""
+func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
+	osVersion := ""
+	if os == "windows" {
+		version := system.GetOSVersion()
+		osVersion = fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
+		logrus.Debugf("will only match entries with version %s", osVersion)
 	}
 
 	var matches []manifestlist.ManifestDescriptor
 	for _, manifestDescriptor := range manifests {
-		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == lookingForOS {
-			if lookingForOS == "windows" && !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
+		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
+			if os == "windows" && !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
+				logrus.Debugf("skipping %s", manifestDescriptor.Platform.OSVersion)
 				continue
 			}
 			matches = append(matches, manifestDescriptor)
-
-			logrus.Debugf("found match for %s/%s with media type %s, digest %s", lookingForOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
+			logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
 		}
 	}
-	sort.Stable(manifestsByVersion(matches))
+	if os == "windows" {
+		sort.Stable(manifestsByVersion(matches))
+	}
 	return matches
 }
 

+ 1 - 0
distribution/registry_unit_test.go

@@ -4,6 +4,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/url"
+	"runtime"
 	"strings"
 	"testing"
 

+ 6 - 6
image/image_test.go

@@ -62,7 +62,7 @@ func TestImage(t *testing.T) {
 		Domainname: "domain",
 		User:       "root",
 	}
-	platform := runtime.GOOS
+	os := runtime.GOOS
 
 	img := &Image{
 		V1Image: V1Image{
@@ -73,19 +73,19 @@ func TestImage(t *testing.T) {
 
 	assert.Equal(t, cid, img.ImageID())
 	assert.Equal(t, cid, img.ID().String())
-	assert.Equal(t, platform, img.Platform())
+	assert.Equal(t, os, img.OperatingSystem())
 	assert.Equal(t, config, img.RunConfig())
 }
 
-func TestImagePlatformNotEmpty(t *testing.T) {
-	platform := "platform"
+func TestImageOSNotEmpty(t *testing.T) {
+	os := "os"
 	img := &Image{
 		V1Image: V1Image{
-			OS: platform,
+			OS: os,
 		},
 		OSVersion: "osversion",
 	}
-	assert.Equal(t, platform, img.Platform())
+	assert.Equal(t, os, img.OperatingSystem())
 }
 
 func TestNewChildImageFromImageWithRootFS(t *testing.T) {

+ 3 - 16
pkg/system/lcow.go

@@ -8,21 +8,6 @@ import (
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
-// IsPlatformEmpty determines if an OCI image-spec platform structure is not populated.
-// TODO This is a temporary function - can be replaced by parsing from
-// https://github.com/containerd/containerd/pull/1403/files at a later date.
-// @jhowardmsft
-func IsPlatformEmpty(platform specs.Platform) bool {
-	if platform.Architecture == "" &&
-		platform.OS == "" &&
-		len(platform.OSFeatures) == 0 &&
-		platform.OSVersion == "" &&
-		platform.Variant == "" {
-		return true
-	}
-	return false
-}
-
 // ValidatePlatform determines if a platform structure is valid.
 // TODO This is a temporary function - can be replaced by parsing from
 // https://github.com/containerd/containerd/pull/1403/files at a later date.
@@ -30,7 +15,9 @@ func IsPlatformEmpty(platform specs.Platform) bool {
 func ValidatePlatform(platform *specs.Platform) error {
 	platform.Architecture = strings.ToLower(platform.Architecture)
 	platform.OS = strings.ToLower(platform.OS)
-	if platform.Architecture != "" && platform.Architecture != runtime.GOARCH {
+	// Based on https://github.com/moby/moby/pull/34642#issuecomment-330375350, do
+	// not support anything except operating system.
+	if platform.Architecture != "" {
 		return fmt.Errorf("invalid platform architecture %q", platform.Architecture)
 	}
 	if platform.OS != "" {