Browse code

LCOW: API change JSON header to string POST parameter

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

John Howard authored on 2017/09/14 04:49:04
Showing 27 changed files
... ...
@@ -1,17 +1,11 @@
1 1
 package httputils
2 2
 
3 3
 import (
4
-	"encoding/json"
5
-	"fmt"
6 4
 	"io"
7 5
 	"mime"
8 6
 	"net/http"
9
-	"runtime"
10 7
 	"strings"
11 8
 
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"
15 9
 	"github.com/pkg/errors"
16 10
 	"github.com/sirupsen/logrus"
17 11
 	"golang.org/x/net/context"
... ...
@@ -115,27 +109,3 @@ func matchesContentType(contentType, expectedType string) bool {
115 115
 	}
116 116
 	return err == nil && mimetype == expectedType
117 117
 }
118
-
119
-// GetRequestedPlatform extracts an optional platform structure from an HTTP request header
120
-func GetRequestedPlatform(ctx context.Context, r *http.Request) (*specs.Platform, error) {
121
-	platform := &specs.Platform{}
122
-	version := VersionFromContext(ctx)
123
-	if versions.GreaterThanOrEqualTo(version, "1.32") {
124
-		requestedPlatform := r.Header.Get("X-Requested-Platform")
125
-		if requestedPlatform != "" {
126
-			if err := json.Unmarshal([]byte(requestedPlatform), platform); err != nil {
127
-				return nil, fmt.Errorf("invalid X-Requested-Platform header: %s", err)
128
-			}
129
-		}
130
-		if err := system.ValidatePlatform(platform); err != nil {
131
-			return nil, err
132
-		}
133
-	}
134
-	if platform.OS == "" {
135
-		platform.OS = runtime.GOOS
136
-	}
137
-	if platform.Architecture == "" {
138
-		platform.Architecture = runtime.GOARCH
139
-	}
140
-	return platform, nil
141
-}
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"fmt"
8 8
 	"io"
9 9
 	"net/http"
10
+	"os"
10 11
 	"runtime"
11 12
 	"strconv"
12 13
 	"strings"
... ...
@@ -20,6 +21,7 @@ import (
20 20
 	"github.com/docker/docker/pkg/ioutils"
21 21
 	"github.com/docker/docker/pkg/progress"
22 22
 	"github.com/docker/docker/pkg/streamformatter"
23
+	"github.com/docker/docker/pkg/system"
23 24
 	units "github.com/docker/go-units"
24 25
 	"github.com/pkg/errors"
25 26
 	"github.com/sirupsen/logrus"
... ...
@@ -67,6 +69,24 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
67 67
 	options.Squash = httputils.BoolValue(r, "squash")
68 68
 	options.Target = r.FormValue("target")
69 69
 	options.RemoteContext = r.FormValue("remote")
70
+	if versions.GreaterThanOrEqualTo(version, "1.32") {
71
+		// TODO @jhowardmsft. The following environment variable is an interim
72
+		// measure to allow the daemon to have a default platform if omitted by
73
+		// the client. This allows LCOW and WCOW to work with a down-level CLI
74
+		// for a short period of time, as the CLI changes can't be merged
75
+		// until after the daemon changes have been merged. Once the CLI is
76
+		// updated, this can be removed. PR for CLI is currently in
77
+		// https://github.com/docker/cli/pull/474.
78
+		apiPlatform := r.FormValue("platform")
79
+		if system.LCOWSupported() && apiPlatform == "" {
80
+			apiPlatform = os.Getenv("LCOW_API_PLATFORM_IF_OMITTED")
81
+		}
82
+		p := system.ParsePlatform(apiPlatform)
83
+		if err := system.ValidatePlatform(p); err != nil {
84
+			return nil, validationError{fmt.Errorf("invalid platform: %s", err)}
85
+		}
86
+		options.Platform = p.OS
87
+	}
70 88
 
71 89
 	if r.Form.Get("shmsize") != "" {
72 90
 		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
... ...
@@ -87,12 +107,6 @@ 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
-
96 90
 	var buildUlimits = []*units.Ulimit{}
97 91
 	ulimitsJSON := r.FormValue("ulimits")
98 92
 	if ulimitsJSON != "" {
... ...
@@ -3,8 +3,10 @@ package image
3 3
 import (
4 4
 	"encoding/base64"
5 5
 	"encoding/json"
6
+	"fmt"
6 7
 	"io"
7 8
 	"net/http"
9
+	"os"
8 10
 	"strconv"
9 11
 	"strings"
10 12
 
... ...
@@ -16,6 +18,7 @@ import (
16 16
 	"github.com/docker/docker/api/types/versions"
17 17
 	"github.com/docker/docker/pkg/ioutils"
18 18
 	"github.com/docker/docker/pkg/streamformatter"
19
+	"github.com/docker/docker/pkg/system"
19 20
 	"github.com/docker/docker/registry"
20 21
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
21 22
 	"github.com/pkg/errors"
... ...
@@ -70,6 +73,7 @@ func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *
70 70
 
71 71
 // Creates an image from Pull or from Import
72 72
 func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
73
+
73 74
 	if err := httputils.ParseForm(r); err != nil {
74 75
 		return err
75 76
 	}
... ...
@@ -87,7 +91,25 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
87 87
 
88 88
 	w.Header().Set("Content-Type", "application/json")
89 89
 
90
-	platform, err = httputils.GetRequestedPlatform(ctx, r)
90
+	version := httputils.VersionFromContext(ctx)
91
+	if versions.GreaterThanOrEqualTo(version, "1.32") {
92
+		// TODO @jhowardmsft. The following environment variable is an interim
93
+		// measure to allow the daemon to have a default platform if omitted by
94
+		// the client. This allows LCOW and WCOW to work with a down-level CLI
95
+		// for a short period of time, as the CLI changes can't be merged
96
+		// until after the daemon changes have been merged. Once the CLI is
97
+		// updated, this can be removed. PR for CLI is currently in
98
+		// https://github.com/docker/cli/pull/474.
99
+		apiPlatform := r.FormValue("platform")
100
+		if system.LCOWSupported() && apiPlatform == "" {
101
+			apiPlatform = os.Getenv("LCOW_API_PLATFORM_IF_OMITTED")
102
+		}
103
+		platform = system.ParsePlatform(apiPlatform)
104
+		if err = system.ValidatePlatform(platform); err != nil {
105
+			err = fmt.Errorf("invalid platform: %s", err)
106
+		}
107
+	}
108
+
91 109
 	if err == nil {
92 110
 		if image != "" { //pull
93 111
 			metaHeaders := map[string][]string{}
... ...
@@ -6181,17 +6181,9 @@ 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
-            ```
6184
+        - name: "platform"
6185
+          in: "query"
6186
+          description: "Platform in the format os[/arch[/variant]]"
6195 6187
           type: "string"
6196 6188
           default: ""
6197 6189
       responses:
... ...
@@ -6275,17 +6267,9 @@ paths:
6275 6275
           in: "header"
6276 6276
           description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)"
6277 6277
           type: "string"
6278
-        - name: "X-Requested-Platform"
6279
-          in: "header"
6280
-          description: |
6281
-            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:
6282
-
6283
-            ```
6284
-            {
6285
-              "architecture": "amd64",
6286
-              "os": "linux"
6287
-            }
6288
-            ```
6278
+        - name: "platform"
6279
+          in: "query"
6280
+          description: "Platform in the format os[/arch[/variant]]"
6289 6281
           type: "string"
6290 6282
           default: ""
6291 6283
       tags: ["Image"]
... ...
@@ -8,7 +8,6 @@ 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"
12 11
 )
13 12
 
14 13
 // CheckpointCreateOptions holds parameters to create a checkpoint from a container
... ...
@@ -180,7 +179,7 @@ type ImageBuildOptions struct {
180 180
 	ExtraHosts  []string // List of extra hosts
181 181
 	Target      string
182 182
 	SessionID   string
183
-	Platform    specs.Platform
183
+	Platform    string
184 184
 }
185 185
 
186 186
 // ImageBuildResponse holds information
... ...
@@ -193,8 +192,8 @@ type ImageBuildResponse struct {
193 193
 
194 194
 // ImageCreateOptions holds information to create images.
195 195
 type ImageCreateOptions struct {
196
-	RegistryAuth string         // RegistryAuth is the base64 encoded credentials for the registry.
197
-	Platform     specs.Platform // Platform is the target platform of the image if it needs to be pulled from the registry.
196
+	RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry.
197
+	Platform     string // Platform is the target platform of the image if it needs to be pulled from the registry.
198 198
 }
199 199
 
200 200
 // ImageImportSource holds source information for ImageImport
... ...
@@ -205,9 +204,10 @@ type ImageImportSource struct {
205 205
 
206 206
 // ImageImportOptions holds information to import images from the client host.
207 207
 type ImageImportOptions struct {
208
-	Tag     string   // Tag is the name to tag this image with. This attribute is deprecated.
209
-	Message string   // Message is the message to tag the image with
210
-	Changes []string // Changes are the raw changes to apply to this image
208
+	Tag      string   // Tag is the name to tag this image with. This attribute is deprecated.
209
+	Message  string   // Message is the message to tag the image with
210
+	Changes  []string // Changes are the raw changes to apply to this image
211
+	Platform string   // Platform is the target platform of the image
211 212
 }
212 213
 
213 214
 // ImageListOptions holds parameters to filter the list of images with.
... ...
@@ -228,7 +228,7 @@ type ImagePullOptions struct {
228 228
 	All           bool
229 229
 	RegistryAuth  string // RegistryAuth is the base64 encoded credentials for the registry
230 230
 	PrivilegeFunc RequestPrivilegeFunc
231
-	Platform      specs.Platform
231
+	Platform      string
232 232
 }
233 233
 
234 234
 // RequestPrivilegeFunc is a function interface that
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	"github.com/docker/docker/api/types/registry"
16 16
 	"github.com/docker/docker/api/types/swarm"
17 17
 	"github.com/docker/go-connections/nat"
18
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
18 19
 )
19 20
 
20 21
 // RootFS returns Image's RootFS description including the layer IDs.
... ...
@@ -327,7 +328,7 @@ type ContainerJSONBase struct {
327 327
 	Name            string
328 328
 	RestartCount    int
329 329
 	Driver          string
330
-	OS              string
330
+	Platform        specs.Platform
331 331
 	MountLabel      string
332 332
 	ProcessLabel    string
333 333
 	AppArmorProfile string
... ...
@@ -20,6 +20,7 @@ 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"
23 24
 	"github.com/moby/buildkit/session"
24 25
 	"github.com/pkg/errors"
25 26
 	"github.com/sirupsen/logrus"
... ...
@@ -102,15 +103,16 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
102 102
 	}
103 103
 
104 104
 	os := runtime.GOOS
105
+	optionsPlatform := system.ParsePlatform(config.Options.Platform)
105 106
 	if dockerfile.OS != "" {
106
-		if config.Options.Platform.OS != "" && config.Options.Platform.OS != dockerfile.OS {
107
+		if optionsPlatform.OS != "" && optionsPlatform.OS != dockerfile.OS {
107 108
 			return nil, fmt.Errorf("invalid platform")
108 109
 		}
109 110
 		os = dockerfile.OS
110
-	} else if config.Options.Platform.OS != "" {
111
-		os = config.Options.Platform.OS
111
+	} else if optionsPlatform.OS != "" {
112
+		os = optionsPlatform.OS
112 113
 	}
113
-	config.Options.Platform.OS = os
114
+	config.Options.Platform = os
114 115
 	dockerfile.OS = os
115 116
 
116 117
 	builderOptions := builderOptions{
... ...
@@ -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.options.Platform.OS,
85
+		platform:    req.builder.options.Platform,
86 86
 	}
87 87
 }
88 88
 
... ...
@@ -194,11 +194,6 @@ func dispatchTriggeredOnBuild(d dispatchRequest, triggers []string) error {
194 194
 	return nil
195 195
 }
196 196
 
197
-// scratchImage is used as a token for the empty base image. It uses buildStage
198
-// as a convenient implementation of builder.Image, but is not actually a
199
-// buildStage.
200
-var scratchImage builder.Image = &image.Image{}
201
-
202 197
 func (d *dispatchRequest) getExpandedImageName(shlex *ShellLex, name string) (string, error) {
203 198
 	substitutionArgs := []string{}
204 199
 	for key, value := range d.state.buildArgs.GetAllMeta() {
... ...
@@ -223,8 +218,9 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
223 223
 		imageImage := &image.Image{}
224 224
 		imageImage.OS = runtime.GOOS
225 225
 		if runtime.GOOS == "windows" {
226
-			switch d.builder.options.Platform.OS {
227
-			case "windows":
226
+			optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
227
+			switch optionsOS {
228
+			case "windows", "":
228 229
 				return nil, errors.New("Windows does not support FROM scratch")
229 230
 			case "linux":
230 231
 				if !system.LCOWSupported() {
... ...
@@ -232,7 +228,7 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
232 232
 				}
233 233
 				imageImage.OS = "linux"
234 234
 			default:
235
-				return nil, errors.Errorf("operating system %q is not supported", d.builder.options.Platform.OS)
235
+				return nil, errors.Errorf("operating system %q is not supported", optionsOS)
236 236
 			}
237 237
 		}
238 238
 		return builder.Image(imageImage), nil
... ...
@@ -264,7 +260,8 @@ func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error {
264 264
 func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
265 265
 	runConfig := d.state.runConfig
266 266
 	var err error
267
-	runConfig.WorkingDir, err = normalizeWorkdir(d.builder.options.Platform.OS, runConfig.WorkingDir, c.Path)
267
+	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
268
+	runConfig.WorkingDir, err = normalizeWorkdir(optionsOS, runConfig.WorkingDir, c.Path)
268 269
 	if err != nil {
269 270
 		return err
270 271
 	}
... ...
@@ -280,7 +277,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
280 280
 	}
281 281
 
282 282
 	comment := "WORKDIR " + runConfig.WorkingDir
283
-	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.builder.options.Platform.OS))
283
+	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, optionsOS))
284 284
 	containerID, err := d.builder.probeAndCreate(d.state, runConfigWithCommentCmd)
285 285
 	if err != nil || containerID == "" {
286 286
 		return err
... ...
@@ -313,7 +310,8 @@ func resolveCmdLine(cmd instructions.ShellDependantCmdLine, runConfig *container
313 313
 func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
314 314
 
315 315
 	stateRunConfig := d.state.runConfig
316
-	cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.builder.options.Platform.OS)
316
+	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
317
+	cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, optionsOS)
317 318
 	buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env)
318 319
 
319 320
 	saveCmd := cmdFromArgs
... ...
@@ -390,7 +388,8 @@ func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.S
390 390
 //
391 391
 func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
392 392
 	runConfig := d.state.runConfig
393
-	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
393
+	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
394
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS)
394 395
 	runConfig.Cmd = cmd
395 396
 	// set config as already being escaped, this prevents double escaping on windows
396 397
 	runConfig.ArgsEscaped = true
... ...
@@ -433,7 +432,8 @@ func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand)
433 433
 //
434 434
 func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error {
435 435
 	runConfig := d.state.runConfig
436
-	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.builder.options.Platform.OS)
436
+	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
437
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS)
437 438
 	runConfig.Entrypoint = cmd
438 439
 	if !d.state.cmdSet {
439 440
 		runConfig.Cmd = nil
... ...
@@ -14,7 +14,6 @@ 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"
18 17
 	"github.com/stretchr/testify/assert"
19 18
 	"github.com/stretchr/testify/require"
20 19
 )
... ...
@@ -23,13 +22,13 @@ func newBuilderWithMockBackend() *Builder {
23 23
 	mockBackend := &MockBackend{}
24 24
 	ctx := context.Background()
25 25
 	b := &Builder{
26
-		options:       &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
26
+		options:       &types.ImageBuildOptions{Platform: runtime.GOOS},
27 27
 		docker:        mockBackend,
28 28
 		Stdout:        new(bytes.Buffer),
29 29
 		clientCtx:     ctx,
30 30
 		disableCommit: true,
31 31
 		imageSources: newImageSources(ctx, builderOptions{
32
-			Options: &types.ImageBuildOptions{Platform: specs.Platform{OS: runtime.GOOS}},
32
+			Options: &types.ImageBuildOptions{Platform: runtime.GOOS},
33 33
 			Backend: mockBackend,
34 34
 		}),
35 35
 		imageProber:      newImageProber(mockBackend, nil, runtime.GOOS, false),
... ...
@@ -34,7 +34,8 @@ import (
34 34
 
35 35
 func dispatch(d dispatchRequest, cmd instructions.Command) error {
36 36
 	if c, ok := cmd.(instructions.PlatformSpecific); ok {
37
-		err := c.CheckPlatform(d.builder.options.Platform.OS)
37
+		optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
38
+		err := c.CheckPlatform(optionsOS)
38 39
 		if err != nil {
39 40
 			return validationError{err}
40 41
 		}
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"github.com/docker/docker/builder"
6 6
 	"github.com/docker/docker/builder/remotecontext"
7 7
 	dockerimage "github.com/docker/docker/image"
8
+	"github.com/docker/docker/pkg/system"
8 9
 	"github.com/pkg/errors"
9 10
 	"github.com/sirupsen/logrus"
10 11
 	"golang.org/x/net/context"
... ...
@@ -30,11 +31,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
30 30
 				pullOption = backend.PullOptionPreferLocal
31 31
 			}
32 32
 		}
33
+		optionsPlatform := system.ParsePlatform(options.Options.Platform)
33 34
 		return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{
34 35
 			PullOption: pullOption,
35 36
 			AuthConfig: options.Options.AuthConfigs,
36 37
 			Output:     options.ProgressWriter.Output,
37
-			OS:         options.Options.Platform.OS,
38
+			OS:         optionsPlatform.OS,
38 39
 		})
39 40
 	}
40 41
 
... ...
@@ -83,7 +83,8 @@ 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.options.Platform.OS))
86
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
87
+	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, optionsPlatform.OS))
87 88
 	hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
88 89
 	if err != nil || hit {
89 90
 		return err
... ...
@@ -122,7 +123,8 @@ 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.options.Platform.OS)
125
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
126
+	newLayer, err := imageMount.Layer().Commit(optionsPlatform.OS)
126 127
 	if err != nil {
127 128
 		return err
128 129
 	}
... ...
@@ -170,9 +172,10 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
170 170
 	commentStr := fmt.Sprintf("%s %s%s in %s ", inst.cmdName, chownComment, srcHash, inst.dest)
171 171
 
172 172
 	// TODO: should this have been using origPaths instead of srcHash in the comment?
173
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
173 174
 	runConfigWithCommentCmd := copyRunConfig(
174 175
 		state.runConfig,
175
-		withCmdCommentString(commentStr, b.options.Platform.OS))
176
+		withCmdCommentString(commentStr, optionsPlatform.OS))
176 177
 	hit, err := b.probeCache(state, runConfigWithCommentCmd)
177 178
 	if err != nil || hit {
178 179
 		return err
... ...
@@ -183,7 +186,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.options.Platform.OS)
186
+	destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, imageMount, b.options.Platform)
187 187
 	if err != nil {
188 188
 		return err
189 189
 	}
... ...
@@ -463,13 +466,15 @@ 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.options.Platform.OS)
466
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
467
+	container, err := b.containerManager.Create(runConfig, hostConfig, optionsPlatform.OS)
467 468
 	return container.ID, err
468 469
 }
469 470
 
470 471
 func (b *Builder) create(runConfig *container.Config) (string, error) {
471 472
 	hostConfig := hostConfigFromOptions(b.options)
472
-	container, err := b.containerManager.Create(runConfig, hostConfig, b.options.Platform.OS)
473
+	optionsPlatform := system.ParsePlatform(b.options.Platform)
474
+	container, err := b.containerManager.Create(runConfig, hostConfig, optionsPlatform.OS)
473 475
 	if err != nil {
474 476
 		return "", err
475 477
 	}
... ...
@@ -7,12 +7,12 @@ import (
7 7
 	"net/http"
8 8
 	"net/url"
9 9
 	"strconv"
10
+	"strings"
10 11
 
11 12
 	"golang.org/x/net/context"
12 13
 
13 14
 	"github.com/docker/docker/api/types"
14 15
 	"github.com/docker/docker/api/types/container"
15
-	"github.com/docker/docker/pkg/system"
16 16
 )
17 17
 
18 18
 // ImageBuild sends request to the daemon to build images.
... ...
@@ -31,18 +31,11 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
31 31
 	}
32 32
 	headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
33 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(options.Platform) {
34
+	if options.Platform != "" {
38 35
 		if err := cli.NewVersionError("1.32", "platform"); err != nil {
39 36
 			return types.ImageBuildResponse{}, err
40 37
 		}
41
-		platformJSON, err := json.Marshal(options.Platform)
42
-		if err != nil {
43
-			return types.ImageBuildResponse{}, err
44
-		}
45
-		headers.Add("X-Requested-Platform", string(platformJSON[:]))
38
+		query.Set("platform", options.Platform)
46 39
 	}
47 40
 	headers.Set("Content-Type", "application/x-tar")
48 41
 
... ...
@@ -138,5 +131,8 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
138 138
 	if options.SessionID != "" {
139 139
 		query.Set("session", options.SessionID)
140 140
 	}
141
+	if options.Platform != "" {
142
+		query.Set("platform", strings.ToLower(options.Platform))
143
+	}
141 144
 	return query, nil
142 145
 }
... ...
@@ -1,16 +1,14 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"encoding/json"
5 4
 	"io"
6 5
 	"net/url"
6
+	"strings"
7 7
 
8 8
 	"golang.org/x/net/context"
9 9
 
10 10
 	"github.com/docker/distribution/reference"
11 11
 	"github.com/docker/docker/api/types"
12
-	"github.com/docker/docker/pkg/system"
13
-	specs "github.com/opencontainers/image-spec/specs-go/v1"
14 12
 )
15 13
 
16 14
 // ImageCreate creates a new image based in the parent options.
... ...
@@ -24,25 +22,17 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
24 24
 	query := url.Values{}
25 25
 	query.Set("fromImage", reference.FamiliarName(ref))
26 26
 	query.Set("tag", getAPITagFromNamedRef(ref))
27
-	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
27
+	if options.Platform != "" {
28
+		query.Set("platform", strings.ToLower(options.Platform))
29
+	}
30
+	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
28 31
 	if err != nil {
29 32
 		return nil, err
30 33
 	}
31 34
 	return resp.body, nil
32 35
 }
33 36
 
34
-func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string, platform specs.Platform) (serverResponse, error) {
37
+func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
35 38
 	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
36
-
37
-	// TODO @jhowardmsft: system.IsPlatformEmpty is a temporary function. We need to move
38
-	// (in the reasonably short future) to a package which supports all the platform
39
-	// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
40
-	if !system.IsPlatformEmpty(platform) {
41
-		platformJSON, err := json.Marshal(platform)
42
-		if err != nil {
43
-			return serverResponse{}, err
44
-		}
45
-		headers["X-Requested-Platform"] = []string{string(platformJSON[:])}
46
-	}
47 39
 	return cli.post(ctx, "/images/create", query, nil, headers)
48 40
 }
... ...
@@ -3,6 +3,7 @@ package client
3 3
 import (
4 4
 	"io"
5 5
 	"net/url"
6
+	"strings"
6 7
 
7 8
 	"golang.org/x/net/context"
8 9
 
... ...
@@ -25,6 +26,9 @@ func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSour
25 25
 	query.Set("repo", ref)
26 26
 	query.Set("tag", options.Tag)
27 27
 	query.Set("message", options.Message)
28
+	if options.Platform != "" {
29
+		query.Set("platform", strings.ToLower(options.Platform))
30
+	}
28 31
 	for _, change := range options.Changes {
29 32
 		query.Add("changes", change)
30 33
 	}
... ...
@@ -4,12 +4,12 @@ import (
4 4
 	"io"
5 5
 	"net/http"
6 6
 	"net/url"
7
+	"strings"
7 8
 
8 9
 	"golang.org/x/net/context"
9 10
 
10 11
 	"github.com/docker/distribution/reference"
11 12
 	"github.com/docker/docker/api/types"
12
-	"github.com/docker/docker/pkg/system"
13 13
 )
14 14
 
15 15
 // ImagePull requests the docker host to pull an image from a remote registry.
... ...
@@ -31,30 +31,17 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options types.I
31 31
 	if !options.All {
32 32
 		query.Set("tag", getAPITagFromNamedRef(ref))
33 33
 	}
34
-
35
-	// TODO 1: Extend to include "and the platform is supported by the daemon".
36
-	// This is dependent on https://github.com/moby/moby/pull/34628 though,
37
-	// and the daemon returning the set of platforms it supports via the _ping
38
-	// API endpoint.
39
-	//
40
-	// TODO 2: system.IsPlatformEmpty is a temporary function. We need to move
41
-	// (in the reasonably short future) to a package which supports all the platform
42
-	// validation such as is proposed in https://github.com/containerd/containerd/pull/1403
43
-	//
44
-	// @jhowardmsft.
45
-	if !system.IsPlatformEmpty(options.Platform) {
46
-		if err := cli.NewVersionError("1.32", "platform"); err != nil {
47
-			return nil, err
48
-		}
34
+	if options.Platform != "" {
35
+		query.Set("platform", strings.ToLower(options.Platform))
49 36
 	}
50 37
 
51
-	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth, options.Platform)
38
+	resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
52 39
 	if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
53 40
 		newAuthHeader, privilegeErr := options.PrivilegeFunc()
54 41
 		if privilegeErr != nil {
55 42
 			return nil, privilegeErr
56 43
 		}
57
-		resp, err = cli.tryImageCreate(ctx, query, newAuthHeader, options.Platform)
44
+		resp, err = cli.tryImageCreate(ctx, query, newAuthHeader)
58 45
 	}
59 46
 	if err != nil {
60 47
 		return nil, err
... ...
@@ -66,7 +66,7 @@ func (container *Container) BuildHostnameFile() error {
66 66
 func (container *Container) NetworkMounts() []Mount {
67 67
 	var mounts []Mount
68 68
 	shared := container.HostConfig.NetworkMode.IsContainer()
69
-	parser := volume.NewParser(container.Platform)
69
+	parser := volume.NewParser(container.OS)
70 70
 	if container.ResolvConfPath != "" {
71 71
 		if _, err := os.Stat(container.ResolvConfPath); err != nil {
72 72
 			logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
... ...
@@ -195,7 +195,7 @@ func (container *Container) UnmountIpcMount(unmount func(pth string) error) erro
195 195
 // IpcMounts returns the list of IPC mounts
196 196
 func (container *Container) IpcMounts() []Mount {
197 197
 	var mounts []Mount
198
-	parser := volume.NewParser(container.Platform)
198
+	parser := volume.NewParser(container.OS)
199 199
 
200 200
 	if container.HasMountFor("/dev/shm") {
201 201
 		return mounts
... ...
@@ -429,7 +429,7 @@ func copyOwnership(source, destination string) error {
429 429
 
430 430
 // TmpfsMounts returns the list of tmpfs mounts
431 431
 func (container *Container) TmpfsMounts() ([]Mount, error) {
432
-	parser := volume.NewParser(container.Platform)
432
+	parser := volume.NewParser(container.OS)
433 433
 	var mounts []Mount
434 434
 	for dest, data := range container.HostConfig.Tmpfs {
435 435
 		mounts = append(mounts, Mount{
... ...
@@ -12,7 +12,7 @@ import (
12 12
 // cannot be configured with a read-only rootfs.
13 13
 func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
14 14
 	var toVolume bool
15
-	parser := volume.NewParser(container.Platform)
15
+	parser := volume.NewParser(container.OS)
16 16
 	for _, mnt := range container.MountPoints {
17 17
 		if toVolume = parser.HasResource(mnt, absPath); toVolume {
18 18
 			if mnt.RW {
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"github.com/docker/docker/daemon/network"
14 14
 	volumestore "github.com/docker/docker/volume/store"
15 15
 	"github.com/docker/go-connections/nat"
16
+	specs "github.com/opencontainers/image-spec/specs-go/v1"
16 17
 )
17 18
 
18 19
 // ContainerInspect returns low-level information about a
... ...
@@ -171,7 +172,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
-		OS:           container.OS,
174
+		Platform:     specs.Platform{OS: container.OS},
175 175
 		MountLabel:   container.MountLabel,
176 176
 		ProcessLabel: container.ProcessLabel,
177 177
 		ExecIDs:      container.GetExecIDs(),
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	"time"
13 13
 
14 14
 	"github.com/docker/distribution/reference"
15
+	"github.com/docker/distribution/registry/client/auth"
15 16
 	"github.com/docker/distribution/registry/client/transport"
16 17
 	"github.com/docker/docker/distribution/metadata"
17 18
 	"github.com/docker/docker/distribution/xfer"
... ...
@@ -68,7 +69,9 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named, platform strin
68 68
 	return nil
69 69
 }
70 70
 
71
-func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) error {
71
+// Note use auth.Scope rather than reference.Named due to this warning causing Jenkins CI to fail:
72
+// warning: ref can be github.com/docker/docker/vendor/github.com/docker/distribution/registry/client/auth.Scope (interfacer)
73
+func (p *v1Puller) pullRepository(ctx context.Context, ref auth.Scope) error {
72 74
 	progress.Message(p.config.ProgressOutput, "", "Pulling repository "+p.repoInfo.Name.Name())
73 75
 
74 76
 	tagged, isTagged := ref.(reference.NamedTagged)
... ...
@@ -510,7 +510,7 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
510 510
 
511 511
 	// Early bath if the requested OS doesn't match that of the configuration.
512 512
 	// This avoids doing the download, only to potentially fail later.
513
-	if !strings.EqualFold(string(configOS), requestedOS) {
513
+	if !strings.EqualFold(configOS, requestedOS) {
514 514
 		return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
515 515
 	}
516 516
 
... ...
@@ -651,7 +651,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
651 651
 	}
652 652
 
653 653
 	if configJSON == nil {
654
-		configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
654
+		configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
655 655
 		if err == nil && configRootFS == nil {
656 656
 			err = errRootFSInvalid
657 657
 		}
... ...
@@ -723,7 +723,7 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
723 723
 
724 724
 	logrus.Debugf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", ref, len(mfstList.Manifests), os, runtime.GOARCH)
725 725
 
726
-	manifestMatches := filterManifests(mfstList.Manifests)
726
+	manifestMatches := filterManifests(mfstList.Manifests, os)
727 727
 
728 728
 	if len(manifestMatches) == 0 {
729 729
 		errMsg := fmt.Sprintf("no matching manifest for %s/%s in the manifest list entries", os, runtime.GOARCH)
... ...
@@ -16,13 +16,13 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
16 16
 	return blobs.Open(ctx, ld.digest)
17 17
 }
18 18
 
19
-func filterManifests(manifests []manifestlist.ManifestDescriptor) []manifestlist.ManifestDescriptor {
19
+func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
20 20
 	var matches []manifestlist.ManifestDescriptor
21 21
 	for _, manifestDescriptor := range manifests {
22
-		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
22
+		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
23 23
 			matches = append(matches, manifestDescriptor)
24 24
 
25
-			logrus.Debugf("found match for %s/%s with media type %s, digest %s", runtime.GOOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
25
+			logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
26 26
 		}
27 27
 	}
28 28
 	return matches
... ...
@@ -62,29 +62,28 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
62 62
 	return rsc, err
63 63
 }
64 64
 
65
-func filterManifests(manifests []manifestlist.ManifestDescriptor) []manifestlist.ManifestDescriptor {
66
-	version := system.GetOSVersion()
67
-
68
-	// TODO @jhowardmsft LCOW Support: Need to remove the hard coding in LCOW mode.
69
-	lookingForOS := runtime.GOOS
70
-	osVersion := fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
71
-	if system.LCOWSupported() {
72
-		lookingForOS = "linux"
73
-		osVersion = ""
65
+func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
66
+	osVersion := ""
67
+	if os == "windows" {
68
+		version := system.GetOSVersion()
69
+		osVersion = fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
70
+		logrus.Debugf("will only match entries with version %s", osVersion)
74 71
 	}
75 72
 
76 73
 	var matches []manifestlist.ManifestDescriptor
77 74
 	for _, manifestDescriptor := range manifests {
78
-		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == lookingForOS {
79
-			if lookingForOS == "windows" && !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
75
+		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
76
+			if os == "windows" && !versionMatch(manifestDescriptor.Platform.OSVersion, osVersion) {
77
+				logrus.Debugf("skipping %s", manifestDescriptor.Platform.OSVersion)
80 78
 				continue
81 79
 			}
82 80
 			matches = append(matches, manifestDescriptor)
83
-
84
-			logrus.Debugf("found match for %s/%s with media type %s, digest %s", lookingForOS, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
81
+			logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
85 82
 		}
86 83
 	}
87
-	sort.Stable(manifestsByVersion(matches))
84
+	if os == "windows" {
85
+		sort.Stable(manifestsByVersion(matches))
86
+	}
88 87
 	return matches
89 88
 }
90 89
 
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"net/http"
5 5
 	"net/http/httptest"
6 6
 	"net/url"
7
+	"runtime"
7 8
 	"strings"
8 9
 	"testing"
9 10
 
... ...
@@ -62,7 +62,7 @@ func TestImage(t *testing.T) {
62 62
 		Domainname: "domain",
63 63
 		User:       "root",
64 64
 	}
65
-	platform := runtime.GOOS
65
+	os := runtime.GOOS
66 66
 
67 67
 	img := &Image{
68 68
 		V1Image: V1Image{
... ...
@@ -73,19 +73,19 @@ func TestImage(t *testing.T) {
73 73
 
74 74
 	assert.Equal(t, cid, img.ImageID())
75 75
 	assert.Equal(t, cid, img.ID().String())
76
-	assert.Equal(t, platform, img.Platform())
76
+	assert.Equal(t, os, img.OperatingSystem())
77 77
 	assert.Equal(t, config, img.RunConfig())
78 78
 }
79 79
 
80
-func TestImagePlatformNotEmpty(t *testing.T) {
81
-	platform := "platform"
80
+func TestImageOSNotEmpty(t *testing.T) {
81
+	os := "os"
82 82
 	img := &Image{
83 83
 		V1Image: V1Image{
84
-			OS: platform,
84
+			OS: os,
85 85
 		},
86 86
 		OSVersion: "osversion",
87 87
 	}
88
-	assert.Equal(t, platform, img.Platform())
88
+	assert.Equal(t, os, img.OperatingSystem())
89 89
 }
90 90
 
91 91
 func TestNewChildImageFromImageWithRootFS(t *testing.T) {
... ...
@@ -8,21 +8,6 @@ import (
8 8
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
9 9
 )
10 10
 
11
-// IsPlatformEmpty determines if an OCI image-spec platform structure is not populated.
12
-// TODO This is a temporary function - can be replaced by parsing from
13
-// https://github.com/containerd/containerd/pull/1403/files at a later date.
14
-// @jhowardmsft
15
-func IsPlatformEmpty(platform specs.Platform) bool {
16
-	if platform.Architecture == "" &&
17
-		platform.OS == "" &&
18
-		len(platform.OSFeatures) == 0 &&
19
-		platform.OSVersion == "" &&
20
-		platform.Variant == "" {
21
-		return true
22
-	}
23
-	return false
24
-}
25
-
26 11
 // ValidatePlatform determines if a platform structure is valid.
27 12
 // TODO This is a temporary function - can be replaced by parsing from
28 13
 // https://github.com/containerd/containerd/pull/1403/files at a later date.
... ...
@@ -30,7 +15,9 @@ func IsPlatformEmpty(platform specs.Platform) bool {
30 30
 func ValidatePlatform(platform *specs.Platform) error {
31 31
 	platform.Architecture = strings.ToLower(platform.Architecture)
32 32
 	platform.OS = strings.ToLower(platform.OS)
33
-	if platform.Architecture != "" && platform.Architecture != runtime.GOARCH {
33
+	// Based on https://github.com/moby/moby/pull/34642#issuecomment-330375350, do
34
+	// not support anything except operating system.
35
+	if platform.Architecture != "" {
34 36
 		return fmt.Errorf("invalid platform architecture %q", platform.Architecture)
35 37
 	}
36 38
 	if platform.OS != "" {