Преглед изворни кода

LCOW: API: Add platform to /images/create and /build

Signed-off-by: John Howard <jhoward@microsoft.com>

This PR has the API changes described in https://github.com/moby/moby/issues/34617.
Specifically, it adds an HTTP header "X-Requested-Platform" which is a JSON-encoded
OCI Image-spec `Platform` structure.

In addition, it renames (almost all) uses of a string variable platform (and associated)
methods/functions to os. This makes it much clearer to disambiguate with the swarm
"platform" which is really os/arch. This is a stepping stone to getting the daemon towards
fully multi-platform/arch-aware, and makes it clear when "operating system" is being
referred to rather than "platform" which is misleadingly used - sometimes in the swarm
meaning, but more often as just the operating system.
John Howard пре 8 година
родитељ
комит
0380fbff37
82 измењених фајлова са 622 додато и 510 уклоњено
  1. 30 0
      api/server/httputils/httputils.go
  2. 6 0
      api/server/router/build/build_routes.go
  3. 32 65
      api/server/router/image/image_routes.go
  4. 26 0
      api/swagger.yaml
  5. 1 1
      api/types/backend/build.go
  6. 5 5
      api/types/client.go
  7. 0 1
      api/types/configs.go
  8. 1 1
      api/types/types.go
  9. 1 0
      builder/builder.go
  10. 22 47
      builder/dockerfile/builder.go
  11. 1 1
      builder/dockerfile/builder_unix.go
  12. 2 2
      builder/dockerfile/builder_windows.go
  13. 0 1
      builder/dockerfile/containerbackend.go
  14. 1 1
      builder/dockerfile/copy.go
  15. 17 7
      builder/dockerfile/dispatchers.go
  16. 3 6
      builder/dockerfile/dispatchers_test.go
  17. 5 10
      builder/dockerfile/evaluator.go
  18. 1 3
      builder/dockerfile/imagecontext.go
  19. 8 8
      builder/dockerfile/internals.go
  20. 1 1
      builder/dockerfile/internals_test.go
  21. 5 0
      builder/dockerfile/mockbackend_test.go
  22. 6 9
      builder/dockerfile/parser/parser.go
  23. 15 1
      client/image_build.go
  24. 16 2
      client/image_create.go
  25. 19 2
      client/image_pull.go
  26. 0 4
      cmd/dockerd/daemon.go
  27. 12 13
      container/container.go
  28. 6 6
      daemon/build.go
  29. 0 4
      daemon/cluster/executor/container/adapter.go
  30. 10 10
      daemon/commit.go
  31. 3 3
      daemon/container.go
  32. 24 24
      daemon/create.go
  33. 2 2
      daemon/create_unix.go
  34. 4 4
      daemon/create_windows.go
  35. 13 13
      daemon/daemon.go
  36. 6 4
      daemon/daemon_windows.go
  37. 2 2
      daemon/delete.go
  38. 1 1
      daemon/exec_windows.go
  39. 5 5
      daemon/export.go
  40. 0 22
      daemon/graphdriver/windows/windows.go
  41. 13 13
      daemon/image.go
  42. 6 6
      daemon/image_delete.go
  43. 4 4
      daemon/image_tag.go
  44. 8 8
      daemon/images.go
  45. 9 9
      daemon/import.go
  46. 1 1
      daemon/inspect.go
  47. 3 3
      daemon/list.go
  48. 2 2
      daemon/oci_windows.go
  49. 2 2
      daemon/start.go
  50. 1 1
      daemon/start_windows.go
  51. 1 1
      daemon/update.go
  52. 2 2
      daemon/volumes.go
  53. 6 6
      distribution/config.go
  54. 9 2
      distribution/pull.go
  55. 1 1
      distribution/pull_v1.go
  56. 44 30
      distribution/pull_v2.go
  57. 2 2
      distribution/pull_v2_windows.go
  58. 1 1
      distribution/push_v2.go
  59. 1 1
      distribution/registry_unit_test.go
  60. 19 19
      distribution/xfer/download.go
  61. 7 7
      distribution/xfer/download_test.go
  62. 2 2
      image/image.go
  63. 6 6
      image/store.go
  64. 9 9
      image/tarexport/load.go
  65. 1 1
      layer/empty.go
  66. 4 4
      layer/filestore_unix.go
  67. 10 10
      layer/filestore_windows.go
  68. 12 12
      layer/layer.go
  69. 15 15
      layer/layer_store.go
  70. 2 2
      layer/layer_store_windows.go
  71. 7 7
      layer/layer_test.go
  72. 4 4
      layer/migration_test.go
  73. 2 2
      layer/ro_layer.go
  74. 1 1
      layer/ro_layer_unix.go
  75. 3 3
      layer/ro_layer_windows.go
  76. 1 1
      migrate/v1/migratev1_test.go
  77. 6 7
      pkg/system/init_windows.go
  78. 71 0
      pkg/system/lcow.go
  79. 2 2
      pkg/system/path.go
  80. 2 2
      plugin/backend_linux.go
  81. 2 2
      plugin/blobstore.go
  82. 6 6
      plugin/manager.go

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

@@ -1,11 +1,17 @@
 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"
@@ -109,3 +115,27 @@ 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
+}

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

@@ -87,6 +87,12 @@ 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 != "" {

+ 32 - 65
api/server/router/image/image_routes.go

@@ -5,7 +5,6 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
-	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 
 
@@ -17,8 +16,8 @@ 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"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -76,78 +75,46 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
 	}
 	}
 
 
 	var (
 	var (
-		image   = r.Form.Get("fromImage")
-		repo    = r.Form.Get("repo")
-		tag     = r.Form.Get("tag")
-		message = r.Form.Get("message")
-		err     error
-		output  = ioutils.NewWriteFlusher(w)
+		image    = r.Form.Get("fromImage")
+		repo     = r.Form.Get("repo")
+		tag      = r.Form.Get("tag")
+		message  = r.Form.Get("message")
+		err      error
+		output   = ioutils.NewWriteFlusher(w)
+		platform = &specs.Platform{}
 	)
 	)
 	defer output.Close()
 	defer output.Close()
 
 
-	// TODO @jhowardmsft LCOW Support: Eventually we will need an API change
-	// so that platform comes from (for example) r.Form.Get("platform"). For
-	// the initial implementation, we assume that the platform is the
-	// runtime OS of the host. It will also need a validation function such
-	// as below which should be called after getting it from the API.
-	//
-	// Ensures the requested platform is valid and normalized
-	//func validatePlatform(req string) (string, error) {
-	//	req = strings.ToLower(req)
-	//	if req == "" {
-	//		req = runtime.GOOS // default to host platform
-	//	}
-	//	valid := []string{runtime.GOOS}
-	//
-	//	if system.LCOWSupported() {
-	//		valid = append(valid, "linux")
-	//	}
-	//
-	//	for _, item := range valid {
-	//		if req == item {
-	//			return req, nil
-	//		}
-	//	}
-	//	return "", fmt.Errorf("invalid platform requested: %s", req)
-	//}
-	//
-	// And in the call-site:
-	//	if platform, err = validatePlatform(platform); err != nil {
-	//		return err
-	//	}
-	platform := runtime.GOOS
-	if system.LCOWSupported() {
-		platform = "linux"
-	}
-
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 
 
-	if image != "" { //pull
-		metaHeaders := map[string][]string{}
-		for k, v := range r.Header {
-			if strings.HasPrefix(k, "X-Meta-") {
-				metaHeaders[k] = v
+	platform, err = httputils.GetRequestedPlatform(ctx, r)
+	if err == nil {
+		if image != "" { //pull
+			metaHeaders := map[string][]string{}
+			for k, v := range r.Header {
+				if strings.HasPrefix(k, "X-Meta-") {
+					metaHeaders[k] = v
+				}
 			}
 			}
-		}
 
 
-		authEncoded := r.Header.Get("X-Registry-Auth")
-		authConfig := &types.AuthConfig{}
-		if authEncoded != "" {
-			authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
-			if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
-				// for a pull it is not an error if no auth was given
-				// to increase compatibility with the existing api it is defaulting to be empty
-				authConfig = &types.AuthConfig{}
+			authEncoded := r.Header.Get("X-Registry-Auth")
+			authConfig := &types.AuthConfig{}
+			if authEncoded != "" {
+				authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
+				if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
+					// for a pull it is not an error if no auth was given
+					// to increase compatibility with the existing api it is defaulting to be empty
+					authConfig = &types.AuthConfig{}
+				}
 			}
 			}
+			err = s.backend.PullImage(ctx, image, tag, platform.OS, metaHeaders, authConfig, output)
+		} else { //import
+			src := r.Form.Get("fromSrc")
+			// 'err' MUST NOT be defined within this block, we need any error
+			// generated from the download to be available to the output
+			// stream processing below
+			err = s.backend.ImportImage(src, repo, platform.OS, tag, message, r.Body, output, r.Form["changes"])
 		}
 		}
-
-		err = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output)
-	} else { //import
-		src := r.Form.Get("fromSrc")
-		// 'err' MUST NOT be defined within this block, we need any error
-		// generated from the download to be available to the output
-		// stream processing below
-		err = s.backend.ImportImage(src, repo, platform, tag, message, r.Body, output, r.Form["changes"])
 	}
 	}
 	if err != nil {
 	if err != nil {
 		if !output.Flushed() {
 		if !output.Flushed() {

+ 26 - 0
api/swagger.yaml

@@ -6181,6 +6181,19 @@ 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"
+            }
+            ```
+          type: "string"
+          default: ""
       responses:
       responses:
         200:
         200:
           description: "no error"
           description: "no error"
@@ -6262,6 +6275,19 @@ 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"
+            }
+            ```
+          type: "string"
+          default: ""
       tags: ["Image"]
       tags: ["Image"]
   /images/{name}/json:
   /images/{name}/json:
     get:
     get:

+ 1 - 1
api/types/backend/build.go

@@ -40,5 +40,5 @@ type GetImageAndLayerOptions struct {
 	PullOption PullOption
 	PullOption PullOption
 	AuthConfig map[string]types.AuthConfig
 	AuthConfig map[string]types.AuthConfig
 	Output     io.Writer
 	Output     io.Writer
-	Platform   string
+	OS         string
 }
 }

+ 5 - 5
api/types/client.go

@@ -8,6 +8,7 @@ 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
@@ -179,10 +180,7 @@ type ImageBuildOptions struct {
 	ExtraHosts  []string // List of extra hosts
 	ExtraHosts  []string // List of extra hosts
 	Target      string
 	Target      string
 	SessionID   string
 	SessionID   string
-
-	// TODO @jhowardmsft LCOW Support: This will require extending to include
-	// `Platform string`, but is omitted for now as it's hard-coded temporarily
-	// to avoid API changes.
+	Platform    specs.Platform
 }
 }
 
 
 // ImageBuildResponse holds information
 // ImageBuildResponse holds information
@@ -195,7 +193,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
+	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.
 }
 }
 
 
 // ImageImportSource holds source information for ImageImport
 // ImageImportSource holds source information for ImageImport
@@ -229,6 +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
 }
 }
 
 
 // RequestPrivilegeFunc is a function interface that
 // RequestPrivilegeFunc is a function interface that

+ 0 - 1
api/types/configs.go

@@ -16,7 +16,6 @@ type ContainerCreateConfig struct {
 	HostConfig       *container.HostConfig
 	HostConfig       *container.HostConfig
 	NetworkingConfig *network.NetworkingConfig
 	NetworkingConfig *network.NetworkingConfig
 	AdjustCPUShares  bool
 	AdjustCPUShares  bool
-	Platform         string
 }
 }
 
 
 // ContainerRmConfig holds arguments for the container remove
 // ContainerRmConfig holds arguments for the container remove

+ 1 - 1
api/types/types.go

@@ -327,7 +327,7 @@ type ContainerJSONBase struct {
 	Name            string
 	Name            string
 	RestartCount    int
 	RestartCount    int
 	Driver          string
 	Driver          string
-	Platform        string
+	OS              string
 	MountLabel      string
 	MountLabel      string
 	ProcessLabel    string
 	ProcessLabel    string
 	AppArmorProfile string
 	AppArmorProfile string

+ 1 - 0
builder/builder.go

@@ -95,6 +95,7 @@ type Image interface {
 	ImageID() string
 	ImageID() string
 	RunConfig() *container.Config
 	RunConfig() *container.Config
 	MarshalJSON() ([]byte, error)
 	MarshalJSON() ([]byte, error)
+	OperatingSystem() string
 }
 }
 
 
 // ReleaseableLayer is an image layer that can be mounted and released
 // ReleaseableLayer is an image layer that can be mounted and released

+ 22 - 47
builder/dockerfile/builder.go

@@ -20,7 +20,6 @@ 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"
@@ -93,15 +92,6 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
 		}
 		}
 	}()
 	}()
 
 
-	// TODO @jhowardmsft LCOW support - this will require rework to allow both linux and Windows simultaneously.
-	// This is an interim solution to hardcode to linux if LCOW is turned on.
-	if dockerfile.Platform == "" {
-		dockerfile.Platform = runtime.GOOS
-		if dockerfile.Platform == "windows" && system.LCOWSupported() {
-			dockerfile.Platform = "linux"
-		}
-	}
-
 	ctx, cancel := context.WithCancel(ctx)
 	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
 	defer cancel()
 
 
@@ -111,16 +101,26 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
 		source = src
 		source = src
 	}
 	}
 
 
+	os := runtime.GOOS
+	if dockerfile.OS != "" {
+		if config.Options.Platform.OS != "" && config.Options.Platform.OS != dockerfile.OS {
+			return nil, fmt.Errorf("invalid platform")
+		}
+		os = dockerfile.OS
+	} else if config.Options.Platform.OS != "" {
+		os = config.Options.Platform.OS
+	}
+	config.Options.Platform.OS = os
+	dockerfile.OS = os
+
 	builderOptions := builderOptions{
 	builderOptions := builderOptions{
 		Options:        config.Options,
 		Options:        config.Options,
 		ProgressWriter: config.ProgressWriter,
 		ProgressWriter: config.ProgressWriter,
 		Backend:        bm.backend,
 		Backend:        bm.backend,
 		PathCache:      bm.pathCache,
 		PathCache:      bm.pathCache,
 		IDMappings:     bm.idMappings,
 		IDMappings:     bm.idMappings,
-		Platform:       dockerfile.Platform,
 	}
 	}
-
-	return newBuilder(ctx, builderOptions).build(source, dockerfile)
+	return newBuilder(ctx, builderOptions, os).build(source, dockerfile)
 }
 }
 
 
 func (bm *BuildManager) initializeClientSession(ctx context.Context, cancel func(), options *types.ImageBuildOptions) (builder.Source, error) {
 func (bm *BuildManager) initializeClientSession(ctx context.Context, cancel func(), options *types.ImageBuildOptions) (builder.Source, error) {
@@ -163,7 +163,6 @@ type builderOptions struct {
 	ProgressWriter backend.ProgressWriter
 	ProgressWriter backend.ProgressWriter
 	PathCache      pathCache
 	PathCache      pathCache
 	IDMappings     *idtools.IDMappings
 	IDMappings     *idtools.IDMappings
-	Platform       string
 }
 }
 
 
 // Builder is a Dockerfile builder
 // Builder is a Dockerfile builder
@@ -185,32 +184,15 @@ type Builder struct {
 	pathCache        pathCache
 	pathCache        pathCache
 	containerManager *containerManager
 	containerManager *containerManager
 	imageProber      ImageProber
 	imageProber      ImageProber
-
-	// TODO @jhowardmft LCOW Support. This will be moved to options at a later
-	// stage, however that cannot be done now as it affects the public API
-	// if it were.
-	platform string
 }
 }
 
 
 // newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
 // newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
-// TODO @jhowardmsft LCOW support: Eventually platform can be moved into the builder
-// options, however, that would be an API change as it shares types.ImageBuildOptions.
-func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
+func newBuilder(clientCtx context.Context, options builderOptions, os string) *Builder {
 	config := options.Options
 	config := options.Options
 	if config == nil {
 	if config == nil {
 		config = new(types.ImageBuildOptions)
 		config = new(types.ImageBuildOptions)
 	}
 	}
 
 
-	// @jhowardmsft LCOW Support. For the time being, this is interim. Eventually
-	// will be moved to types.ImageBuildOptions, but it can't for now as that would
-	// be an API change.
-	if options.Platform == "" {
-		options.Platform = runtime.GOOS
-	}
-	if options.Platform == "windows" && system.LCOWSupported() {
-		options.Platform = "linux"
-	}
-
 	b := &Builder{
 	b := &Builder{
 		clientCtx:        clientCtx,
 		clientCtx:        clientCtx,
 		options:          config,
 		options:          config,
@@ -222,9 +204,8 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
 		idMappings:       options.IDMappings,
 		idMappings:       options.IDMappings,
 		imageSources:     newImageSources(clientCtx, options),
 		imageSources:     newImageSources(clientCtx, options),
 		pathCache:        options.PathCache,
 		pathCache:        options.PathCache,
-		imageProber:      newImageProber(options.Backend, config.CacheFrom, options.Platform, config.NoCache),
+		imageProber:      newImageProber(options.Backend, config.CacheFrom, os, config.NoCache),
 		containerManager: newContainerManager(options.Backend),
 		containerManager: newContainerManager(options.Backend),
-		platform:         options.Platform,
 	}
 	}
 
 
 	return b
 	return b
@@ -382,25 +363,19 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
 		return config, nil
 		return config, nil
 	}
 	}
 
 
-	b := newBuilder(context.Background(), builderOptions{
-		Options: &types.ImageBuildOptions{NoCache: true},
-	})
-
 	dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
 	dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
 	if err != nil {
 	if err != nil {
 		return nil, validationError{err}
 		return nil, validationError{err}
 	}
 	}
 
 
-	// TODO @jhowardmsft LCOW support. For now, if LCOW enabled, switch to linux.
-	// Also explicitly set the platform. Ultimately this will be in the builder
-	// options, but we can't do that yet as it would change the API.
-	if dockerfile.Platform == "" {
-		dockerfile.Platform = runtime.GOOS
+	os := runtime.GOOS
+	if dockerfile.OS != "" {
+		os = dockerfile.OS
 	}
 	}
-	if dockerfile.Platform == "windows" && system.LCOWSupported() {
-		dockerfile.Platform = "linux"
-	}
-	b.platform = dockerfile.Platform
+
+	b := newBuilder(context.Background(), builderOptions{
+		Options: &types.ImageBuildOptions{NoCache: true},
+	}, os)
 
 
 	// ensure that the commands are valid
 	// ensure that the commands are valid
 	for _, n := range dockerfile.AST.Children {
 	for _, n := range dockerfile.AST.Children {

+ 1 - 1
builder/dockerfile/builder_unix.go

@@ -2,6 +2,6 @@
 
 
 package dockerfile
 package dockerfile
 
 
-func defaultShellForPlatform(platform string) []string {
+func defaultShellForOS(os string) []string {
 	return []string{"/bin/sh", "-c"}
 	return []string{"/bin/sh", "-c"}
 }
 }

+ 2 - 2
builder/dockerfile/builder_windows.go

@@ -1,7 +1,7 @@
 package dockerfile
 package dockerfile
 
 
-func defaultShellForPlatform(platform string) []string {
-	if platform == "linux" {
+func defaultShellForOS(os string) []string {
+	if os == "linux" {
 		return []string{"/bin/sh", "-c"}
 		return []string{"/bin/sh", "-c"}
 	}
 	}
 	return []string{"cmd", "/S", "/C"}
 	return []string{"cmd", "/S", "/C"}

+ 0 - 1
builder/dockerfile/containerbackend.go

@@ -32,7 +32,6 @@ func (c *containerManager) Create(runConfig *container.Config, hostConfig *conta
 	container, err := c.backend.ContainerCreate(types.ContainerCreateConfig{
 	container, err := c.backend.ContainerCreate(types.ContainerCreateConfig{
 		Config:     runConfig,
 		Config:     runConfig,
 		HostConfig: hostConfig,
 		HostConfig: hostConfig,
-		Platform:   platform,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		return container, err
 		return container, err

+ 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.platform,
+		platform:    req.builder.options.Platform.OS,
 	}
 	}
 }
 }
 
 

+ 17 - 7
builder/dockerfile/dispatchers.go

@@ -220,12 +220,22 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
 
 
 	// Windows cannot support a container with no base image unless it is LCOW.
 	// Windows cannot support a container with no base image unless it is LCOW.
 	if name == api.NoBaseImageSpecifier {
 	if name == api.NoBaseImageSpecifier {
+		imageImage := &image.Image{}
+		imageImage.OS = runtime.GOOS
 		if runtime.GOOS == "windows" {
 		if runtime.GOOS == "windows" {
-			if d.builder.platform == "windows" || (d.builder.platform != "windows" && !system.LCOWSupported()) {
+			switch d.builder.options.Platform.OS {
+			case "windows":
 				return nil, errors.New("Windows does not support FROM scratch")
 				return nil, errors.New("Windows does not support FROM scratch")
+			case "linux":
+				if !system.LCOWSupported() {
+					return nil, errors.New("Linux containers are not supported on this system")
+				}
+				imageImage.OS = "linux"
+			default:
+				return nil, errors.Errorf("operating system %q is not supported", d.builder.options.Platform.OS)
 			}
 			}
 		}
 		}
-		return scratchImage, nil
+		return builder.Image(imageImage), nil
 	}
 	}
 	imageMount, err := d.builder.imageSources.Get(name, localOnly)
 	imageMount, err := d.builder.imageSources.Get(name, localOnly)
 	if err != nil {
 	if err != nil {
@@ -254,7 +264,7 @@ 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.platform, runConfig.WorkingDir, c.Path)
+	runConfig.WorkingDir, err = normalizeWorkdir(d.builder.options.Platform.OS, runConfig.WorkingDir, c.Path)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -270,7 +280,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
 	}
 	}
 
 
 	comment := "WORKDIR " + runConfig.WorkingDir
 	comment := "WORKDIR " + runConfig.WorkingDir
-	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.builder.platform))
+	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.builder.options.Platform.OS))
 	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
@@ -303,7 +313,7 @@ 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.platform)
+	cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.builder.options.Platform.OS)
 	buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env)
 	buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env)
 
 
 	saveCmd := cmdFromArgs
 	saveCmd := cmdFromArgs
@@ -380,7 +390,7 @@ 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.platform)
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
 	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
@@ -423,7 +433,7 @@ 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.platform)
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
 	runConfig.Entrypoint = cmd
 	runConfig.Entrypoint = cmd
 	if !d.state.cmdSet {
 	if !d.state.cmdSet {
 		runConfig.Cmd = nil
 		runConfig.Cmd = nil

+ 3 - 6
builder/dockerfile/dispatchers_test.go

@@ -14,6 +14,7 @@ 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"
 )
 )
@@ -22,13 +23,13 @@ func newBuilderWithMockBackend() *Builder {
 	mockBackend := &MockBackend{}
 	mockBackend := &MockBackend{}
 	ctx := context.Background()
 	ctx := context.Background()
 	b := &Builder{
 	b := &Builder{
-		options:       &types.ImageBuildOptions{},
+		options:       &types.ImageBuildOptions{Platform: specs.Platform{OS: 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{},
+			Options: &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
 			Backend: mockBackend,
 			Backend: mockBackend,
 		}),
 		}),
 		imageProber:      newImageProber(mockBackend, nil, runtime.GOOS, false),
 		imageProber:      newImageProber(mockBackend, nil, runtime.GOOS, false),
@@ -118,11 +119,7 @@ func TestFromScratch(t *testing.T) {
 	require.NoError(t, err)
 	require.NoError(t, err)
 	assert.True(t, sb.state.hasFromImage())
 	assert.True(t, sb.state.hasFromImage())
 	assert.Equal(t, "", sb.state.imageID)
 	assert.Equal(t, "", sb.state.imageID)
-	// Windows does not set the default path. TODO @jhowardmsft LCOW support. This will need revisiting as we get further into the implementation
 	expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
 	expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
-	if runtime.GOOS == "windows" {
-		expected = ""
-	}
 	assert.Equal(t, []string{expected}, sb.state.runConfig.Env)
 	assert.Equal(t, []string{expected}, sb.state.runConfig.Env)
 }
 }
 
 

+ 5 - 10
builder/dockerfile/evaluator.go

@@ -21,7 +21,6 @@ package dockerfile
 
 
 import (
 import (
 	"reflect"
 	"reflect"
-	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 
 
@@ -35,7 +34,7 @@ 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.platform)
+		err := c.CheckPlatform(d.builder.options.Platform.OS)
 		if err != nil {
 		if err != nil {
 			return validationError{err}
 			return validationError{err}
 		}
 		}
@@ -218,19 +217,15 @@ func (s *dispatchState) beginStage(stageName string, image builder.Image) {
 	s.runConfig.StdinOnce = false
 	s.runConfig.StdinOnce = false
 }
 }
 
 
-// Add the default PATH to runConfig.ENV if one exists for the platform and there
+// Add the default PATH to runConfig.ENV if one exists for the operating system and there
 // is no PATH set. Note that Windows containers on Windows won't have one as it's set by HCS
 // is no PATH set. Note that Windows containers on Windows won't have one as it's set by HCS
 func (s *dispatchState) setDefaultPath() {
 func (s *dispatchState) setDefaultPath() {
-	// TODO @jhowardmsft LCOW Support - This will need revisiting later
-	platform := runtime.GOOS
-	if system.LCOWSupported() {
-		platform = "linux"
-	}
-	if system.DefaultPathEnv(platform) == "" {
+	defaultPath := system.DefaultPathEnv(s.baseImage.OperatingSystem())
+	if defaultPath == "" {
 		return
 		return
 	}
 	}
 	envMap := opts.ConvertKVStringsToMap(s.runConfig.Env)
 	envMap := opts.ConvertKVStringsToMap(s.runConfig.Env)
 	if _, ok := envMap["PATH"]; !ok {
 	if _, ok := envMap["PATH"]; !ok {
-		s.runConfig.Env = append(s.runConfig.Env, "PATH="+system.DefaultPathEnv(platform))
+		s.runConfig.Env = append(s.runConfig.Env, "PATH="+defaultPath)
 	}
 	}
 }
 }

+ 1 - 3
builder/dockerfile/imagecontext.go

@@ -20,8 +20,6 @@ type imageSources struct {
 	getImage  getAndMountFunc
 	getImage  getAndMountFunc
 }
 }
 
 
-// TODO @jhowardmsft LCOW Support: Eventually, platform can be moved to options.Options.Platform,
-// and removed from builderOptions, but that can't be done yet as it would affect the API.
 func newImageSources(ctx context.Context, options builderOptions) *imageSources {
 func newImageSources(ctx context.Context, options builderOptions) *imageSources {
 	getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ReleaseableLayer, error) {
 	getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ReleaseableLayer, error) {
 		pullOption := backend.PullOptionNoPull
 		pullOption := backend.PullOptionNoPull
@@ -36,7 +34,7 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
 			PullOption: pullOption,
 			PullOption: pullOption,
 			AuthConfig: options.Options.AuthConfigs,
 			AuthConfig: options.Options.AuthConfigs,
 			Output:     options.ProgressWriter.Output,
 			Output:     options.ProgressWriter.Output,
-			Platform:   options.Platform,
+			OS:         options.Options.Platform.OS,
 		})
 		})
 	}
 	}
 
 

+ 8 - 8
builder/dockerfile/internals.go

@@ -83,7 +83,7 @@ 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.platform))
+	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, b.options.Platform.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 +122,7 @@ 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.platform)
+	newLayer, err := imageMount.Layer().Commit(b.options.Platform.OS)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -172,7 +172,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
 	// 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?
 	runConfigWithCommentCmd := copyRunConfig(
 	runConfigWithCommentCmd := copyRunConfig(
 		state.runConfig,
 		state.runConfig,
-		withCmdCommentString(commentStr, b.platform))
+		withCmdCommentString(commentStr, b.options.Platform.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 +183,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.platform)
+	destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, imageMount, b.options.Platform.OS)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -437,9 +437,9 @@ func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier
 
 
 // getShell is a helper function which gets the right shell for prefixing the
 // getShell is a helper function which gets the right shell for prefixing the
 // shell-form of RUN, ENTRYPOINT and CMD instructions
 // shell-form of RUN, ENTRYPOINT and CMD instructions
-func getShell(c *container.Config, platform string) []string {
+func getShell(c *container.Config, os string) []string {
 	if 0 == len(c.Shell) {
 	if 0 == len(c.Shell) {
-		return append([]string{}, defaultShellForPlatform(platform)[:]...)
+		return append([]string{}, defaultShellForOS(os)[:]...)
 	}
 	}
 	return append([]string{}, c.Shell[:]...)
 	return append([]string{}, c.Shell[:]...)
 }
 }
@@ -463,13 +463,13 @@ 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.platform)
+	container, err := b.containerManager.Create(runConfig, hostConfig, b.options.Platform.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.platform)
+	container, err := b.containerManager.Create(runConfig, hostConfig, b.options.Platform.OS)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}

+ 1 - 1
builder/dockerfile/internals_test.go

@@ -103,7 +103,7 @@ func TestCopyRunConfig(t *testing.T) {
 			doc:       "Set the command to a comment",
 			doc:       "Set the command to a comment",
 			modifiers: []runConfigModifier{withCmdComment("comment", runtime.GOOS)},
 			modifiers: []runConfigModifier{withCmdComment("comment", runtime.GOOS)},
 			expected: &container.Config{
 			expected: &container.Config{
-				Cmd: append(defaultShellForPlatform(runtime.GOOS), "#(nop) ", "comment"),
+				Cmd: append(defaultShellForOS(runtime.GOOS), "#(nop) ", "comment"),
 				Env: defaultEnv,
 				Env: defaultEnv,
 			},
 			},
 		},
 		},

+ 5 - 0
builder/dockerfile/mockbackend_test.go

@@ -3,6 +3,7 @@ package dockerfile
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"io"
 	"io"
+	"runtime"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
@@ -96,6 +97,10 @@ func (i *mockImage) RunConfig() *container.Config {
 	return i.config
 	return i.config
 }
 }
 
 
+func (i *mockImage) OperatingSystem() string {
+	return runtime.GOOS
+}
+
 func (i *mockImage) MarshalJSON() ([]byte, error) {
 func (i *mockImage) MarshalJSON() ([]byte, error) {
 	type rawImage mockImage
 	type rawImage mockImage
 	return json.Marshal(rawImage(*i))
 	return json.Marshal(rawImage(*i))

+ 6 - 9
builder/dockerfile/parser/parser.go

@@ -91,9 +91,6 @@ var (
 // DefaultEscapeToken is the default escape token
 // DefaultEscapeToken is the default escape token
 const DefaultEscapeToken = '\\'
 const DefaultEscapeToken = '\\'
 
 
-// defaultPlatformToken is the platform assumed for the build if not explicitly provided
-var defaultPlatformToken = runtime.GOOS
-
 // Directive is the structure used during a build run to hold the state of
 // Directive is the structure used during a build run to hold the state of
 // parsing directives.
 // parsing directives.
 type Directive struct {
 type Directive struct {
@@ -152,8 +149,7 @@ func (d *Directive) possibleParserDirective(line string) error {
 		}
 		}
 	}
 	}
 
 
-	// TODO @jhowardmsft LCOW Support: Eventually this check can be removed,
-	// but only recognise a platform token if running in LCOW mode.
+	// Only recognise a platform token if LCOW is supported
 	if system.LCOWSupported() {
 	if system.LCOWSupported() {
 		tpcMatch := tokenPlatformCommand.FindStringSubmatch(strings.ToLower(line))
 		tpcMatch := tokenPlatformCommand.FindStringSubmatch(strings.ToLower(line))
 		if len(tpcMatch) != 0 {
 		if len(tpcMatch) != 0 {
@@ -177,7 +173,6 @@ func (d *Directive) possibleParserDirective(line string) error {
 func NewDefaultDirective() *Directive {
 func NewDefaultDirective() *Directive {
 	directive := Directive{}
 	directive := Directive{}
 	directive.setEscapeToken(string(DefaultEscapeToken))
 	directive.setEscapeToken(string(DefaultEscapeToken))
-	directive.setPlatformToken(defaultPlatformToken)
 	return &directive
 	return &directive
 }
 }
 
 
@@ -242,8 +237,10 @@ func newNodeFromLine(line string, directive *Directive) (*Node, error) {
 type Result struct {
 type Result struct {
 	AST         *Node
 	AST         *Node
 	EscapeToken rune
 	EscapeToken rune
-	Platform    string
-	Warnings    []string
+	// TODO @jhowardmsft - see https://github.com/moby/moby/issues/34617
+	// This next field will be removed in a future update for LCOW support.
+	OS       string
+	Warnings []string
 }
 }
 
 
 // PrintWarnings to the writer
 // PrintWarnings to the writer
@@ -323,7 +320,7 @@ func Parse(rwc io.Reader) (*Result, error) {
 		AST:         root,
 		AST:         root,
 		Warnings:    warnings,
 		Warnings:    warnings,
 		EscapeToken: d.escapeToken,
 		EscapeToken: d.escapeToken,
-		Platform:    d.platformToken,
+		OS:          d.platformToken,
 	}, nil
 	}, nil
 }
 }
 
 

+ 15 - 1
client/image_build.go

@@ -12,6 +12,7 @@ import (
 
 
 	"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.
@@ -29,6 +30,20 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
 		return types.ImageBuildResponse{}, err
 		return types.ImageBuildResponse{}, err
 	}
 	}
 	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 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[:]))
+	}
 	headers.Set("Content-Type", "application/x-tar")
 	headers.Set("Content-Type", "application/x-tar")
 
 
 	serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
 	serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
@@ -123,6 +138,5 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
 	if options.SessionID != "" {
 	if options.SessionID != "" {
 		query.Set("session", options.SessionID)
 		query.Set("session", options.SessionID)
 	}
 	}
-
 	return query, nil
 	return query, nil
 }
 }

+ 16 - 2
client/image_create.go

@@ -1,6 +1,7 @@
 package client
 package client
 
 
 import (
 import (
+	"encoding/json"
 	"io"
 	"io"
 	"net/url"
 	"net/url"
 
 
@@ -8,6 +9,8 @@ import (
 
 
 	"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.
@@ -21,14 +24,25 @@ 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)
+	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
 	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) (serverResponse, error) {
+func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string, platform specs.Platform) (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)
 }
 }

+ 19 - 2
client/image_pull.go

@@ -9,6 +9,7 @@ import (
 
 
 	"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,13 +32,29 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options types.I
 		query.Set("tag", getAPITagFromNamedRef(ref))
 		query.Set("tag", getAPITagFromNamedRef(ref))
 	}
 	}
 
 
-	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
+	// 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
+		}
+	}
+
+	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
 	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)
+		resp, err = cli.tryImageCreate(ctx, query, newAuthHeader, options.Platform)
 	}
 	}
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 0 - 4
cmd/dockerd/daemon.go

@@ -222,10 +222,6 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
 		logrus.Fatalf("Error creating middlewares: %v", err)
 		logrus.Fatalf("Error creating middlewares: %v", err)
 	}
 	}
 
 
-	if system.LCOWSupported() {
-		logrus.Warnln("LCOW support is enabled - this feature is incomplete")
-	}
-
 	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore)
 	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("Error starting daemon: %v", err)
 		return fmt.Errorf("Error starting daemon: %v", err)

+ 12 - 13
container/container.go

@@ -80,7 +80,7 @@ type Container struct {
 	LogPath         string
 	LogPath         string
 	Name            string
 	Name            string
 	Driver          string
 	Driver          string
-	Platform        string
+	OS              string
 	// MountLabel contains the options for the 'mount' command
 	// MountLabel contains the options for the 'mount' command
 	MountLabel             string
 	MountLabel             string
 	ProcessLabel           string
 	ProcessLabel           string
@@ -147,11 +147,11 @@ func (container *Container) FromDisk() error {
 		return err
 		return err
 	}
 	}
 
 
-	// Ensure the platform is set if blank. Assume it is the platform of the
-	// host OS if not, to ensure containers created before multiple-platform
+	// Ensure the operating system is set if blank. Assume it is the OS of the
+	// host OS if not, to ensure containers created before multiple-OS
 	// support are migrated
 	// support are migrated
-	if container.Platform == "" {
-		container.Platform = runtime.GOOS
+	if container.OS == "" {
+		container.OS = runtime.GOOS
 	}
 	}
 
 
 	return container.readHostConfig()
 	return container.readHostConfig()
@@ -264,7 +264,7 @@ func (container *Container) WriteHostConfig() (*containertypes.HostConfig, error
 func (container *Container) SetupWorkingDirectory(rootIDs idtools.IDPair) error {
 func (container *Container) SetupWorkingDirectory(rootIDs idtools.IDPair) error {
 	// TODO @jhowardmsft, @gupta-ak LCOW Support. This will need revisiting.
 	// TODO @jhowardmsft, @gupta-ak LCOW Support. This will need revisiting.
 	// We will need to do remote filesystem operations here.
 	// We will need to do remote filesystem operations here.
-	if container.Platform != runtime.GOOS {
+	if container.OS != runtime.GOOS {
 		return nil
 		return nil
 	}
 	}
 
 
@@ -434,7 +434,7 @@ func (container *Container) ShouldRestart() bool {
 
 
 // AddMountPointWithVolume adds a new mount point configured with a volume to the container.
 // AddMountPointWithVolume adds a new mount point configured with a volume to the container.
 func (container *Container) AddMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
 func (container *Container) AddMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
-	operatingSystem := container.Platform
+	operatingSystem := container.OS
 	if operatingSystem == "" {
 	if operatingSystem == "" {
 		operatingSystem = runtime.GOOS
 		operatingSystem = runtime.GOOS
 	}
 	}
@@ -1047,15 +1047,14 @@ func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference)
 // CreateDaemonEnvironment creates a new environment variable slice for this container.
 // CreateDaemonEnvironment creates a new environment variable slice for this container.
 func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
 func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
 	// Setup environment
 	// Setup environment
-	// TODO @jhowardmsft LCOW Support. This will need revisiting later.
-	platform := container.Platform
-	if platform == "" {
-		platform = runtime.GOOS
+	os := container.OS
+	if os == "" {
+		os = runtime.GOOS
 	}
 	}
 	env := []string{}
 	env := []string{}
-	if runtime.GOOS != "windows" || (system.LCOWSupported() && platform == "linux") {
+	if runtime.GOOS != "windows" || (runtime.GOOS == "windows" && os == "linux") {
 		env = []string{
 		env = []string{
-			"PATH=" + system.DefaultPathEnv(platform),
+			"PATH=" + system.DefaultPathEnv(os),
 			"HOSTNAME=" + container.Config.Hostname,
 			"HOSTNAME=" + container.Config.Hostname,
 		}
 		}
 		if tty {
 		if tty {

+ 6 - 6
daemon/build.go

@@ -55,7 +55,7 @@ func (rl *releaseableLayer) Mount() (containerfs.ContainerFS, error) {
 	return mountPath, nil
 	return mountPath, nil
 }
 }
 
 
-func (rl *releaseableLayer) Commit(platform string) (builder.ReleaseableLayer, error) {
+func (rl *releaseableLayer) Commit(os string) (builder.ReleaseableLayer, error) {
 	var chainID layer.ChainID
 	var chainID layer.ChainID
 	if rl.roLayer != nil {
 	if rl.roLayer != nil {
 		chainID = rl.roLayer.ChainID()
 		chainID = rl.roLayer.ChainID()
@@ -67,7 +67,7 @@ func (rl *releaseableLayer) Commit(platform string) (builder.ReleaseableLayer, e
 	}
 	}
 	defer stream.Close()
 	defer stream.Close()
 
 
-	newLayer, err := rl.layerStore.Register(stream, chainID, layer.Platform(platform))
+	newLayer, err := rl.layerStore.Register(stream, chainID, layer.OS(os))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -176,7 +176,7 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi
 // leaking of layers.
 // leaking of layers.
 func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
 func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
 	if refOrID == "" {
 	if refOrID == "" {
-		layer, err := newReleasableLayerForImage(nil, daemon.stores[opts.Platform].layerStore)
+		layer, err := newReleasableLayerForImage(nil, daemon.stores[opts.OS].layerStore)
 		return nil, layer, err
 		return nil, layer, err
 	}
 	}
 
 
@@ -187,16 +187,16 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st
 		}
 		}
 		// TODO: shouldn't we error out if error is different from "not found" ?
 		// TODO: shouldn't we error out if error is different from "not found" ?
 		if image != nil {
 		if image != nil {
-			layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
+			layer, err := newReleasableLayerForImage(image, daemon.stores[opts.OS].layerStore)
 			return image, layer, err
 			return image, layer, err
 		}
 		}
 	}
 	}
 
 
-	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
+	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.OS)
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		return nil, nil, err
 	}
 	}
-	layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
+	layer, err := newReleasableLayerForImage(image, daemon.stores[opts.OS].layerStore)
 	return image, layer, err
 	return image, layer, err
 }
 }
 
 

+ 0 - 4
daemon/cluster/executor/container/adapter.go

@@ -20,7 +20,6 @@ import (
 	containerpkg "github.com/docker/docker/container"
 	containerpkg "github.com/docker/docker/container"
 	"github.com/docker/docker/daemon/cluster/convert"
 	"github.com/docker/docker/daemon/cluster/convert"
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
-	"github.com/docker/docker/pkg/system"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
 	"github.com/docker/swarmkit/agent/exec"
 	"github.com/docker/swarmkit/agent/exec"
 	"github.com/docker/swarmkit/api"
 	"github.com/docker/swarmkit/api"
@@ -93,9 +92,6 @@ func (c *containerAdapter) pullImage(ctx context.Context) error {
 		// TODO @jhowardmsft LCOW Support: This will need revisiting as
 		// TODO @jhowardmsft LCOW Support: This will need revisiting as
 		// the stack is built up to include LCOW support for swarm.
 		// the stack is built up to include LCOW support for swarm.
 		platform := runtime.GOOS
 		platform := runtime.GOOS
-		if system.LCOWSupported() {
-			platform = "linux"
-		}
 		err := c.backend.PullImage(ctx, c.container.image(), "", platform, metaHeaders, authConfig, pw)
 		err := c.backend.PullImage(ctx, c.container.image(), "", platform, metaHeaders, authConfig, pw)
 		pw.CloseWithError(err)
 		pw.CloseWithError(err)
 	}()
 	}()

+ 10 - 10
daemon/commit.go

@@ -175,17 +175,17 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
 		parent = new(image.Image)
 		parent = new(image.Image)
 		parent.RootFS = image.NewRootFS()
 		parent.RootFS = image.NewRootFS()
 	} else {
 	} else {
-		parent, err = daemon.stores[container.Platform].imageStore.Get(container.ImageID)
+		parent, err = daemon.stores[container.OS].imageStore.Get(container.ImageID)
 		if err != nil {
 		if err != nil {
 			return "", err
 			return "", err
 		}
 		}
 	}
 	}
 
 
-	l, err := daemon.stores[container.Platform].layerStore.Register(rwTar, parent.RootFS.ChainID(), layer.Platform(container.Platform))
+	l, err := daemon.stores[container.OS].layerStore.Register(rwTar, parent.RootFS.ChainID(), layer.OS(container.OS))
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
-	defer layer.ReleaseAndLog(daemon.stores[container.Platform].layerStore, l)
+	defer layer.ReleaseAndLog(daemon.stores[container.OS].layerStore, l)
 
 
 	containerConfig := c.ContainerConfig
 	containerConfig := c.ContainerConfig
 	if containerConfig == nil {
 	if containerConfig == nil {
@@ -199,18 +199,18 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
 		Config:          newConfig,
 		Config:          newConfig,
 		DiffID:          l.DiffID(),
 		DiffID:          l.DiffID(),
 	}
 	}
-	config, err := json.Marshal(image.NewChildImage(parent, cc, container.Platform))
+	config, err := json.Marshal(image.NewChildImage(parent, cc, container.OS))
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
 
 
-	id, err := daemon.stores[container.Platform].imageStore.Create(config)
+	id, err := daemon.stores[container.OS].imageStore.Create(config)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
 
 
 	if container.ImageID != "" {
 	if container.ImageID != "" {
-		if err := daemon.stores[container.Platform].imageStore.SetParent(id, container.ImageID); err != nil {
+		if err := daemon.stores[container.OS].imageStore.SetParent(id, container.ImageID); err != nil {
 			return "", err
 			return "", err
 		}
 		}
 	}
 	}
@@ -229,7 +229,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
 				return "", err
 				return "", err
 			}
 			}
 		}
 		}
-		if err := daemon.TagImageWithReference(id, container.Platform, newTag); err != nil {
+		if err := daemon.TagImageWithReference(id, container.OS, newTag); err != nil {
 			return "", err
 			return "", err
 		}
 		}
 		imageRef = reference.FamiliarString(newTag)
 		imageRef = reference.FamiliarString(newTag)
@@ -246,13 +246,13 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
 }
 }
 
 
 func (daemon *Daemon) exportContainerRw(container *container.Container) (arch io.ReadCloser, err error) {
 func (daemon *Daemon) exportContainerRw(container *container.Container) (arch io.ReadCloser, err error) {
-	rwlayer, err := daemon.stores[container.Platform].layerStore.GetRWLayer(container.ID)
+	rwlayer, err := daemon.stores[container.OS].layerStore.GetRWLayer(container.ID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	defer func() {
 	defer func() {
 		if err != nil {
 		if err != nil {
-			daemon.stores[container.Platform].layerStore.ReleaseRWLayer(rwlayer)
+			daemon.stores[container.OS].layerStore.ReleaseRWLayer(rwlayer)
 		}
 		}
 	}()
 	}()
 
 
@@ -273,7 +273,7 @@ func (daemon *Daemon) exportContainerRw(container *container.Container) (arch io
 	return ioutils.NewReadCloserWrapper(archive, func() error {
 	return ioutils.NewReadCloserWrapper(archive, func() error {
 			archive.Close()
 			archive.Close()
 			err = rwlayer.Unmount()
 			err = rwlayer.Unmount()
-			daemon.stores[container.Platform].layerStore.ReleaseRWLayer(rwlayer)
+			daemon.stores[container.OS].layerStore.ReleaseRWLayer(rwlayer)
 			return err
 			return err
 		}),
 		}),
 		nil
 		nil

+ 3 - 3
daemon/container.go

@@ -123,7 +123,7 @@ func (daemon *Daemon) Register(c *container.Container) error {
 	return c.CheckpointTo(daemon.containersReplica)
 	return c.CheckpointTo(daemon.containersReplica)
 }
 }
 
 
-func (daemon *Daemon) newContainer(name string, platform string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
+func (daemon *Daemon) newContainer(name string, operatingSystem string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
 	var (
 	var (
 		id             string
 		id             string
 		err            error
 		err            error
@@ -156,8 +156,8 @@ func (daemon *Daemon) newContainer(name string, platform string, config *contain
 	base.ImageID = imgID
 	base.ImageID = imgID
 	base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
 	base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
 	base.Name = name
 	base.Name = name
-	base.Driver = daemon.GraphDriverName(platform)
-	base.Platform = platform
+	base.Driver = daemon.GraphDriverName(operatingSystem)
+	base.OS = operatingSystem
 	return base, err
 	return base, err
 }
 }
 
 

+ 24 - 24
daemon/create.go

@@ -39,17 +39,21 @@ func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, manage
 		return containertypes.ContainerCreateCreatedBody{}, validationError{errors.New("Config cannot be empty in order to create a container")}
 		return containertypes.ContainerCreateCreatedBody{}, validationError{errors.New("Config cannot be empty in order to create a container")}
 	}
 	}
 
 
-	// TODO: @jhowardmsft LCOW support - at a later point, can remove the hard-coding
-	// to force the platform to be linux.
-	// Default the platform if not supplied
-	if params.Platform == "" {
-		params.Platform = runtime.GOOS
-	}
-	if system.LCOWSupported() {
-		params.Platform = "linux"
+	os := runtime.GOOS
+	if params.Config.Image != "" {
+		img, err := daemon.GetImage(params.Config.Image)
+		if err == nil {
+			os = img.OS
+		}
+	} else {
+		// This mean scratch. On Windows, we can safely assume that this is a linux
+		// container. On other platforms, it's the host OS (which it already is)
+		if runtime.GOOS == "windows" && system.LCOWSupported() {
+			os = "linux"
+		}
 	}
 	}
 
 
-	warnings, err := daemon.verifyContainerSettings(params.Platform, params.HostConfig, params.Config, false)
+	warnings, err := daemon.verifyContainerSettings(os, params.HostConfig, params.Config, false)
 	if err != nil {
 	if err != nil {
 		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
 		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
 	}
 	}
@@ -85,29 +89,25 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
 		err       error
 		err       error
 	)
 	)
 
 
+	os := runtime.GOOS
 	if params.Config.Image != "" {
 	if params.Config.Image != "" {
 		img, err = daemon.GetImage(params.Config.Image)
 		img, err = daemon.GetImage(params.Config.Image)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
+		os = img.OS
 
 
 		if runtime.GOOS == "solaris" && img.OS != "solaris " {
 		if runtime.GOOS == "solaris" && img.OS != "solaris " {
-			return nil, errors.New("platform on which parent image was created is not Solaris")
+			return nil, errors.New("operating system on which parent image was created is not Solaris")
 		}
 		}
 		imgID = img.ID()
 		imgID = img.ID()
 
 
 		if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() {
 		if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() {
-			return nil, errors.New("platform on which parent image was created is not Windows")
+			return nil, errors.New("operating system on which parent image was created is not Windows")
 		}
 		}
-	}
-
-	// Make sure the platform requested matches the image
-	if img != nil {
-		if params.Platform != img.Platform() {
-			// Ignore this in LCOW mode. @jhowardmsft TODO - This will need revisiting later.
-			if !system.LCOWSupported() {
-				return nil, fmt.Errorf("cannot create a %s container from a %s image", params.Platform, img.Platform())
-			}
+	} else {
+		if runtime.GOOS == "windows" {
+			os = "linux" // 'scratch' case.
 		}
 		}
 	}
 	}
 
 
@@ -119,7 +119,7 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
 		return nil, validationError{err}
 		return nil, validationError{err}
 	}
 	}
 
 
-	if container, err = daemon.newContainer(params.Name, params.Platform, params.Config, params.HostConfig, imgID, managed); err != nil {
+	if container, err = daemon.newContainer(params.Name, os, params.Config, params.HostConfig, imgID, managed); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	defer func() {
 	defer func() {
@@ -170,7 +170,7 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	if err := daemon.createContainerPlatformSpecificSettings(container, params.Config, params.HostConfig); err != nil {
+	if err := daemon.createContainerOSSpecificSettings(container, params.Config, params.HostConfig); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
@@ -253,7 +253,7 @@ func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig)
 func (daemon *Daemon) setRWLayer(container *container.Container) error {
 func (daemon *Daemon) setRWLayer(container *container.Container) error {
 	var layerID layer.ChainID
 	var layerID layer.ChainID
 	if container.ImageID != "" {
 	if container.ImageID != "" {
-		img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID)
+		img, err := daemon.stores[container.OS].imageStore.Get(container.ImageID)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -266,7 +266,7 @@ func (daemon *Daemon) setRWLayer(container *container.Container) error {
 		StorageOpt: container.HostConfig.StorageOpt,
 		StorageOpt: container.HostConfig.StorageOpt,
 	}
 	}
 
 
-	rwLayer, err := daemon.stores[container.Platform].layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
+	rwLayer, err := daemon.stores[container.OS].layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 2 - 2
daemon/create_unix.go

@@ -15,8 +15,8 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
-// createContainerPlatformSpecificSettings performs platform specific container create functionality
-func (daemon *Daemon) createContainerPlatformSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
+// createContainerOSSpecificSettings performs host-OS specific container create functionality
+func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
 	if err := daemon.Mount(container); err != nil {
 	if err := daemon.Mount(container); err != nil {
 		return err
 		return err
 	}
 	}

+ 4 - 4
daemon/create_windows.go

@@ -10,10 +10,10 @@ import (
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 )
 )
 
 
-// createContainerPlatformSpecificSettings performs platform specific container create functionality
-func (daemon *Daemon) createContainerPlatformSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
+// createContainerOSSpecificSettings performs host-OS specific container create functionality
+func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
 
 
-	if container.Platform == runtime.GOOS {
+	if container.OS == runtime.GOOS {
 		// Make sure the host config has the default daemon isolation if not specified by caller.
 		// Make sure the host config has the default daemon isolation if not specified by caller.
 		if containertypes.Isolation.IsDefault(containertypes.Isolation(hostConfig.Isolation)) {
 		if containertypes.Isolation.IsDefault(containertypes.Isolation(hostConfig.Isolation)) {
 			hostConfig.Isolation = daemon.defaultIsolation
 			hostConfig.Isolation = daemon.defaultIsolation
@@ -26,7 +26,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain
 		}
 		}
 		hostConfig.Isolation = "hyperv"
 		hostConfig.Isolation = "hyperv"
 	}
 	}
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 	for spec := range config.Volumes {
 	for spec := range config.Volumes {
 
 
 		mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver)
 		mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver)

+ 13 - 13
daemon/daemon.go

@@ -162,9 +162,9 @@ func (daemon *Daemon) restore() error {
 		}
 		}
 
 
 		// Ignore the container if it does not support the current driver being used by the graph
 		// Ignore the container if it does not support the current driver being used by the graph
-		currentDriverForContainerPlatform := daemon.stores[container.Platform].graphDriver
-		if (container.Driver == "" && currentDriverForContainerPlatform == "aufs") || container.Driver == currentDriverForContainerPlatform {
-			rwlayer, err := daemon.stores[container.Platform].layerStore.GetRWLayer(container.ID)
+		currentDriverForContainerOS := daemon.stores[container.OS].graphDriver
+		if (container.Driver == "" && currentDriverForContainerOS == "aufs") || container.Driver == currentDriverForContainerOS {
+			rwlayer, err := daemon.stores[container.OS].layerStore.GetRWLayer(container.ID)
 			if err != nil {
 			if err != nil {
 				logrus.Errorf("Failed to load container mount %v: %v", id, err)
 				logrus.Errorf("Failed to load container mount %v: %v", id, err)
 				continue
 				continue
@@ -664,7 +664,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 	}
 	}
 
 
 	var graphDrivers []string
 	var graphDrivers []string
-	for platform, ds := range d.stores {
+	for operatingSystem, ds := range d.stores {
 		ls, err := layer.NewStoreFromOptions(layer.StoreOptions{
 		ls, err := layer.NewStoreFromOptions(layer.StoreOptions{
 			StorePath:                 config.Root,
 			StorePath:                 config.Root,
 			MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),
 			MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),
@@ -673,14 +673,14 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 			IDMappings:                idMappings,
 			IDMappings:                idMappings,
 			PluginGetter:              d.PluginStore,
 			PluginGetter:              d.PluginStore,
 			ExperimentalEnabled:       config.Experimental,
 			ExperimentalEnabled:       config.Experimental,
-			Platform:                  platform,
+			OS:                        operatingSystem,
 		})
 		})
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 		ds.graphDriver = ls.DriverName() // As layerstore may set the driver
 		ds.graphDriver = ls.DriverName() // As layerstore may set the driver
 		ds.layerStore = ls
 		ds.layerStore = ls
-		d.stores[platform] = ds
+		d.stores[operatingSystem] = ds
 		graphDrivers = append(graphDrivers, ls.DriverName())
 		graphDrivers = append(graphDrivers, ls.DriverName())
 	}
 	}
 
 
@@ -691,13 +691,13 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 
 
 	logrus.Debugf("Max Concurrent Downloads: %d", *config.MaxConcurrentDownloads)
 	logrus.Debugf("Max Concurrent Downloads: %d", *config.MaxConcurrentDownloads)
 	lsMap := make(map[string]layer.Store)
 	lsMap := make(map[string]layer.Store)
-	for platform, ds := range d.stores {
-		lsMap[platform] = ds.layerStore
+	for operatingSystem, ds := range d.stores {
+		lsMap[operatingSystem] = ds.layerStore
 	}
 	}
 	d.downloadManager = xfer.NewLayerDownloadManager(lsMap, *config.MaxConcurrentDownloads)
 	d.downloadManager = xfer.NewLayerDownloadManager(lsMap, *config.MaxConcurrentDownloads)
 	logrus.Debugf("Max Concurrent Uploads: %d", *config.MaxConcurrentUploads)
 	logrus.Debugf("Max Concurrent Uploads: %d", *config.MaxConcurrentUploads)
 	d.uploadManager = xfer.NewLayerUploadManager(*config.MaxConcurrentUploads)
 	d.uploadManager = xfer.NewLayerUploadManager(*config.MaxConcurrentUploads)
-	for platform, ds := range d.stores {
+	for operatingSystem, ds := range d.stores {
 		imageRoot := filepath.Join(config.Root, "image", ds.graphDriver)
 		imageRoot := filepath.Join(config.Root, "image", ds.graphDriver)
 		ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb"))
 		ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb"))
 		if err != nil {
 		if err != nil {
@@ -705,13 +705,13 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
 		}
 		}
 
 
 		var is image.Store
 		var is image.Store
-		is, err = image.NewImageStore(ifs, platform, ds.layerStore)
+		is, err = image.NewImageStore(ifs, operatingSystem, ds.layerStore)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 		ds.imageRoot = imageRoot
 		ds.imageRoot = imageRoot
 		ds.imageStore = is
 		ds.imageStore = is
-		d.stores[platform] = ds
+		d.stores[operatingSystem] = ds
 	}
 	}
 
 
 	// Configure the volumes driver
 	// Configure the volumes driver
@@ -921,7 +921,7 @@ func (daemon *Daemon) Shutdown() error {
 				logrus.Errorf("Stop container error: %v", err)
 				logrus.Errorf("Stop container error: %v", err)
 				return
 				return
 			}
 			}
-			if mountid, err := daemon.stores[c.Platform].layerStore.GetMountID(c.ID); err == nil {
+			if mountid, err := daemon.stores[c.OS].layerStore.GetMountID(c.ID); err == nil {
 				daemon.cleanupMountsByID(mountid)
 				daemon.cleanupMountsByID(mountid)
 			}
 			}
 			logrus.Debugf("container stopped %s", c.ID)
 			logrus.Debugf("container stopped %s", c.ID)
@@ -981,7 +981,7 @@ func (daemon *Daemon) Mount(container *container.Container) error {
 		if runtime.GOOS != "windows" {
 		if runtime.GOOS != "windows" {
 			daemon.Unmount(container)
 			daemon.Unmount(container)
 			return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
 			return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
-				daemon.GraphDriverName(container.Platform), container.ID, container.BaseFS, dir)
+				daemon.GraphDriverName(container.OS), container.ID, container.BaseFS, dir)
 		}
 		}
 	}
 	}
 	container.BaseFS = dir // TODO: combine these fields
 	container.BaseFS = dir // TODO: combine these fields

+ 6 - 4
daemon/daemon_windows.go

@@ -493,12 +493,14 @@ func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig
 // conditionalMountOnStart is a platform specific helper function during the
 // conditionalMountOnStart is a platform specific helper function during the
 // container start to call mount.
 // container start to call mount.
 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
-	// Bail out now for Linux containers
-	if system.LCOWSupported() && container.Platform != "windows" {
+	// Bail out now for Linux containers. We cannot mount the containers filesystem on the
+	// host as it is a non-Windows filesystem.
+	if system.LCOWSupported() && container.OS != "windows" {
 		return nil
 		return nil
 	}
 	}
 
 
-	// We do not mount if a Hyper-V container
+	// We do not mount if a Hyper-V container as it needs to be mounted inside the
+	// utility VM, not the host.
 	if !daemon.runAsHyperVContainer(container.HostConfig) {
 	if !daemon.runAsHyperVContainer(container.HostConfig) {
 		return daemon.Mount(container)
 		return daemon.Mount(container)
 	}
 	}
@@ -509,7 +511,7 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
 // during the cleanup of a container to unmount.
 // during the cleanup of a container to unmount.
 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
 	// Bail out now for Linux containers
 	// Bail out now for Linux containers
-	if system.LCOWSupported() && container.Platform != "windows" {
+	if system.LCOWSupported() && container.OS != "windows" {
 		return nil
 		return nil
 	}
 	}
 
 

+ 2 - 2
daemon/delete.go

@@ -116,10 +116,10 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
 	// When container creation fails and `RWLayer` has not been created yet, we
 	// When container creation fails and `RWLayer` has not been created yet, we
 	// do not call `ReleaseRWLayer`
 	// do not call `ReleaseRWLayer`
 	if container.RWLayer != nil {
 	if container.RWLayer != nil {
-		metadata, err := daemon.stores[container.Platform].layerStore.ReleaseRWLayer(container.RWLayer)
+		metadata, err := daemon.stores[container.OS].layerStore.ReleaseRWLayer(container.RWLayer)
 		layer.LogReleaseMetadata(metadata)
 		layer.LogReleaseMetadata(metadata)
 		if err != nil && err != layer.ErrMountDoesNotExist && !os.IsNotExist(errors.Cause(err)) {
 		if err != nil && err != layer.ErrMountDoesNotExist && !os.IsNotExist(errors.Cause(err)) {
-			return errors.Wrapf(err, "driver %q failed to remove root filesystem for %s", daemon.GraphDriverName(container.Platform), container.ID)
+			return errors.Wrapf(err, "driver %q failed to remove root filesystem for %s", daemon.GraphDriverName(container.OS), container.ID)
 		}
 		}
 	}
 	}
 
 

+ 1 - 1
daemon/exec_windows.go

@@ -8,7 +8,7 @@ import (
 
 
 func execSetPlatformOpt(c *container.Container, ec *exec.Config, p *libcontainerd.Process) error {
 func execSetPlatformOpt(c *container.Container, ec *exec.Config, p *libcontainerd.Process) error {
 	// Process arguments need to be escaped before sending to OCI.
 	// Process arguments need to be escaped before sending to OCI.
-	if c.Platform == "windows" {
+	if c.OS == "windows" {
 		p.Args = escapeArgs(p.Args)
 		p.Args = escapeArgs(p.Args)
 		p.User.Username = ec.User
 		p.User.Username = ec.User
 	}
 	}

+ 5 - 5
daemon/export.go

@@ -18,8 +18,8 @@ func (daemon *Daemon) ContainerExport(name string, out io.Writer) error {
 		return err
 		return err
 	}
 	}
 
 
-	if runtime.GOOS == "windows" && container.Platform == "windows" {
-		return fmt.Errorf("the daemon on this platform does not support exporting Windows containers")
+	if runtime.GOOS == "windows" && container.OS == "windows" {
+		return fmt.Errorf("the daemon on this operating system does not support exporting Windows containers")
 	}
 	}
 
 
 	if container.IsDead() {
 	if container.IsDead() {
@@ -46,13 +46,13 @@ func (daemon *Daemon) ContainerExport(name string, out io.Writer) error {
 }
 }
 
 
 func (daemon *Daemon) containerExport(container *container.Container) (arch io.ReadCloser, err error) {
 func (daemon *Daemon) containerExport(container *container.Container) (arch io.ReadCloser, err error) {
-	rwlayer, err := daemon.stores[container.Platform].layerStore.GetRWLayer(container.ID)
+	rwlayer, err := daemon.stores[container.OS].layerStore.GetRWLayer(container.ID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	defer func() {
 	defer func() {
 		if err != nil {
 		if err != nil {
-			daemon.stores[container.Platform].layerStore.ReleaseRWLayer(rwlayer)
+			daemon.stores[container.OS].layerStore.ReleaseRWLayer(rwlayer)
 		}
 		}
 	}()
 	}()
 
 
@@ -73,7 +73,7 @@ func (daemon *Daemon) containerExport(container *container.Container) (arch io.R
 	arch = ioutils.NewReadCloserWrapper(archive, func() error {
 	arch = ioutils.NewReadCloserWrapper(archive, func() error {
 		err := archive.Close()
 		err := archive.Close()
 		rwlayer.Unmount()
 		rwlayer.Unmount()
-		daemon.stores[container.Platform].layerStore.ReleaseRWLayer(rwlayer)
+		daemon.stores[container.OS].layerStore.ReleaseRWLayer(rwlayer)
 		return err
 		return err
 	})
 	})
 	daemon.LogContainerEvent(container, "export")
 	daemon.LogContainerEvent(container, "export")

+ 0 - 22
daemon/graphdriver/windows/windows.go

@@ -154,19 +154,8 @@ func (d *Driver) Status() [][2]string {
 	}
 	}
 }
 }
 
 
-// panicIfUsedByLcow does exactly what it says.
-// TODO @jhowardmsft - this is a temporary measure for the bring-up of
-// Linux containers on Windows. It is a failsafe to ensure that the right
-// graphdriver is used.
-func panicIfUsedByLcow() {
-	if system.LCOWSupported() {
-		panic("inconsistency - windowsfilter graphdriver should not be used when in LCOW mode")
-	}
-}
-
 // Exists returns true if the given id is registered with this driver.
 // Exists returns true if the given id is registered with this driver.
 func (d *Driver) Exists(id string) bool {
 func (d *Driver) Exists(id string) bool {
-	panicIfUsedByLcow()
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return false
 		return false
@@ -181,7 +170,6 @@ func (d *Driver) Exists(id string) bool {
 // CreateReadWrite creates a layer that is writable for use as a container
 // CreateReadWrite creates a layer that is writable for use as a container
 // file system.
 // file system.
 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
-	panicIfUsedByLcow()
 	if opts != nil {
 	if opts != nil {
 		return d.create(id, parent, opts.MountLabel, false, opts.StorageOpt)
 		return d.create(id, parent, opts.MountLabel, false, opts.StorageOpt)
 	}
 	}
@@ -190,7 +178,6 @@ func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts
 
 
 // Create creates a new read-only layer with the given id.
 // Create creates a new read-only layer with the given id.
 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
-	panicIfUsedByLcow()
 	if opts != nil {
 	if opts != nil {
 		return d.create(id, parent, opts.MountLabel, true, opts.StorageOpt)
 		return d.create(id, parent, opts.MountLabel, true, opts.StorageOpt)
 	}
 	}
@@ -274,7 +261,6 @@ func (d *Driver) dir(id string) string {
 
 
 // Remove unmounts and removes the dir information.
 // Remove unmounts and removes the dir information.
 func (d *Driver) Remove(id string) error {
 func (d *Driver) Remove(id string) error {
-	panicIfUsedByLcow()
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -356,7 +342,6 @@ func (d *Driver) Remove(id string) error {
 
 
 // Get returns the rootfs path for the id. This will mount the dir at its given path.
 // Get returns the rootfs path for the id. This will mount the dir at its given path.
 func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
 func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
-	panicIfUsedByLcow()
 	logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel)
 	logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel)
 	var dir string
 	var dir string
 
 
@@ -415,7 +400,6 @@ func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
 
 
 // Put adds a new layer to the driver.
 // Put adds a new layer to the driver.
 func (d *Driver) Put(id string) error {
 func (d *Driver) Put(id string) error {
-	panicIfUsedByLcow()
 	logrus.Debugf("WindowsGraphDriver Put() id %s", id)
 	logrus.Debugf("WindowsGraphDriver Put() id %s", id)
 
 
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
@@ -474,7 +458,6 @@ func (d *Driver) Cleanup() error {
 // layer and its parent layer which may be "".
 // layer and its parent layer which may be "".
 // The layer should be mounted when calling this function
 // The layer should be mounted when calling this function
 func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
 func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
-	panicIfUsedByLcow()
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return
 		return
@@ -511,7 +494,6 @@ func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
 // and its parent layer. If parent is "", then all changes will be ADD changes.
 // and its parent layer. If parent is "", then all changes will be ADD changes.
 // The layer should not be mounted when calling this function.
 // The layer should not be mounted when calling this function.
 func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
 func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
-	panicIfUsedByLcow()
 	rID, err := d.resolveID(id)
 	rID, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -567,7 +549,6 @@ func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
 // new layer in bytes.
 // new layer in bytes.
 // The layer should not be mounted when calling this function
 // The layer should not be mounted when calling this function
 func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
 func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
-	panicIfUsedByLcow()
 	var layerChain []string
 	var layerChain []string
 	if parent != "" {
 	if parent != "" {
 		rPId, err := d.resolveID(parent)
 		rPId, err := d.resolveID(parent)
@@ -602,7 +583,6 @@ func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
 // and its parent and returns the size in bytes of the changes
 // and its parent and returns the size in bytes of the changes
 // relative to its base filesystem directory.
 // relative to its base filesystem directory.
 func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
 func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
-	panicIfUsedByLcow()
 	rPId, err := d.resolveID(parent)
 	rPId, err := d.resolveID(parent)
 	if err != nil {
 	if err != nil {
 		return
 		return
@@ -624,7 +604,6 @@ func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
 
 
 // GetMetadata returns custom driver information.
 // GetMetadata returns custom driver information.
 func (d *Driver) GetMetadata(id string) (map[string]string, error) {
 func (d *Driver) GetMetadata(id string) (map[string]string, error) {
-	panicIfUsedByLcow()
 	m := make(map[string]string)
 	m := make(map[string]string)
 	m["dir"] = d.dir(id)
 	m["dir"] = d.dir(id)
 	return m, nil
 	return m, nil
@@ -927,7 +906,6 @@ func (fg *fileGetCloserWithBackupPrivileges) Close() error {
 // DiffGetter returns a FileGetCloser that can read files from the directory that
 // DiffGetter returns a FileGetCloser that can read files from the directory that
 // contains files for the layer differences. Used for direct access for tar-split.
 // contains files for the layer differences. Used for direct access for tar-split.
 func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
 func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
-	panicIfUsedByLcow()
 	id, err := d.resolveID(id)
 	id, err := d.resolveID(id)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 13 - 13
daemon/image.go

@@ -24,9 +24,9 @@ func (e errImageDoesNotExist) Error() string {
 
 
 func (e errImageDoesNotExist) NotFound() {}
 func (e errImageDoesNotExist) NotFound() {}
 
 
-// GetImageIDAndPlatform returns an image ID and platform corresponding to the image referred to by
+// GetImageIDAndOS returns an image ID and operating system corresponding to the image referred to by
 // refOrID.
 // refOrID.
-func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, error) {
+func (daemon *Daemon) GetImageIDAndOS(refOrID string) (image.ID, string, error) {
 	ref, err := reference.ParseAnyReference(refOrID)
 	ref, err := reference.ParseAnyReference(refOrID)
 	if err != nil {
 	if err != nil {
 		return "", "", validationError{err}
 		return "", "", validationError{err}
@@ -47,16 +47,16 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
 	}
 	}
 
 
 	if digest, err := daemon.referenceStore.Get(namedRef); err == nil {
 	if digest, err := daemon.referenceStore.Get(namedRef); err == nil {
-		// Search the image stores to get the platform, defaulting to host OS.
-		imagePlatform := runtime.GOOS
+		// Search the image stores to get the operating system, defaulting to host OS.
+		imageOS := runtime.GOOS
 		id := image.IDFromDigest(digest)
 		id := image.IDFromDigest(digest)
-		for platform := range daemon.stores {
-			if img, err := daemon.stores[platform].imageStore.Get(id); err == nil {
-				imagePlatform = img.Platform()
+		for os := range daemon.stores {
+			if img, err := daemon.stores[os].imageStore.Get(id); err == nil {
+				imageOS = img.OperatingSystem()
 				break
 				break
 			}
 			}
 		}
 		}
-		return id, imagePlatform, nil
+		return id, imageOS, nil
 	}
 	}
 
 
 	// deprecated: repo:shortid https://github.com/docker/docker/pull/799
 	// deprecated: repo:shortid https://github.com/docker/docker/pull/799
@@ -75,9 +75,9 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
 	}
 	}
 
 
 	// Search based on ID
 	// Search based on ID
-	for platform := range daemon.stores {
-		if id, err := daemon.stores[platform].imageStore.Search(refOrID); err == nil {
-			return id, platform, nil
+	for os := range daemon.stores {
+		if id, err := daemon.stores[os].imageStore.Search(refOrID); err == nil {
+			return id, os, nil
 		}
 		}
 	}
 	}
 
 
@@ -86,9 +86,9 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
 
 
 // GetImage returns an image corresponding to the image referred to by refOrID.
 // GetImage returns an image corresponding to the image referred to by refOrID.
 func (daemon *Daemon) GetImage(refOrID string) (*image.Image, error) {
 func (daemon *Daemon) GetImage(refOrID string) (*image.Image, error) {
-	imgID, platform, err := daemon.GetImageIDAndPlatform(refOrID)
+	imgID, os, err := daemon.GetImageIDAndOS(refOrID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	return daemon.stores[platform].imageStore.Get(imgID)
+	return daemon.stores[os].imageStore.Get(imgID)
 }
 }

+ 6 - 6
daemon/image_delete.go

@@ -65,7 +65,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 	start := time.Now()
 	start := time.Now()
 	records := []types.ImageDeleteResponseItem{}
 	records := []types.ImageDeleteResponseItem{}
 
 
-	imgID, platform, err := daemon.GetImageIDAndPlatform(imageRef)
+	imgID, os, err := daemon.GetImageIDAndOS(imageRef)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -94,7 +94,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 			return nil, err
 			return nil, err
 		}
 		}
 
 
-		parsedRef, err = daemon.removeImageRef(platform, parsedRef)
+		parsedRef, err = daemon.removeImageRef(os, parsedRef)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -122,7 +122,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 				remainingRefs := []reference.Named{}
 				remainingRefs := []reference.Named{}
 				for _, repoRef := range repoRefs {
 				for _, repoRef := range repoRefs {
 					if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
 					if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
-						if _, err := daemon.removeImageRef(platform, repoRef); err != nil {
+						if _, err := daemon.removeImageRef(os, repoRef); err != nil {
 							return records, err
 							return records, err
 						}
 						}
 
 
@@ -152,12 +152,12 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 			if !force {
 			if !force {
 				c |= conflictSoft &^ conflictActiveReference
 				c |= conflictSoft &^ conflictActiveReference
 			}
 			}
-			if conflict := daemon.checkImageDeleteConflict(imgID, platform, c); conflict != nil {
+			if conflict := daemon.checkImageDeleteConflict(imgID, os, c); conflict != nil {
 				return nil, conflict
 				return nil, conflict
 			}
 			}
 
 
 			for _, repoRef := range repoRefs {
 			for _, repoRef := range repoRefs {
-				parsedRef, err := daemon.removeImageRef(platform, repoRef)
+				parsedRef, err := daemon.removeImageRef(os, repoRef)
 				if err != nil {
 				if err != nil {
 					return nil, err
 					return nil, err
 				}
 				}
@@ -170,7 +170,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 		}
 		}
 	}
 	}
 
 
-	if err := daemon.imageDeleteHelper(imgID, platform, &records, force, prune, removedRepositoryRef); err != nil {
+	if err := daemon.imageDeleteHelper(imgID, os, &records, force, prune, removedRepositoryRef); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 

+ 4 - 4
daemon/image_tag.go

@@ -8,7 +8,7 @@ import (
 // TagImage creates the tag specified by newTag, pointing to the image named
 // TagImage creates the tag specified by newTag, pointing to the image named
 // imageName (alternatively, imageName can also be an image ID).
 // imageName (alternatively, imageName can also be an image ID).
 func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
 func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
-	imageID, platform, err := daemon.GetImageIDAndPlatform(imageName)
+	imageID, os, err := daemon.GetImageIDAndOS(imageName)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -23,16 +23,16 @@ func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
 		}
 		}
 	}
 	}
 
 
-	return daemon.TagImageWithReference(imageID, platform, newTag)
+	return daemon.TagImageWithReference(imageID, os, newTag)
 }
 }
 
 
 // TagImageWithReference adds the given reference to the image ID provided.
 // TagImageWithReference adds the given reference to the image ID provided.
-func (daemon *Daemon) TagImageWithReference(imageID image.ID, platform string, newTag reference.Named) error {
+func (daemon *Daemon) TagImageWithReference(imageID image.ID, os string, newTag reference.Named) error {
 	if err := daemon.referenceStore.AddTag(newTag, imageID.Digest(), true); err != nil {
 	if err := daemon.referenceStore.AddTag(newTag, imageID.Digest(), true); err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	if err := daemon.stores[platform].imageStore.SetLastUpdated(imageID); err != nil {
+	if err := daemon.stores[os].imageStore.SetLastUpdated(imageID); err != nil {
 		return err
 		return err
 	}
 	}
 	daemon.LogImageEvent(imageID.String(), reference.FamiliarString(newTag), "tag")
 	daemon.LogImageEvent(imageID.String(), reference.FamiliarString(newTag), "tag")

+ 8 - 8
daemon/images.go

@@ -36,7 +36,7 @@ func (r byCreated) Less(i, j int) bool { return r[i].Created < r[j].Created }
 
 
 // Map returns a map of all images in the ImageStore
 // Map returns a map of all images in the ImageStore
 func (daemon *Daemon) Map() map[image.ID]*image.Image {
 func (daemon *Daemon) Map() map[image.ID]*image.Image {
-	// TODO @jhowardmsft LCOW. This will need  work to enumerate the stores for all platforms.
+	// TODO @jhowardmsft LCOW. This can be removed when imagestores are coalesced
 	platform := runtime.GOOS
 	platform := runtime.GOOS
 	if system.LCOWSupported() {
 	if system.LCOWSupported() {
 		platform = "linux"
 		platform = "linux"
@@ -51,7 +51,7 @@ func (daemon *Daemon) Map() map[image.ID]*image.Image {
 // the heads.
 // the heads.
 func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) {
 func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) {
 
 
-	// TODO @jhowardmsft LCOW. This will need  work to enumerate the stores for all platforms.
+	// TODO @jhowardmsft LCOW. This can be removed when imagestores are coalesced
 	platform := runtime.GOOS
 	platform := runtime.GOOS
 	if system.LCOWSupported() {
 	if system.LCOWSupported() {
 		platform = "linux"
 		platform = "linux"
@@ -273,7 +273,7 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
 	var parentImg *image.Image
 	var parentImg *image.Image
 	var parentChainID layer.ChainID
 	var parentChainID layer.ChainID
 	if len(parent) != 0 {
 	if len(parent) != 0 {
-		parentImg, err = daemon.stores[img.Platform()].imageStore.Get(image.ID(parent))
+		parentImg, err = daemon.stores[img.OperatingSystem()].imageStore.Get(image.ID(parent))
 		if err != nil {
 		if err != nil {
 			return "", errors.Wrap(err, "error getting specified parent layer")
 			return "", errors.Wrap(err, "error getting specified parent layer")
 		}
 		}
@@ -283,11 +283,11 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
 		parentImg = &image.Image{RootFS: rootFS}
 		parentImg = &image.Image{RootFS: rootFS}
 	}
 	}
 
 
-	l, err := daemon.stores[img.Platform()].layerStore.Get(img.RootFS.ChainID())
+	l, err := daemon.stores[img.OperatingSystem()].layerStore.Get(img.RootFS.ChainID())
 	if err != nil {
 	if err != nil {
 		return "", errors.Wrap(err, "error getting image layer")
 		return "", errors.Wrap(err, "error getting image layer")
 	}
 	}
-	defer daemon.stores[img.Platform()].layerStore.Release(l)
+	defer daemon.stores[img.OperatingSystem()].layerStore.Release(l)
 
 
 	ts, err := l.TarStreamFrom(parentChainID)
 	ts, err := l.TarStreamFrom(parentChainID)
 	if err != nil {
 	if err != nil {
@@ -295,11 +295,11 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
 	}
 	}
 	defer ts.Close()
 	defer ts.Close()
 
 
-	newL, err := daemon.stores[img.Platform()].layerStore.Register(ts, parentChainID, layer.Platform(img.Platform()))
+	newL, err := daemon.stores[img.OperatingSystem()].layerStore.Register(ts, parentChainID, layer.OS(img.OperatingSystem()))
 	if err != nil {
 	if err != nil {
 		return "", errors.Wrap(err, "error registering layer")
 		return "", errors.Wrap(err, "error registering layer")
 	}
 	}
-	defer daemon.stores[img.Platform()].layerStore.Release(newL)
+	defer daemon.stores[img.OperatingSystem()].layerStore.Release(newL)
 
 
 	newImage := *img
 	newImage := *img
 	newImage.RootFS = nil
 	newImage.RootFS = nil
@@ -334,7 +334,7 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
 		return "", errors.Wrap(err, "error marshalling image config")
 		return "", errors.Wrap(err, "error marshalling image config")
 	}
 	}
 
 
-	newImgID, err := daemon.stores[img.Platform()].imageStore.Create(b)
+	newImgID, err := daemon.stores[img.OperatingSystem()].imageStore.Create(b)
 	if err != nil {
 	if err != nil {
 		return "", errors.Wrap(err, "error creating new image after squash")
 		return "", errors.Wrap(err, "error creating new image after squash")
 	}
 	}

+ 9 - 9
daemon/import.go

@@ -26,16 +26,16 @@ import (
 // inConfig (if src is "-"), or from a URI specified in src. Progress output is
 // inConfig (if src is "-"), or from a URI specified in src. Progress output is
 // written to outStream. Repository and tag names can optionally be given in
 // written to outStream. Repository and tag names can optionally be given in
 // the repo and tag arguments, respectively.
 // the repo and tag arguments, respectively.
-func (daemon *Daemon) ImportImage(src string, repository, platform string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
+func (daemon *Daemon) ImportImage(src string, repository, os string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
 	var (
 	var (
 		rc     io.ReadCloser
 		rc     io.ReadCloser
 		resp   *http.Response
 		resp   *http.Response
 		newRef reference.Named
 		newRef reference.Named
 	)
 	)
 
 
-	// Default the platform if not supplied.
-	if platform == "" {
-		platform = runtime.GOOS
+	// Default the operating system if not supplied.
+	if os == "" {
+		os = runtime.GOOS
 	}
 	}
 
 
 	if repository != "" {
 	if repository != "" {
@@ -90,11 +90,11 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	l, err := daemon.stores[platform].layerStore.Register(inflatedLayerData, "", layer.Platform(platform))
+	l, err := daemon.stores[os].layerStore.Register(inflatedLayerData, "", layer.OS(os))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	defer layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
+	defer layer.ReleaseAndLog(daemon.stores[os].layerStore, l)
 
 
 	created := time.Now().UTC()
 	created := time.Now().UTC()
 	imgConfig, err := json.Marshal(&image.Image{
 	imgConfig, err := json.Marshal(&image.Image{
@@ -102,7 +102,7 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
 			DockerVersion: dockerversion.Version,
 			DockerVersion: dockerversion.Version,
 			Config:        config,
 			Config:        config,
 			Architecture:  runtime.GOARCH,
 			Architecture:  runtime.GOARCH,
-			OS:            platform,
+			OS:            os,
 			Created:       created,
 			Created:       created,
 			Comment:       msg,
 			Comment:       msg,
 		},
 		},
@@ -119,14 +119,14 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
 		return err
 		return err
 	}
 	}
 
 
-	id, err := daemon.stores[platform].imageStore.Create(imgConfig)
+	id, err := daemon.stores[os].imageStore.Create(imgConfig)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	// FIXME: connect with commit code and call refstore directly
 	// FIXME: connect with commit code and call refstore directly
 	if newRef != nil {
 	if newRef != nil {
-		if err := daemon.TagImageWithReference(id, platform, newRef); err != nil {
+		if err := daemon.TagImageWithReference(id, os, newRef); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}

+ 1 - 1
daemon/inspect.go

@@ -171,7 +171,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,
-		Platform:     container.Platform,
+		OS:           container.OS,
 		MountLabel:   container.MountLabel,
 		MountLabel:   container.MountLabel,
 		ProcessLabel: container.ProcessLabel,
 		ProcessLabel: container.ProcessLabel,
 		ExecIDs:      container.GetExecIDs(),
 		ExecIDs:      container.GetExecIDs(),

+ 3 - 3
daemon/list.go

@@ -322,7 +322,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
 	if psFilters.Contains("ancestor") {
 	if psFilters.Contains("ancestor") {
 		ancestorFilter = true
 		ancestorFilter = true
 		psFilters.WalkValues("ancestor", func(ancestor string) error {
 		psFilters.WalkValues("ancestor", func(ancestor string) error {
-			id, platform, err := daemon.GetImageIDAndPlatform(ancestor)
+			id, os, err := daemon.GetImageIDAndOS(ancestor)
 			if err != nil {
 			if err != nil {
 				logrus.Warnf("Error while looking up for image %v", ancestor)
 				logrus.Warnf("Error while looking up for image %v", ancestor)
 				return nil
 				return nil
@@ -332,7 +332,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
 				return nil
 				return nil
 			}
 			}
 			// Then walk down the graph and put the imageIds in imagesFilter
 			// Then walk down the graph and put the imageIds in imagesFilter
-			populateImageFilterByParents(imagesFilter, id, daemon.stores[platform].imageStore.Children)
+			populateImageFilterByParents(imagesFilter, id, daemon.stores[os].imageStore.Children)
 			return nil
 			return nil
 		})
 		})
 	}
 	}
@@ -566,7 +566,7 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty
 	c := s.Container
 	c := s.Container
 	image := s.Image // keep the original ref if still valid (hasn't changed)
 	image := s.Image // keep the original ref if still valid (hasn't changed)
 	if image != s.ImageID {
 	if image != s.ImageID {
-		id, _, err := daemon.GetImageIDAndPlatform(image)
+		id, _, err := daemon.GetImageIDAndOS(image)
 		if _, isDNE := err.(errImageDoesNotExist); err != nil && !isDNE {
 		if _, isDNE := err.(errImageDoesNotExist); err != nil && !isDNE {
 			return nil, err
 			return nil, err
 		}
 		}

+ 2 - 2
daemon/oci_windows.go

@@ -138,9 +138,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 	max := len(img.RootFS.DiffIDs)
 	max := len(img.RootFS.DiffIDs)
 	for i := 1; i <= max; i++ {
 	for i := 1; i <= max; i++ {
 		img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
 		img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
-		layerPath, err := layer.GetLayerPath(daemon.stores[c.Platform].layerStore, img.RootFS.ChainID())
+		layerPath, err := layer.GetLayerPath(daemon.stores[c.OS].layerStore, img.RootFS.ChainID())
 		if err != nil {
 		if err != nil {
-			return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[c.Platform].layerStore, img.RootFS.ChainID(), err)
+			return nil, fmt.Errorf("failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.stores[c.OS].layerStore, img.RootFS.ChainID(), err)
 		}
 		}
 		// Reverse order, expecting parent most first
 		// Reverse order, expecting parent most first
 		s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)
 		s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)

+ 2 - 2
daemon/start.go

@@ -79,7 +79,7 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.Hos
 
 
 	// check if hostConfig is in line with the current system settings.
 	// check if hostConfig is in line with the current system settings.
 	// It may happen cgroups are umounted or the like.
 	// It may happen cgroups are umounted or the like.
-	if _, err = daemon.verifyContainerSettings(container.Platform, container.HostConfig, nil, false); err != nil {
+	if _, err = daemon.verifyContainerSettings(container.OS, container.HostConfig, nil, false); err != nil {
 		return validationError{err}
 		return validationError{err}
 	}
 	}
 	// Adapt for old containers in case we have updates in this function and
 	// Adapt for old containers in case we have updates in this function and
@@ -191,7 +191,7 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
 	if err := daemon.conditionalUnmountOnCleanup(container); err != nil {
 	if err := daemon.conditionalUnmountOnCleanup(container); err != nil {
 		// FIXME: remove once reference counting for graphdrivers has been refactored
 		// FIXME: remove once reference counting for graphdrivers has been refactored
 		// Ensure that all the mounts are gone
 		// Ensure that all the mounts are gone
-		if mountid, err := daemon.stores[container.Platform].layerStore.GetMountID(container.ID); err == nil {
+		if mountid, err := daemon.stores[container.OS].layerStore.GetMountID(container.ID); err == nil {
 			daemon.cleanupMountsByID(mountid)
 			daemon.cleanupMountsByID(mountid)
 		}
 		}
 	}
 	}

+ 1 - 1
daemon/start_windows.go

@@ -10,7 +10,7 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
 	createOptions := []libcontainerd.CreateOption{}
 	createOptions := []libcontainerd.CreateOption{}
 
 
 	// LCOW options.
 	// LCOW options.
-	if container.Platform == "linux" {
+	if container.OS == "linux" {
 		config := &client.Config{}
 		config := &client.Config{}
 		if err := config.GenerateDefault(daemon.configStore.GraphOptions); err != nil {
 		if err := config.GenerateDefault(daemon.configStore.GraphOptions); err != nil {
 			return nil, err
 			return nil, err

+ 1 - 1
daemon/update.go

@@ -16,7 +16,7 @@ func (daemon *Daemon) ContainerUpdate(name string, hostConfig *container.HostCon
 		return container.ContainerUpdateOKBody{Warnings: warnings}, err
 		return container.ContainerUpdateOKBody{Warnings: warnings}, err
 	}
 	}
 
 
-	warnings, err = daemon.verifyContainerSettings(c.Platform, hostConfig, nil, true)
+	warnings, err = daemon.verifyContainerSettings(c.OS, hostConfig, nil, true)
 	if err != nil {
 	if err != nil {
 		return container.ContainerUpdateOKBody{Warnings: warnings}, validationError{err}
 		return container.ContainerUpdateOKBody{Warnings: warnings}, validationError{err}
 	}
 	}

+ 2 - 2
daemon/volumes.go

@@ -75,7 +75,7 @@ func (m mounts) parts(i int) int {
 func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) {
 func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) {
 	binds := map[string]bool{}
 	binds := map[string]bool{}
 	mountPoints := map[string]*volume.MountPoint{}
 	mountPoints := map[string]*volume.MountPoint{}
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 	defer func() {
 	defer func() {
 		// clean up the container mountpoints once return with error
 		// clean up the container mountpoints once return with error
 		if retErr != nil {
 		if retErr != nil {
@@ -256,7 +256,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
 	container.Lock()
 	container.Lock()
 	defer container.Unlock()
 	defer container.Unlock()
 
 
-	parser := volume.NewParser(container.Platform)
+	parser := volume.NewParser(container.OS)
 
 
 	maybeUpdate := make(map[string]bool)
 	maybeUpdate := make(map[string]bool)
 	for _, mp := range container.MountPoints {
 	for _, mp := range container.MountPoints {

+ 6 - 6
distribution/config.go

@@ -86,7 +86,7 @@ type ImagePushConfig struct {
 type ImageConfigStore interface {
 type ImageConfigStore interface {
 	Put([]byte) (digest.Digest, error)
 	Put([]byte) (digest.Digest, error)
 	Get(digest.Digest) ([]byte, error)
 	Get(digest.Digest) ([]byte, error)
-	RootFSAndPlatformFromConfig([]byte) (*image.RootFS, layer.Platform, error)
+	RootFSAndOSFromConfig([]byte) (*image.RootFS, layer.OS, error)
 }
 }
 
 
 // PushLayerProvider provides layers to be pushed by ChainID.
 // PushLayerProvider provides layers to be pushed by ChainID.
@@ -112,7 +112,7 @@ type RootFSDownloadManager interface {
 	// returns the final rootfs.
 	// returns the final rootfs.
 	// Given progress output to track download progress
 	// Given progress output to track download progress
 	// Returns function to release download resources
 	// Returns function to release download resources
-	Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error)
+	Download(ctx context.Context, initialRootFS image.RootFS, os layer.OS, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error)
 }
 }
 
 
 type imageConfigStore struct {
 type imageConfigStore struct {
@@ -140,7 +140,7 @@ func (s *imageConfigStore) Get(d digest.Digest) ([]byte, error) {
 	return img.RawJSON(), nil
 	return img.RawJSON(), nil
 }
 }
 
 
-func (s *imageConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
+func (s *imageConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, layer.OS, error) {
 	var unmarshalledConfig image.Image
 	var unmarshalledConfig image.Image
 	if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
 	if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
 		return nil, "", err
 		return nil, "", err
@@ -154,11 +154,11 @@ func (s *imageConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS,
 		return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
 		return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
 	}
 	}
 
 
-	platform := ""
+	os := ""
 	if runtime.GOOS == "windows" {
 	if runtime.GOOS == "windows" {
-		platform = unmarshalledConfig.OS
+		os = unmarshalledConfig.OS
 	}
 	}
-	return unmarshalledConfig.RootFS, layer.Platform(platform), nil
+	return unmarshalledConfig.RootFS, layer.OS(os), nil
 }
 }
 
 
 type storeLayerProvider struct {
 type storeLayerProvider struct {

+ 9 - 2
distribution/pull.go

@@ -2,6 +2,7 @@ package distribution
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"runtime"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
@@ -20,7 +21,7 @@ type Puller interface {
 	// Pull tries to pull the image referenced by `tag`
 	// Pull tries to pull the image referenced by `tag`
 	// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
 	// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
 	//
 	//
-	Pull(ctx context.Context, ref reference.Named) error
+	Pull(ctx context.Context, ref reference.Named, platform string) error
 }
 }
 
 
 // newPuller returns a Puller interface that will pull from either a v1 or v2
 // newPuller returns a Puller interface that will pull from either a v1 or v2
@@ -113,7 +114,13 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
 			lastErr = err
 			lastErr = err
 			continue
 			continue
 		}
 		}
-		if err := puller.Pull(ctx, ref); err != nil {
+
+		// Make sure we default the platform if it hasn't been supplied
+		if imagePullConfig.Platform == "" {
+			imagePullConfig.Platform = runtime.GOOS
+		}
+
+		if err := puller.Pull(ctx, ref, imagePullConfig.Platform); err != nil {
 			// Was this pull cancelled? If so, don't try to fall
 			// Was this pull cancelled? If so, don't try to fall
 			// back.
 			// back.
 			fallback := false
 			fallback := false

+ 1 - 1
distribution/pull_v1.go

@@ -35,7 +35,7 @@ type v1Puller struct {
 	session     *registry.Session
 	session     *registry.Session
 }
 }
 
 
-func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
+func (p *v1Puller) Pull(ctx context.Context, ref reference.Named, platform string) error {
 	if _, isCanonical := ref.(reference.Canonical); isCanonical {
 	if _, isCanonical := ref.(reference.Canonical); isCanonical {
 		// Allowing fallback, because HTTPS v1 is before HTTP v2
 		// Allowing fallback, because HTTPS v1 is before HTTP v2
 		return fallbackError{err: ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
 		return fallbackError{err: ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}

+ 44 - 30
distribution/pull_v2.go

@@ -8,6 +8,7 @@ import (
 	"net/url"
 	"net/url"
 	"os"
 	"os"
 	"runtime"
 	"runtime"
+	"strings"
 
 
 	"github.com/docker/distribution"
 	"github.com/docker/distribution"
 	"github.com/docker/distribution/manifest/manifestlist"
 	"github.com/docker/distribution/manifest/manifestlist"
@@ -61,7 +62,7 @@ type v2Puller struct {
 	confirmedV2 bool
 	confirmedV2 bool
 }
 }
 
 
-func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
+func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform string) (err error) {
 	// TODO(tiborvass): was ReceiveTimeout
 	// TODO(tiborvass): was ReceiveTimeout
 	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
 	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
 	if err != nil {
 	if err != nil {
@@ -69,7 +70,7 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
 		return err
 		return err
 	}
 	}
 
 
-	if err = p.pullV2Repository(ctx, ref); err != nil {
+	if err = p.pullV2Repository(ctx, ref, platform); err != nil {
 		if _, ok := err.(fallbackError); ok {
 		if _, ok := err.(fallbackError); ok {
 			return err
 			return err
 		}
 		}
@@ -84,10 +85,10 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
 	return err
 	return err
 }
 }
 
 
-func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
+func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named, platform string) (err error) {
 	var layersDownloaded bool
 	var layersDownloaded bool
 	if !reference.IsNameOnly(ref) {
 	if !reference.IsNameOnly(ref) {
-		layersDownloaded, err = p.pullV2Tag(ctx, ref)
+		layersDownloaded, err = p.pullV2Tag(ctx, ref, platform)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -109,7 +110,7 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
-			pulledNew, err := p.pullV2Tag(ctx, tagRef)
+			pulledNew, err := p.pullV2Tag(ctx, tagRef, platform)
 			if err != nil {
 			if err != nil {
 				// Since this is the pull-all-tags case, don't
 				// Since this is the pull-all-tags case, don't
 				// allow an error pulling a particular tag to
 				// allow an error pulling a particular tag to
@@ -325,7 +326,7 @@ func (ld *v2LayerDescriptor) Registered(diffID layer.DiffID) {
 	ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.Name.Name()})
 	ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.Name.Name()})
 }
 }
 
 
-func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdated bool, err error) {
+func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, os string) (tagUpdated bool, err error) {
 	manSvc, err := p.repo.Manifests(ctx)
 	manSvc, err := p.repo.Manifests(ctx)
 	if err != nil {
 	if err != nil {
 		return false, err
 		return false, err
@@ -389,17 +390,17 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
 		if p.config.RequireSchema2 {
 		if p.config.RequireSchema2 {
 			return false, fmt.Errorf("invalid manifest: not schema2")
 			return false, fmt.Errorf("invalid manifest: not schema2")
 		}
 		}
-		id, manifestDigest, err = p.pullSchema1(ctx, ref, v)
+		id, manifestDigest, err = p.pullSchema1(ctx, ref, v, os)
 		if err != nil {
 		if err != nil {
 			return false, err
 			return false, err
 		}
 		}
 	case *schema2.DeserializedManifest:
 	case *schema2.DeserializedManifest:
-		id, manifestDigest, err = p.pullSchema2(ctx, ref, v)
+		id, manifestDigest, err = p.pullSchema2(ctx, ref, v, os)
 		if err != nil {
 		if err != nil {
 			return false, err
 			return false, err
 		}
 		}
 	case *manifestlist.DeserializedManifestList:
 	case *manifestlist.DeserializedManifestList:
-		id, manifestDigest, err = p.pullManifestList(ctx, ref, v)
+		id, manifestDigest, err = p.pullManifestList(ctx, ref, v, os)
 		if err != nil {
 		if err != nil {
 			return false, err
 			return false, err
 		}
 		}
@@ -435,7 +436,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
 	return true, nil
 	return true, nil
 }
 }
 
 
-func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest) (id digest.Digest, manifestDigest digest.Digest, err error) {
+func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest, requestedOS string) (id digest.Digest, manifestDigest digest.Digest, err error) {
 	var verifiedManifest *schema1.Manifest
 	var verifiedManifest *schema1.Manifest
 	verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)
 	verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)
 	if err != nil {
 	if err != nil {
@@ -490,7 +491,8 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
 	// The v1 manifest itself doesn't directly contain a platform. However,
 	// The v1 manifest itself doesn't directly contain a platform. However,
 	// the history does, but unfortunately that's a string, so search through
 	// the history does, but unfortunately that's a string, so search through
 	// all the history until hopefully we find one which indicates the os.
 	// all the history until hopefully we find one which indicates the os.
-	platform := runtime.GOOS
+	// supertest2014/nyan is an example of a registry image with schemav1.
+	configOS := runtime.GOOS
 	if system.LCOWSupported() {
 	if system.LCOWSupported() {
 		type config struct {
 		type config struct {
 			Os string `json:"os,omitempty"`
 			Os string `json:"os,omitempty"`
@@ -499,14 +501,20 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
 			var c config
 			var c config
 			if err := json.Unmarshal([]byte(v.V1Compatibility), &c); err == nil {
 			if err := json.Unmarshal([]byte(v.V1Compatibility), &c); err == nil {
 				if c.Os != "" {
 				if c.Os != "" {
-					platform = c.Os
+					configOS = c.Os
 					break
 					break
 				}
 				}
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, layer.Platform(platform), descriptors, p.config.ProgressOutput)
+	// 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) {
+		return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
+	}
+
+	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, layer.OS(configOS), descriptors, p.config.ProgressOutput)
 	if err != nil {
 	if err != nil {
 		return "", "", err
 		return "", "", err
 	}
 	}
@@ -527,7 +535,7 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
 	return imageID, manifestDigest, nil
 	return imageID, manifestDigest, nil
 }
 }
 
 
-func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest) (id digest.Digest, manifestDigest digest.Digest, err error) {
+func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest, requestedOS string) (id digest.Digest, manifestDigest digest.Digest, err error) {
 	manifestDigest, err = schema2ManifestDigest(ref, mfst)
 	manifestDigest, err = schema2ManifestDigest(ref, mfst)
 	if err != nil {
 	if err != nil {
 		return "", "", err
 		return "", "", err
@@ -576,11 +584,11 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 	}()
 	}()
 
 
 	var (
 	var (
-		configJSON       []byte         // raw serialized image config
-		downloadedRootFS *image.RootFS  // rootFS from registered layers
-		configRootFS     *image.RootFS  // rootFS from configuration
-		release          func()         // release resources from rootFS download
-		platform         layer.Platform // for LCOW when registering downloaded layers
+		configJSON       []byte        // raw serialized image config
+		downloadedRootFS *image.RootFS // rootFS from registered layers
+		configRootFS     *image.RootFS // rootFS from configuration
+		release          func()        // release resources from rootFS download
+		configOS         layer.OS      // for LCOW when registering downloaded layers
 	)
 	)
 
 
 	// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
 	// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
@@ -592,7 +600,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 	// check to block Windows images being pulled on Linux is implemented, it
 	// check to block Windows images being pulled on Linux is implemented, it
 	// may be necessary to perform the same type of serialisation.
 	// may be necessary to perform the same type of serialisation.
 	if runtime.GOOS == "windows" {
 	if runtime.GOOS == "windows" {
-		configJSON, configRootFS, platform, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
+		configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
 		if err != nil {
 		if err != nil {
 			return "", "", err
 			return "", "", err
 		}
 		}
@@ -605,6 +613,12 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 			return "", "", errRootFSMismatch
 			return "", "", errRootFSMismatch
 		}
 		}
 
 
+		// 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) {
+			return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
+		}
+
 		// Populate diff ids in descriptors to avoid downloading foreign layers
 		// Populate diff ids in descriptors to avoid downloading foreign layers
 		// which have been side loaded
 		// which have been side loaded
 		for i := range descriptors {
 		for i := range descriptors {
@@ -619,7 +633,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 				rootFS image.RootFS
 				rootFS image.RootFS
 			)
 			)
 			downloadRootFS := *image.NewRootFS()
 			downloadRootFS := *image.NewRootFS()
-			rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, platform, descriptors, p.config.ProgressOutput)
+			rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, layer.OS(requestedOS), descriptors, p.config.ProgressOutput)
 			if err != nil {
 			if err != nil {
 				// Intentionally do not cancel the config download here
 				// Intentionally do not cancel the config download here
 				// as the error from config download (if there is one)
 				// as the error from config download (if there is one)
@@ -637,7 +651,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 	}
 	}
 
 
 	if configJSON == nil {
 	if configJSON == nil {
-		configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
+		configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
 		if err == nil && configRootFS == nil {
 		if err == nil && configRootFS == nil {
 			err = errRootFSInvalid
 			err = errRootFSInvalid
 		}
 		}
@@ -684,14 +698,14 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
 	return imageID, manifestDigest, nil
 	return imageID, manifestDigest, nil
 }
 }
 
 
-func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, layer.Platform, error) {
+func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, layer.OS, error) {
 	select {
 	select {
 	case configJSON := <-configChan:
 	case configJSON := <-configChan:
-		rootfs, platform, err := s.RootFSAndPlatformFromConfig(configJSON)
+		rootfs, os, err := s.RootFSAndOSFromConfig(configJSON)
 		if err != nil {
 		if err != nil {
 			return nil, nil, "", err
 			return nil, nil, "", err
 		}
 		}
-		return configJSON, rootfs, platform, nil
+		return configJSON, rootfs, os, nil
 	case err := <-errChan:
 	case err := <-errChan:
 		return nil, nil, "", err
 		return nil, nil, "", err
 		// Don't need a case for ctx.Done in the select because cancellation
 		// Don't need a case for ctx.Done in the select because cancellation
@@ -701,18 +715,18 @@ func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan
 
 
 // pullManifestList handles "manifest lists" which point to various
 // pullManifestList handles "manifest lists" which point to various
 // platform-specific manifests.
 // platform-specific manifests.
-func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList) (id digest.Digest, manifestListDigest digest.Digest, err error) {
+func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, os string) (id digest.Digest, manifestListDigest digest.Digest, err error) {
 	manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
 	manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
 	if err != nil {
 	if err != nil {
 		return "", "", err
 		return "", "", err
 	}
 	}
 
 
-	logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a os/arch match", ref, len(mfstList.Manifests))
+	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)
 
 
 	if len(manifestMatches) == 0 {
 	if len(manifestMatches) == 0 {
-		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", runtime.GOOS, runtime.GOARCH)
+		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", os, runtime.GOARCH)
 		logrus.Debugf(errMsg)
 		logrus.Debugf(errMsg)
 		return "", "", errors.New(errMsg)
 		return "", "", errors.New(errMsg)
 	}
 	}
@@ -739,12 +753,12 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
 
 
 	switch v := manifest.(type) {
 	switch v := manifest.(type) {
 	case *schema1.SignedManifest:
 	case *schema1.SignedManifest:
-		id, _, err = p.pullSchema1(ctx, manifestRef, v)
+		id, _, err = p.pullSchema1(ctx, manifestRef, v, os)
 		if err != nil {
 		if err != nil {
 			return "", "", err
 			return "", "", err
 		}
 		}
 	case *schema2.DeserializedManifest:
 	case *schema2.DeserializedManifest:
-		id, _, err = p.pullSchema2(ctx, manifestRef, v)
+		id, _, err = p.pullSchema2(ctx, manifestRef, v, os)
 		if err != nil {
 		if err != nil {
 			return "", "", err
 			return "", "", err
 		}
 		}

+ 2 - 2
distribution/pull_v2_windows.go

@@ -76,12 +76,12 @@ func filterManifests(manifests []manifestlist.ManifestDescriptor) []manifestlist
 	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 manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == lookingForOS {
-			if !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
+			if lookingForOS == "windows" && !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
 				continue
 				continue
 			}
 			}
 			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", lookingForOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
 		}
 		}
 	}
 	}
 	sort.Stable(manifestsByVersion(matches))
 	sort.Stable(manifestsByVersion(matches))

+ 1 - 1
distribution/push_v2.go

@@ -118,7 +118,7 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id
 		return fmt.Errorf("could not find image from tag %s: %v", reference.FamiliarString(ref), err)
 		return fmt.Errorf("could not find image from tag %s: %v", reference.FamiliarString(ref), err)
 	}
 	}
 
 
-	rootfs, _, err := p.config.ImageStore.RootFSAndPlatformFromConfig(imgConfig)
+	rootfs, _, err := p.config.ImageStore.RootFSAndOSFromConfig(imgConfig)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("unable to get rootfs for image %s: %s", reference.FamiliarString(ref), err)
 		return fmt.Errorf("unable to get rootfs for image %s: %s", reference.FamiliarString(ref), err)
 	}
 	}

+ 1 - 1
distribution/registry_unit_test.go

@@ -83,7 +83,7 @@ func testTokenPassThru(t *testing.T, ts *httptest.Server) {
 	logrus.Debug("About to pull")
 	logrus.Debug("About to pull")
 	// We expect it to fail, since we haven't mock'd the full registry exchange in our handler above
 	// We expect it to fail, since we haven't mock'd the full registry exchange in our handler above
 	tag, _ := reference.WithTag(n, "tag_goes_here")
 	tag, _ := reference.WithTag(n, "tag_goes_here")
-	_ = p.pullV2Repository(ctx, tag)
+	_ = p.pullV2Repository(ctx, tag, runtime.GOOS)
 }
 }
 
 
 func TestTokenPassThru(t *testing.T) {
 func TestTokenPassThru(t *testing.T) {

+ 19 - 19
distribution/xfer/download.go

@@ -95,7 +95,7 @@ type DownloadDescriptorWithRegistered interface {
 // Download method is called to get the layer tar data. Layers are then
 // Download method is called to get the layer tar data. Layers are then
 // registered in the appropriate order.  The caller must call the returned
 // registered in the appropriate order.  The caller must call the returned
 // release function once it is done with the returned RootFS object.
 // release function once it is done with the returned RootFS object.
-func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
+func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS image.RootFS, os layer.OS, layers []DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
 	var (
 	var (
 		topLayer       layer.Layer
 		topLayer       layer.Layer
 		topDownload    *downloadTransfer
 		topDownload    *downloadTransfer
@@ -105,9 +105,9 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 		downloadsByKey = make(map[string]*downloadTransfer)
 		downloadsByKey = make(map[string]*downloadTransfer)
 	)
 	)
 
 
-	// Assume that the platform is the host OS if blank
-	if platform == "" {
-		platform = layer.Platform(runtime.GOOS)
+	// Assume that the operating system is the host OS if blank
+	if os == "" {
+		os = layer.OS(runtime.GOOS)
 	}
 	}
 
 
 	rootFS := initialRootFS
 	rootFS := initialRootFS
@@ -121,13 +121,13 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 			if err == nil {
 			if err == nil {
 				getRootFS := rootFS
 				getRootFS := rootFS
 				getRootFS.Append(diffID)
 				getRootFS.Append(diffID)
-				l, err := ldm.layerStores[string(platform)].Get(getRootFS.ChainID())
+				l, err := ldm.layerStores[string(os)].Get(getRootFS.ChainID())
 				if err == nil {
 				if err == nil {
 					// Layer already exists.
 					// Layer already exists.
 					logrus.Debugf("Layer already exists: %s", descriptor.ID())
 					logrus.Debugf("Layer already exists: %s", descriptor.ID())
 					progress.Update(progressOutput, descriptor.ID(), "Already exists")
 					progress.Update(progressOutput, descriptor.ID(), "Already exists")
 					if topLayer != nil {
 					if topLayer != nil {
-						layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
+						layer.ReleaseAndLog(ldm.layerStores[string(os)], topLayer)
 					}
 					}
 					topLayer = l
 					topLayer = l
 					missingLayer = false
 					missingLayer = false
@@ -146,7 +146,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 		// the stack? If so, avoid downloading it more than once.
 		// the stack? If so, avoid downloading it more than once.
 		var topDownloadUncasted Transfer
 		var topDownloadUncasted Transfer
 		if existingDownload, ok := downloadsByKey[key]; ok {
 		if existingDownload, ok := downloadsByKey[key]; ok {
-			xferFunc := ldm.makeDownloadFuncFromDownload(descriptor, existingDownload, topDownload, platform)
+			xferFunc := ldm.makeDownloadFuncFromDownload(descriptor, existingDownload, topDownload, os)
 			defer topDownload.Transfer.Release(watcher)
 			defer topDownload.Transfer.Release(watcher)
 			topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
 			topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
 			topDownload = topDownloadUncasted.(*downloadTransfer)
 			topDownload = topDownloadUncasted.(*downloadTransfer)
@@ -158,10 +158,10 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 
 
 		var xferFunc DoFunc
 		var xferFunc DoFunc
 		if topDownload != nil {
 		if topDownload != nil {
-			xferFunc = ldm.makeDownloadFunc(descriptor, "", topDownload, platform)
+			xferFunc = ldm.makeDownloadFunc(descriptor, "", topDownload, os)
 			defer topDownload.Transfer.Release(watcher)
 			defer topDownload.Transfer.Release(watcher)
 		} else {
 		} else {
-			xferFunc = ldm.makeDownloadFunc(descriptor, rootFS.ChainID(), nil, platform)
+			xferFunc = ldm.makeDownloadFunc(descriptor, rootFS.ChainID(), nil, os)
 		}
 		}
 		topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
 		topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
 		topDownload = topDownloadUncasted.(*downloadTransfer)
 		topDownload = topDownloadUncasted.(*downloadTransfer)
@@ -171,7 +171,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 	if topDownload == nil {
 	if topDownload == nil {
 		return rootFS, func() {
 		return rootFS, func() {
 			if topLayer != nil {
 			if topLayer != nil {
-				layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
+				layer.ReleaseAndLog(ldm.layerStores[string(os)], topLayer)
 			}
 			}
 		}, nil
 		}, nil
 	}
 	}
@@ -182,7 +182,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 
 
 	defer func() {
 	defer func() {
 		if topLayer != nil {
 		if topLayer != nil {
-			layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
+			layer.ReleaseAndLog(ldm.layerStores[string(os)], topLayer)
 		}
 		}
 	}()
 	}()
 
 
@@ -218,11 +218,11 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
 // complete before the registration step, and registers the downloaded data
 // complete before the registration step, and registers the downloaded data
 // on top of parentDownload's resulting layer. Otherwise, it registers the
 // on top of parentDownload's resulting layer. Otherwise, it registers the
 // layer on top of the ChainID given by parentLayer.
 // layer on top of the ChainID given by parentLayer.
-func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor, parentLayer layer.ChainID, parentDownload *downloadTransfer, platform layer.Platform) DoFunc {
+func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor, parentLayer layer.ChainID, parentDownload *downloadTransfer, os layer.OS) DoFunc {
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
 		d := &downloadTransfer{
 		d := &downloadTransfer{
 			Transfer:   NewTransfer(),
 			Transfer:   NewTransfer(),
-			layerStore: ldm.layerStores[string(platform)],
+			layerStore: ldm.layerStores[string(os)],
 		}
 		}
 
 
 		go func() {
 		go func() {
@@ -341,9 +341,9 @@ func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor,
 				src = fs.Descriptor()
 				src = fs.Descriptor()
 			}
 			}
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
-				d.layer, err = ds.RegisterWithDescriptor(inflatedLayerData, parentLayer, platform, src)
+				d.layer, err = ds.RegisterWithDescriptor(inflatedLayerData, parentLayer, os, src)
 			} else {
 			} else {
-				d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer, platform)
+				d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer, os)
 			}
 			}
 			if err != nil {
 			if err != nil {
 				select {
 				select {
@@ -382,11 +382,11 @@ func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor,
 // parentDownload. This function does not log progress output because it would
 // parentDownload. This function does not log progress output because it would
 // interfere with the progress reporting for sourceDownload, which has the same
 // interfere with the progress reporting for sourceDownload, which has the same
 // Key.
 // Key.
-func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor DownloadDescriptor, sourceDownload *downloadTransfer, parentDownload *downloadTransfer, platform layer.Platform) DoFunc {
+func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor DownloadDescriptor, sourceDownload *downloadTransfer, parentDownload *downloadTransfer, os layer.OS) DoFunc {
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
 		d := &downloadTransfer{
 		d := &downloadTransfer{
 			Transfer:   NewTransfer(),
 			Transfer:   NewTransfer(),
-			layerStore: ldm.layerStores[string(platform)],
+			layerStore: ldm.layerStores[string(os)],
 		}
 		}
 
 
 		go func() {
 		go func() {
@@ -440,9 +440,9 @@ func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor Downloa
 				src = fs.Descriptor()
 				src = fs.Descriptor()
 			}
 			}
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
-				d.layer, err = ds.RegisterWithDescriptor(layerReader, parentLayer, platform, src)
+				d.layer, err = ds.RegisterWithDescriptor(layerReader, parentLayer, os, src)
 			} else {
 			} else {
-				d.layer, err = d.layerStore.Register(layerReader, parentLayer, platform)
+				d.layer, err = d.layerStore.Register(layerReader, parentLayer, os)
 			}
 			}
 			if err != nil {
 			if err != nil {
 				d.err = fmt.Errorf("failed to register layer: %v", err)
 				d.err = fmt.Errorf("failed to register layer: %v", err)

+ 7 - 7
distribution/xfer/download_test.go

@@ -26,7 +26,7 @@ type mockLayer struct {
 	diffID    layer.DiffID
 	diffID    layer.DiffID
 	chainID   layer.ChainID
 	chainID   layer.ChainID
 	parent    layer.Layer
 	parent    layer.Layer
-	platform  layer.Platform
+	os        layer.OS
 }
 }
 
 
 func (ml *mockLayer) TarStream() (io.ReadCloser, error) {
 func (ml *mockLayer) TarStream() (io.ReadCloser, error) {
@@ -57,8 +57,8 @@ func (ml *mockLayer) DiffSize() (size int64, err error) {
 	return 0, nil
 	return 0, nil
 }
 }
 
 
-func (ml *mockLayer) Platform() layer.Platform {
-	return ml.platform
+func (ml *mockLayer) OS() layer.OS {
+	return ml.os
 }
 }
 
 
 func (ml *mockLayer) Metadata() (map[string]string, error) {
 func (ml *mockLayer) Metadata() (map[string]string, error) {
@@ -91,7 +91,7 @@ func (ls *mockLayerStore) Map() map[layer.ChainID]layer.Layer {
 	return layers
 	return layers
 }
 }
 
 
-func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID, platform layer.Platform) (layer.Layer, error) {
+func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID, os layer.OS) (layer.Layer, error) {
 	return ls.RegisterWithDescriptor(reader, parentID, distribution.Descriptor{})
 	return ls.RegisterWithDescriptor(reader, parentID, distribution.Descriptor{})
 }
 }
 
 
@@ -293,13 +293,13 @@ func TestSuccessfulDownload(t *testing.T) {
 	firstDescriptor := descriptors[0].(*mockDownloadDescriptor)
 	firstDescriptor := descriptors[0].(*mockDownloadDescriptor)
 
 
 	// Pre-register the first layer to simulate an already-existing layer
 	// Pre-register the first layer to simulate an already-existing layer
-	l, err := layerStore.Register(firstDescriptor.mockTarStream(), "", layer.Platform(runtime.GOOS))
+	l, err := layerStore.Register(firstDescriptor.mockTarStream(), "", layer.OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	firstDescriptor.diffID = l.DiffID()
 	firstDescriptor.diffID = l.DiffID()
 
 
-	rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), layer.Platform(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
+	rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), layer.OS(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
 	if err != nil {
 	if err != nil {
 		t.Fatalf("download error: %v", err)
 		t.Fatalf("download error: %v", err)
 	}
 	}
@@ -357,7 +357,7 @@ func TestCancelledDownload(t *testing.T) {
 	}()
 	}()
 
 
 	descriptors := downloadDescriptors(nil)
 	descriptors := downloadDescriptors(nil)
-	_, _, err := ldm.Download(ctx, *image.NewRootFS(), layer.Platform(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
+	_, _, err := ldm.Download(ctx, *image.NewRootFS(), layer.OS(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
 	if err != context.Canceled {
 	if err != context.Canceled {
 		t.Fatal("expected download to be cancelled")
 		t.Fatal("expected download to be cancelled")
 	}
 	}

+ 2 - 2
image/image.go

@@ -96,8 +96,8 @@ func (img *Image) RunConfig() *container.Config {
 	return img.Config
 	return img.Config
 }
 }
 
 
-// Platform returns the image's operating system. If not populated, defaults to the host runtime OS.
-func (img *Image) Platform() string {
+// OperatingSystem returns the image's operating system. If not populated, defaults to the host runtime OS.
+func (img *Image) OperatingSystem() string {
 	os := img.OS
 	os := img.OS
 	if os == "" {
 	if os == "" {
 		os = runtime.GOOS
 		os = runtime.GOOS

+ 6 - 6
image/store.go

@@ -47,17 +47,17 @@ type store struct {
 	images    map[ID]*imageMeta
 	images    map[ID]*imageMeta
 	fs        StoreBackend
 	fs        StoreBackend
 	digestSet *digestset.Set
 	digestSet *digestset.Set
-	platform  string
+	os        string
 }
 }
 
 
 // NewImageStore returns new store object for given layer store
 // NewImageStore returns new store object for given layer store
-func NewImageStore(fs StoreBackend, platform string, ls LayerGetReleaser) (Store, error) {
+func NewImageStore(fs StoreBackend, os string, ls LayerGetReleaser) (Store, error) {
 	is := &store{
 	is := &store{
 		ls:        ls,
 		ls:        ls,
 		images:    make(map[ID]*imageMeta),
 		images:    make(map[ID]*imageMeta),
 		fs:        fs,
 		fs:        fs,
 		digestSet: digestset.NewSet(),
 		digestSet: digestset.NewSet(),
-		platform:  platform,
+		os:        os,
 	}
 	}
 
 
 	// load all current images and retain layers
 	// load all current images and retain layers
@@ -118,11 +118,11 @@ func (is *store) Create(config []byte) (ID, error) {
 		return "", err
 		return "", err
 	}
 	}
 
 
-	// TODO @jhowardmsft - LCOW Support. This will need revisiting.
+	// TODO @jhowardmsft - LCOW Support. This will need revisiting when coalescing the image stores.
 	// Integrity check - ensure we are creating something for the correct platform
 	// Integrity check - ensure we are creating something for the correct platform
 	if system.LCOWSupported() {
 	if system.LCOWSupported() {
-		if strings.ToLower(img.Platform()) != strings.ToLower(is.platform) {
-			return "", fmt.Errorf("cannot create entry for platform %q in image store for platform %q", img.Platform(), is.platform)
+		if strings.ToLower(img.OperatingSystem()) != strings.ToLower(is.os) {
+			return "", fmt.Errorf("cannot create entry for operating system %q in image store for operating system %q", img.OperatingSystem(), is.os)
 		}
 		}
 	}
 	}
 
 

+ 9 - 9
image/tarexport/load.go

@@ -90,13 +90,13 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
 		}
 		}
 
 
 		// On Windows, validate the platform, defaulting to windows if not present.
 		// On Windows, validate the platform, defaulting to windows if not present.
-		platform := layer.Platform(img.OS)
+		os := layer.OS(img.OS)
 		if runtime.GOOS == "windows" {
 		if runtime.GOOS == "windows" {
-			if platform == "" {
-				platform = "windows"
+			if os == "" {
+				os = "windows"
 			}
 			}
-			if (platform != "windows") && (platform != "linux") {
-				return fmt.Errorf("configuration for this image has an unsupported platform: %s", platform)
+			if (os != "windows") && (os != "linux") {
+				return fmt.Errorf("configuration for this image has an unsupported operating system: %s", os)
 			}
 			}
 		}
 		}
 
 
@@ -109,7 +109,7 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
 			r.Append(diffID)
 			r.Append(diffID)
 			newLayer, err := l.ls.Get(r.ChainID())
 			newLayer, err := l.ls.Get(r.ChainID())
 			if err != nil {
 			if err != nil {
-				newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), platform, m.LayerSources[diffID], progressOutput)
+				newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), os, m.LayerSources[diffID], progressOutput)
 				if err != nil {
 				if err != nil {
 					return err
 					return err
 				}
 				}
@@ -176,7 +176,7 @@ func (l *tarexporter) setParentID(id, parentID image.ID) error {
 	return l.is.SetParent(id, parentID)
 	return l.is.SetParent(id, parentID)
 }
 }
 
 
-func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, platform layer.Platform, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
+func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, os layer.OS, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
 	// We use system.OpenSequential to use sequential file access on Windows, avoiding
 	// We use system.OpenSequential to use sequential file access on Windows, avoiding
 	// depleting the standby list. On Linux, this equates to a regular os.Open.
 	// depleting the standby list. On Linux, this equates to a regular os.Open.
 	rawTar, err := system.OpenSequential(filename)
 	rawTar, err := system.OpenSequential(filename)
@@ -206,9 +206,9 @@ func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string,
 	defer inflatedLayerData.Close()
 	defer inflatedLayerData.Close()
 
 
 	if ds, ok := l.ls.(layer.DescribableStore); ok {
 	if ds, ok := l.ls.(layer.DescribableStore); ok {
-		return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), platform, foreignSrc)
+		return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), os, foreignSrc)
 	}
 	}
-	return l.ls.Register(inflatedLayerData, rootFS.ChainID(), platform)
+	return l.ls.Register(inflatedLayerData, rootFS.ChainID(), os)
 }
 }
 
 
 func (l *tarexporter) setLoadedTag(ref reference.Named, imgID digest.Digest, outStream io.Writer) error {
 func (l *tarexporter) setLoadedTag(ref reference.Named, imgID digest.Digest, outStream io.Writer) error {

+ 1 - 1
layer/empty.go

@@ -55,7 +55,7 @@ func (el *emptyLayer) Metadata() (map[string]string, error) {
 	return make(map[string]string), nil
 	return make(map[string]string), nil
 }
 }
 
 
-func (el *emptyLayer) Platform() Platform {
+func (el *emptyLayer) OS() OS {
 	return ""
 	return ""
 }
 }
 
 

+ 4 - 4
layer/filestore_unix.go

@@ -2,12 +2,12 @@
 
 
 package layer
 package layer
 
 
-// SetPlatform writes the "platform" file to the layer filestore
-func (fm *fileMetadataTransaction) SetPlatform(platform Platform) error {
+// SetOS writes the "os" file to the layer filestore
+func (fm *fileMetadataTransaction) SetOS(os OS) error {
 	return nil
 	return nil
 }
 }
 
 
-// GetPlatform reads the "platform" file from the layer filestore
-func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
+// GetOS reads the "os" file from the layer filestore
+func (fms *fileMetadataStore) GetOS(layer ChainID) (OS, error) {
 	return "", nil
 	return "", nil
 }
 }

+ 10 - 10
layer/filestore_windows.go

@@ -7,19 +7,19 @@ import (
 	"strings"
 	"strings"
 )
 )
 
 
-// SetPlatform writes the "platform" file to the layer filestore
-func (fm *fileMetadataTransaction) SetPlatform(platform Platform) error {
-	if platform == "" {
+// SetOS writes the "os" file to the layer filestore
+func (fm *fileMetadataTransaction) SetOS(os OS) error {
+	if os == "" {
 		return nil
 		return nil
 	}
 	}
-	return fm.ws.WriteFile("platform", []byte(platform), 0644)
+	return fm.ws.WriteFile("os", []byte(os), 0644)
 }
 }
 
 
-// GetPlatform reads the "platform" file from the layer filestore
-func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
-	contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "platform"))
+// GetOS reads the "os" file from the layer filestore
+func (fms *fileMetadataStore) GetOS(layer ChainID) (OS, error) {
+	contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "os"))
 	if err != nil {
 	if err != nil {
-		// For backwards compatibility, the platform file may not exist. Default to "windows" if missing.
+		// For backwards compatibility, the os file may not exist. Default to "windows" if missing.
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			return "windows", nil
 			return "windows", nil
 		}
 		}
@@ -28,8 +28,8 @@ func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
 	content := strings.TrimSpace(string(contentBytes))
 	content := strings.TrimSpace(string(contentBytes))
 
 
 	if content != "windows" && content != "linux" {
 	if content != "windows" && content != "linux" {
-		return "", fmt.Errorf("invalid platform value: %s", content)
+		return "", fmt.Errorf("invalid operating system value: %s", content)
 	}
 	}
 
 
-	return Platform(content), nil
+	return OS(content), nil
 }
 }

+ 12 - 12
layer/layer.go

@@ -53,8 +53,8 @@ var (
 	ErrMaxDepthExceeded = errors.New("max depth exceeded")
 	ErrMaxDepthExceeded = errors.New("max depth exceeded")
 
 
 	// ErrNotSupported is used when the action is not supported
 	// ErrNotSupported is used when the action is not supported
-	// on the current platform
-	ErrNotSupported = errors.New("not support on this platform")
+	// on the current host operating system.
+	ErrNotSupported = errors.New("not support on this host operating system")
 )
 )
 
 
 // ChainID is the content-addressable ID of a layer.
 // ChainID is the content-addressable ID of a layer.
@@ -65,11 +65,11 @@ func (id ChainID) String() string {
 	return string(id)
 	return string(id)
 }
 }
 
 
-// Platform is the platform of a layer
-type Platform string
+// OS is the operating system of a layer
+type OS string
 
 
-// String returns a string rendition of layers target platform
-func (id Platform) String() string {
+// String returns a string rendition of layers target operating system
+func (id OS) String() string {
 	return string(id)
 	return string(id)
 }
 }
 
 
@@ -108,8 +108,8 @@ type Layer interface {
 	// Parent returns the next layer in the layer chain.
 	// Parent returns the next layer in the layer chain.
 	Parent() Layer
 	Parent() Layer
 
 
-	// Platform returns the platform of the layer
-	Platform() Platform
+	// OS returns the operating system of the layer
+	OS() OS
 
 
 	// Size returns the size of the entire layer chain. The size
 	// Size returns the size of the entire layer chain. The size
 	// is calculated from the total size of all files in the layers.
 	// is calculated from the total size of all files in the layers.
@@ -191,7 +191,7 @@ type CreateRWLayerOpts struct {
 // Store represents a backend for managing both
 // Store represents a backend for managing both
 // read-only and read-write layers.
 // read-only and read-write layers.
 type Store interface {
 type Store interface {
-	Register(io.Reader, ChainID, Platform) (Layer, error)
+	Register(io.Reader, ChainID, OS) (Layer, error)
 	Get(ChainID) (Layer, error)
 	Get(ChainID) (Layer, error)
 	Map() map[ChainID]Layer
 	Map() map[ChainID]Layer
 	Release(Layer) ([]Metadata, error)
 	Release(Layer) ([]Metadata, error)
@@ -209,7 +209,7 @@ type Store interface {
 // DescribableStore represents a layer store capable of storing
 // DescribableStore represents a layer store capable of storing
 // descriptors for layers.
 // descriptors for layers.
 type DescribableStore interface {
 type DescribableStore interface {
-	RegisterWithDescriptor(io.Reader, ChainID, Platform, distribution.Descriptor) (Layer, error)
+	RegisterWithDescriptor(io.Reader, ChainID, OS, distribution.Descriptor) (Layer, error)
 }
 }
 
 
 // MetadataTransaction represents functions for setting layer metadata
 // MetadataTransaction represents functions for setting layer metadata
@@ -220,7 +220,7 @@ type MetadataTransaction interface {
 	SetDiffID(DiffID) error
 	SetDiffID(DiffID) error
 	SetCacheID(string) error
 	SetCacheID(string) error
 	SetDescriptor(distribution.Descriptor) error
 	SetDescriptor(distribution.Descriptor) error
-	SetPlatform(Platform) error
+	SetOS(OS) error
 	TarSplitWriter(compressInput bool) (io.WriteCloser, error)
 	TarSplitWriter(compressInput bool) (io.WriteCloser, error)
 
 
 	Commit(ChainID) error
 	Commit(ChainID) error
@@ -241,7 +241,7 @@ type MetadataStore interface {
 	GetDiffID(ChainID) (DiffID, error)
 	GetDiffID(ChainID) (DiffID, error)
 	GetCacheID(ChainID) (string, error)
 	GetCacheID(ChainID) (string, error)
 	GetDescriptor(ChainID) (distribution.Descriptor, error)
 	GetDescriptor(ChainID) (distribution.Descriptor, error)
-	GetPlatform(ChainID) (Platform, error)
+	GetOS(ChainID) (OS, error)
 	TarSplitReader(ChainID) (io.ReadCloser, error)
 	TarSplitReader(ChainID) (io.ReadCloser, error)
 
 
 	SetMountID(string, string) error
 	SetMountID(string, string) error

+ 15 - 15
layer/layer_store.go

@@ -39,7 +39,7 @@ type layerStore struct {
 
 
 	useTarSplit bool
 	useTarSplit bool
 
 
-	platform string
+	os string
 }
 }
 
 
 // StoreOptions are the options used to create a new Store instance
 // StoreOptions are the options used to create a new Store instance
@@ -51,7 +51,7 @@ type StoreOptions struct {
 	IDMappings                *idtools.IDMappings
 	IDMappings                *idtools.IDMappings
 	PluginGetter              plugingetter.PluginGetter
 	PluginGetter              plugingetter.PluginGetter
 	ExperimentalEnabled       bool
 	ExperimentalEnabled       bool
-	Platform                  string
+	OS                        string
 }
 }
 
 
 // NewStoreFromOptions creates a new Store instance
 // NewStoreFromOptions creates a new Store instance
@@ -73,13 +73,13 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	return NewStoreFromGraphDriver(fms, driver, options.Platform)
+	return NewStoreFromGraphDriver(fms, driver, options.OS)
 }
 }
 
 
 // NewStoreFromGraphDriver creates a new Store instance using the provided
 // NewStoreFromGraphDriver creates a new Store instance using the provided
 // metadata store and graph driver. The metadata store will be used to restore
 // metadata store and graph driver. The metadata store will be used to restore
 // the Store.
 // the Store.
-func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver, platform string) (Store, error) {
+func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver, os string) (Store, error) {
 	caps := graphdriver.Capabilities{}
 	caps := graphdriver.Capabilities{}
 	if capDriver, ok := driver.(graphdriver.CapabilityDriver); ok {
 	if capDriver, ok := driver.(graphdriver.CapabilityDriver); ok {
 		caps = capDriver.Capabilities()
 		caps = capDriver.Capabilities()
@@ -91,7 +91,7 @@ func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver, pla
 		layerMap:    map[ChainID]*roLayer{},
 		layerMap:    map[ChainID]*roLayer{},
 		mounts:      map[string]*mountedLayer{},
 		mounts:      map[string]*mountedLayer{},
 		useTarSplit: !caps.ReproducesExactDiffs,
 		useTarSplit: !caps.ReproducesExactDiffs,
-		platform:    platform,
+		os:          os,
 	}
 	}
 
 
 	ids, mounts, err := store.List()
 	ids, mounts, err := store.List()
@@ -150,9 +150,9 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
 		return nil, fmt.Errorf("failed to get descriptor for %s: %s", layer, err)
 		return nil, fmt.Errorf("failed to get descriptor for %s: %s", layer, err)
 	}
 	}
 
 
-	platform, err := ls.store.GetPlatform(layer)
+	os, err := ls.store.GetOS(layer)
 	if err != nil {
 	if err != nil {
-		return nil, fmt.Errorf("failed to get platform for %s: %s", layer, err)
+		return nil, fmt.Errorf("failed to get operating system for %s: %s", layer, err)
 	}
 	}
 
 
 	cl = &roLayer{
 	cl = &roLayer{
@@ -163,7 +163,7 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
 		layerStore: ls,
 		layerStore: ls,
 		references: map[Layer]struct{}{},
 		references: map[Layer]struct{}{},
 		descriptor: descriptor,
 		descriptor: descriptor,
-		platform:   platform,
+		os:         os,
 	}
 	}
 
 
 	if parent != "" {
 	if parent != "" {
@@ -259,11 +259,11 @@ func (ls *layerStore) applyTar(tx MetadataTransaction, ts io.Reader, parent stri
 	return nil
 	return nil
 }
 }
 
 
-func (ls *layerStore) Register(ts io.Reader, parent ChainID, platform Platform) (Layer, error) {
-	return ls.registerWithDescriptor(ts, parent, platform, distribution.Descriptor{})
+func (ls *layerStore) Register(ts io.Reader, parent ChainID, os OS) (Layer, error) {
+	return ls.registerWithDescriptor(ts, parent, os, distribution.Descriptor{})
 }
 }
 
 
-func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, platform Platform, descriptor distribution.Descriptor) (Layer, error) {
+func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, os OS, descriptor distribution.Descriptor) (Layer, error) {
 	// err is used to hold the error which will always trigger
 	// err is used to hold the error which will always trigger
 	// cleanup of creates sources but may not be an error returned
 	// cleanup of creates sources but may not be an error returned
 	// to the caller (already exists).
 	// to the caller (already exists).
@@ -271,10 +271,10 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, platf
 	var pid string
 	var pid string
 	var p *roLayer
 	var p *roLayer
 
 
-	// Integrity check - ensure we are creating something for the correct platform
+	// Integrity check - ensure we are creating something for the correct operating system
 	if system.LCOWSupported() {
 	if system.LCOWSupported() {
-		if strings.ToLower(ls.platform) != strings.ToLower(string(platform)) {
-			return nil, fmt.Errorf("cannot create entry for platform %q in layer store for platform %q", platform, ls.platform)
+		if strings.ToLower(ls.os) != strings.ToLower(string(os)) {
+			return nil, fmt.Errorf("cannot create entry for operating system %q in layer store for operating system %q", os, ls.os)
 		}
 		}
 	}
 	}
 
 
@@ -306,7 +306,7 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, platf
 		layerStore:     ls,
 		layerStore:     ls,
 		references:     map[Layer]struct{}{},
 		references:     map[Layer]struct{}{},
 		descriptor:     descriptor,
 		descriptor:     descriptor,
-		platform:       platform,
+		os:             os,
 	}
 	}
 
 
 	if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil {
 	if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil {

+ 2 - 2
layer/layer_store_windows.go

@@ -6,6 +6,6 @@ import (
 	"github.com/docker/distribution"
 	"github.com/docker/distribution"
 )
 )
 
 
-func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, platform Platform, descriptor distribution.Descriptor) (Layer, error) {
-	return ls.registerWithDescriptor(ts, parent, platform, descriptor)
+func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, os OS, descriptor distribution.Descriptor) (Layer, error) {
+	return ls.registerWithDescriptor(ts, parent, os, descriptor)
 }
 }

+ 7 - 7
layer/layer_test.go

@@ -108,7 +108,7 @@ func createLayer(ls Store, parent ChainID, layerFunc layerInit) (Layer, error) {
 	}
 	}
 	defer ts.Close()
 	defer ts.Close()
 
 
-	layer, err := ls.Register(ts, parent, Platform(runtime.GOOS))
+	layer, err := ls.Register(ts, parent, OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -501,7 +501,7 @@ func TestTarStreamStability(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer1, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
+	layer1, err := ls.Register(bytes.NewReader(tar1), "", OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -520,7 +520,7 @@ func TestTarStreamStability(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID(), Platform(runtime.GOOS))
+	layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID(), OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -688,12 +688,12 @@ func TestRegisterExistingLayer(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), Platform(runtime.GOOS))
+	layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), Platform(runtime.GOOS))
+	layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -728,12 +728,12 @@ func TestTarStreamVerification(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer1, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
+	layer1, err := ls.Register(bytes.NewReader(tar1), "", OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer2, err := ls.Register(bytes.NewReader(tar2), "", Platform(runtime.GOOS))
+	layer2, err := ls.Register(bytes.NewReader(tar2), "", OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 4 - 4
layer/migration_test.go

@@ -110,14 +110,14 @@ func TestLayerMigration(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer1b, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
+	layer1b, err := ls.Register(bytes.NewReader(tar1), "", OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
 	assertReferences(t, layer1a, layer1b)
 	assertReferences(t, layer1a, layer1b)
 	// Attempt register, should be same
 	// Attempt register, should be same
-	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), Platform(runtime.GOOS))
+	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -238,7 +238,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	layer1b, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
+	layer1b, err := ls.Register(bytes.NewReader(tar1), "", OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -246,7 +246,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
 	assertReferences(t, layer1a, layer1b)
 	assertReferences(t, layer1a, layer1b)
 
 
 	// Attempt register, should be same
 	// Attempt register, should be same
-	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), Platform(runtime.GOOS))
+	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), OS(runtime.GOOS))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 2 - 2
layer/ro_layer.go

@@ -16,7 +16,7 @@ type roLayer struct {
 	size       int64
 	size       int64
 	layerStore *layerStore
 	layerStore *layerStore
 	descriptor distribution.Descriptor
 	descriptor distribution.Descriptor
-	platform   Platform
+	os         OS
 
 
 	referenceCount int
 	referenceCount int
 	references     map[Layer]struct{}
 	references     map[Layer]struct{}
@@ -143,7 +143,7 @@ func storeLayer(tx MetadataTransaction, layer *roLayer) error {
 			return err
 			return err
 		}
 		}
 	}
 	}
-	if err := tx.SetPlatform(layer.platform); err != nil {
+	if err := tx.SetOS(layer.os); err != nil {
 		return err
 		return err
 	}
 	}
 
 

+ 1 - 1
layer/ro_layer_unix.go

@@ -2,6 +2,6 @@
 
 
 package layer
 package layer
 
 
-func (rl *roLayer) Platform() Platform {
+func (rl *roLayer) OS() OS {
 	return ""
 	return ""
 }
 }

+ 3 - 3
layer/ro_layer_windows.go

@@ -8,9 +8,9 @@ func (rl *roLayer) Descriptor() distribution.Descriptor {
 	return rl.descriptor
 	return rl.descriptor
 }
 }
 
 
-func (rl *roLayer) Platform() Platform {
-	if rl.platform == "" {
+func (rl *roLayer) OS() OS {
+	if rl.os == "" {
 		return "windows"
 		return "windows"
 	}
 	}
-	return rl.platform
+	return rl.os
 }
 }

+ 1 - 1
migrate/v1/migratev1_test.go

@@ -433,7 +433,7 @@ func (l *mockLayer) DiffSize() (int64, error) {
 	return 0, nil
 	return 0, nil
 }
 }
 
 
-func (l *mockLayer) Platform() layer.Platform {
+func (l *mockLayer) OS() layer.OS {
 	return ""
 	return ""
 }
 }
 
 

+ 6 - 7
pkg/system/init_windows.go

@@ -2,17 +2,16 @@ package system
 
 
 import "os"
 import "os"
 
 
-// LCOWSupported determines if Linux Containers on Windows are supported.
-// Note: This feature is in development (06/17) and enabled through an
-// environment variable. At a future time, it will be enabled based
-// on build number. @jhowardmsft
+// lcowSupported determines if Linux Containers on Windows are supported.
 var lcowSupported = false
 var lcowSupported = false
 
 
 // InitLCOW sets whether LCOW is supported or not
 // InitLCOW sets whether LCOW is supported or not
+// TODO @jhowardmsft.
+// 1. Replace with RS3 RTM build number.
+// 2. Remove the getenv check when image-store is coalesced as shouldn't be needed anymore.
 func InitLCOW(experimental bool) {
 func InitLCOW(experimental bool) {
-	// LCOW initialization
-	if experimental && os.Getenv("LCOW_SUPPORTED") != "" {
+	v := GetOSVersion()
+	if experimental && v.Build > 16270 && os.Getenv("LCOW_SUPPORTED") != "" {
 		lcowSupported = true
 		lcowSupported = true
 	}
 	}
-
 }
 }

+ 71 - 0
pkg/system/lcow.go

@@ -0,0 +1,71 @@
+package system
+
+import (
+	"fmt"
+	"runtime"
+	"strings"
+
+	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.
+// @jhowardmsft
+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 {
+		return fmt.Errorf("invalid platform architecture %q", platform.Architecture)
+	}
+	if platform.OS != "" {
+		if !(platform.OS == runtime.GOOS || (LCOWSupported() && platform.OS == "linux")) {
+			return fmt.Errorf("invalid platform os %q", platform.OS)
+		}
+	}
+	if len(platform.OSFeatures) != 0 {
+		return fmt.Errorf("invalid platform osfeatures %q", platform.OSFeatures)
+	}
+	if platform.OSVersion != "" {
+		return fmt.Errorf("invalid platform osversion %q", platform.OSVersion)
+	}
+	if platform.Variant != "" {
+		return fmt.Errorf("invalid platform variant %q", platform.Variant)
+	}
+	return nil
+}
+
+// ParsePlatform parses a platform string in the format os[/arch[/variant]
+// into an OCI image-spec platform structure.
+// 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 ParsePlatform(in string) *specs.Platform {
+	p := &specs.Platform{}
+	elements := strings.SplitN(strings.ToLower(in), "/", 3)
+	if len(elements) == 3 {
+		p.Variant = elements[2]
+	}
+	if len(elements) >= 2 {
+		p.Architecture = elements[1]
+	}
+	if len(elements) >= 1 {
+		p.OS = elements[0]
+	}
+	return p
+}

+ 2 - 2
pkg/system/path.go

@@ -14,9 +14,9 @@ const defaultUnixPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/s
 // DefaultPathEnv is unix style list of directories to search for
 // DefaultPathEnv is unix style list of directories to search for
 // executables. Each directory is separated from the next by a colon
 // executables. Each directory is separated from the next by a colon
 // ':' character .
 // ':' character .
-func DefaultPathEnv(platform string) string {
+func DefaultPathEnv(os string) string {
 	if runtime.GOOS == "windows" {
 	if runtime.GOOS == "windows" {
-		if platform != runtime.GOOS && LCOWSupported() {
+		if os != runtime.GOOS {
 			return defaultUnixPathEnv
 			return defaultUnixPathEnv
 		}
 		}
 		// Deliberately empty on Windows containers on Windows as the default path will be set by
 		// Deliberately empty on Windows containers on Windows as the default path will be set by

+ 2 - 2
plugin/backend_linux.go

@@ -146,7 +146,7 @@ func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) {
 	return s.config, nil
 	return s.config, nil
 }
 }
 
 
-func (s *tempConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
+func (s *tempConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, layer.OS, error) {
 	return configToRootFS(c)
 	return configToRootFS(c)
 }
 }
 
 
@@ -533,7 +533,7 @@ func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) {
 	return ioutil.ReadAll(rwc)
 	return ioutil.ReadAll(rwc)
 }
 }
 
 
-func (s *pluginConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
+func (s *pluginConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, layer.OS, error) {
 	return configToRootFS(c)
 	return configToRootFS(c)
 }
 }
 
 

+ 2 - 2
plugin/blobstore.go

@@ -126,7 +126,7 @@ type downloadManager struct {
 	configDigest digest.Digest
 	configDigest digest.Digest
 }
 }
 
 
-func (dm *downloadManager) Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
+func (dm *downloadManager) Download(ctx context.Context, initialRootFS image.RootFS, os layer.OS, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
 	// TODO @jhowardmsft LCOW: May need revisiting.
 	// TODO @jhowardmsft LCOW: May need revisiting.
 	for _, l := range layers {
 	for _, l := range layers {
 		b, err := dm.blobStore.New()
 		b, err := dm.blobStore.New()
@@ -179,6 +179,6 @@ func (dm *downloadManager) Put(dt []byte) (digest.Digest, error) {
 func (dm *downloadManager) Get(d digest.Digest) ([]byte, error) {
 func (dm *downloadManager) Get(d digest.Digest) ([]byte, error) {
 	return nil, fmt.Errorf("digest not found")
 	return nil, fmt.Errorf("digest not found")
 }
 }
-func (dm *downloadManager) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
+func (dm *downloadManager) RootFSAndOSFromConfig(c []byte) (*image.RootFS, layer.OS, error) {
 	return configToRootFS(c)
 	return configToRootFS(c)
 }
 }

+ 6 - 6
plugin/manager.go

@@ -379,11 +379,11 @@ func isEqualPrivilege(a, b types.PluginPrivilege) bool {
 	return reflect.DeepEqual(a.Value, b.Value)
 	return reflect.DeepEqual(a.Value, b.Value)
 }
 }
 
 
-func configToRootFS(c []byte) (*image.RootFS, layer.Platform, error) {
-	// TODO @jhowardmsft LCOW - Will need to revisit this. For now, calculate the platform.
-	platform := layer.Platform(runtime.GOOS)
+func configToRootFS(c []byte) (*image.RootFS, layer.OS, error) {
+	// TODO @jhowardmsft LCOW - Will need to revisit this. For now, calculate the operating system.
+	os := layer.OS(runtime.GOOS)
 	if system.LCOWSupported() {
 	if system.LCOWSupported() {
-		platform = "linux"
+		os = "linux"
 	}
 	}
 	var pluginConfig types.PluginConfig
 	var pluginConfig types.PluginConfig
 	if err := json.Unmarshal(c, &pluginConfig); err != nil {
 	if err := json.Unmarshal(c, &pluginConfig); err != nil {
@@ -391,10 +391,10 @@ func configToRootFS(c []byte) (*image.RootFS, layer.Platform, error) {
 	}
 	}
 	// validation for empty rootfs is in distribution code
 	// validation for empty rootfs is in distribution code
 	if pluginConfig.Rootfs == nil {
 	if pluginConfig.Rootfs == nil {
-		return nil, platform, nil
+		return nil, os, nil
 	}
 	}
 
 
-	return rootFSFromPlugin(pluginConfig.Rootfs), platform, nil
+	return rootFSFromPlugin(pluginConfig.Rootfs), os, nil
 }
 }
 
 
 func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS {
 func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS {