Browse code

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 authored on 2017/08/09 04:43:48
Showing 82 changed files
... ...
@@ -1,11 +1,17 @@
1 1
 package httputils
2 2
 
3 3
 import (
4
+	"encoding/json"
5
+	"fmt"
4 6
 	"io"
5 7
 	"mime"
6 8
 	"net/http"
9
+	"runtime"
7 10
 	"strings"
8 11
 
12
+	"github.com/docker/docker/api/types/versions"
13
+	"github.com/docker/docker/pkg/system"
14
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
9 15
 	"github.com/pkg/errors"
10 16
 	"github.com/sirupsen/logrus"
11 17
 	"golang.org/x/net/context"
... ...
@@ -109,3 +115,27 @@ func matchesContentType(contentType, expectedType string) bool {
109 109
 	}
110 110
 	return err == nil && mimetype == expectedType
111 111
 }
112
+
113
+// GetRequestedPlatform extracts an optional platform structure from an HTTP request header
114
+func GetRequestedPlatform(ctx context.Context, r *http.Request) (*specs.Platform, error) {
115
+	platform := &specs.Platform{}
116
+	version := VersionFromContext(ctx)
117
+	if versions.GreaterThanOrEqualTo(version, "1.32") {
118
+		requestedPlatform := r.Header.Get("X-Requested-Platform")
119
+		if requestedPlatform != "" {
120
+			if err := json.Unmarshal([]byte(requestedPlatform), platform); err != nil {
121
+				return nil, fmt.Errorf("invalid X-Requested-Platform header: %s", err)
122
+			}
123
+		}
124
+		if err := system.ValidatePlatform(platform); err != nil {
125
+			return nil, err
126
+		}
127
+	}
128
+	if platform.OS == "" {
129
+		platform.OS = runtime.GOOS
130
+	}
131
+	if platform.Architecture == "" {
132
+		platform.Architecture = runtime.GOARCH
133
+	}
134
+	return platform, nil
135
+}
... ...
@@ -87,6 +87,12 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
87 87
 		return nil, validationError{fmt.Errorf("The daemon on this platform does not support setting security options on build")}
88 88
 	}
89 89
 
90
+	platform, err := httputils.GetRequestedPlatform(ctx, r)
91
+	if err != nil {
92
+		return nil, err
93
+	}
94
+	options.Platform = *platform
95
+
90 96
 	var buildUlimits = []*units.Ulimit{}
91 97
 	ulimitsJSON := r.FormValue("ulimits")
92 98
 	if ulimitsJSON != "" {
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"io"
7 7
 	"net/http"
8
-	"runtime"
9 8
 	"strconv"
10 9
 	"strings"
11 10
 
... ...
@@ -17,8 +16,8 @@ import (
17 17
 	"github.com/docker/docker/api/types/versions"
18 18
 	"github.com/docker/docker/pkg/ioutils"
19 19
 	"github.com/docker/docker/pkg/streamformatter"
20
-	"github.com/docker/docker/pkg/system"
21 20
 	"github.com/docker/docker/registry"
21
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
22 22
 	"github.com/pkg/errors"
23 23
 	"golang.org/x/net/context"
24 24
 )
... ...
@@ -76,78 +75,46 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
76 76
 	}
77 77
 
78 78
 	var (
79
-		image   = r.Form.Get("fromImage")
80
-		repo    = r.Form.Get("repo")
81
-		tag     = r.Form.Get("tag")
82
-		message = r.Form.Get("message")
83
-		err     error
84
-		output  = ioutils.NewWriteFlusher(w)
79
+		image    = r.Form.Get("fromImage")
80
+		repo     = r.Form.Get("repo")
81
+		tag      = r.Form.Get("tag")
82
+		message  = r.Form.Get("message")
83
+		err      error
84
+		output   = ioutils.NewWriteFlusher(w)
85
+		platform = &specs.Platform{}
85 86
 	)
86 87
 	defer output.Close()
87 88
 
88
-	// TODO @jhowardmsft LCOW Support: Eventually we will need an API change
89
-	// so that platform comes from (for example) r.Form.Get("platform"). For
90
-	// the initial implementation, we assume that the platform is the
91
-	// runtime OS of the host. It will also need a validation function such
92
-	// as below which should be called after getting it from the API.
93
-	//
94
-	// Ensures the requested platform is valid and normalized
95
-	//func validatePlatform(req string) (string, error) {
96
-	//	req = strings.ToLower(req)
97
-	//	if req == "" {
98
-	//		req = runtime.GOOS // default to host platform
99
-	//	}
100
-	//	valid := []string{runtime.GOOS}
101
-	//
102
-	//	if system.LCOWSupported() {
103
-	//		valid = append(valid, "linux")
104
-	//	}
105
-	//
106
-	//	for _, item := range valid {
107
-	//		if req == item {
108
-	//			return req, nil
109
-	//		}
110
-	//	}
111
-	//	return "", fmt.Errorf("invalid platform requested: %s", req)
112
-	//}
113
-	//
114
-	// And in the call-site:
115
-	//	if platform, err = validatePlatform(platform); err != nil {
116
-	//		return err
117
-	//	}
118
-	platform := runtime.GOOS
119
-	if system.LCOWSupported() {
120
-		platform = "linux"
121
-	}
122
-
123 89
 	w.Header().Set("Content-Type", "application/json")
124 90
 
125
-	if image != "" { //pull
126
-		metaHeaders := map[string][]string{}
127
-		for k, v := range r.Header {
128
-			if strings.HasPrefix(k, "X-Meta-") {
129
-				metaHeaders[k] = v
91
+	platform, err = httputils.GetRequestedPlatform(ctx, r)
92
+	if err == nil {
93
+		if image != "" { //pull
94
+			metaHeaders := map[string][]string{}
95
+			for k, v := range r.Header {
96
+				if strings.HasPrefix(k, "X-Meta-") {
97
+					metaHeaders[k] = v
98
+				}
130 99
 			}
131
-		}
132 100
 
133
-		authEncoded := r.Header.Get("X-Registry-Auth")
134
-		authConfig := &types.AuthConfig{}
135
-		if authEncoded != "" {
136
-			authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
137
-			if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
138
-				// for a pull it is not an error if no auth was given
139
-				// to increase compatibility with the existing api it is defaulting to be empty
140
-				authConfig = &types.AuthConfig{}
101
+			authEncoded := r.Header.Get("X-Registry-Auth")
102
+			authConfig := &types.AuthConfig{}
103
+			if authEncoded != "" {
104
+				authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
105
+				if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
106
+					// for a pull it is not an error if no auth was given
107
+					// to increase compatibility with the existing api it is defaulting to be empty
108
+					authConfig = &types.AuthConfig{}
109
+				}
141 110
 			}
111
+			err = s.backend.PullImage(ctx, image, tag, platform.OS, metaHeaders, authConfig, output)
112
+		} else { //import
113
+			src := r.Form.Get("fromSrc")
114
+			// 'err' MUST NOT be defined within this block, we need any error
115
+			// generated from the download to be available to the output
116
+			// stream processing below
117
+			err = s.backend.ImportImage(src, repo, platform.OS, tag, message, r.Body, output, r.Form["changes"])
142 118
 		}
143
-
144
-		err = s.backend.PullImage(ctx, image, tag, platform, metaHeaders, authConfig, output)
145
-	} else { //import
146
-		src := r.Form.Get("fromSrc")
147
-		// 'err' MUST NOT be defined within this block, we need any error
148
-		// generated from the download to be available to the output
149
-		// stream processing below
150
-		err = s.backend.ImportImage(src, repo, platform, tag, message, r.Body, output, r.Form["changes"])
151 119
 	}
152 120
 	if err != nil {
153 121
 		if !output.Flushed() {
... ...
@@ -6181,6 +6181,19 @@ paths:
6181 6181
 
6182 6182
             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.
6183 6183
           type: "string"
6184
+        - name: "X-Requested-Platform"
6185
+          in: "header"
6186
+          description: |
6187
+            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:
6188
+
6189
+            ```
6190
+            {
6191
+              "architecture": "amd64",
6192
+              "os": "linux"
6193
+            }
6194
+            ```
6195
+          type: "string"
6196
+          default: ""
6184 6197
       responses:
6185 6198
         200:
6186 6199
           description: "no error"
... ...
@@ -6262,6 +6275,19 @@ paths:
6262 6262
           in: "header"
6263 6263
           description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)"
6264 6264
           type: "string"
6265
+        - name: "X-Requested-Platform"
6266
+          in: "header"
6267
+          description: |
6268
+            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:
6269
+
6270
+            ```
6271
+            {
6272
+              "architecture": "amd64",
6273
+              "os": "linux"
6274
+            }
6275
+            ```
6276
+          type: "string"
6277
+          default: ""
6265 6278
       tags: ["Image"]
6266 6279
   /images/{name}/json:
6267 6280
     get:
... ...
@@ -40,5 +40,5 @@ type GetImageAndLayerOptions struct {
40 40
 	PullOption PullOption
41 41
 	AuthConfig map[string]types.AuthConfig
42 42
 	Output     io.Writer
43
-	Platform   string
43
+	OS         string
44 44
 }
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/docker/docker/api/types/container"
9 9
 	"github.com/docker/docker/api/types/filters"
10 10
 	units "github.com/docker/go-units"
11
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
11 12
 )
12 13
 
13 14
 // CheckpointCreateOptions holds parameters to create a checkpoint from a container
... ...
@@ -179,10 +180,7 @@ type ImageBuildOptions struct {
179 179
 	ExtraHosts  []string // List of extra hosts
180 180
 	Target      string
181 181
 	SessionID   string
182
-
183
-	// TODO @jhowardmsft LCOW Support: This will require extending to include
184
-	// `Platform string`, but is omitted for now as it's hard-coded temporarily
185
-	// to avoid API changes.
182
+	Platform    specs.Platform
186 183
 }
187 184
 
188 185
 // ImageBuildResponse holds information
... ...
@@ -195,7 +193,8 @@ type ImageBuildResponse struct {
195 195
 
196 196
 // ImageCreateOptions holds information to create images.
197 197
 type ImageCreateOptions struct {
198
-	RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
198
+	RegistryAuth string         // RegistryAuth is the base64 encoded credentials for the registry.
199
+	Platform     specs.Platform // Platform is the target platform of the image if it needs to be pulled from the registry.
199 200
 }
200 201
 
201 202
 // ImageImportSource holds source information for ImageImport
... ...
@@ -229,6 +228,7 @@ type ImagePullOptions struct {
229 229
 	All           bool
230 230
 	RegistryAuth  string // RegistryAuth is the base64 encoded credentials for the registry
231 231
 	PrivilegeFunc RequestPrivilegeFunc
232
+	Platform      specs.Platform
232 233
 }
233 234
 
234 235
 // RequestPrivilegeFunc is a function interface that
... ...
@@ -16,7 +16,6 @@ type ContainerCreateConfig struct {
16 16
 	HostConfig       *container.HostConfig
17 17
 	NetworkingConfig *network.NetworkingConfig
18 18
 	AdjustCPUShares  bool
19
-	Platform         string
20 19
 }
21 20
 
22 21
 // ContainerRmConfig holds arguments for the container remove
... ...
@@ -327,7 +327,7 @@ type ContainerJSONBase struct {
327 327
 	Name            string
328 328
 	RestartCount    int
329 329
 	Driver          string
330
-	Platform        string
330
+	OS              string
331 331
 	MountLabel      string
332 332
 	ProcessLabel    string
333 333
 	AppArmorProfile string
... ...
@@ -95,6 +95,7 @@ type Image interface {
95 95
 	ImageID() string
96 96
 	RunConfig() *container.Config
97 97
 	MarshalJSON() ([]byte, error)
98
+	OperatingSystem() string
98 99
 }
99 100
 
100 101
 // ReleaseableLayer is an image layer that can be mounted and released
... ...
@@ -20,7 +20,6 @@ import (
20 20
 	"github.com/docker/docker/pkg/idtools"
21 21
 	"github.com/docker/docker/pkg/streamformatter"
22 22
 	"github.com/docker/docker/pkg/stringid"
23
-	"github.com/docker/docker/pkg/system"
24 23
 	"github.com/moby/buildkit/session"
25 24
 	"github.com/pkg/errors"
26 25
 	"github.com/sirupsen/logrus"
... ...
@@ -93,15 +92,6 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
93 93
 		}
94 94
 	}()
95 95
 
96
-	// TODO @jhowardmsft LCOW support - this will require rework to allow both linux and Windows simultaneously.
97
-	// This is an interim solution to hardcode to linux if LCOW is turned on.
98
-	if dockerfile.Platform == "" {
99
-		dockerfile.Platform = runtime.GOOS
100
-		if dockerfile.Platform == "windows" && system.LCOWSupported() {
101
-			dockerfile.Platform = "linux"
102
-		}
103
-	}
104
-
105 96
 	ctx, cancel := context.WithCancel(ctx)
106 97
 	defer cancel()
107 98
 
... ...
@@ -111,16 +101,26 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
111 111
 		source = src
112 112
 	}
113 113
 
114
+	os := runtime.GOOS
115
+	if dockerfile.OS != "" {
116
+		if config.Options.Platform.OS != "" && config.Options.Platform.OS != dockerfile.OS {
117
+			return nil, fmt.Errorf("invalid platform")
118
+		}
119
+		os = dockerfile.OS
120
+	} else if config.Options.Platform.OS != "" {
121
+		os = config.Options.Platform.OS
122
+	}
123
+	config.Options.Platform.OS = os
124
+	dockerfile.OS = os
125
+
114 126
 	builderOptions := builderOptions{
115 127
 		Options:        config.Options,
116 128
 		ProgressWriter: config.ProgressWriter,
117 129
 		Backend:        bm.backend,
118 130
 		PathCache:      bm.pathCache,
119 131
 		IDMappings:     bm.idMappings,
120
-		Platform:       dockerfile.Platform,
121 132
 	}
122
-
123
-	return newBuilder(ctx, builderOptions).build(source, dockerfile)
133
+	return newBuilder(ctx, builderOptions, os).build(source, dockerfile)
124 134
 }
125 135
 
126 136
 func (bm *BuildManager) initializeClientSession(ctx context.Context, cancel func(), options *types.ImageBuildOptions) (builder.Source, error) {
... ...
@@ -163,7 +163,6 @@ type builderOptions struct {
163 163
 	ProgressWriter backend.ProgressWriter
164 164
 	PathCache      pathCache
165 165
 	IDMappings     *idtools.IDMappings
166
-	Platform       string
167 166
 }
168 167
 
169 168
 // Builder is a Dockerfile builder
... ...
@@ -185,32 +184,15 @@ type Builder struct {
185 185
 	pathCache        pathCache
186 186
 	containerManager *containerManager
187 187
 	imageProber      ImageProber
188
-
189
-	// TODO @jhowardmft LCOW Support. This will be moved to options at a later
190
-	// stage, however that cannot be done now as it affects the public API
191
-	// if it were.
192
-	platform string
193 188
 }
194 189
 
195 190
 // newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
196
-// TODO @jhowardmsft LCOW support: Eventually platform can be moved into the builder
197
-// options, however, that would be an API change as it shares types.ImageBuildOptions.
198
-func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
191
+func newBuilder(clientCtx context.Context, options builderOptions, os string) *Builder {
199 192
 	config := options.Options
200 193
 	if config == nil {
201 194
 		config = new(types.ImageBuildOptions)
202 195
 	}
203 196
 
204
-	// @jhowardmsft LCOW Support. For the time being, this is interim. Eventually
205
-	// will be moved to types.ImageBuildOptions, but it can't for now as that would
206
-	// be an API change.
207
-	if options.Platform == "" {
208
-		options.Platform = runtime.GOOS
209
-	}
210
-	if options.Platform == "windows" && system.LCOWSupported() {
211
-		options.Platform = "linux"
212
-	}
213
-
214 197
 	b := &Builder{
215 198
 		clientCtx:        clientCtx,
216 199
 		options:          config,
... ...
@@ -222,9 +204,8 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
222 222
 		idMappings:       options.IDMappings,
223 223
 		imageSources:     newImageSources(clientCtx, options),
224 224
 		pathCache:        options.PathCache,
225
-		imageProber:      newImageProber(options.Backend, config.CacheFrom, options.Platform, config.NoCache),
225
+		imageProber:      newImageProber(options.Backend, config.CacheFrom, os, config.NoCache),
226 226
 		containerManager: newContainerManager(options.Backend),
227
-		platform:         options.Platform,
228 227
 	}
229 228
 
230 229
 	return b
... ...
@@ -382,25 +363,19 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
382 382
 		return config, nil
383 383
 	}
384 384
 
385
-	b := newBuilder(context.Background(), builderOptions{
386
-		Options: &types.ImageBuildOptions{NoCache: true},
387
-	})
388
-
389 385
 	dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
390 386
 	if err != nil {
391 387
 		return nil, validationError{err}
392 388
 	}
393 389
 
394
-	// TODO @jhowardmsft LCOW support. For now, if LCOW enabled, switch to linux.
395
-	// Also explicitly set the platform. Ultimately this will be in the builder
396
-	// options, but we can't do that yet as it would change the API.
397
-	if dockerfile.Platform == "" {
398
-		dockerfile.Platform = runtime.GOOS
390
+	os := runtime.GOOS
391
+	if dockerfile.OS != "" {
392
+		os = dockerfile.OS
399 393
 	}
400
-	if dockerfile.Platform == "windows" && system.LCOWSupported() {
401
-		dockerfile.Platform = "linux"
402
-	}
403
-	b.platform = dockerfile.Platform
394
+
395
+	b := newBuilder(context.Background(), builderOptions{
396
+		Options: &types.ImageBuildOptions{NoCache: true},
397
+	}, os)
404 398
 
405 399
 	// ensure that the commands are valid
406 400
 	for _, n := range dockerfile.AST.Children {
... ...
@@ -2,6 +2,6 @@
2 2
 
3 3
 package dockerfile
4 4
 
5
-func defaultShellForPlatform(platform string) []string {
5
+func defaultShellForOS(os string) []string {
6 6
 	return []string{"/bin/sh", "-c"}
7 7
 }
... ...
@@ -1,7 +1,7 @@
1 1
 package dockerfile
2 2
 
3
-func defaultShellForPlatform(platform string) []string {
4
-	if platform == "linux" {
3
+func defaultShellForOS(os string) []string {
4
+	if os == "linux" {
5 5
 		return []string{"/bin/sh", "-c"}
6 6
 	}
7 7
 	return []string{"cmd", "/S", "/C"}
... ...
@@ -32,7 +32,6 @@ func (c *containerManager) Create(runConfig *container.Config, hostConfig *conta
32 32
 	container, err := c.backend.ContainerCreate(types.ContainerCreateConfig{
33 33
 		Config:     runConfig,
34 34
 		HostConfig: hostConfig,
35
-		Platform:   platform,
36 35
 	})
37 36
 	if err != nil {
38 37
 		return container, err
... ...
@@ -82,7 +82,7 @@ func copierFromDispatchRequest(req dispatchRequest, download sourceDownloader, i
82 82
 		pathCache:   req.builder.pathCache,
83 83
 		download:    download,
84 84
 		imageSource: imageSource,
85
-		platform:    req.builder.platform,
85
+		platform:    req.builder.options.Platform.OS,
86 86
 	}
87 87
 }
88 88
 
... ...
@@ -220,12 +220,22 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
220 220
 
221 221
 	// Windows cannot support a container with no base image unless it is LCOW.
222 222
 	if name == api.NoBaseImageSpecifier {
223
+		imageImage := &image.Image{}
224
+		imageImage.OS = runtime.GOOS
223 225
 		if runtime.GOOS == "windows" {
224
-			if d.builder.platform == "windows" || (d.builder.platform != "windows" && !system.LCOWSupported()) {
226
+			switch d.builder.options.Platform.OS {
227
+			case "windows":
225 228
 				return nil, errors.New("Windows does not support FROM scratch")
229
+			case "linux":
230
+				if !system.LCOWSupported() {
231
+					return nil, errors.New("Linux containers are not supported on this system")
232
+				}
233
+				imageImage.OS = "linux"
234
+			default:
235
+				return nil, errors.Errorf("operating system %q is not supported", d.builder.options.Platform.OS)
226 236
 			}
227 237
 		}
228
-		return scratchImage, nil
238
+		return builder.Image(imageImage), nil
229 239
 	}
230 240
 	imageMount, err := d.builder.imageSources.Get(name, localOnly)
231 241
 	if err != nil {
... ...
@@ -254,7 +264,7 @@ func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error {
254 254
 func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
255 255
 	runConfig := d.state.runConfig
256 256
 	var err error
257
-	runConfig.WorkingDir, err = normalizeWorkdir(d.builder.platform, runConfig.WorkingDir, c.Path)
257
+	runConfig.WorkingDir, err = normalizeWorkdir(d.builder.options.Platform.OS, runConfig.WorkingDir, c.Path)
258 258
 	if err != nil {
259 259
 		return err
260 260
 	}
... ...
@@ -270,7 +280,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
270 270
 	}
271 271
 
272 272
 	comment := "WORKDIR " + runConfig.WorkingDir
273
-	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.builder.platform))
273
+	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.builder.options.Platform.OS))
274 274
 	containerID, err := d.builder.probeAndCreate(d.state, runConfigWithCommentCmd)
275 275
 	if err != nil || containerID == "" {
276 276
 		return err
... ...
@@ -303,7 +313,7 @@ func resolveCmdLine(cmd instructions.ShellDependantCmdLine, runConfig *container
303 303
 func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
304 304
 
305 305
 	stateRunConfig := d.state.runConfig
306
-	cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.builder.platform)
306
+	cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.builder.options.Platform.OS)
307 307
 	buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env)
308 308
 
309 309
 	saveCmd := cmdFromArgs
... ...
@@ -380,7 +390,7 @@ func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.S
380 380
 //
381 381
 func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
382 382
 	runConfig := d.state.runConfig
383
-	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.platform)
383
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
384 384
 	runConfig.Cmd = cmd
385 385
 	// set config as already being escaped, this prevents double escaping on windows
386 386
 	runConfig.ArgsEscaped = true
... ...
@@ -423,7 +433,7 @@ func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand)
423 423
 //
424 424
 func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error {
425 425
 	runConfig := d.state.runConfig
426
-	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.platform)
426
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
427 427
 	runConfig.Entrypoint = cmd
428 428
 	if !d.state.cmdSet {
429 429
 		runConfig.Cmd = nil
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/builder/dockerfile/instructions"
15 15
 	"github.com/docker/docker/pkg/system"
16 16
 	"github.com/docker/go-connections/nat"
17
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
17 18
 	"github.com/stretchr/testify/assert"
18 19
 	"github.com/stretchr/testify/require"
19 20
 )
... ...
@@ -22,13 +23,13 @@ func newBuilderWithMockBackend() *Builder {
22 22
 	mockBackend := &MockBackend{}
23 23
 	ctx := context.Background()
24 24
 	b := &Builder{
25
-		options:       &types.ImageBuildOptions{},
25
+		options:       &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
26 26
 		docker:        mockBackend,
27 27
 		Stdout:        new(bytes.Buffer),
28 28
 		clientCtx:     ctx,
29 29
 		disableCommit: true,
30 30
 		imageSources: newImageSources(ctx, builderOptions{
31
-			Options: &types.ImageBuildOptions{},
31
+			Options: &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
32 32
 			Backend: mockBackend,
33 33
 		}),
34 34
 		imageProber:      newImageProber(mockBackend, nil, runtime.GOOS, false),
... ...
@@ -118,11 +119,7 @@ func TestFromScratch(t *testing.T) {
118 118
 	require.NoError(t, err)
119 119
 	assert.True(t, sb.state.hasFromImage())
120 120
 	assert.Equal(t, "", sb.state.imageID)
121
-	// Windows does not set the default path. TODO @jhowardmsft LCOW support. This will need revisiting as we get further into the implementation
122 121
 	expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
123
-	if runtime.GOOS == "windows" {
124
-		expected = ""
125
-	}
126 122
 	assert.Equal(t, []string{expected}, sb.state.runConfig.Env)
127 123
 }
128 124
 
... ...
@@ -21,7 +21,6 @@ package dockerfile
21 21
 
22 22
 import (
23 23
 	"reflect"
24
-	"runtime"
25 24
 	"strconv"
26 25
 	"strings"
27 26
 
... ...
@@ -35,7 +34,7 @@ import (
35 35
 
36 36
 func dispatch(d dispatchRequest, cmd instructions.Command) error {
37 37
 	if c, ok := cmd.(instructions.PlatformSpecific); ok {
38
-		err := c.CheckPlatform(d.builder.platform)
38
+		err := c.CheckPlatform(d.builder.options.Platform.OS)
39 39
 		if err != nil {
40 40
 			return validationError{err}
41 41
 		}
... ...
@@ -218,19 +217,15 @@ func (s *dispatchState) beginStage(stageName string, image builder.Image) {
218 218
 	s.runConfig.StdinOnce = false
219 219
 }
220 220
 
221
-// Add the default PATH to runConfig.ENV if one exists for the platform and there
221
+// Add the default PATH to runConfig.ENV if one exists for the operating system and there
222 222
 // is no PATH set. Note that Windows containers on Windows won't have one as it's set by HCS
223 223
 func (s *dispatchState) setDefaultPath() {
224
-	// TODO @jhowardmsft LCOW Support - This will need revisiting later
225
-	platform := runtime.GOOS
226
-	if system.LCOWSupported() {
227
-		platform = "linux"
228
-	}
229
-	if system.DefaultPathEnv(platform) == "" {
224
+	defaultPath := system.DefaultPathEnv(s.baseImage.OperatingSystem())
225
+	if defaultPath == "" {
230 226
 		return
231 227
 	}
232 228
 	envMap := opts.ConvertKVStringsToMap(s.runConfig.Env)
233 229
 	if _, ok := envMap["PATH"]; !ok {
234
-		s.runConfig.Env = append(s.runConfig.Env, "PATH="+system.DefaultPathEnv(platform))
230
+		s.runConfig.Env = append(s.runConfig.Env, "PATH="+defaultPath)
235 231
 	}
236 232
 }
... ...
@@ -20,8 +20,6 @@ type imageSources struct {
20 20
 	getImage  getAndMountFunc
21 21
 }
22 22
 
23
-// TODO @jhowardmsft LCOW Support: Eventually, platform can be moved to options.Options.Platform,
24
-// and removed from builderOptions, but that can't be done yet as it would affect the API.
25 23
 func newImageSources(ctx context.Context, options builderOptions) *imageSources {
26 24
 	getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ReleaseableLayer, error) {
27 25
 		pullOption := backend.PullOptionNoPull
... ...
@@ -36,7 +34,7 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
36 36
 			PullOption: pullOption,
37 37
 			AuthConfig: options.Options.AuthConfigs,
38 38
 			Output:     options.ProgressWriter.Output,
39
-			Platform:   options.Platform,
39
+			OS:         options.Options.Platform.OS,
40 40
 		})
41 41
 	}
42 42
 
... ...
@@ -83,7 +83,7 @@ func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
83 83
 		return errors.New("Please provide a source image with `from` prior to commit")
84 84
 	}
85 85
 
86
-	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, b.platform))
86
+	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, b.options.Platform.OS))
87 87
 	hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
88 88
 	if err != nil || hit {
89 89
 		return err
... ...
@@ -122,7 +122,7 @@ func (b *Builder) commitContainer(dispatchState *dispatchState, id string, conta
122 122
 }
123 123
 
124 124
 func (b *Builder) exportImage(state *dispatchState, imageMount *imageMount, runConfig *container.Config) error {
125
-	newLayer, err := imageMount.Layer().Commit(b.platform)
125
+	newLayer, err := imageMount.Layer().Commit(b.options.Platform.OS)
126 126
 	if err != nil {
127 127
 		return err
128 128
 	}
... ...
@@ -172,7 +172,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
172 172
 	// TODO: should this have been using origPaths instead of srcHash in the comment?
173 173
 	runConfigWithCommentCmd := copyRunConfig(
174 174
 		state.runConfig,
175
-		withCmdCommentString(commentStr, b.platform))
175
+		withCmdCommentString(commentStr, b.options.Platform.OS))
176 176
 	hit, err := b.probeCache(state, runConfigWithCommentCmd)
177 177
 	if err != nil || hit {
178 178
 		return err
... ...
@@ -183,7 +183,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
183 183
 		return errors.Wrapf(err, "failed to get destination image %q", state.imageID)
184 184
 	}
185 185
 
186
-	destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, imageMount, b.platform)
186
+	destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, imageMount, b.options.Platform.OS)
187 187
 	if err != nil {
188 188
 		return err
189 189
 	}
... ...
@@ -437,9 +437,9 @@ func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier
437 437
 
438 438
 // getShell is a helper function which gets the right shell for prefixing the
439 439
 // shell-form of RUN, ENTRYPOINT and CMD instructions
440
-func getShell(c *container.Config, platform string) []string {
440
+func getShell(c *container.Config, os string) []string {
441 441
 	if 0 == len(c.Shell) {
442
-		return append([]string{}, defaultShellForPlatform(platform)[:]...)
442
+		return append([]string{}, defaultShellForOS(os)[:]...)
443 443
 	}
444 444
 	return append([]string{}, c.Shell[:]...)
445 445
 }
... ...
@@ -463,13 +463,13 @@ func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *contai
463 463
 	}
464 464
 	// Set a log config to override any default value set on the daemon
465 465
 	hostConfig := &container.HostConfig{LogConfig: defaultLogConfig}
466
-	container, err := b.containerManager.Create(runConfig, hostConfig, b.platform)
466
+	container, err := b.containerManager.Create(runConfig, hostConfig, b.options.Platform.OS)
467 467
 	return container.ID, err
468 468
 }
469 469
 
470 470
 func (b *Builder) create(runConfig *container.Config) (string, error) {
471 471
 	hostConfig := hostConfigFromOptions(b.options)
472
-	container, err := b.containerManager.Create(runConfig, hostConfig, b.platform)
472
+	container, err := b.containerManager.Create(runConfig, hostConfig, b.options.Platform.OS)
473 473
 	if err != nil {
474 474
 		return "", err
475 475
 	}
... ...
@@ -103,7 +103,7 @@ func TestCopyRunConfig(t *testing.T) {
103 103
 			doc:       "Set the command to a comment",
104 104
 			modifiers: []runConfigModifier{withCmdComment("comment", runtime.GOOS)},
105 105
 			expected: &container.Config{
106
-				Cmd: append(defaultShellForPlatform(runtime.GOOS), "#(nop) ", "comment"),
106
+				Cmd: append(defaultShellForOS(runtime.GOOS), "#(nop) ", "comment"),
107 107
 				Env: defaultEnv,
108 108
 			},
109 109
 		},
... ...
@@ -3,6 +3,7 @@ package dockerfile
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"io"
6
+	"runtime"
6 7
 
7 8
 	"github.com/docker/docker/api/types"
8 9
 	"github.com/docker/docker/api/types/backend"
... ...
@@ -96,6 +97,10 @@ func (i *mockImage) RunConfig() *container.Config {
96 96
 	return i.config
97 97
 }
98 98
 
99
+func (i *mockImage) OperatingSystem() string {
100
+	return runtime.GOOS
101
+}
102
+
99 103
 func (i *mockImage) MarshalJSON() ([]byte, error) {
100 104
 	type rawImage mockImage
101 105
 	return json.Marshal(rawImage(*i))
... ...
@@ -91,9 +91,6 @@ var (
91 91
 // DefaultEscapeToken is the default escape token
92 92
 const DefaultEscapeToken = '\\'
93 93
 
94
-// defaultPlatformToken is the platform assumed for the build if not explicitly provided
95
-var defaultPlatformToken = runtime.GOOS
96
-
97 94
 // Directive is the structure used during a build run to hold the state of
98 95
 // parsing directives.
99 96
 type Directive struct {
... ...
@@ -152,8 +149,7 @@ func (d *Directive) possibleParserDirective(line string) error {
152 152
 		}
153 153
 	}
154 154
 
155
-	// TODO @jhowardmsft LCOW Support: Eventually this check can be removed,
156
-	// but only recognise a platform token if running in LCOW mode.
155
+	// Only recognise a platform token if LCOW is supported
157 156
 	if system.LCOWSupported() {
158 157
 		tpcMatch := tokenPlatformCommand.FindStringSubmatch(strings.ToLower(line))
159 158
 		if len(tpcMatch) != 0 {
... ...
@@ -177,7 +173,6 @@ func (d *Directive) possibleParserDirective(line string) error {
177 177
 func NewDefaultDirective() *Directive {
178 178
 	directive := Directive{}
179 179
 	directive.setEscapeToken(string(DefaultEscapeToken))
180
-	directive.setPlatformToken(defaultPlatformToken)
181 180
 	return &directive
182 181
 }
183 182
 
... ...
@@ -242,8 +237,10 @@ func newNodeFromLine(line string, directive *Directive) (*Node, error) {
242 242
 type Result struct {
243 243
 	AST         *Node
244 244
 	EscapeToken rune
245
-	Platform    string
246
-	Warnings    []string
245
+	// TODO @jhowardmsft - see https://github.com/moby/moby/issues/34617
246
+	// This next field will be removed in a future update for LCOW support.
247
+	OS       string
248
+	Warnings []string
247 249
 }
248 250
 
249 251
 // PrintWarnings to the writer
... ...
@@ -323,7 +320,7 @@ func Parse(rwc io.Reader) (*Result, error) {
323 323
 		AST:         root,
324 324
 		Warnings:    warnings,
325 325
 		EscapeToken: d.escapeToken,
326
-		Platform:    d.platformToken,
326
+		OS:          d.platformToken,
327 327
 	}, nil
328 328
 }
329 329
 
... ...
@@ -12,6 +12,7 @@ import (
12 12
 
13 13
 	"github.com/docker/docker/api/types"
14 14
 	"github.com/docker/docker/api/types/container"
15
+	"github.com/docker/docker/pkg/system"
15 16
 )
16 17
 
17 18
 // ImageBuild sends request to the daemon to build images.
... ...
@@ -29,6 +30,20 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
29 29
 		return types.ImageBuildResponse{}, err
30 30
 	}
31 31
 	headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
32
+
33
+	// TODO @jhowardmsft: system.IsPlatformEmpty is a temporary function. We need to move
34
+	// (in the reasonably short future) to a package which supports all the platform
35
+	// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
36
+	if !system.IsPlatformEmpty(options.Platform) {
37
+		if err := cli.NewVersionError("1.32", "platform"); err != nil {
38
+			return types.ImageBuildResponse{}, err
39
+		}
40
+		platformJSON, err := json.Marshal(options.Platform)
41
+		if err != nil {
42
+			return types.ImageBuildResponse{}, err
43
+		}
44
+		headers.Add("X-Requested-Platform", string(platformJSON[:]))
45
+	}
32 46
 	headers.Set("Content-Type", "application/x-tar")
33 47
 
34 48
 	serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
... ...
@@ -123,6 +138,5 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
123 123
 	if options.SessionID != "" {
124 124
 		query.Set("session", options.SessionID)
125 125
 	}
126
-
127 126
 	return query, nil
128 127
 }
... ...
@@ -1,6 +1,7 @@
1 1
 package client
2 2
 
3 3
 import (
4
+	"encoding/json"
4 5
 	"io"
5 6
 	"net/url"
6 7
 
... ...
@@ -8,6 +9,8 @@ import (
8 8
 
9 9
 	"github.com/docker/distribution/reference"
10 10
 	"github.com/docker/docker/api/types"
11
+	"github.com/docker/docker/pkg/system"
12
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
11 13
 )
12 14
 
13 15
 // ImageCreate creates a new image based in the parent options.
... ...
@@ -21,14 +24,25 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
21 21
 	query := url.Values{}
22 22
 	query.Set("fromImage", reference.FamiliarName(ref))
23 23
 	query.Set("tag", getAPITagFromNamedRef(ref))
24
-	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
24
+	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
25 25
 	if err != nil {
26 26
 		return nil, err
27 27
 	}
28 28
 	return resp.body, nil
29 29
 }
30 30
 
31
-func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
31
+func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string, platform specs.Platform) (serverResponse, error) {
32 32
 	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
33
+
34
+	// TODO @jhowardmsft: system.IsPlatformEmpty is a temporary function. We need to move
35
+	// (in the reasonably short future) to a package which supports all the platform
36
+	// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
37
+	if !system.IsPlatformEmpty(platform) {
38
+		platformJSON, err := json.Marshal(platform)
39
+		if err != nil {
40
+			return serverResponse{}, err
41
+		}
42
+		headers["X-Requested-Platform"] = []string{string(platformJSON[:])}
43
+	}
33 44
 	return cli.post(ctx, "/images/create", query, nil, headers)
34 45
 }
... ...
@@ -9,6 +9,7 @@ import (
9 9
 
10 10
 	"github.com/docker/distribution/reference"
11 11
 	"github.com/docker/docker/api/types"
12
+	"github.com/docker/docker/pkg/system"
12 13
 )
13 14
 
14 15
 // 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
31 31
 		query.Set("tag", getAPITagFromNamedRef(ref))
32 32
 	}
33 33
 
34
-	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
34
+	// TODO 1: Extend to include "and the platform is supported by the daemon".
35
+	// This is dependent on https://github.com/moby/moby/pull/34628 though,
36
+	// and the daemon returning the set of platforms it supports via the _ping
37
+	// API endpoint.
38
+	//
39
+	// TODO 2: system.IsPlatformEmpty is a temporary function. We need to move
40
+	// (in the reasonably short future) to a package which supports all the platform
41
+	// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
42
+	//
43
+	// @jhowardmsft.
44
+	if !system.IsPlatformEmpty(options.Platform) {
45
+		if err := cli.NewVersionError("1.32", "platform"); err != nil {
46
+			return nil, err
47
+		}
48
+	}
49
+
50
+	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
35 51
 	if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
36 52
 		newAuthHeader, privilegeErr := options.PrivilegeFunc()
37 53
 		if privilegeErr != nil {
38 54
 			return nil, privilegeErr
39 55
 		}
40
-		resp, err = cli.tryImageCreate(ctx, query, newAuthHeader)
56
+		resp, err = cli.tryImageCreate(ctx, query, newAuthHeader, options.Platform)
41 57
 	}
42 58
 	if err != nil {
43 59
 		return nil, err
... ...
@@ -222,10 +222,6 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
222 222
 		logrus.Fatalf("Error creating middlewares: %v", err)
223 223
 	}
224 224
 
225
-	if system.LCOWSupported() {
226
-		logrus.Warnln("LCOW support is enabled - this feature is incomplete")
227
-	}
228
-
229 225
 	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore)
230 226
 	if err != nil {
231 227
 		return fmt.Errorf("Error starting daemon: %v", err)
... ...
@@ -80,7 +80,7 @@ type Container struct {
80 80
 	LogPath         string
81 81
 	Name            string
82 82
 	Driver          string
83
-	Platform        string
83
+	OS              string
84 84
 	// MountLabel contains the options for the 'mount' command
85 85
 	MountLabel             string
86 86
 	ProcessLabel           string
... ...
@@ -147,11 +147,11 @@ func (container *Container) FromDisk() error {
147 147
 		return err
148 148
 	}
149 149
 
150
-	// Ensure the platform is set if blank. Assume it is the platform of the
151
-	// host OS if not, to ensure containers created before multiple-platform
150
+	// Ensure the operating system is set if blank. Assume it is the OS of the
151
+	// host OS if not, to ensure containers created before multiple-OS
152 152
 	// support are migrated
153
-	if container.Platform == "" {
154
-		container.Platform = runtime.GOOS
153
+	if container.OS == "" {
154
+		container.OS = runtime.GOOS
155 155
 	}
156 156
 
157 157
 	return container.readHostConfig()
... ...
@@ -264,7 +264,7 @@ func (container *Container) WriteHostConfig() (*containertypes.HostConfig, error
264 264
 func (container *Container) SetupWorkingDirectory(rootIDs idtools.IDPair) error {
265 265
 	// TODO @jhowardmsft, @gupta-ak LCOW Support. This will need revisiting.
266 266
 	// We will need to do remote filesystem operations here.
267
-	if container.Platform != runtime.GOOS {
267
+	if container.OS != runtime.GOOS {
268 268
 		return nil
269 269
 	}
270 270
 
... ...
@@ -434,7 +434,7 @@ func (container *Container) ShouldRestart() bool {
434 434
 
435 435
 // AddMountPointWithVolume adds a new mount point configured with a volume to the container.
436 436
 func (container *Container) AddMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
437
-	operatingSystem := container.Platform
437
+	operatingSystem := container.OS
438 438
 	if operatingSystem == "" {
439 439
 		operatingSystem = runtime.GOOS
440 440
 	}
... ...
@@ -1047,15 +1047,14 @@ func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference)
1047 1047
 // CreateDaemonEnvironment creates a new environment variable slice for this container.
1048 1048
 func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
1049 1049
 	// Setup environment
1050
-	// TODO @jhowardmsft LCOW Support. This will need revisiting later.
1051
-	platform := container.Platform
1052
-	if platform == "" {
1053
-		platform = runtime.GOOS
1050
+	os := container.OS
1051
+	if os == "" {
1052
+		os = runtime.GOOS
1054 1053
 	}
1055 1054
 	env := []string{}
1056
-	if runtime.GOOS != "windows" || (system.LCOWSupported() && platform == "linux") {
1055
+	if runtime.GOOS != "windows" || (runtime.GOOS == "windows" && os == "linux") {
1057 1056
 		env = []string{
1058
-			"PATH=" + system.DefaultPathEnv(platform),
1057
+			"PATH=" + system.DefaultPathEnv(os),
1059 1058
 			"HOSTNAME=" + container.Config.Hostname,
1060 1059
 		}
1061 1060
 		if tty {
... ...
@@ -55,7 +55,7 @@ func (rl *releaseableLayer) Mount() (containerfs.ContainerFS, error) {
55 55
 	return mountPath, nil
56 56
 }
57 57
 
58
-func (rl *releaseableLayer) Commit(platform string) (builder.ReleaseableLayer, error) {
58
+func (rl *releaseableLayer) Commit(os string) (builder.ReleaseableLayer, error) {
59 59
 	var chainID layer.ChainID
60 60
 	if rl.roLayer != nil {
61 61
 		chainID = rl.roLayer.ChainID()
... ...
@@ -67,7 +67,7 @@ func (rl *releaseableLayer) Commit(platform string) (builder.ReleaseableLayer, e
67 67
 	}
68 68
 	defer stream.Close()
69 69
 
70
-	newLayer, err := rl.layerStore.Register(stream, chainID, layer.Platform(platform))
70
+	newLayer, err := rl.layerStore.Register(stream, chainID, layer.OS(os))
71 71
 	if err != nil {
72 72
 		return nil, err
73 73
 	}
... ...
@@ -176,7 +176,7 @@ func (daemon *Daemon) pullForBuilder(ctx context.Context, name string, authConfi
176 176
 // leaking of layers.
177 177
 func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ReleaseableLayer, error) {
178 178
 	if refOrID == "" {
179
-		layer, err := newReleasableLayerForImage(nil, daemon.stores[opts.Platform].layerStore)
179
+		layer, err := newReleasableLayerForImage(nil, daemon.stores[opts.OS].layerStore)
180 180
 		return nil, layer, err
181 181
 	}
182 182
 
... ...
@@ -187,16 +187,16 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st
187 187
 		}
188 188
 		// TODO: shouldn't we error out if error is different from "not found" ?
189 189
 		if image != nil {
190
-			layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
190
+			layer, err := newReleasableLayerForImage(image, daemon.stores[opts.OS].layerStore)
191 191
 			return image, layer, err
192 192
 		}
193 193
 	}
194 194
 
195
-	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.Platform)
195
+	image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output, opts.OS)
196 196
 	if err != nil {
197 197
 		return nil, nil, err
198 198
 	}
199
-	layer, err := newReleasableLayerForImage(image, daemon.stores[opts.Platform].layerStore)
199
+	layer, err := newReleasableLayerForImage(image, daemon.stores[opts.OS].layerStore)
200 200
 	return image, layer, err
201 201
 }
202 202
 
... ...
@@ -20,7 +20,6 @@ import (
20 20
 	containerpkg "github.com/docker/docker/container"
21 21
 	"github.com/docker/docker/daemon/cluster/convert"
22 22
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
23
-	"github.com/docker/docker/pkg/system"
24 23
 	"github.com/docker/libnetwork"
25 24
 	"github.com/docker/swarmkit/agent/exec"
26 25
 	"github.com/docker/swarmkit/api"
... ...
@@ -93,9 +92,6 @@ func (c *containerAdapter) pullImage(ctx context.Context) error {
93 93
 		// TODO @jhowardmsft LCOW Support: This will need revisiting as
94 94
 		// the stack is built up to include LCOW support for swarm.
95 95
 		platform := runtime.GOOS
96
-		if system.LCOWSupported() {
97
-			platform = "linux"
98
-		}
99 96
 		err := c.backend.PullImage(ctx, c.container.image(), "", platform, metaHeaders, authConfig, pw)
100 97
 		pw.CloseWithError(err)
101 98
 	}()
... ...
@@ -175,17 +175,17 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
175 175
 		parent = new(image.Image)
176 176
 		parent.RootFS = image.NewRootFS()
177 177
 	} else {
178
-		parent, err = daemon.stores[container.Platform].imageStore.Get(container.ImageID)
178
+		parent, err = daemon.stores[container.OS].imageStore.Get(container.ImageID)
179 179
 		if err != nil {
180 180
 			return "", err
181 181
 		}
182 182
 	}
183 183
 
184
-	l, err := daemon.stores[container.Platform].layerStore.Register(rwTar, parent.RootFS.ChainID(), layer.Platform(container.Platform))
184
+	l, err := daemon.stores[container.OS].layerStore.Register(rwTar, parent.RootFS.ChainID(), layer.OS(container.OS))
185 185
 	if err != nil {
186 186
 		return "", err
187 187
 	}
188
-	defer layer.ReleaseAndLog(daemon.stores[container.Platform].layerStore, l)
188
+	defer layer.ReleaseAndLog(daemon.stores[container.OS].layerStore, l)
189 189
 
190 190
 	containerConfig := c.ContainerConfig
191 191
 	if containerConfig == nil {
... ...
@@ -199,18 +199,18 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
199 199
 		Config:          newConfig,
200 200
 		DiffID:          l.DiffID(),
201 201
 	}
202
-	config, err := json.Marshal(image.NewChildImage(parent, cc, container.Platform))
202
+	config, err := json.Marshal(image.NewChildImage(parent, cc, container.OS))
203 203
 	if err != nil {
204 204
 		return "", err
205 205
 	}
206 206
 
207
-	id, err := daemon.stores[container.Platform].imageStore.Create(config)
207
+	id, err := daemon.stores[container.OS].imageStore.Create(config)
208 208
 	if err != nil {
209 209
 		return "", err
210 210
 	}
211 211
 
212 212
 	if container.ImageID != "" {
213
-		if err := daemon.stores[container.Platform].imageStore.SetParent(id, container.ImageID); err != nil {
213
+		if err := daemon.stores[container.OS].imageStore.SetParent(id, container.ImageID); err != nil {
214 214
 			return "", err
215 215
 		}
216 216
 	}
... ...
@@ -229,7 +229,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
229 229
 				return "", err
230 230
 			}
231 231
 		}
232
-		if err := daemon.TagImageWithReference(id, container.Platform, newTag); err != nil {
232
+		if err := daemon.TagImageWithReference(id, container.OS, newTag); err != nil {
233 233
 			return "", err
234 234
 		}
235 235
 		imageRef = reference.FamiliarString(newTag)
... ...
@@ -246,13 +246,13 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
246 246
 }
247 247
 
248 248
 func (daemon *Daemon) exportContainerRw(container *container.Container) (arch io.ReadCloser, err error) {
249
-	rwlayer, err := daemon.stores[container.Platform].layerStore.GetRWLayer(container.ID)
249
+	rwlayer, err := daemon.stores[container.OS].layerStore.GetRWLayer(container.ID)
250 250
 	if err != nil {
251 251
 		return nil, err
252 252
 	}
253 253
 	defer func() {
254 254
 		if err != nil {
255
-			daemon.stores[container.Platform].layerStore.ReleaseRWLayer(rwlayer)
255
+			daemon.stores[container.OS].layerStore.ReleaseRWLayer(rwlayer)
256 256
 		}
257 257
 	}()
258 258
 
... ...
@@ -273,7 +273,7 @@ func (daemon *Daemon) exportContainerRw(container *container.Container) (arch io
273 273
 	return ioutils.NewReadCloserWrapper(archive, func() error {
274 274
 			archive.Close()
275 275
 			err = rwlayer.Unmount()
276
-			daemon.stores[container.Platform].layerStore.ReleaseRWLayer(rwlayer)
276
+			daemon.stores[container.OS].layerStore.ReleaseRWLayer(rwlayer)
277 277
 			return err
278 278
 		}),
279 279
 		nil
... ...
@@ -123,7 +123,7 @@ func (daemon *Daemon) Register(c *container.Container) error {
123 123
 	return c.CheckpointTo(daemon.containersReplica)
124 124
 }
125 125
 
126
-func (daemon *Daemon) newContainer(name string, platform string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
126
+func (daemon *Daemon) newContainer(name string, operatingSystem string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
127 127
 	var (
128 128
 		id             string
129 129
 		err            error
... ...
@@ -156,8 +156,8 @@ func (daemon *Daemon) newContainer(name string, platform string, config *contain
156 156
 	base.ImageID = imgID
157 157
 	base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
158 158
 	base.Name = name
159
-	base.Driver = daemon.GraphDriverName(platform)
160
-	base.Platform = platform
159
+	base.Driver = daemon.GraphDriverName(operatingSystem)
160
+	base.OS = operatingSystem
161 161
 	return base, err
162 162
 }
163 163
 
... ...
@@ -39,17 +39,21 @@ func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, manage
39 39
 		return containertypes.ContainerCreateCreatedBody{}, validationError{errors.New("Config cannot be empty in order to create a container")}
40 40
 	}
41 41
 
42
-	// TODO: @jhowardmsft LCOW support - at a later point, can remove the hard-coding
43
-	// to force the platform to be linux.
44
-	// Default the platform if not supplied
45
-	if params.Platform == "" {
46
-		params.Platform = runtime.GOOS
47
-	}
48
-	if system.LCOWSupported() {
49
-		params.Platform = "linux"
42
+	os := runtime.GOOS
43
+	if params.Config.Image != "" {
44
+		img, err := daemon.GetImage(params.Config.Image)
45
+		if err == nil {
46
+			os = img.OS
47
+		}
48
+	} else {
49
+		// This mean scratch. On Windows, we can safely assume that this is a linux
50
+		// container. On other platforms, it's the host OS (which it already is)
51
+		if runtime.GOOS == "windows" && system.LCOWSupported() {
52
+			os = "linux"
53
+		}
50 54
 	}
51 55
 
52
-	warnings, err := daemon.verifyContainerSettings(params.Platform, params.HostConfig, params.Config, false)
56
+	warnings, err := daemon.verifyContainerSettings(os, params.HostConfig, params.Config, false)
53 57
 	if err != nil {
54 58
 		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
55 59
 	}
... ...
@@ -85,29 +89,25 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
85 85
 		err       error
86 86
 	)
87 87
 
88
+	os := runtime.GOOS
88 89
 	if params.Config.Image != "" {
89 90
 		img, err = daemon.GetImage(params.Config.Image)
90 91
 		if err != nil {
91 92
 			return nil, err
92 93
 		}
94
+		os = img.OS
93 95
 
94 96
 		if runtime.GOOS == "solaris" && img.OS != "solaris " {
95
-			return nil, errors.New("platform on which parent image was created is not Solaris")
97
+			return nil, errors.New("operating system on which parent image was created is not Solaris")
96 98
 		}
97 99
 		imgID = img.ID()
98 100
 
99 101
 		if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() {
100
-			return nil, errors.New("platform on which parent image was created is not Windows")
102
+			return nil, errors.New("operating system on which parent image was created is not Windows")
101 103
 		}
102
-	}
103
-
104
-	// Make sure the platform requested matches the image
105
-	if img != nil {
106
-		if params.Platform != img.Platform() {
107
-			// Ignore this in LCOW mode. @jhowardmsft TODO - This will need revisiting later.
108
-			if !system.LCOWSupported() {
109
-				return nil, fmt.Errorf("cannot create a %s container from a %s image", params.Platform, img.Platform())
110
-			}
104
+	} else {
105
+		if runtime.GOOS == "windows" {
106
+			os = "linux" // 'scratch' case.
111 107
 		}
112 108
 	}
113 109
 
... ...
@@ -119,7 +119,7 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
119 119
 		return nil, validationError{err}
120 120
 	}
121 121
 
122
-	if container, err = daemon.newContainer(params.Name, params.Platform, params.Config, params.HostConfig, imgID, managed); err != nil {
122
+	if container, err = daemon.newContainer(params.Name, os, params.Config, params.HostConfig, imgID, managed); err != nil {
123 123
 		return nil, err
124 124
 	}
125 125
 	defer func() {
... ...
@@ -170,7 +170,7 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
170 170
 		return nil, err
171 171
 	}
172 172
 
173
-	if err := daemon.createContainerPlatformSpecificSettings(container, params.Config, params.HostConfig); err != nil {
173
+	if err := daemon.createContainerOSSpecificSettings(container, params.Config, params.HostConfig); err != nil {
174 174
 		return nil, err
175 175
 	}
176 176
 
... ...
@@ -253,7 +253,7 @@ func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig)
253 253
 func (daemon *Daemon) setRWLayer(container *container.Container) error {
254 254
 	var layerID layer.ChainID
255 255
 	if container.ImageID != "" {
256
-		img, err := daemon.stores[container.Platform].imageStore.Get(container.ImageID)
256
+		img, err := daemon.stores[container.OS].imageStore.Get(container.ImageID)
257 257
 		if err != nil {
258 258
 			return err
259 259
 		}
... ...
@@ -266,7 +266,7 @@ func (daemon *Daemon) setRWLayer(container *container.Container) error {
266 266
 		StorageOpt: container.HostConfig.StorageOpt,
267 267
 	}
268 268
 
269
-	rwLayer, err := daemon.stores[container.Platform].layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
269
+	rwLayer, err := daemon.stores[container.OS].layerStore.CreateRWLayer(container.ID, layerID, rwLayerOpts)
270 270
 	if err != nil {
271 271
 		return err
272 272
 	}
... ...
@@ -15,8 +15,8 @@ import (
15 15
 	"github.com/sirupsen/logrus"
16 16
 )
17 17
 
18
-// createContainerPlatformSpecificSettings performs platform specific container create functionality
19
-func (daemon *Daemon) createContainerPlatformSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
18
+// createContainerOSSpecificSettings performs host-OS specific container create functionality
19
+func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
20 20
 	if err := daemon.Mount(container); err != nil {
21 21
 		return err
22 22
 	}
... ...
@@ -10,10 +10,10 @@ import (
10 10
 	"github.com/docker/docker/volume"
11 11
 )
12 12
 
13
-// createContainerPlatformSpecificSettings performs platform specific container create functionality
14
-func (daemon *Daemon) createContainerPlatformSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
13
+// createContainerOSSpecificSettings performs host-OS specific container create functionality
14
+func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
15 15
 
16
-	if container.Platform == runtime.GOOS {
16
+	if container.OS == runtime.GOOS {
17 17
 		// Make sure the host config has the default daemon isolation if not specified by caller.
18 18
 		if containertypes.Isolation.IsDefault(containertypes.Isolation(hostConfig.Isolation)) {
19 19
 			hostConfig.Isolation = daemon.defaultIsolation
... ...
@@ -26,7 +26,7 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain
26 26
 		}
27 27
 		hostConfig.Isolation = "hyperv"
28 28
 	}
29
-	parser := volume.NewParser(container.Platform)
29
+	parser := volume.NewParser(container.OS)
30 30
 	for spec := range config.Volumes {
31 31
 
32 32
 		mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver)
... ...
@@ -162,9 +162,9 @@ func (daemon *Daemon) restore() error {
162 162
 		}
163 163
 
164 164
 		// Ignore the container if it does not support the current driver being used by the graph
165
-		currentDriverForContainerPlatform := daemon.stores[container.Platform].graphDriver
166
-		if (container.Driver == "" && currentDriverForContainerPlatform == "aufs") || container.Driver == currentDriverForContainerPlatform {
167
-			rwlayer, err := daemon.stores[container.Platform].layerStore.GetRWLayer(container.ID)
165
+		currentDriverForContainerOS := daemon.stores[container.OS].graphDriver
166
+		if (container.Driver == "" && currentDriverForContainerOS == "aufs") || container.Driver == currentDriverForContainerOS {
167
+			rwlayer, err := daemon.stores[container.OS].layerStore.GetRWLayer(container.ID)
168 168
 			if err != nil {
169 169
 				logrus.Errorf("Failed to load container mount %v: %v", id, err)
170 170
 				continue
... ...
@@ -664,7 +664,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
664 664
 	}
665 665
 
666 666
 	var graphDrivers []string
667
-	for platform, ds := range d.stores {
667
+	for operatingSystem, ds := range d.stores {
668 668
 		ls, err := layer.NewStoreFromOptions(layer.StoreOptions{
669 669
 			StorePath:                 config.Root,
670 670
 			MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"),
... ...
@@ -673,14 +673,14 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
673 673
 			IDMappings:                idMappings,
674 674
 			PluginGetter:              d.PluginStore,
675 675
 			ExperimentalEnabled:       config.Experimental,
676
-			Platform:                  platform,
676
+			OS:                        operatingSystem,
677 677
 		})
678 678
 		if err != nil {
679 679
 			return nil, err
680 680
 		}
681 681
 		ds.graphDriver = ls.DriverName() // As layerstore may set the driver
682 682
 		ds.layerStore = ls
683
-		d.stores[platform] = ds
683
+		d.stores[operatingSystem] = ds
684 684
 		graphDrivers = append(graphDrivers, ls.DriverName())
685 685
 	}
686 686
 
... ...
@@ -691,13 +691,13 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
691 691
 
692 692
 	logrus.Debugf("Max Concurrent Downloads: %d", *config.MaxConcurrentDownloads)
693 693
 	lsMap := make(map[string]layer.Store)
694
-	for platform, ds := range d.stores {
695
-		lsMap[platform] = ds.layerStore
694
+	for operatingSystem, ds := range d.stores {
695
+		lsMap[operatingSystem] = ds.layerStore
696 696
 	}
697 697
 	d.downloadManager = xfer.NewLayerDownloadManager(lsMap, *config.MaxConcurrentDownloads)
698 698
 	logrus.Debugf("Max Concurrent Uploads: %d", *config.MaxConcurrentUploads)
699 699
 	d.uploadManager = xfer.NewLayerUploadManager(*config.MaxConcurrentUploads)
700
-	for platform, ds := range d.stores {
700
+	for operatingSystem, ds := range d.stores {
701 701
 		imageRoot := filepath.Join(config.Root, "image", ds.graphDriver)
702 702
 		ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb"))
703 703
 		if err != nil {
... ...
@@ -705,13 +705,13 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
705 705
 		}
706 706
 
707 707
 		var is image.Store
708
-		is, err = image.NewImageStore(ifs, platform, ds.layerStore)
708
+		is, err = image.NewImageStore(ifs, operatingSystem, ds.layerStore)
709 709
 		if err != nil {
710 710
 			return nil, err
711 711
 		}
712 712
 		ds.imageRoot = imageRoot
713 713
 		ds.imageStore = is
714
-		d.stores[platform] = ds
714
+		d.stores[operatingSystem] = ds
715 715
 	}
716 716
 
717 717
 	// Configure the volumes driver
... ...
@@ -921,7 +921,7 @@ func (daemon *Daemon) Shutdown() error {
921 921
 				logrus.Errorf("Stop container error: %v", err)
922 922
 				return
923 923
 			}
924
-			if mountid, err := daemon.stores[c.Platform].layerStore.GetMountID(c.ID); err == nil {
924
+			if mountid, err := daemon.stores[c.OS].layerStore.GetMountID(c.ID); err == nil {
925 925
 				daemon.cleanupMountsByID(mountid)
926 926
 			}
927 927
 			logrus.Debugf("container stopped %s", c.ID)
... ...
@@ -981,7 +981,7 @@ func (daemon *Daemon) Mount(container *container.Container) error {
981 981
 		if runtime.GOOS != "windows" {
982 982
 			daemon.Unmount(container)
983 983
 			return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
984
-				daemon.GraphDriverName(container.Platform), container.ID, container.BaseFS, dir)
984
+				daemon.GraphDriverName(container.OS), container.ID, container.BaseFS, dir)
985 985
 		}
986 986
 	}
987 987
 	container.BaseFS = dir // TODO: combine these fields
... ...
@@ -493,12 +493,14 @@ func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig
493 493
 // conditionalMountOnStart is a platform specific helper function during the
494 494
 // container start to call mount.
495 495
 func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
496
-	// Bail out now for Linux containers
497
-	if system.LCOWSupported() && container.Platform != "windows" {
496
+	// Bail out now for Linux containers. We cannot mount the containers filesystem on the
497
+	// host as it is a non-Windows filesystem.
498
+	if system.LCOWSupported() && container.OS != "windows" {
498 499
 		return nil
499 500
 	}
500 501
 
501
-	// We do not mount if a Hyper-V container
502
+	// We do not mount if a Hyper-V container as it needs to be mounted inside the
503
+	// utility VM, not the host.
502 504
 	if !daemon.runAsHyperVContainer(container.HostConfig) {
503 505
 		return daemon.Mount(container)
504 506
 	}
... ...
@@ -509,7 +511,7 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
509 509
 // during the cleanup of a container to unmount.
510 510
 func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
511 511
 	// Bail out now for Linux containers
512
-	if system.LCOWSupported() && container.Platform != "windows" {
512
+	if system.LCOWSupported() && container.OS != "windows" {
513 513
 		return nil
514 514
 	}
515 515
 
... ...
@@ -116,10 +116,10 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
116 116
 	// When container creation fails and `RWLayer` has not been created yet, we
117 117
 	// do not call `ReleaseRWLayer`
118 118
 	if container.RWLayer != nil {
119
-		metadata, err := daemon.stores[container.Platform].layerStore.ReleaseRWLayer(container.RWLayer)
119
+		metadata, err := daemon.stores[container.OS].layerStore.ReleaseRWLayer(container.RWLayer)
120 120
 		layer.LogReleaseMetadata(metadata)
121 121
 		if err != nil && err != layer.ErrMountDoesNotExist && !os.IsNotExist(errors.Cause(err)) {
122
-			return errors.Wrapf(err, "driver %q failed to remove root filesystem for %s", daemon.GraphDriverName(container.Platform), container.ID)
122
+			return errors.Wrapf(err, "driver %q failed to remove root filesystem for %s", daemon.GraphDriverName(container.OS), container.ID)
123 123
 		}
124 124
 	}
125 125
 
... ...
@@ -8,7 +8,7 @@ import (
8 8
 
9 9
 func execSetPlatformOpt(c *container.Container, ec *exec.Config, p *libcontainerd.Process) error {
10 10
 	// Process arguments need to be escaped before sending to OCI.
11
-	if c.Platform == "windows" {
11
+	if c.OS == "windows" {
12 12
 		p.Args = escapeArgs(p.Args)
13 13
 		p.User.Username = ec.User
14 14
 	}
... ...
@@ -18,8 +18,8 @@ func (daemon *Daemon) ContainerExport(name string, out io.Writer) error {
18 18
 		return err
19 19
 	}
20 20
 
21
-	if runtime.GOOS == "windows" && container.Platform == "windows" {
22
-		return fmt.Errorf("the daemon on this platform does not support exporting Windows containers")
21
+	if runtime.GOOS == "windows" && container.OS == "windows" {
22
+		return fmt.Errorf("the daemon on this operating system does not support exporting Windows containers")
23 23
 	}
24 24
 
25 25
 	if container.IsDead() {
... ...
@@ -46,13 +46,13 @@ func (daemon *Daemon) ContainerExport(name string, out io.Writer) error {
46 46
 }
47 47
 
48 48
 func (daemon *Daemon) containerExport(container *container.Container) (arch io.ReadCloser, err error) {
49
-	rwlayer, err := daemon.stores[container.Platform].layerStore.GetRWLayer(container.ID)
49
+	rwlayer, err := daemon.stores[container.OS].layerStore.GetRWLayer(container.ID)
50 50
 	if err != nil {
51 51
 		return nil, err
52 52
 	}
53 53
 	defer func() {
54 54
 		if err != nil {
55
-			daemon.stores[container.Platform].layerStore.ReleaseRWLayer(rwlayer)
55
+			daemon.stores[container.OS].layerStore.ReleaseRWLayer(rwlayer)
56 56
 		}
57 57
 	}()
58 58
 
... ...
@@ -73,7 +73,7 @@ func (daemon *Daemon) containerExport(container *container.Container) (arch io.R
73 73
 	arch = ioutils.NewReadCloserWrapper(archive, func() error {
74 74
 		err := archive.Close()
75 75
 		rwlayer.Unmount()
76
-		daemon.stores[container.Platform].layerStore.ReleaseRWLayer(rwlayer)
76
+		daemon.stores[container.OS].layerStore.ReleaseRWLayer(rwlayer)
77 77
 		return err
78 78
 	})
79 79
 	daemon.LogContainerEvent(container, "export")
... ...
@@ -154,19 +154,8 @@ func (d *Driver) Status() [][2]string {
154 154
 	}
155 155
 }
156 156
 
157
-// panicIfUsedByLcow does exactly what it says.
158
-// TODO @jhowardmsft - this is a temporary measure for the bring-up of
159
-// Linux containers on Windows. It is a failsafe to ensure that the right
160
-// graphdriver is used.
161
-func panicIfUsedByLcow() {
162
-	if system.LCOWSupported() {
163
-		panic("inconsistency - windowsfilter graphdriver should not be used when in LCOW mode")
164
-	}
165
-}
166
-
167 157
 // Exists returns true if the given id is registered with this driver.
168 158
 func (d *Driver) Exists(id string) bool {
169
-	panicIfUsedByLcow()
170 159
 	rID, err := d.resolveID(id)
171 160
 	if err != nil {
172 161
 		return false
... ...
@@ -181,7 +170,6 @@ func (d *Driver) Exists(id string) bool {
181 181
 // CreateReadWrite creates a layer that is writable for use as a container
182 182
 // file system.
183 183
 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
184
-	panicIfUsedByLcow()
185 184
 	if opts != nil {
186 185
 		return d.create(id, parent, opts.MountLabel, false, opts.StorageOpt)
187 186
 	}
... ...
@@ -190,7 +178,6 @@ func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts
190 190
 
191 191
 // Create creates a new read-only layer with the given id.
192 192
 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
193
-	panicIfUsedByLcow()
194 193
 	if opts != nil {
195 194
 		return d.create(id, parent, opts.MountLabel, true, opts.StorageOpt)
196 195
 	}
... ...
@@ -274,7 +261,6 @@ func (d *Driver) dir(id string) string {
274 274
 
275 275
 // Remove unmounts and removes the dir information.
276 276
 func (d *Driver) Remove(id string) error {
277
-	panicIfUsedByLcow()
278 277
 	rID, err := d.resolveID(id)
279 278
 	if err != nil {
280 279
 		return err
... ...
@@ -356,7 +342,6 @@ func (d *Driver) Remove(id string) error {
356 356
 
357 357
 // Get returns the rootfs path for the id. This will mount the dir at its given path.
358 358
 func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
359
-	panicIfUsedByLcow()
360 359
 	logrus.Debugf("WindowsGraphDriver Get() id %s mountLabel %s", id, mountLabel)
361 360
 	var dir string
362 361
 
... ...
@@ -415,7 +400,6 @@ func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) {
415 415
 
416 416
 // Put adds a new layer to the driver.
417 417
 func (d *Driver) Put(id string) error {
418
-	panicIfUsedByLcow()
419 418
 	logrus.Debugf("WindowsGraphDriver Put() id %s", id)
420 419
 
421 420
 	rID, err := d.resolveID(id)
... ...
@@ -474,7 +458,6 @@ func (d *Driver) Cleanup() error {
474 474
 // layer and its parent layer which may be "".
475 475
 // The layer should be mounted when calling this function
476 476
 func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
477
-	panicIfUsedByLcow()
478 477
 	rID, err := d.resolveID(id)
479 478
 	if err != nil {
480 479
 		return
... ...
@@ -511,7 +494,6 @@ func (d *Driver) Diff(id, parent string) (_ io.ReadCloser, err error) {
511 511
 // and its parent layer. If parent is "", then all changes will be ADD changes.
512 512
 // The layer should not be mounted when calling this function.
513 513
 func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
514
-	panicIfUsedByLcow()
515 514
 	rID, err := d.resolveID(id)
516 515
 	if err != nil {
517 516
 		return nil, err
... ...
@@ -567,7 +549,6 @@ func (d *Driver) Changes(id, parent string) ([]archive.Change, error) {
567 567
 // new layer in bytes.
568 568
 // The layer should not be mounted when calling this function
569 569
 func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
570
-	panicIfUsedByLcow()
571 570
 	var layerChain []string
572 571
 	if parent != "" {
573 572
 		rPId, err := d.resolveID(parent)
... ...
@@ -602,7 +583,6 @@ func (d *Driver) ApplyDiff(id, parent string, diff io.Reader) (int64, error) {
602 602
 // and its parent and returns the size in bytes of the changes
603 603
 // relative to its base filesystem directory.
604 604
 func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
605
-	panicIfUsedByLcow()
606 605
 	rPId, err := d.resolveID(parent)
607 606
 	if err != nil {
608 607
 		return
... ...
@@ -624,7 +604,6 @@ func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
624 624
 
625 625
 // GetMetadata returns custom driver information.
626 626
 func (d *Driver) GetMetadata(id string) (map[string]string, error) {
627
-	panicIfUsedByLcow()
628 627
 	m := make(map[string]string)
629 628
 	m["dir"] = d.dir(id)
630 629
 	return m, nil
... ...
@@ -927,7 +906,6 @@ func (fg *fileGetCloserWithBackupPrivileges) Close() error {
927 927
 // DiffGetter returns a FileGetCloser that can read files from the directory that
928 928
 // contains files for the layer differences. Used for direct access for tar-split.
929 929
 func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
930
-	panicIfUsedByLcow()
931 930
 	id, err := d.resolveID(id)
932 931
 	if err != nil {
933 932
 		return nil, err
... ...
@@ -24,9 +24,9 @@ func (e errImageDoesNotExist) Error() string {
24 24
 
25 25
 func (e errImageDoesNotExist) NotFound() {}
26 26
 
27
-// GetImageIDAndPlatform returns an image ID and platform corresponding to the image referred to by
27
+// GetImageIDAndOS returns an image ID and operating system corresponding to the image referred to by
28 28
 // refOrID.
29
-func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, error) {
29
+func (daemon *Daemon) GetImageIDAndOS(refOrID string) (image.ID, string, error) {
30 30
 	ref, err := reference.ParseAnyReference(refOrID)
31 31
 	if err != nil {
32 32
 		return "", "", validationError{err}
... ...
@@ -47,16 +47,16 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
47 47
 	}
48 48
 
49 49
 	if digest, err := daemon.referenceStore.Get(namedRef); err == nil {
50
-		// Search the image stores to get the platform, defaulting to host OS.
51
-		imagePlatform := runtime.GOOS
50
+		// Search the image stores to get the operating system, defaulting to host OS.
51
+		imageOS := runtime.GOOS
52 52
 		id := image.IDFromDigest(digest)
53
-		for platform := range daemon.stores {
54
-			if img, err := daemon.stores[platform].imageStore.Get(id); err == nil {
55
-				imagePlatform = img.Platform()
53
+		for os := range daemon.stores {
54
+			if img, err := daemon.stores[os].imageStore.Get(id); err == nil {
55
+				imageOS = img.OperatingSystem()
56 56
 				break
57 57
 			}
58 58
 		}
59
-		return id, imagePlatform, nil
59
+		return id, imageOS, nil
60 60
 	}
61 61
 
62 62
 	// deprecated: repo:shortid https://github.com/docker/docker/pull/799
... ...
@@ -75,9 +75,9 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
75 75
 	}
76 76
 
77 77
 	// Search based on ID
78
-	for platform := range daemon.stores {
79
-		if id, err := daemon.stores[platform].imageStore.Search(refOrID); err == nil {
80
-			return id, platform, nil
78
+	for os := range daemon.stores {
79
+		if id, err := daemon.stores[os].imageStore.Search(refOrID); err == nil {
80
+			return id, os, nil
81 81
 		}
82 82
 	}
83 83
 
... ...
@@ -86,9 +86,9 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
86 86
 
87 87
 // GetImage returns an image corresponding to the image referred to by refOrID.
88 88
 func (daemon *Daemon) GetImage(refOrID string) (*image.Image, error) {
89
-	imgID, platform, err := daemon.GetImageIDAndPlatform(refOrID)
89
+	imgID, os, err := daemon.GetImageIDAndOS(refOrID)
90 90
 	if err != nil {
91 91
 		return nil, err
92 92
 	}
93
-	return daemon.stores[platform].imageStore.Get(imgID)
93
+	return daemon.stores[os].imageStore.Get(imgID)
94 94
 }
... ...
@@ -65,7 +65,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
65 65
 	start := time.Now()
66 66
 	records := []types.ImageDeleteResponseItem{}
67 67
 
68
-	imgID, platform, err := daemon.GetImageIDAndPlatform(imageRef)
68
+	imgID, os, err := daemon.GetImageIDAndOS(imageRef)
69 69
 	if err != nil {
70 70
 		return nil, err
71 71
 	}
... ...
@@ -94,7 +94,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
94 94
 			return nil, err
95 95
 		}
96 96
 
97
-		parsedRef, err = daemon.removeImageRef(platform, parsedRef)
97
+		parsedRef, err = daemon.removeImageRef(os, parsedRef)
98 98
 		if err != nil {
99 99
 			return nil, err
100 100
 		}
... ...
@@ -122,7 +122,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
122 122
 				remainingRefs := []reference.Named{}
123 123
 				for _, repoRef := range repoRefs {
124 124
 					if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
125
-						if _, err := daemon.removeImageRef(platform, repoRef); err != nil {
125
+						if _, err := daemon.removeImageRef(os, repoRef); err != nil {
126 126
 							return records, err
127 127
 						}
128 128
 
... ...
@@ -152,12 +152,12 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
152 152
 			if !force {
153 153
 				c |= conflictSoft &^ conflictActiveReference
154 154
 			}
155
-			if conflict := daemon.checkImageDeleteConflict(imgID, platform, c); conflict != nil {
155
+			if conflict := daemon.checkImageDeleteConflict(imgID, os, c); conflict != nil {
156 156
 				return nil, conflict
157 157
 			}
158 158
 
159 159
 			for _, repoRef := range repoRefs {
160
-				parsedRef, err := daemon.removeImageRef(platform, repoRef)
160
+				parsedRef, err := daemon.removeImageRef(os, repoRef)
161 161
 				if err != nil {
162 162
 					return nil, err
163 163
 				}
... ...
@@ -170,7 +170,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
170 170
 		}
171 171
 	}
172 172
 
173
-	if err := daemon.imageDeleteHelper(imgID, platform, &records, force, prune, removedRepositoryRef); err != nil {
173
+	if err := daemon.imageDeleteHelper(imgID, os, &records, force, prune, removedRepositoryRef); err != nil {
174 174
 		return nil, err
175 175
 	}
176 176
 
... ...
@@ -8,7 +8,7 @@ import (
8 8
 // TagImage creates the tag specified by newTag, pointing to the image named
9 9
 // imageName (alternatively, imageName can also be an image ID).
10 10
 func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
11
-	imageID, platform, err := daemon.GetImageIDAndPlatform(imageName)
11
+	imageID, os, err := daemon.GetImageIDAndOS(imageName)
12 12
 	if err != nil {
13 13
 		return err
14 14
 	}
... ...
@@ -23,16 +23,16 @@ func (daemon *Daemon) TagImage(imageName, repository, tag string) error {
23 23
 		}
24 24
 	}
25 25
 
26
-	return daemon.TagImageWithReference(imageID, platform, newTag)
26
+	return daemon.TagImageWithReference(imageID, os, newTag)
27 27
 }
28 28
 
29 29
 // TagImageWithReference adds the given reference to the image ID provided.
30
-func (daemon *Daemon) TagImageWithReference(imageID image.ID, platform string, newTag reference.Named) error {
30
+func (daemon *Daemon) TagImageWithReference(imageID image.ID, os string, newTag reference.Named) error {
31 31
 	if err := daemon.referenceStore.AddTag(newTag, imageID.Digest(), true); err != nil {
32 32
 		return err
33 33
 	}
34 34
 
35
-	if err := daemon.stores[platform].imageStore.SetLastUpdated(imageID); err != nil {
35
+	if err := daemon.stores[os].imageStore.SetLastUpdated(imageID); err != nil {
36 36
 		return err
37 37
 	}
38 38
 	daemon.LogImageEvent(imageID.String(), reference.FamiliarString(newTag), "tag")
... ...
@@ -36,7 +36,7 @@ func (r byCreated) Less(i, j int) bool { return r[i].Created < r[j].Created }
36 36
 
37 37
 // Map returns a map of all images in the ImageStore
38 38
 func (daemon *Daemon) Map() map[image.ID]*image.Image {
39
-	// TODO @jhowardmsft LCOW. This will need  work to enumerate the stores for all platforms.
39
+	// TODO @jhowardmsft LCOW. This can be removed when imagestores are coalesced
40 40
 	platform := runtime.GOOS
41 41
 	if system.LCOWSupported() {
42 42
 		platform = "linux"
... ...
@@ -51,7 +51,7 @@ func (daemon *Daemon) Map() map[image.ID]*image.Image {
51 51
 // the heads.
52 52
 func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) {
53 53
 
54
-	// TODO @jhowardmsft LCOW. This will need  work to enumerate the stores for all platforms.
54
+	// TODO @jhowardmsft LCOW. This can be removed when imagestores are coalesced
55 55
 	platform := runtime.GOOS
56 56
 	if system.LCOWSupported() {
57 57
 		platform = "linux"
... ...
@@ -273,7 +273,7 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
273 273
 	var parentImg *image.Image
274 274
 	var parentChainID layer.ChainID
275 275
 	if len(parent) != 0 {
276
-		parentImg, err = daemon.stores[img.Platform()].imageStore.Get(image.ID(parent))
276
+		parentImg, err = daemon.stores[img.OperatingSystem()].imageStore.Get(image.ID(parent))
277 277
 		if err != nil {
278 278
 			return "", errors.Wrap(err, "error getting specified parent layer")
279 279
 		}
... ...
@@ -283,11 +283,11 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
283 283
 		parentImg = &image.Image{RootFS: rootFS}
284 284
 	}
285 285
 
286
-	l, err := daemon.stores[img.Platform()].layerStore.Get(img.RootFS.ChainID())
286
+	l, err := daemon.stores[img.OperatingSystem()].layerStore.Get(img.RootFS.ChainID())
287 287
 	if err != nil {
288 288
 		return "", errors.Wrap(err, "error getting image layer")
289 289
 	}
290
-	defer daemon.stores[img.Platform()].layerStore.Release(l)
290
+	defer daemon.stores[img.OperatingSystem()].layerStore.Release(l)
291 291
 
292 292
 	ts, err := l.TarStreamFrom(parentChainID)
293 293
 	if err != nil {
... ...
@@ -295,11 +295,11 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
295 295
 	}
296 296
 	defer ts.Close()
297 297
 
298
-	newL, err := daemon.stores[img.Platform()].layerStore.Register(ts, parentChainID, layer.Platform(img.Platform()))
298
+	newL, err := daemon.stores[img.OperatingSystem()].layerStore.Register(ts, parentChainID, layer.OS(img.OperatingSystem()))
299 299
 	if err != nil {
300 300
 		return "", errors.Wrap(err, "error registering layer")
301 301
 	}
302
-	defer daemon.stores[img.Platform()].layerStore.Release(newL)
302
+	defer daemon.stores[img.OperatingSystem()].layerStore.Release(newL)
303 303
 
304 304
 	newImage := *img
305 305
 	newImage.RootFS = nil
... ...
@@ -334,7 +334,7 @@ func (daemon *Daemon) SquashImage(id, parent string) (string, error) {
334 334
 		return "", errors.Wrap(err, "error marshalling image config")
335 335
 	}
336 336
 
337
-	newImgID, err := daemon.stores[img.Platform()].imageStore.Create(b)
337
+	newImgID, err := daemon.stores[img.OperatingSystem()].imageStore.Create(b)
338 338
 	if err != nil {
339 339
 		return "", errors.Wrap(err, "error creating new image after squash")
340 340
 	}
... ...
@@ -26,16 +26,16 @@ import (
26 26
 // inConfig (if src is "-"), or from a URI specified in src. Progress output is
27 27
 // written to outStream. Repository and tag names can optionally be given in
28 28
 // the repo and tag arguments, respectively.
29
-func (daemon *Daemon) ImportImage(src string, repository, platform string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
29
+func (daemon *Daemon) ImportImage(src string, repository, os string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
30 30
 	var (
31 31
 		rc     io.ReadCloser
32 32
 		resp   *http.Response
33 33
 		newRef reference.Named
34 34
 	)
35 35
 
36
-	// Default the platform if not supplied.
37
-	if platform == "" {
38
-		platform = runtime.GOOS
36
+	// Default the operating system if not supplied.
37
+	if os == "" {
38
+		os = runtime.GOOS
39 39
 	}
40 40
 
41 41
 	if repository != "" {
... ...
@@ -90,11 +90,11 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
90 90
 	if err != nil {
91 91
 		return err
92 92
 	}
93
-	l, err := daemon.stores[platform].layerStore.Register(inflatedLayerData, "", layer.Platform(platform))
93
+	l, err := daemon.stores[os].layerStore.Register(inflatedLayerData, "", layer.OS(os))
94 94
 	if err != nil {
95 95
 		return err
96 96
 	}
97
-	defer layer.ReleaseAndLog(daemon.stores[platform].layerStore, l)
97
+	defer layer.ReleaseAndLog(daemon.stores[os].layerStore, l)
98 98
 
99 99
 	created := time.Now().UTC()
100 100
 	imgConfig, err := json.Marshal(&image.Image{
... ...
@@ -102,7 +102,7 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
102 102
 			DockerVersion: dockerversion.Version,
103 103
 			Config:        config,
104 104
 			Architecture:  runtime.GOARCH,
105
-			OS:            platform,
105
+			OS:            os,
106 106
 			Created:       created,
107 107
 			Comment:       msg,
108 108
 		},
... ...
@@ -119,14 +119,14 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
119 119
 		return err
120 120
 	}
121 121
 
122
-	id, err := daemon.stores[platform].imageStore.Create(imgConfig)
122
+	id, err := daemon.stores[os].imageStore.Create(imgConfig)
123 123
 	if err != nil {
124 124
 		return err
125 125
 	}
126 126
 
127 127
 	// FIXME: connect with commit code and call refstore directly
128 128
 	if newRef != nil {
129
-		if err := daemon.TagImageWithReference(id, platform, newRef); err != nil {
129
+		if err := daemon.TagImageWithReference(id, os, newRef); err != nil {
130 130
 			return err
131 131
 		}
132 132
 	}
... ...
@@ -171,7 +171,7 @@ func (daemon *Daemon) getInspectData(container *container.Container) (*types.Con
171 171
 		Name:         container.Name,
172 172
 		RestartCount: container.RestartCount,
173 173
 		Driver:       container.Driver,
174
-		Platform:     container.Platform,
174
+		OS:           container.OS,
175 175
 		MountLabel:   container.MountLabel,
176 176
 		ProcessLabel: container.ProcessLabel,
177 177
 		ExecIDs:      container.GetExecIDs(),
... ...
@@ -322,7 +322,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
322 322
 	if psFilters.Contains("ancestor") {
323 323
 		ancestorFilter = true
324 324
 		psFilters.WalkValues("ancestor", func(ancestor string) error {
325
-			id, platform, err := daemon.GetImageIDAndPlatform(ancestor)
325
+			id, os, err := daemon.GetImageIDAndOS(ancestor)
326 326
 			if err != nil {
327 327
 				logrus.Warnf("Error while looking up for image %v", ancestor)
328 328
 				return nil
... ...
@@ -332,7 +332,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
332 332
 				return nil
333 333
 			}
334 334
 			// Then walk down the graph and put the imageIds in imagesFilter
335
-			populateImageFilterByParents(imagesFilter, id, daemon.stores[platform].imageStore.Children)
335
+			populateImageFilterByParents(imagesFilter, id, daemon.stores[os].imageStore.Children)
336 336
 			return nil
337 337
 		})
338 338
 	}
... ...
@@ -566,7 +566,7 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty
566 566
 	c := s.Container
567 567
 	image := s.Image // keep the original ref if still valid (hasn't changed)
568 568
 	if image != s.ImageID {
569
-		id, _, err := daemon.GetImageIDAndPlatform(image)
569
+		id, _, err := daemon.GetImageIDAndOS(image)
570 570
 		if _, isDNE := err.(errImageDoesNotExist); err != nil && !isDNE {
571 571
 			return nil, err
572 572
 		}
... ...
@@ -138,9 +138,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
138 138
 	max := len(img.RootFS.DiffIDs)
139 139
 	for i := 1; i <= max; i++ {
140 140
 		img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
141
-		layerPath, err := layer.GetLayerPath(daemon.stores[c.Platform].layerStore, img.RootFS.ChainID())
141
+		layerPath, err := layer.GetLayerPath(daemon.stores[c.OS].layerStore, img.RootFS.ChainID())
142 142
 		if err != nil {
143
-			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)
143
+			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)
144 144
 		}
145 145
 		// Reverse order, expecting parent most first
146 146
 		s.Windows.LayerFolders = append([]string{layerPath}, s.Windows.LayerFolders...)
... ...
@@ -79,7 +79,7 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.Hos
79 79
 
80 80
 	// check if hostConfig is in line with the current system settings.
81 81
 	// It may happen cgroups are umounted or the like.
82
-	if _, err = daemon.verifyContainerSettings(container.Platform, container.HostConfig, nil, false); err != nil {
82
+	if _, err = daemon.verifyContainerSettings(container.OS, container.HostConfig, nil, false); err != nil {
83 83
 		return validationError{err}
84 84
 	}
85 85
 	// Adapt for old containers in case we have updates in this function and
... ...
@@ -191,7 +191,7 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
191 191
 	if err := daemon.conditionalUnmountOnCleanup(container); err != nil {
192 192
 		// FIXME: remove once reference counting for graphdrivers has been refactored
193 193
 		// Ensure that all the mounts are gone
194
-		if mountid, err := daemon.stores[container.Platform].layerStore.GetMountID(container.ID); err == nil {
194
+		if mountid, err := daemon.stores[container.OS].layerStore.GetMountID(container.ID); err == nil {
195 195
 			daemon.cleanupMountsByID(mountid)
196 196
 		}
197 197
 	}
... ...
@@ -10,7 +10,7 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
10 10
 	createOptions := []libcontainerd.CreateOption{}
11 11
 
12 12
 	// LCOW options.
13
-	if container.Platform == "linux" {
13
+	if container.OS == "linux" {
14 14
 		config := &client.Config{}
15 15
 		if err := config.GenerateDefault(daemon.configStore.GraphOptions); err != nil {
16 16
 			return nil, err
... ...
@@ -16,7 +16,7 @@ func (daemon *Daemon) ContainerUpdate(name string, hostConfig *container.HostCon
16 16
 		return container.ContainerUpdateOKBody{Warnings: warnings}, err
17 17
 	}
18 18
 
19
-	warnings, err = daemon.verifyContainerSettings(c.Platform, hostConfig, nil, true)
19
+	warnings, err = daemon.verifyContainerSettings(c.OS, hostConfig, nil, true)
20 20
 	if err != nil {
21 21
 		return container.ContainerUpdateOKBody{Warnings: warnings}, validationError{err}
22 22
 	}
... ...
@@ -75,7 +75,7 @@ func (m mounts) parts(i int) int {
75 75
 func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) {
76 76
 	binds := map[string]bool{}
77 77
 	mountPoints := map[string]*volume.MountPoint{}
78
-	parser := volume.NewParser(container.Platform)
78
+	parser := volume.NewParser(container.OS)
79 79
 	defer func() {
80 80
 		// clean up the container mountpoints once return with error
81 81
 		if retErr != nil {
... ...
@@ -256,7 +256,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
256 256
 	container.Lock()
257 257
 	defer container.Unlock()
258 258
 
259
-	parser := volume.NewParser(container.Platform)
259
+	parser := volume.NewParser(container.OS)
260 260
 
261 261
 	maybeUpdate := make(map[string]bool)
262 262
 	for _, mp := range container.MountPoints {
... ...
@@ -86,7 +86,7 @@ type ImagePushConfig struct {
86 86
 type ImageConfigStore interface {
87 87
 	Put([]byte) (digest.Digest, error)
88 88
 	Get(digest.Digest) ([]byte, error)
89
-	RootFSAndPlatformFromConfig([]byte) (*image.RootFS, layer.Platform, error)
89
+	RootFSAndOSFromConfig([]byte) (*image.RootFS, layer.OS, error)
90 90
 }
91 91
 
92 92
 // PushLayerProvider provides layers to be pushed by ChainID.
... ...
@@ -112,7 +112,7 @@ type RootFSDownloadManager interface {
112 112
 	// returns the final rootfs.
113 113
 	// Given progress output to track download progress
114 114
 	// Returns function to release download resources
115
-	Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error)
115
+	Download(ctx context.Context, initialRootFS image.RootFS, os layer.OS, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error)
116 116
 }
117 117
 
118 118
 type imageConfigStore struct {
... ...
@@ -140,7 +140,7 @@ func (s *imageConfigStore) Get(d digest.Digest) ([]byte, error) {
140 140
 	return img.RawJSON(), nil
141 141
 }
142 142
 
143
-func (s *imageConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
143
+func (s *imageConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, layer.OS, error) {
144 144
 	var unmarshalledConfig image.Image
145 145
 	if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
146 146
 		return nil, "", err
... ...
@@ -154,11 +154,11 @@ func (s *imageConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS,
154 154
 		return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
155 155
 	}
156 156
 
157
-	platform := ""
157
+	os := ""
158 158
 	if runtime.GOOS == "windows" {
159
-		platform = unmarshalledConfig.OS
159
+		os = unmarshalledConfig.OS
160 160
 	}
161
-	return unmarshalledConfig.RootFS, layer.Platform(platform), nil
161
+	return unmarshalledConfig.RootFS, layer.OS(os), nil
162 162
 }
163 163
 
164 164
 type storeLayerProvider struct {
... ...
@@ -2,6 +2,7 @@ package distribution
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"runtime"
5 6
 
6 7
 	"github.com/docker/distribution/reference"
7 8
 	"github.com/docker/docker/api"
... ...
@@ -20,7 +21,7 @@ type Puller interface {
20 20
 	// Pull tries to pull the image referenced by `tag`
21 21
 	// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
22 22
 	//
23
-	Pull(ctx context.Context, ref reference.Named) error
23
+	Pull(ctx context.Context, ref reference.Named, platform string) error
24 24
 }
25 25
 
26 26
 // 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
113 113
 			lastErr = err
114 114
 			continue
115 115
 		}
116
-		if err := puller.Pull(ctx, ref); err != nil {
116
+
117
+		// Make sure we default the platform if it hasn't been supplied
118
+		if imagePullConfig.Platform == "" {
119
+			imagePullConfig.Platform = runtime.GOOS
120
+		}
121
+
122
+		if err := puller.Pull(ctx, ref, imagePullConfig.Platform); err != nil {
117 123
 			// Was this pull cancelled? If so, don't try to fall
118 124
 			// back.
119 125
 			fallback := false
... ...
@@ -35,7 +35,7 @@ type v1Puller struct {
35 35
 	session     *registry.Session
36 36
 }
37 37
 
38
-func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
38
+func (p *v1Puller) Pull(ctx context.Context, ref reference.Named, platform string) error {
39 39
 	if _, isCanonical := ref.(reference.Canonical); isCanonical {
40 40
 		// Allowing fallback, because HTTPS v1 is before HTTP v2
41 41
 		return fallbackError{err: ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"net/url"
9 9
 	"os"
10 10
 	"runtime"
11
+	"strings"
11 12
 
12 13
 	"github.com/docker/distribution"
13 14
 	"github.com/docker/distribution/manifest/manifestlist"
... ...
@@ -61,7 +62,7 @@ type v2Puller struct {
61 61
 	confirmedV2 bool
62 62
 }
63 63
 
64
-func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
64
+func (p *v2Puller) Pull(ctx context.Context, ref reference.Named, platform string) (err error) {
65 65
 	// TODO(tiborvass): was ReceiveTimeout
66 66
 	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
67 67
 	if err != nil {
... ...
@@ -69,7 +70,7 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
69 69
 		return err
70 70
 	}
71 71
 
72
-	if err = p.pullV2Repository(ctx, ref); err != nil {
72
+	if err = p.pullV2Repository(ctx, ref, platform); err != nil {
73 73
 		if _, ok := err.(fallbackError); ok {
74 74
 			return err
75 75
 		}
... ...
@@ -84,10 +85,10 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
84 84
 	return err
85 85
 }
86 86
 
87
-func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
87
+func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named, platform string) (err error) {
88 88
 	var layersDownloaded bool
89 89
 	if !reference.IsNameOnly(ref) {
90
-		layersDownloaded, err = p.pullV2Tag(ctx, ref)
90
+		layersDownloaded, err = p.pullV2Tag(ctx, ref, platform)
91 91
 		if err != nil {
92 92
 			return err
93 93
 		}
... ...
@@ -109,7 +110,7 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
109 109
 			if err != nil {
110 110
 				return err
111 111
 			}
112
-			pulledNew, err := p.pullV2Tag(ctx, tagRef)
112
+			pulledNew, err := p.pullV2Tag(ctx, tagRef, platform)
113 113
 			if err != nil {
114 114
 				// Since this is the pull-all-tags case, don't
115 115
 				// allow an error pulling a particular tag to
... ...
@@ -325,7 +326,7 @@ func (ld *v2LayerDescriptor) Registered(diffID layer.DiffID) {
325 325
 	ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.Name.Name()})
326 326
 }
327 327
 
328
-func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdated bool, err error) {
328
+func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, os string) (tagUpdated bool, err error) {
329 329
 	manSvc, err := p.repo.Manifests(ctx)
330 330
 	if err != nil {
331 331
 		return false, err
... ...
@@ -389,17 +390,17 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
389 389
 		if p.config.RequireSchema2 {
390 390
 			return false, fmt.Errorf("invalid manifest: not schema2")
391 391
 		}
392
-		id, manifestDigest, err = p.pullSchema1(ctx, ref, v)
392
+		id, manifestDigest, err = p.pullSchema1(ctx, ref, v, os)
393 393
 		if err != nil {
394 394
 			return false, err
395 395
 		}
396 396
 	case *schema2.DeserializedManifest:
397
-		id, manifestDigest, err = p.pullSchema2(ctx, ref, v)
397
+		id, manifestDigest, err = p.pullSchema2(ctx, ref, v, os)
398 398
 		if err != nil {
399 399
 			return false, err
400 400
 		}
401 401
 	case *manifestlist.DeserializedManifestList:
402
-		id, manifestDigest, err = p.pullManifestList(ctx, ref, v)
402
+		id, manifestDigest, err = p.pullManifestList(ctx, ref, v, os)
403 403
 		if err != nil {
404 404
 			return false, err
405 405
 		}
... ...
@@ -435,7 +436,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
435 435
 	return true, nil
436 436
 }
437 437
 
438
-func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest) (id digest.Digest, manifestDigest digest.Digest, err error) {
438
+func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest, requestedOS string) (id digest.Digest, manifestDigest digest.Digest, err error) {
439 439
 	var verifiedManifest *schema1.Manifest
440 440
 	verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)
441 441
 	if err != nil {
... ...
@@ -490,7 +491,8 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
490 490
 	// The v1 manifest itself doesn't directly contain a platform. However,
491 491
 	// the history does, but unfortunately that's a string, so search through
492 492
 	// all the history until hopefully we find one which indicates the os.
493
-	platform := runtime.GOOS
493
+	// supertest2014/nyan is an example of a registry image with schemav1.
494
+	configOS := runtime.GOOS
494 495
 	if system.LCOWSupported() {
495 496
 		type config struct {
496 497
 			Os string `json:"os,omitempty"`
... ...
@@ -499,14 +501,20 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
499 499
 			var c config
500 500
 			if err := json.Unmarshal([]byte(v.V1Compatibility), &c); err == nil {
501 501
 				if c.Os != "" {
502
-					platform = c.Os
502
+					configOS = c.Os
503 503
 					break
504 504
 				}
505 505
 			}
506 506
 		}
507 507
 	}
508 508
 
509
-	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, layer.Platform(platform), descriptors, p.config.ProgressOutput)
509
+	// Early bath if the requested OS doesn't match that of the configuration.
510
+	// This avoids doing the download, only to potentially fail later.
511
+	if !strings.EqualFold(string(configOS), requestedOS) {
512
+		return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
513
+	}
514
+
515
+	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, layer.OS(configOS), descriptors, p.config.ProgressOutput)
510 516
 	if err != nil {
511 517
 		return "", "", err
512 518
 	}
... ...
@@ -527,7 +535,7 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
527 527
 	return imageID, manifestDigest, nil
528 528
 }
529 529
 
530
-func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest) (id digest.Digest, manifestDigest digest.Digest, err error) {
530
+func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest, requestedOS string) (id digest.Digest, manifestDigest digest.Digest, err error) {
531 531
 	manifestDigest, err = schema2ManifestDigest(ref, mfst)
532 532
 	if err != nil {
533 533
 		return "", "", err
... ...
@@ -576,11 +584,11 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
576 576
 	}()
577 577
 
578 578
 	var (
579
-		configJSON       []byte         // raw serialized image config
580
-		downloadedRootFS *image.RootFS  // rootFS from registered layers
581
-		configRootFS     *image.RootFS  // rootFS from configuration
582
-		release          func()         // release resources from rootFS download
583
-		platform         layer.Platform // for LCOW when registering downloaded layers
579
+		configJSON       []byte        // raw serialized image config
580
+		downloadedRootFS *image.RootFS // rootFS from registered layers
581
+		configRootFS     *image.RootFS // rootFS from configuration
582
+		release          func()        // release resources from rootFS download
583
+		configOS         layer.OS      // for LCOW when registering downloaded layers
584 584
 	)
585 585
 
586 586
 	// 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
592 592
 	// check to block Windows images being pulled on Linux is implemented, it
593 593
 	// may be necessary to perform the same type of serialisation.
594 594
 	if runtime.GOOS == "windows" {
595
-		configJSON, configRootFS, platform, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
595
+		configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
596 596
 		if err != nil {
597 597
 			return "", "", err
598 598
 		}
... ...
@@ -605,6 +613,12 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
605 605
 			return "", "", errRootFSMismatch
606 606
 		}
607 607
 
608
+		// Early bath if the requested OS doesn't match that of the configuration.
609
+		// This avoids doing the download, only to potentially fail later.
610
+		if !strings.EqualFold(string(configOS), requestedOS) {
611
+			return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
612
+		}
613
+
608 614
 		// Populate diff ids in descriptors to avoid downloading foreign layers
609 615
 		// which have been side loaded
610 616
 		for i := range descriptors {
... ...
@@ -619,7 +633,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
619 619
 				rootFS image.RootFS
620 620
 			)
621 621
 			downloadRootFS := *image.NewRootFS()
622
-			rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, platform, descriptors, p.config.ProgressOutput)
622
+			rootFS, release, err = p.config.DownloadManager.Download(ctx, downloadRootFS, layer.OS(requestedOS), descriptors, p.config.ProgressOutput)
623 623
 			if err != nil {
624 624
 				// Intentionally do not cancel the config download here
625 625
 				// 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
637 637
 	}
638 638
 
639 639
 	if configJSON == nil {
640
-		configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
640
+		configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
641 641
 		if err == nil && configRootFS == nil {
642 642
 			err = errRootFSInvalid
643 643
 		}
... ...
@@ -684,14 +698,14 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
684 684
 	return imageID, manifestDigest, nil
685 685
 }
686 686
 
687
-func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, layer.Platform, error) {
687
+func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, layer.OS, error) {
688 688
 	select {
689 689
 	case configJSON := <-configChan:
690
-		rootfs, platform, err := s.RootFSAndPlatformFromConfig(configJSON)
690
+		rootfs, os, err := s.RootFSAndOSFromConfig(configJSON)
691 691
 		if err != nil {
692 692
 			return nil, nil, "", err
693 693
 		}
694
-		return configJSON, rootfs, platform, nil
694
+		return configJSON, rootfs, os, nil
695 695
 	case err := <-errChan:
696 696
 		return nil, nil, "", err
697 697
 		// 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
701 701
 
702 702
 // pullManifestList handles "manifest lists" which point to various
703 703
 // platform-specific manifests.
704
-func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList) (id digest.Digest, manifestListDigest digest.Digest, err error) {
704
+func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, os string) (id digest.Digest, manifestListDigest digest.Digest, err error) {
705 705
 	manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
706 706
 	if err != nil {
707 707
 		return "", "", err
708 708
 	}
709 709
 
710
-	logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a os/arch match", ref, len(mfstList.Manifests))
710
+	logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), os, runtime.GOARCH)
711 711
 
712 712
 	manifestMatches := filterManifests(mfstList.Manifests)
713 713
 
714 714
 	if len(manifestMatches) == 0 {
715
-		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", runtime.GOOS, runtime.GOARCH)
715
+		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", os, runtime.GOARCH)
716 716
 		logrus.Debugf(errMsg)
717 717
 		return "", "", errors.New(errMsg)
718 718
 	}
... ...
@@ -739,12 +753,12 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
739 739
 
740 740
 	switch v := manifest.(type) {
741 741
 	case *schema1.SignedManifest:
742
-		id, _, err = p.pullSchema1(ctx, manifestRef, v)
742
+		id, _, err = p.pullSchema1(ctx, manifestRef, v, os)
743 743
 		if err != nil {
744 744
 			return "", "", err
745 745
 		}
746 746
 	case *schema2.DeserializedManifest:
747
-		id, _, err = p.pullSchema2(ctx, manifestRef, v)
747
+		id, _, err = p.pullSchema2(ctx, manifestRef, v, os)
748 748
 		if err != nil {
749 749
 			return "", "", err
750 750
 		}
... ...
@@ -76,12 +76,12 @@ func filterManifests(manifests []manifestlist.ManifestDescriptor) []manifestlist
76 76
 	var matches []manifestlist.ManifestDescriptor
77 77
 	for _, manifestDescriptor := range manifests {
78 78
 		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == lookingForOS {
79
-			if !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
79
+			if lookingForOS == "windows" && !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
80 80
 				continue
81 81
 			}
82 82
 			matches = append(matches, manifestDescriptor)
83 83
 
84
-			logrus.Debugf("found match for %s/%s with media type %s, digest %s", runtime.GOOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
84
+			logrus.Debugf("found match for %s/%s with media type %s, digest %s", lookingForOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
85 85
 		}
86 86
 	}
87 87
 	sort.Stable(manifestsByVersion(matches))
... ...
@@ -118,7 +118,7 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id
118 118
 		return fmt.Errorf("could not find image from tag %s: %v", reference.FamiliarString(ref), err)
119 119
 	}
120 120
 
121
-	rootfs, _, err := p.config.ImageStore.RootFSAndPlatformFromConfig(imgConfig)
121
+	rootfs, _, err := p.config.ImageStore.RootFSAndOSFromConfig(imgConfig)
122 122
 	if err != nil {
123 123
 		return fmt.Errorf("unable to get rootfs for image %s: %s", reference.FamiliarString(ref), err)
124 124
 	}
... ...
@@ -83,7 +83,7 @@ func testTokenPassThru(t *testing.T, ts *httptest.Server) {
83 83
 	logrus.Debug("About to pull")
84 84
 	// We expect it to fail, since we haven't mock'd the full registry exchange in our handler above
85 85
 	tag, _ := reference.WithTag(n, "tag_goes_here")
86
-	_ = p.pullV2Repository(ctx, tag)
86
+	_ = p.pullV2Repository(ctx, tag, runtime.GOOS)
87 87
 }
88 88
 
89 89
 func TestTokenPassThru(t *testing.T) {
... ...
@@ -95,7 +95,7 @@ type DownloadDescriptorWithRegistered interface {
95 95
 // Download method is called to get the layer tar data. Layers are then
96 96
 // registered in the appropriate order.  The caller must call the returned
97 97
 // release function once it is done with the returned RootFS object.
98
-func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
98
+func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS image.RootFS, os layer.OS, layers []DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
99 99
 	var (
100 100
 		topLayer       layer.Layer
101 101
 		topDownload    *downloadTransfer
... ...
@@ -105,9 +105,9 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
105 105
 		downloadsByKey = make(map[string]*downloadTransfer)
106 106
 	)
107 107
 
108
-	// Assume that the platform is the host OS if blank
109
-	if platform == "" {
110
-		platform = layer.Platform(runtime.GOOS)
108
+	// Assume that the operating system is the host OS if blank
109
+	if os == "" {
110
+		os = layer.OS(runtime.GOOS)
111 111
 	}
112 112
 
113 113
 	rootFS := initialRootFS
... ...
@@ -121,13 +121,13 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
121 121
 			if err == nil {
122 122
 				getRootFS := rootFS
123 123
 				getRootFS.Append(diffID)
124
-				l, err := ldm.layerStores[string(platform)].Get(getRootFS.ChainID())
124
+				l, err := ldm.layerStores[string(os)].Get(getRootFS.ChainID())
125 125
 				if err == nil {
126 126
 					// Layer already exists.
127 127
 					logrus.Debugf("Layer already exists: %s", descriptor.ID())
128 128
 					progress.Update(progressOutput, descriptor.ID(), "Already exists")
129 129
 					if topLayer != nil {
130
-						layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
130
+						layer.ReleaseAndLog(ldm.layerStores[string(os)], topLayer)
131 131
 					}
132 132
 					topLayer = l
133 133
 					missingLayer = false
... ...
@@ -146,7 +146,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
146 146
 		// the stack? If so, avoid downloading it more than once.
147 147
 		var topDownloadUncasted Transfer
148 148
 		if existingDownload, ok := downloadsByKey[key]; ok {
149
-			xferFunc := ldm.makeDownloadFuncFromDownload(descriptor, existingDownload, topDownload, platform)
149
+			xferFunc := ldm.makeDownloadFuncFromDownload(descriptor, existingDownload, topDownload, os)
150 150
 			defer topDownload.Transfer.Release(watcher)
151 151
 			topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
152 152
 			topDownload = topDownloadUncasted.(*downloadTransfer)
... ...
@@ -158,10 +158,10 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
158 158
 
159 159
 		var xferFunc DoFunc
160 160
 		if topDownload != nil {
161
-			xferFunc = ldm.makeDownloadFunc(descriptor, "", topDownload, platform)
161
+			xferFunc = ldm.makeDownloadFunc(descriptor, "", topDownload, os)
162 162
 			defer topDownload.Transfer.Release(watcher)
163 163
 		} else {
164
-			xferFunc = ldm.makeDownloadFunc(descriptor, rootFS.ChainID(), nil, platform)
164
+			xferFunc = ldm.makeDownloadFunc(descriptor, rootFS.ChainID(), nil, os)
165 165
 		}
166 166
 		topDownloadUncasted, watcher = ldm.tm.Transfer(transferKey, xferFunc, progressOutput)
167 167
 		topDownload = topDownloadUncasted.(*downloadTransfer)
... ...
@@ -171,7 +171,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
171 171
 	if topDownload == nil {
172 172
 		return rootFS, func() {
173 173
 			if topLayer != nil {
174
-				layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
174
+				layer.ReleaseAndLog(ldm.layerStores[string(os)], topLayer)
175 175
 			}
176 176
 		}, nil
177 177
 	}
... ...
@@ -182,7 +182,7 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
182 182
 
183 183
 	defer func() {
184 184
 		if topLayer != nil {
185
-			layer.ReleaseAndLog(ldm.layerStores[string(platform)], topLayer)
185
+			layer.ReleaseAndLog(ldm.layerStores[string(os)], topLayer)
186 186
 		}
187 187
 	}()
188 188
 
... ...
@@ -218,11 +218,11 @@ func (ldm *LayerDownloadManager) Download(ctx context.Context, initialRootFS ima
218 218
 // complete before the registration step, and registers the downloaded data
219 219
 // on top of parentDownload's resulting layer. Otherwise, it registers the
220 220
 // layer on top of the ChainID given by parentLayer.
221
-func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor, parentLayer layer.ChainID, parentDownload *downloadTransfer, platform layer.Platform) DoFunc {
221
+func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor, parentLayer layer.ChainID, parentDownload *downloadTransfer, os layer.OS) DoFunc {
222 222
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
223 223
 		d := &downloadTransfer{
224 224
 			Transfer:   NewTransfer(),
225
-			layerStore: ldm.layerStores[string(platform)],
225
+			layerStore: ldm.layerStores[string(os)],
226 226
 		}
227 227
 
228 228
 		go func() {
... ...
@@ -341,9 +341,9 @@ func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor,
341 341
 				src = fs.Descriptor()
342 342
 			}
343 343
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
344
-				d.layer, err = ds.RegisterWithDescriptor(inflatedLayerData, parentLayer, platform, src)
344
+				d.layer, err = ds.RegisterWithDescriptor(inflatedLayerData, parentLayer, os, src)
345 345
 			} else {
346
-				d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer, platform)
346
+				d.layer, err = d.layerStore.Register(inflatedLayerData, parentLayer, os)
347 347
 			}
348 348
 			if err != nil {
349 349
 				select {
... ...
@@ -382,11 +382,11 @@ func (ldm *LayerDownloadManager) makeDownloadFunc(descriptor DownloadDescriptor,
382 382
 // parentDownload. This function does not log progress output because it would
383 383
 // interfere with the progress reporting for sourceDownload, which has the same
384 384
 // Key.
385
-func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor DownloadDescriptor, sourceDownload *downloadTransfer, parentDownload *downloadTransfer, platform layer.Platform) DoFunc {
385
+func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor DownloadDescriptor, sourceDownload *downloadTransfer, parentDownload *downloadTransfer, os layer.OS) DoFunc {
386 386
 	return func(progressChan chan<- progress.Progress, start <-chan struct{}, inactive chan<- struct{}) Transfer {
387 387
 		d := &downloadTransfer{
388 388
 			Transfer:   NewTransfer(),
389
-			layerStore: ldm.layerStores[string(platform)],
389
+			layerStore: ldm.layerStores[string(os)],
390 390
 		}
391 391
 
392 392
 		go func() {
... ...
@@ -440,9 +440,9 @@ func (ldm *LayerDownloadManager) makeDownloadFuncFromDownload(descriptor Downloa
440 440
 				src = fs.Descriptor()
441 441
 			}
442 442
 			if ds, ok := d.layerStore.(layer.DescribableStore); ok {
443
-				d.layer, err = ds.RegisterWithDescriptor(layerReader, parentLayer, platform, src)
443
+				d.layer, err = ds.RegisterWithDescriptor(layerReader, parentLayer, os, src)
444 444
 			} else {
445
-				d.layer, err = d.layerStore.Register(layerReader, parentLayer, platform)
445
+				d.layer, err = d.layerStore.Register(layerReader, parentLayer, os)
446 446
 			}
447 447
 			if err != nil {
448 448
 				d.err = fmt.Errorf("failed to register layer: %v", err)
... ...
@@ -26,7 +26,7 @@ type mockLayer struct {
26 26
 	diffID    layer.DiffID
27 27
 	chainID   layer.ChainID
28 28
 	parent    layer.Layer
29
-	platform  layer.Platform
29
+	os        layer.OS
30 30
 }
31 31
 
32 32
 func (ml *mockLayer) TarStream() (io.ReadCloser, error) {
... ...
@@ -57,8 +57,8 @@ func (ml *mockLayer) DiffSize() (size int64, err error) {
57 57
 	return 0, nil
58 58
 }
59 59
 
60
-func (ml *mockLayer) Platform() layer.Platform {
61
-	return ml.platform
60
+func (ml *mockLayer) OS() layer.OS {
61
+	return ml.os
62 62
 }
63 63
 
64 64
 func (ml *mockLayer) Metadata() (map[string]string, error) {
... ...
@@ -91,7 +91,7 @@ func (ls *mockLayerStore) Map() map[layer.ChainID]layer.Layer {
91 91
 	return layers
92 92
 }
93 93
 
94
-func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID, platform layer.Platform) (layer.Layer, error) {
94
+func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID, os layer.OS) (layer.Layer, error) {
95 95
 	return ls.RegisterWithDescriptor(reader, parentID, distribution.Descriptor{})
96 96
 }
97 97
 
... ...
@@ -293,13 +293,13 @@ func TestSuccessfulDownload(t *testing.T) {
293 293
 	firstDescriptor := descriptors[0].(*mockDownloadDescriptor)
294 294
 
295 295
 	// Pre-register the first layer to simulate an already-existing layer
296
-	l, err := layerStore.Register(firstDescriptor.mockTarStream(), "", layer.Platform(runtime.GOOS))
296
+	l, err := layerStore.Register(firstDescriptor.mockTarStream(), "", layer.OS(runtime.GOOS))
297 297
 	if err != nil {
298 298
 		t.Fatal(err)
299 299
 	}
300 300
 	firstDescriptor.diffID = l.DiffID()
301 301
 
302
-	rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), layer.Platform(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
302
+	rootFS, releaseFunc, err := ldm.Download(context.Background(), *image.NewRootFS(), layer.OS(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
303 303
 	if err != nil {
304 304
 		t.Fatalf("download error: %v", err)
305 305
 	}
... ...
@@ -357,7 +357,7 @@ func TestCancelledDownload(t *testing.T) {
357 357
 	}()
358 358
 
359 359
 	descriptors := downloadDescriptors(nil)
360
-	_, _, err := ldm.Download(ctx, *image.NewRootFS(), layer.Platform(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
360
+	_, _, err := ldm.Download(ctx, *image.NewRootFS(), layer.OS(runtime.GOOS), descriptors, progress.ChanOutput(progressChan))
361 361
 	if err != context.Canceled {
362 362
 		t.Fatal("expected download to be cancelled")
363 363
 	}
... ...
@@ -96,8 +96,8 @@ func (img *Image) RunConfig() *container.Config {
96 96
 	return img.Config
97 97
 }
98 98
 
99
-// Platform returns the image's operating system. If not populated, defaults to the host runtime OS.
100
-func (img *Image) Platform() string {
99
+// OperatingSystem returns the image's operating system. If not populated, defaults to the host runtime OS.
100
+func (img *Image) OperatingSystem() string {
101 101
 	os := img.OS
102 102
 	if os == "" {
103 103
 		os = runtime.GOOS
... ...
@@ -47,17 +47,17 @@ type store struct {
47 47
 	images    map[ID]*imageMeta
48 48
 	fs        StoreBackend
49 49
 	digestSet *digestset.Set
50
-	platform  string
50
+	os        string
51 51
 }
52 52
 
53 53
 // NewImageStore returns new store object for given layer store
54
-func NewImageStore(fs StoreBackend, platform string, ls LayerGetReleaser) (Store, error) {
54
+func NewImageStore(fs StoreBackend, os string, ls LayerGetReleaser) (Store, error) {
55 55
 	is := &store{
56 56
 		ls:        ls,
57 57
 		images:    make(map[ID]*imageMeta),
58 58
 		fs:        fs,
59 59
 		digestSet: digestset.NewSet(),
60
-		platform:  platform,
60
+		os:        os,
61 61
 	}
62 62
 
63 63
 	// load all current images and retain layers
... ...
@@ -118,11 +118,11 @@ func (is *store) Create(config []byte) (ID, error) {
118 118
 		return "", err
119 119
 	}
120 120
 
121
-	// TODO @jhowardmsft - LCOW Support. This will need revisiting.
121
+	// TODO @jhowardmsft - LCOW Support. This will need revisiting when coalescing the image stores.
122 122
 	// Integrity check - ensure we are creating something for the correct platform
123 123
 	if system.LCOWSupported() {
124
-		if strings.ToLower(img.Platform()) != strings.ToLower(is.platform) {
125
-			return "", fmt.Errorf("cannot create entry for platform %q in image store for platform %q", img.Platform(), is.platform)
124
+		if strings.ToLower(img.OperatingSystem()) != strings.ToLower(is.os) {
125
+			return "", fmt.Errorf("cannot create entry for operating system %q in image store for operating system %q", img.OperatingSystem(), is.os)
126 126
 		}
127 127
 	}
128 128
 
... ...
@@ -90,13 +90,13 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
90 90
 		}
91 91
 
92 92
 		// On Windows, validate the platform, defaulting to windows if not present.
93
-		platform := layer.Platform(img.OS)
93
+		os := layer.OS(img.OS)
94 94
 		if runtime.GOOS == "windows" {
95
-			if platform == "" {
96
-				platform = "windows"
95
+			if os == "" {
96
+				os = "windows"
97 97
 			}
98
-			if (platform != "windows") && (platform != "linux") {
99
-				return fmt.Errorf("configuration for this image has an unsupported platform: %s", platform)
98
+			if (os != "windows") && (os != "linux") {
99
+				return fmt.Errorf("configuration for this image has an unsupported operating system: %s", os)
100 100
 			}
101 101
 		}
102 102
 
... ...
@@ -109,7 +109,7 @@ func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool)
109 109
 			r.Append(diffID)
110 110
 			newLayer, err := l.ls.Get(r.ChainID())
111 111
 			if err != nil {
112
-				newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), platform, m.LayerSources[diffID], progressOutput)
112
+				newLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), os, m.LayerSources[diffID], progressOutput)
113 113
 				if err != nil {
114 114
 					return err
115 115
 				}
... ...
@@ -176,7 +176,7 @@ func (l *tarexporter) setParentID(id, parentID image.ID) error {
176 176
 	return l.is.SetParent(id, parentID)
177 177
 }
178 178
 
179
-func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, platform layer.Platform, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
179
+func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, os layer.OS, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {
180 180
 	// We use system.OpenSequential to use sequential file access on Windows, avoiding
181 181
 	// depleting the standby list. On Linux, this equates to a regular os.Open.
182 182
 	rawTar, err := system.OpenSequential(filename)
... ...
@@ -206,9 +206,9 @@ func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string,
206 206
 	defer inflatedLayerData.Close()
207 207
 
208 208
 	if ds, ok := l.ls.(layer.DescribableStore); ok {
209
-		return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), platform, foreignSrc)
209
+		return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), os, foreignSrc)
210 210
 	}
211
-	return l.ls.Register(inflatedLayerData, rootFS.ChainID(), platform)
211
+	return l.ls.Register(inflatedLayerData, rootFS.ChainID(), os)
212 212
 }
213 213
 
214 214
 func (l *tarexporter) setLoadedTag(ref reference.Named, imgID digest.Digest, outStream io.Writer) error {
... ...
@@ -55,7 +55,7 @@ func (el *emptyLayer) Metadata() (map[string]string, error) {
55 55
 	return make(map[string]string), nil
56 56
 }
57 57
 
58
-func (el *emptyLayer) Platform() Platform {
58
+func (el *emptyLayer) OS() OS {
59 59
 	return ""
60 60
 }
61 61
 
... ...
@@ -2,12 +2,12 @@
2 2
 
3 3
 package layer
4 4
 
5
-// SetPlatform writes the "platform" file to the layer filestore
6
-func (fm *fileMetadataTransaction) SetPlatform(platform Platform) error {
5
+// SetOS writes the "os" file to the layer filestore
6
+func (fm *fileMetadataTransaction) SetOS(os OS) error {
7 7
 	return nil
8 8
 }
9 9
 
10
-// GetPlatform reads the "platform" file from the layer filestore
11
-func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
10
+// GetOS reads the "os" file from the layer filestore
11
+func (fms *fileMetadataStore) GetOS(layer ChainID) (OS, error) {
12 12
 	return "", nil
13 13
 }
... ...
@@ -7,19 +7,19 @@ import (
7 7
 	"strings"
8 8
 )
9 9
 
10
-// SetPlatform writes the "platform" file to the layer filestore
11
-func (fm *fileMetadataTransaction) SetPlatform(platform Platform) error {
12
-	if platform == "" {
10
+// SetOS writes the "os" file to the layer filestore
11
+func (fm *fileMetadataTransaction) SetOS(os OS) error {
12
+	if os == "" {
13 13
 		return nil
14 14
 	}
15
-	return fm.ws.WriteFile("platform", []byte(platform), 0644)
15
+	return fm.ws.WriteFile("os", []byte(os), 0644)
16 16
 }
17 17
 
18
-// GetPlatform reads the "platform" file from the layer filestore
19
-func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
20
-	contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "platform"))
18
+// GetOS reads the "os" file from the layer filestore
19
+func (fms *fileMetadataStore) GetOS(layer ChainID) (OS, error) {
20
+	contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "os"))
21 21
 	if err != nil {
22
-		// For backwards compatibility, the platform file may not exist. Default to "windows" if missing.
22
+		// For backwards compatibility, the os file may not exist. Default to "windows" if missing.
23 23
 		if os.IsNotExist(err) {
24 24
 			return "windows", nil
25 25
 		}
... ...
@@ -28,8 +28,8 @@ func (fms *fileMetadataStore) GetPlatform(layer ChainID) (Platform, error) {
28 28
 	content := strings.TrimSpace(string(contentBytes))
29 29
 
30 30
 	if content != "windows" && content != "linux" {
31
-		return "", fmt.Errorf("invalid platform value: %s", content)
31
+		return "", fmt.Errorf("invalid operating system value: %s", content)
32 32
 	}
33 33
 
34
-	return Platform(content), nil
34
+	return OS(content), nil
35 35
 }
... ...
@@ -53,8 +53,8 @@ var (
53 53
 	ErrMaxDepthExceeded = errors.New("max depth exceeded")
54 54
 
55 55
 	// ErrNotSupported is used when the action is not supported
56
-	// on the current platform
57
-	ErrNotSupported = errors.New("not support on this platform")
56
+	// on the current host operating system.
57
+	ErrNotSupported = errors.New("not support on this host operating system")
58 58
 )
59 59
 
60 60
 // ChainID is the content-addressable ID of a layer.
... ...
@@ -65,11 +65,11 @@ func (id ChainID) String() string {
65 65
 	return string(id)
66 66
 }
67 67
 
68
-// Platform is the platform of a layer
69
-type Platform string
68
+// OS is the operating system of a layer
69
+type OS string
70 70
 
71
-// String returns a string rendition of layers target platform
72
-func (id Platform) String() string {
71
+// String returns a string rendition of layers target operating system
72
+func (id OS) String() string {
73 73
 	return string(id)
74 74
 }
75 75
 
... ...
@@ -108,8 +108,8 @@ type Layer interface {
108 108
 	// Parent returns the next layer in the layer chain.
109 109
 	Parent() Layer
110 110
 
111
-	// Platform returns the platform of the layer
112
-	Platform() Platform
111
+	// OS returns the operating system of the layer
112
+	OS() OS
113 113
 
114 114
 	// Size returns the size of the entire layer chain. The size
115 115
 	// is calculated from the total size of all files in the layers.
... ...
@@ -191,7 +191,7 @@ type CreateRWLayerOpts struct {
191 191
 // Store represents a backend for managing both
192 192
 // read-only and read-write layers.
193 193
 type Store interface {
194
-	Register(io.Reader, ChainID, Platform) (Layer, error)
194
+	Register(io.Reader, ChainID, OS) (Layer, error)
195 195
 	Get(ChainID) (Layer, error)
196 196
 	Map() map[ChainID]Layer
197 197
 	Release(Layer) ([]Metadata, error)
... ...
@@ -209,7 +209,7 @@ type Store interface {
209 209
 // DescribableStore represents a layer store capable of storing
210 210
 // descriptors for layers.
211 211
 type DescribableStore interface {
212
-	RegisterWithDescriptor(io.Reader, ChainID, Platform, distribution.Descriptor) (Layer, error)
212
+	RegisterWithDescriptor(io.Reader, ChainID, OS, distribution.Descriptor) (Layer, error)
213 213
 }
214 214
 
215 215
 // MetadataTransaction represents functions for setting layer metadata
... ...
@@ -220,7 +220,7 @@ type MetadataTransaction interface {
220 220
 	SetDiffID(DiffID) error
221 221
 	SetCacheID(string) error
222 222
 	SetDescriptor(distribution.Descriptor) error
223
-	SetPlatform(Platform) error
223
+	SetOS(OS) error
224 224
 	TarSplitWriter(compressInput bool) (io.WriteCloser, error)
225 225
 
226 226
 	Commit(ChainID) error
... ...
@@ -241,7 +241,7 @@ type MetadataStore interface {
241 241
 	GetDiffID(ChainID) (DiffID, error)
242 242
 	GetCacheID(ChainID) (string, error)
243 243
 	GetDescriptor(ChainID) (distribution.Descriptor, error)
244
-	GetPlatform(ChainID) (Platform, error)
244
+	GetOS(ChainID) (OS, error)
245 245
 	TarSplitReader(ChainID) (io.ReadCloser, error)
246 246
 
247 247
 	SetMountID(string, string) error
... ...
@@ -39,7 +39,7 @@ type layerStore struct {
39 39
 
40 40
 	useTarSplit bool
41 41
 
42
-	platform string
42
+	os string
43 43
 }
44 44
 
45 45
 // StoreOptions are the options used to create a new Store instance
... ...
@@ -51,7 +51,7 @@ type StoreOptions struct {
51 51
 	IDMappings                *idtools.IDMappings
52 52
 	PluginGetter              plugingetter.PluginGetter
53 53
 	ExperimentalEnabled       bool
54
-	Platform                  string
54
+	OS                        string
55 55
 }
56 56
 
57 57
 // NewStoreFromOptions creates a new Store instance
... ...
@@ -73,13 +73,13 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) {
73 73
 		return nil, err
74 74
 	}
75 75
 
76
-	return NewStoreFromGraphDriver(fms, driver, options.Platform)
76
+	return NewStoreFromGraphDriver(fms, driver, options.OS)
77 77
 }
78 78
 
79 79
 // NewStoreFromGraphDriver creates a new Store instance using the provided
80 80
 // metadata store and graph driver. The metadata store will be used to restore
81 81
 // the Store.
82
-func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver, platform string) (Store, error) {
82
+func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver, os string) (Store, error) {
83 83
 	caps := graphdriver.Capabilities{}
84 84
 	if capDriver, ok := driver.(graphdriver.CapabilityDriver); ok {
85 85
 		caps = capDriver.Capabilities()
... ...
@@ -91,7 +91,7 @@ func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver, pla
91 91
 		layerMap:    map[ChainID]*roLayer{},
92 92
 		mounts:      map[string]*mountedLayer{},
93 93
 		useTarSplit: !caps.ReproducesExactDiffs,
94
-		platform:    platform,
94
+		os:          os,
95 95
 	}
96 96
 
97 97
 	ids, mounts, err := store.List()
... ...
@@ -150,9 +150,9 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
150 150
 		return nil, fmt.Errorf("failed to get descriptor for %s: %s", layer, err)
151 151
 	}
152 152
 
153
-	platform, err := ls.store.GetPlatform(layer)
153
+	os, err := ls.store.GetOS(layer)
154 154
 	if err != nil {
155
-		return nil, fmt.Errorf("failed to get platform for %s: %s", layer, err)
155
+		return nil, fmt.Errorf("failed to get operating system for %s: %s", layer, err)
156 156
 	}
157 157
 
158 158
 	cl = &roLayer{
... ...
@@ -163,7 +163,7 @@ func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
163 163
 		layerStore: ls,
164 164
 		references: map[Layer]struct{}{},
165 165
 		descriptor: descriptor,
166
-		platform:   platform,
166
+		os:         os,
167 167
 	}
168 168
 
169 169
 	if parent != "" {
... ...
@@ -259,11 +259,11 @@ func (ls *layerStore) applyTar(tx MetadataTransaction, ts io.Reader, parent stri
259 259
 	return nil
260 260
 }
261 261
 
262
-func (ls *layerStore) Register(ts io.Reader, parent ChainID, platform Platform) (Layer, error) {
263
-	return ls.registerWithDescriptor(ts, parent, platform, distribution.Descriptor{})
262
+func (ls *layerStore) Register(ts io.Reader, parent ChainID, os OS) (Layer, error) {
263
+	return ls.registerWithDescriptor(ts, parent, os, distribution.Descriptor{})
264 264
 }
265 265
 
266
-func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, platform Platform, descriptor distribution.Descriptor) (Layer, error) {
266
+func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, os OS, descriptor distribution.Descriptor) (Layer, error) {
267 267
 	// err is used to hold the error which will always trigger
268 268
 	// cleanup of creates sources but may not be an error returned
269 269
 	// to the caller (already exists).
... ...
@@ -271,10 +271,10 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, platf
271 271
 	var pid string
272 272
 	var p *roLayer
273 273
 
274
-	// Integrity check - ensure we are creating something for the correct platform
274
+	// Integrity check - ensure we are creating something for the correct operating system
275 275
 	if system.LCOWSupported() {
276
-		if strings.ToLower(ls.platform) != strings.ToLower(string(platform)) {
277
-			return nil, fmt.Errorf("cannot create entry for platform %q in layer store for platform %q", platform, ls.platform)
276
+		if strings.ToLower(ls.os) != strings.ToLower(string(os)) {
277
+			return nil, fmt.Errorf("cannot create entry for operating system %q in layer store for operating system %q", os, ls.os)
278 278
 		}
279 279
 	}
280 280
 
... ...
@@ -306,7 +306,7 @@ func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, platf
306 306
 		layerStore:     ls,
307 307
 		references:     map[Layer]struct{}{},
308 308
 		descriptor:     descriptor,
309
-		platform:       platform,
309
+		os:             os,
310 310
 	}
311 311
 
312 312
 	if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil {
... ...
@@ -6,6 +6,6 @@ import (
6 6
 	"github.com/docker/distribution"
7 7
 )
8 8
 
9
-func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, platform Platform, descriptor distribution.Descriptor) (Layer, error) {
10
-	return ls.registerWithDescriptor(ts, parent, platform, descriptor)
9
+func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, os OS, descriptor distribution.Descriptor) (Layer, error) {
10
+	return ls.registerWithDescriptor(ts, parent, os, descriptor)
11 11
 }
... ...
@@ -108,7 +108,7 @@ func createLayer(ls Store, parent ChainID, layerFunc layerInit) (Layer, error) {
108 108
 	}
109 109
 	defer ts.Close()
110 110
 
111
-	layer, err := ls.Register(ts, parent, Platform(runtime.GOOS))
111
+	layer, err := ls.Register(ts, parent, OS(runtime.GOOS))
112 112
 	if err != nil {
113 113
 		return nil, err
114 114
 	}
... ...
@@ -501,7 +501,7 @@ func TestTarStreamStability(t *testing.T) {
501 501
 		t.Fatal(err)
502 502
 	}
503 503
 
504
-	layer1, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
504
+	layer1, err := ls.Register(bytes.NewReader(tar1), "", OS(runtime.GOOS))
505 505
 	if err != nil {
506 506
 		t.Fatal(err)
507 507
 	}
... ...
@@ -520,7 +520,7 @@ func TestTarStreamStability(t *testing.T) {
520 520
 		t.Fatal(err)
521 521
 	}
522 522
 
523
-	layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID(), Platform(runtime.GOOS))
523
+	layer2, err := ls.Register(bytes.NewReader(tar2), layer1.ChainID(), OS(runtime.GOOS))
524 524
 	if err != nil {
525 525
 		t.Fatal(err)
526 526
 	}
... ...
@@ -688,12 +688,12 @@ func TestRegisterExistingLayer(t *testing.T) {
688 688
 		t.Fatal(err)
689 689
 	}
690 690
 
691
-	layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), Platform(runtime.GOOS))
691
+	layer2a, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), OS(runtime.GOOS))
692 692
 	if err != nil {
693 693
 		t.Fatal(err)
694 694
 	}
695 695
 
696
-	layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), Platform(runtime.GOOS))
696
+	layer2b, err := ls.Register(bytes.NewReader(tar1), layer1.ChainID(), OS(runtime.GOOS))
697 697
 	if err != nil {
698 698
 		t.Fatal(err)
699 699
 	}
... ...
@@ -728,12 +728,12 @@ func TestTarStreamVerification(t *testing.T) {
728 728
 		t.Fatal(err)
729 729
 	}
730 730
 
731
-	layer1, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
731
+	layer1, err := ls.Register(bytes.NewReader(tar1), "", OS(runtime.GOOS))
732 732
 	if err != nil {
733 733
 		t.Fatal(err)
734 734
 	}
735 735
 
736
-	layer2, err := ls.Register(bytes.NewReader(tar2), "", Platform(runtime.GOOS))
736
+	layer2, err := ls.Register(bytes.NewReader(tar2), "", OS(runtime.GOOS))
737 737
 	if err != nil {
738 738
 		t.Fatal(err)
739 739
 	}
... ...
@@ -110,14 +110,14 @@ func TestLayerMigration(t *testing.T) {
110 110
 		t.Fatal(err)
111 111
 	}
112 112
 
113
-	layer1b, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
113
+	layer1b, err := ls.Register(bytes.NewReader(tar1), "", OS(runtime.GOOS))
114 114
 	if err != nil {
115 115
 		t.Fatal(err)
116 116
 	}
117 117
 
118 118
 	assertReferences(t, layer1a, layer1b)
119 119
 	// Attempt register, should be same
120
-	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), Platform(runtime.GOOS))
120
+	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), OS(runtime.GOOS))
121 121
 	if err != nil {
122 122
 		t.Fatal(err)
123 123
 	}
... ...
@@ -238,7 +238,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
238 238
 		t.Fatal(err)
239 239
 	}
240 240
 
241
-	layer1b, err := ls.Register(bytes.NewReader(tar1), "", Platform(runtime.GOOS))
241
+	layer1b, err := ls.Register(bytes.NewReader(tar1), "", OS(runtime.GOOS))
242 242
 	if err != nil {
243 243
 		t.Fatal(err)
244 244
 	}
... ...
@@ -246,7 +246,7 @@ func TestLayerMigrationNoTarsplit(t *testing.T) {
246 246
 	assertReferences(t, layer1a, layer1b)
247 247
 
248 248
 	// Attempt register, should be same
249
-	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), Platform(runtime.GOOS))
249
+	layer2a, err := ls.Register(bytes.NewReader(tar2), layer1a.ChainID(), OS(runtime.GOOS))
250 250
 	if err != nil {
251 251
 		t.Fatal(err)
252 252
 	}
... ...
@@ -16,7 +16,7 @@ type roLayer struct {
16 16
 	size       int64
17 17
 	layerStore *layerStore
18 18
 	descriptor distribution.Descriptor
19
-	platform   Platform
19
+	os         OS
20 20
 
21 21
 	referenceCount int
22 22
 	references     map[Layer]struct{}
... ...
@@ -143,7 +143,7 @@ func storeLayer(tx MetadataTransaction, layer *roLayer) error {
143 143
 			return err
144 144
 		}
145 145
 	}
146
-	if err := tx.SetPlatform(layer.platform); err != nil {
146
+	if err := tx.SetOS(layer.os); err != nil {
147 147
 		return err
148 148
 	}
149 149
 
... ...
@@ -2,6 +2,6 @@
2 2
 
3 3
 package layer
4 4
 
5
-func (rl *roLayer) Platform() Platform {
5
+func (rl *roLayer) OS() OS {
6 6
 	return ""
7 7
 }
... ...
@@ -8,9 +8,9 @@ func (rl *roLayer) Descriptor() distribution.Descriptor {
8 8
 	return rl.descriptor
9 9
 }
10 10
 
11
-func (rl *roLayer) Platform() Platform {
12
-	if rl.platform == "" {
11
+func (rl *roLayer) OS() OS {
12
+	if rl.os == "" {
13 13
 		return "windows"
14 14
 	}
15
-	return rl.platform
15
+	return rl.os
16 16
 }
... ...
@@ -433,7 +433,7 @@ func (l *mockLayer) DiffSize() (int64, error) {
433 433
 	return 0, nil
434 434
 }
435 435
 
436
-func (l *mockLayer) Platform() layer.Platform {
436
+func (l *mockLayer) OS() layer.OS {
437 437
 	return ""
438 438
 }
439 439
 
... ...
@@ -2,17 +2,16 @@ package system
2 2
 
3 3
 import "os"
4 4
 
5
-// LCOWSupported determines if Linux Containers on Windows are supported.
6
-// Note: This feature is in development (06/17) and enabled through an
7
-// environment variable. At a future time, it will be enabled based
8
-// on build number. @jhowardmsft
5
+// lcowSupported determines if Linux Containers on Windows are supported.
9 6
 var lcowSupported = false
10 7
 
11 8
 // InitLCOW sets whether LCOW is supported or not
9
+// TODO @jhowardmsft.
10
+// 1. Replace with RS3 RTM build number.
11
+// 2. Remove the getenv check when image-store is coalesced as shouldn't be needed anymore.
12 12
 func InitLCOW(experimental bool) {
13
-	// LCOW initialization
14
-	if experimental && os.Getenv("LCOW_SUPPORTED") != "" {
13
+	v := GetOSVersion()
14
+	if experimental && v.Build > 16270 && os.Getenv("LCOW_SUPPORTED") != "" {
15 15
 		lcowSupported = true
16 16
 	}
17
-
18 17
 }
19 18
new file mode 100644
... ...
@@ -0,0 +1,71 @@
0
+package system
1
+
2
+import (
3
+	"fmt"
4
+	"runtime"
5
+	"strings"
6
+
7
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
8
+)
9
+
10
+// IsPlatformEmpty determines if an OCI image-spec platform structure is not populated.
11
+// TODO This is a temporary function - can be replaced by parsing from
12
+// https://github.com/containerd/containerd/pull/1403/files at a later date.
13
+// @jhowardmsft
14
+func IsPlatformEmpty(platform specs.Platform) bool {
15
+	if platform.Architecture == "" &&
16
+		platform.OS == "" &&
17
+		len(platform.OSFeatures) == 0 &&
18
+		platform.OSVersion == "" &&
19
+		platform.Variant == "" {
20
+		return true
21
+	}
22
+	return false
23
+}
24
+
25
+// ValidatePlatform determines if a platform structure is valid.
26
+// TODO This is a temporary function - can be replaced by parsing from
27
+// https://github.com/containerd/containerd/pull/1403/files at a later date.
28
+// @jhowardmsft
29
+func ValidatePlatform(platform *specs.Platform) error {
30
+	platform.Architecture = strings.ToLower(platform.Architecture)
31
+	platform.OS = strings.ToLower(platform.OS)
32
+	if platform.Architecture != "" && platform.Architecture != runtime.GOARCH {
33
+		return fmt.Errorf("invalid platform architecture %q", platform.Architecture)
34
+	}
35
+	if platform.OS != "" {
36
+		if !(platform.OS == runtime.GOOS || (LCOWSupported() && platform.OS == "linux")) {
37
+			return fmt.Errorf("invalid platform os %q", platform.OS)
38
+		}
39
+	}
40
+	if len(platform.OSFeatures) != 0 {
41
+		return fmt.Errorf("invalid platform osfeatures %q", platform.OSFeatures)
42
+	}
43
+	if platform.OSVersion != "" {
44
+		return fmt.Errorf("invalid platform osversion %q", platform.OSVersion)
45
+	}
46
+	if platform.Variant != "" {
47
+		return fmt.Errorf("invalid platform variant %q", platform.Variant)
48
+	}
49
+	return nil
50
+}
51
+
52
+// ParsePlatform parses a platform string in the format os[/arch[/variant]
53
+// into an OCI image-spec platform structure.
54
+// TODO This is a temporary function - can be replaced by parsing from
55
+// https://github.com/containerd/containerd/pull/1403/files at a later date.
56
+// @jhowardmsft
57
+func ParsePlatform(in string) *specs.Platform {
58
+	p := &specs.Platform{}
59
+	elements := strings.SplitN(strings.ToLower(in), "/", 3)
60
+	if len(elements) == 3 {
61
+		p.Variant = elements[2]
62
+	}
63
+	if len(elements) >= 2 {
64
+		p.Architecture = elements[1]
65
+	}
66
+	if len(elements) >= 1 {
67
+		p.OS = elements[0]
68
+	}
69
+	return p
70
+}
... ...
@@ -14,9 +14,9 @@ const defaultUnixPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/s
14 14
 // DefaultPathEnv is unix style list of directories to search for
15 15
 // executables. Each directory is separated from the next by a colon
16 16
 // ':' character .
17
-func DefaultPathEnv(platform string) string {
17
+func DefaultPathEnv(os string) string {
18 18
 	if runtime.GOOS == "windows" {
19
-		if platform != runtime.GOOS && LCOWSupported() {
19
+		if os != runtime.GOOS {
20 20
 			return defaultUnixPathEnv
21 21
 		}
22 22
 		// Deliberately empty on Windows containers on Windows as the default path will be set by
... ...
@@ -146,7 +146,7 @@ func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) {
146 146
 	return s.config, nil
147 147
 }
148 148
 
149
-func (s *tempConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
149
+func (s *tempConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, layer.OS, error) {
150 150
 	return configToRootFS(c)
151 151
 }
152 152
 
... ...
@@ -533,7 +533,7 @@ func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) {
533 533
 	return ioutil.ReadAll(rwc)
534 534
 }
535 535
 
536
-func (s *pluginConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
536
+func (s *pluginConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, layer.OS, error) {
537 537
 	return configToRootFS(c)
538 538
 }
539 539
 
... ...
@@ -126,7 +126,7 @@ type downloadManager struct {
126 126
 	configDigest digest.Digest
127 127
 }
128 128
 
129
-func (dm *downloadManager) Download(ctx context.Context, initialRootFS image.RootFS, platform layer.Platform, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
129
+func (dm *downloadManager) Download(ctx context.Context, initialRootFS image.RootFS, os layer.OS, layers []xfer.DownloadDescriptor, progressOutput progress.Output) (image.RootFS, func(), error) {
130 130
 	// TODO @jhowardmsft LCOW: May need revisiting.
131 131
 	for _, l := range layers {
132 132
 		b, err := dm.blobStore.New()
... ...
@@ -179,6 +179,6 @@ func (dm *downloadManager) Put(dt []byte) (digest.Digest, error) {
179 179
 func (dm *downloadManager) Get(d digest.Digest) ([]byte, error) {
180 180
 	return nil, fmt.Errorf("digest not found")
181 181
 }
182
-func (dm *downloadManager) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS, layer.Platform, error) {
182
+func (dm *downloadManager) RootFSAndOSFromConfig(c []byte) (*image.RootFS, layer.OS, error) {
183 183
 	return configToRootFS(c)
184 184
 }
... ...
@@ -379,11 +379,11 @@ func isEqualPrivilege(a, b types.PluginPrivilege) bool {
379 379
 	return reflect.DeepEqual(a.Value, b.Value)
380 380
 }
381 381
 
382
-func configToRootFS(c []byte) (*image.RootFS, layer.Platform, error) {
383
-	// TODO @jhowardmsft LCOW - Will need to revisit this. For now, calculate the platform.
384
-	platform := layer.Platform(runtime.GOOS)
382
+func configToRootFS(c []byte) (*image.RootFS, layer.OS, error) {
383
+	// TODO @jhowardmsft LCOW - Will need to revisit this. For now, calculate the operating system.
384
+	os := layer.OS(runtime.GOOS)
385 385
 	if system.LCOWSupported() {
386
-		platform = "linux"
386
+		os = "linux"
387 387
 	}
388 388
 	var pluginConfig types.PluginConfig
389 389
 	if err := json.Unmarshal(c, &pluginConfig); err != nil {
... ...
@@ -391,10 +391,10 @@ func configToRootFS(c []byte) (*image.RootFS, layer.Platform, error) {
391 391
 	}
392 392
 	// validation for empty rootfs is in distribution code
393 393
 	if pluginConfig.Rootfs == nil {
394
-		return nil, platform, nil
394
+		return nil, os, nil
395 395
 	}
396 396
 
397
-	return rootFSFromPlugin(pluginConfig.Rootfs), platform, nil
397
+	return rootFSFromPlugin(pluginConfig.Rootfs), os, nil
398 398
 }
399 399
 
400 400
 func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS {