Browse Source

LCOW: API change JSON header to string POST parameter

Signed-off-by: John Howard <jhoward@microsoft.com>
John Howard 7 years ago
parent
commit
d98ecf2d6c

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

@@ -1,17 +1,11 @@
 package httputils
 package httputils
 
 
 import (
 import (
-	"encoding/json"
-	"fmt"
 	"io"
 	"io"
 	"mime"
 	"mime"
 	"net/http"
 	"net/http"
-	"runtime"
 	"strings"
 	"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/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -115,27 +109,3 @@ func matchesContentType(contentType, expectedType string) bool {
 	}
 	}
 	return err == nil && mimetype == expectedType
 	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"
 	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
+	"os"
 	"runtime"
 	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
@@ -20,6 +21,7 @@ import (
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
+	"github.com/docker/docker/pkg/system"
 	units "github.com/docker/go-units"
 	units "github.com/docker/go-units"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"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.Squash = httputils.BoolValue(r, "squash")
 	options.Target = r.FormValue("target")
 	options.Target = r.FormValue("target")
 	options.RemoteContext = r.FormValue("remote")
 	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") != "" {
 	if r.Form.Get("shmsize") != "" {
 		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
 		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")}
 		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{}
 	var buildUlimits = []*units.Ulimit{}
 	ulimitsJSON := r.FormValue("ulimits")
 	ulimitsJSON := r.FormValue("ulimits")
 	if ulimitsJSON != "" {
 	if ulimitsJSON != "" {

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

@@ -3,8 +3,10 @@ package image
 import (
 import (
 	"encoding/base64"
 	"encoding/base64"
 	"encoding/json"
 	"encoding/json"
+	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
+	"os"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 
 
@@ -16,6 +18,7 @@ import (
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
+	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
 	"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
 // 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 {
 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 {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err
 	}
 	}
@@ -87,7 +91,25 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
 
 
 	w.Header().Set("Content-Type", "application/json")
 	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 err == nil {
 		if image != "" { //pull
 		if image != "" { //pull
 			metaHeaders := map[string][]string{}
 			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.
             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"
           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"
           type: "string"
           default: ""
           default: ""
       responses:
       responses:
@@ -6275,17 +6267,9 @@ paths:
           in: "header"
           in: "header"
           description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)"
           description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)"
           type: "string"
           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"
           type: "string"
           default: ""
           default: ""
       tags: ["Image"]
       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/container"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
 	units "github.com/docker/go-units"
 	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
 // CheckpointCreateOptions holds parameters to create a checkpoint from a container
@@ -180,7 +179,7 @@ type ImageBuildOptions struct {
 	ExtraHosts  []string // List of extra hosts
 	ExtraHosts  []string // List of extra hosts
 	Target      string
 	Target      string
 	SessionID   string
 	SessionID   string
-	Platform    specs.Platform
+	Platform    string
 }
 }
 
 
 // ImageBuildResponse holds information
 // ImageBuildResponse holds information
@@ -193,8 +192,8 @@ type ImageBuildResponse struct {
 
 
 // ImageCreateOptions holds information to create images.
 // ImageCreateOptions holds information to create images.
 type ImageCreateOptions struct {
 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
 // ImageImportSource holds source information for ImageImport
@@ -205,9 +204,10 @@ type ImageImportSource struct {
 
 
 // ImageImportOptions holds information to import images from the client host.
 // ImageImportOptions holds information to import images from the client host.
 type ImageImportOptions struct {
 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.
 // ImageListOptions holds parameters to filter the list of images with.
@@ -228,7 +228,7 @@ type ImagePullOptions struct {
 	All           bool
 	All           bool
 	RegistryAuth  string // RegistryAuth is the base64 encoded credentials for the registry
 	RegistryAuth  string // RegistryAuth is the base64 encoded credentials for the registry
 	PrivilegeFunc RequestPrivilegeFunc
 	PrivilegeFunc RequestPrivilegeFunc
-	Platform      specs.Platform
+	Platform      string
 }
 }
 
 
 // RequestPrivilegeFunc is a function interface that
 // 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/registry"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/go-connections/nat"
 	"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.
 // RootFS returns Image's RootFS description including the layer IDs.
@@ -327,7 +328,7 @@ type ContainerJSONBase struct {
 	Name            string
 	Name            string
 	RestartCount    int
 	RestartCount    int
 	Driver          string
 	Driver          string
-	OS              string
+	Platform        specs.Platform
 	MountLabel      string
 	MountLabel      string
 	ProcessLabel    string
 	ProcessLabel    string
 	AppArmorProfile 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/idtools"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/pkg/system"
 	"github.com/moby/buildkit/session"
 	"github.com/moby/buildkit/session"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
@@ -102,15 +103,16 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
 	}
 	}
 
 
 	os := runtime.GOOS
 	os := runtime.GOOS
+	optionsPlatform := system.ParsePlatform(config.Options.Platform)
 	if dockerfile.OS != "" {
 	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")
 			return nil, fmt.Errorf("invalid platform")
 		}
 		}
 		os = dockerfile.OS
 		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
 	dockerfile.OS = os
 
 
 	builderOptions := builderOptions{
 	builderOptions := builderOptions{

+ 1 - 1
builder/dockerfile/copy.go

@@ -82,7 +82,7 @@ func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, i
 		pathCache:   req.builder.pathCache,
 		pathCache:   req.builder.pathCache,
 		download:    download,
 		download:    download,
 		imageSource: imageSource,
 		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
 	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) {
 func (d *dispatchRequest) getExpandedImageName(shlex *ShellLex, name string) (string, error) {
 	substitutionArgs := []string{}
 	substitutionArgs := []string{}
 	for key, value := range d.state.buildArgs.GetAllMeta() {
 	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 := &image.Image{}
 		imageImage.OS = runtime.GOOS
 		imageImage.OS = runtime.GOOS
 		if runtime.GOOS == "windows" {
 		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")
 				return nil, errors.New("Windows does not support FROM scratch")
 			case "linux":
 			case "linux":
 				if !system.LCOWSupported() {
 				if !system.LCOWSupported() {
@@ -232,7 +228,7 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
 				}
 				}
 				imageImage.OS = "linux"
 				imageImage.OS = "linux"
 			default:
 			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
 		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 {
 func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
 	runConfig := d.state.runConfig
 	runConfig := d.state.runConfig
 	var err error
 	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 {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -280,7 +277,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
 	}
 	}
 
 
 	comment := "WORKDIR " + runConfig.WorkingDir
 	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)
 	containerID, err := d.builder.probeAndCreate(d.state, runConfigWithCommentCmd)
 	if err != nil || containerID == "" {
 	if err != nil || containerID == "" {
 		return err
 		return err
@@ -313,7 +310,8 @@ func resolveCmdLine(cmd instructions.ShellDependantCmdLine, runConfig *container
 func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
 func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
 
 
 	stateRunConfig := d.state.runConfig
 	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)
 	buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env)
 
 
 	saveCmd := cmdFromArgs
 	saveCmd := cmdFromArgs
@@ -390,7 +388,8 @@ func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.S
 //
 //
 func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
 func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
 	runConfig := d.state.runConfig
 	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
 	runConfig.Cmd = cmd
 	// set config as already being escaped, this prevents double escaping on windows
 	// set config as already being escaped, this prevents double escaping on windows
 	runConfig.ArgsEscaped = true
 	runConfig.ArgsEscaped = true
@@ -433,7 +432,8 @@ func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand)
 //
 //
 func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error {
 func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error {
 	runConfig := d.state.runConfig
 	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
 	runConfig.Entrypoint = cmd
 	if !d.state.cmdSet {
 	if !d.state.cmdSet {
 		runConfig.Cmd = nil
 		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/builder/dockerfile/instructions"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
-	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/require"
 )
 )
@@ -23,13 +22,13 @@ func newBuilderWithMockBackend() *Builder {
 	mockBackend := &MockBackend{}
 	mockBackend := &MockBackend{}
 	ctx := context.Background()
 	ctx := context.Background()
 	b := &Builder{
 	b := &Builder{
-		options:       &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
+		options:       &types.ImageBuildOptions{Platform: runtime.GOOS},
 		docker:        mockBackend,
 		docker:        mockBackend,
 		Stdout:        new(bytes.Buffer),
 		Stdout:        new(bytes.Buffer),
 		clientCtx:     ctx,
 		clientCtx:     ctx,
 		disableCommit: true,
 		disableCommit: true,
 		imageSources: newImageSources(ctx, builderOptions{
 		imageSources: newImageSources(ctx, builderOptions{
-			Options: &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
+			Options: &types.ImageBuildOptions{Platform: runtime.GOOS},
 			Backend: mockBackend,
 			Backend: mockBackend,
 		}),
 		}),
 		imageProber:      newImageProber(mockBackend, nil, runtime.GOOS, false),
 		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 {
 func dispatch(d dispatchRequest, cmd instructions.Command) error {
 	if c, ok := cmd.(instructions.PlatformSpecific); ok {
 	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 {
 		if err != nil {
 			return validationError{err}
 			return validationError{err}
 		}
 		}

+ 3 - 1
builder/dockerfile/imagecontext.go

@@ -5,6 +5,7 @@ import (
 	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder/remotecontext"
 	"github.com/docker/docker/builder/remotecontext"
 	dockerimage "github.com/docker/docker/image"
 	dockerimage "github.com/docker/docker/image"
+	"github.com/docker/docker/pkg/system"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -30,11 +31,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
 				pullOption = backend.PullOptionPreferLocal
 				pullOption = backend.PullOptionPreferLocal
 			}
 			}
 		}
 		}
+		optionsPlatform := system.ParsePlatform(options.Options.Platform)
 		return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{
 		return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{
 			PullOption: pullOption,
 			PullOption: pullOption,
 			AuthConfig: options.Options.AuthConfigs,
 			AuthConfig: options.Options.AuthConfigs,
 			Output:     options.ProgressWriter.Output,
 			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")
 		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)
 	hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
 	if err != nil || hit {
 	if err != nil || hit {
 		return err
 		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 {
 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 {
 	if err != nil {
 		return err
 		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)
 	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?
 	// TODO: should this have been using origPaths instead of srcHash in the comment?
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
 	runConfigWithCommentCmd := copyRunConfig(
 	runConfigWithCommentCmd := copyRunConfig(
 		state.runConfig,
 		state.runConfig,
-		withCmdCommentString(commentStr, b.options.Platform.OS))
+		withCmdCommentString(commentStr, optionsPlatform.OS))
 	hit, err := b.probeCache(state, runConfigWithCommentCmd)
 	hit, err := b.probeCache(state, runConfigWithCommentCmd)
 	if err != nil || hit {
 	if err != nil || hit {
 		return err
 		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)
 		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 {
 	if err != nil {
 		return err
 		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
 	// Set a log config to override any default value set on the daemon
 	hostConfig := &container.HostConfig{LogConfig: defaultLogConfig}
 	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
 	return container.ID, err
 }
 }
 
 
 func (b *Builder) create(runConfig *container.Config) (string, error) {
 func (b *Builder) create(runConfig *container.Config) (string, error) {
 	hostConfig := hostConfigFromOptions(b.options)
 	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 {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}

+ 6 - 10
client/image_build.go

@@ -7,12 +7,12 @@ import (
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
 	"strconv"
 	"strconv"
+	"strings"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
-	"github.com/docker/docker/pkg/system"
 )
 )
 
 
 // ImageBuild sends request to the daemon to build images.
 // 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))
 	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 {
 		if err := cli.NewVersionError("1.32", "platform"); err != nil {
 			return types.ImageBuildResponse{}, err
 			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")
 	headers.Set("Content-Type", "application/x-tar")
 
 
@@ -138,5 +131,8 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
 	if options.SessionID != "" {
 	if options.SessionID != "" {
 		query.Set("session", options.SessionID)
 		query.Set("session", options.SessionID)
 	}
 	}
+	if options.Platform != "" {
+		query.Set("platform", strings.ToLower(options.Platform))
+	}
 	return query, nil
 	return query, nil
 }
 }

+ 6 - 16
client/image_create.go

@@ -1,16 +1,14 @@
 package client
 package client
 
 
 import (
 import (
-	"encoding/json"
 	"io"
 	"io"
 	"net/url"
 	"net/url"
+	"strings"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	"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.
 // 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 := url.Values{}
 	query.Set("fromImage", reference.FamiliarName(ref))
 	query.Set("fromImage", reference.FamiliarName(ref))
 	query.Set("tag", getAPITagFromNamedRef(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 {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	return resp.body, nil
 	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}}
 	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)
 	return cli.post(ctx, "/images/create", query, nil, headers)
 }
 }

+ 4 - 0
client/image_import.go

@@ -3,6 +3,7 @@ package client
 import (
 import (
 	"io"
 	"io"
 	"net/url"
 	"net/url"
+	"strings"
 
 
 	"golang.org/x/net/context"
 	"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("repo", ref)
 	query.Set("tag", options.Tag)
 	query.Set("tag", options.Tag)
 	query.Set("message", options.Message)
 	query.Set("message", options.Message)
+	if options.Platform != "" {
+		query.Set("platform", strings.ToLower(options.Platform))
+	}
 	for _, change := range options.Changes {
 	for _, change := range options.Changes {
 		query.Add("changes", change)
 		query.Add("changes", change)
 	}
 	}

+ 5 - 18
client/image_pull.go

@@ -4,12 +4,12 @@ import (
 	"io"
 	"io"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
+	"strings"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	"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.
 // 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 {
 	if !options.All {
 		query.Set("tag", getAPITagFromNamedRef(ref))
 		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 {
 	if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
 		newAuthHeader, privilegeErr := options.PrivilegeFunc()
 		newAuthHeader, privilegeErr := options.PrivilegeFunc()
 		if privilegeErr != nil {
 		if privilegeErr != nil {
 			return nil, privilegeErr
 			return nil, privilegeErr
 		}
 		}
-		resp, err = cli.tryImageCreate(ctx, query, newAuthHeader, options.Platform)
+		resp, err = cli.tryImageCreate(ctx, query, newAuthHeader)
 	}
 	}
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 3 - 3
container/container_unix.go

@@ -66,7 +66,7 @@ func (container *Container) BuildHostnameFile() error {
 func (container *Container) NetworkMounts() []Mount {
 func (container *Container) NetworkMounts() []Mount {
 	var mounts []Mount
 	var mounts []Mount
 	shared := container.HostConfig.NetworkMode.IsContainer()
 	shared := container.HostConfig.NetworkMode.IsContainer()
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 	if container.ResolvConfPath != "" {
 	if container.ResolvConfPath != "" {
 		if _, err := os.Stat(container.ResolvConfPath); err != nil {
 		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)
 			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
 // IpcMounts returns the list of IPC mounts
 func (container *Container) IpcMounts() []Mount {
 func (container *Container) IpcMounts() []Mount {
 	var mounts []Mount
 	var mounts []Mount
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 
 
 	if container.HasMountFor("/dev/shm") {
 	if container.HasMountFor("/dev/shm") {
 		return mounts
 		return mounts
@@ -429,7 +429,7 @@ func copyOwnership(source, destination string) error {
 
 
 // TmpfsMounts returns the list of tmpfs mounts
 // TmpfsMounts returns the list of tmpfs mounts
 func (container *Container) TmpfsMounts() ([]Mount, error) {
 func (container *Container) TmpfsMounts() ([]Mount, error) {
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 	var mounts []Mount
 	var mounts []Mount
 	for dest, data := range container.HostConfig.Tmpfs {
 	for dest, data := range container.HostConfig.Tmpfs {
 		mounts = append(mounts, Mount{
 		mounts = append(mounts, Mount{

+ 1 - 1
daemon/archive_unix.go

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

+ 2 - 1
daemon/inspect.go

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

+ 4 - 1
distribution/pull_v1.go

@@ -12,6 +12,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
+	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/distribution/registry/client/transport"
 	"github.com/docker/distribution/registry/client/transport"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/xfer"
 	"github.com/docker/docker/distribution/xfer"
@@ -68,7 +69,9 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named, platform strin
 	return nil
 	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())
 	progress.Message(p.config.ProgressOutput, "", "Pulling repository "+p.repoInfo.Name.Name())
 
 
 	tagged, isTagged := ref.(reference.NamedTagged)
 	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.
 	// Early bath if the requested OS doesn't match that of the configuration.
 	// This avoids doing the download, only to potentially fail later.
 	// 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)
 		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 {
 	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 {
 		if err == nil && configRootFS == nil {
 			err = errRootFSInvalid
 			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)
 	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 {
 	if len(manifestMatches) == 0 {
 		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", os, runtime.GOARCH)
 		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)
 	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
 	var matches []manifestlist.ManifestDescriptor
 	for _, manifestDescriptor := range manifests {
 	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)
 			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
 	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
 	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
 	var matches []manifestlist.ManifestDescriptor
 	for _, manifestDescriptor := range manifests {
 	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
 				continue
 			}
 			}
 			matches = append(matches, manifestDescriptor)
 			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
 	return matches
 }
 }
 
 

+ 1 - 0
distribution/registry_unit_test.go

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

+ 6 - 6
image/image_test.go

@@ -62,7 +62,7 @@ func TestImage(t *testing.T) {
 		Domainname: "domain",
 		Domainname: "domain",
 		User:       "root",
 		User:       "root",
 	}
 	}
-	platform := runtime.GOOS
+	os := runtime.GOOS
 
 
 	img := &Image{
 	img := &Image{
 		V1Image: V1Image{
 		V1Image: V1Image{
@@ -73,19 +73,19 @@ func TestImage(t *testing.T) {
 
 
 	assert.Equal(t, cid, img.ImageID())
 	assert.Equal(t, cid, img.ImageID())
 	assert.Equal(t, cid, img.ID().String())
 	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())
 	assert.Equal(t, config, img.RunConfig())
 }
 }
 
 
-func TestImagePlatformNotEmpty(t *testing.T) {
-	platform := "platform"
+func TestImageOSNotEmpty(t *testing.T) {
+	os := "os"
 	img := &Image{
 	img := &Image{
 		V1Image: V1Image{
 		V1Image: V1Image{
-			OS: platform,
+			OS: os,
 		},
 		},
 		OSVersion: "osversion",
 		OSVersion: "osversion",
 	}
 	}
-	assert.Equal(t, platform, img.Platform())
+	assert.Equal(t, os, img.OperatingSystem())
 }
 }
 
 
 func TestNewChildImageFromImageWithRootFS(t *testing.T) {
 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"
 	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.
 // ValidatePlatform determines if a platform structure is valid.
 // TODO This is a temporary function - can be replaced by parsing from
 // TODO This is a temporary function - can be replaced by parsing from
 // https://github.com/containerd/containerd/pull/1403/files at a later date.
 // 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 {
 func ValidatePlatform(platform *specs.Platform) error {
 	platform.Architecture = strings.ToLower(platform.Architecture)
 	platform.Architecture = strings.ToLower(platform.Architecture)
 	platform.OS = strings.ToLower(platform.OS)
 	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)
 		return fmt.Errorf("invalid platform architecture %q", platform.Architecture)
 	}
 	}
 	if platform.OS != "" {
 	if platform.OS != "" {