Browse code

Builder: Plumbing through platform in `FROM` statement

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

John Howard authored on 2017/10/05 06:26:56
Showing 7 changed files
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"fmt"
6 6
 	"io"
7 7
 	"io/ioutil"
8
-	"runtime"
9 8
 	"strings"
10 9
 	"time"
11 10
 
... ...
@@ -104,7 +103,7 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (
104 104
 		source = src
105 105
 	}
106 106
 
107
-	os := runtime.GOOS
107
+	os := ""
108 108
 	apiPlatform := system.ParsePlatform(config.Options.Platform)
109 109
 	if apiPlatform.OS != "" {
110 110
 		os = apiPlatform.OS
... ...
@@ -145,14 +145,14 @@ func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error
145 145
 		imageRefOrID = stage.Image
146 146
 		localOnly = true
147 147
 	}
148
-	return d.builder.imageSources.Get(imageRefOrID, localOnly)
148
+	return d.builder.imageSources.Get(imageRefOrID, localOnly, d.state.baseImage.OperatingSystem())
149 149
 }
150 150
 
151 151
 // FROM [--platform=platform] imagename[:tag | @digest] [AS build-stage-name]
152 152
 //
153 153
 func initializeStage(d dispatchRequest, cmd *instructions.Stage) error {
154 154
 	d.builder.imageProber.Reset()
155
-	image, err := d.getFromImage(d.shlex, cmd.BaseName)
155
+	image, err := d.getFromImage(d.shlex, cmd.BaseName, cmd.OperatingSystem)
156 156
 	if err != nil {
157 157
 		return err
158 158
 	}
... ...
@@ -210,20 +210,44 @@ func (d *dispatchRequest) getExpandedImageName(shlex *shell.Lex, name string) (s
210 210
 	}
211 211
 	return name, nil
212 212
 }
213
-func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) {
213
+
214
+// getOsFromFlagsAndStage calculates the operating system if we need to pull an image.
215
+// stagePlatform contains the value supplied by optional `--platform=` on
216
+// a current FROM statement. b.builder.options.Platform contains the operating
217
+// system part of the optional flag passed in the API call (or CLI flag
218
+// through `docker build --platform=...`).
219
+func (d *dispatchRequest) getOsFromFlagsAndStage(stagePlatform string) string {
220
+	osForPull := ""
221
+	// First, take the API platform if nothing provided on FROM
222
+	if stagePlatform == "" && d.builder.options.Platform != "" {
223
+		osForPull = d.builder.options.Platform
224
+	}
225
+	// Next, use the FROM flag if that was provided
226
+	if osForPull == "" && stagePlatform != "" {
227
+		osForPull = stagePlatform
228
+	}
229
+	// Finally, assume the host OS
230
+	if osForPull == "" {
231
+		osForPull = runtime.GOOS
232
+	}
233
+	return osForPull
234
+}
235
+
236
+func (d *dispatchRequest) getImageOrStage(name string, stagePlatform string) (builder.Image, error) {
214 237
 	var localOnly bool
215 238
 	if im, ok := d.stages.getByName(name); ok {
216 239
 		name = im.Image
217 240
 		localOnly = true
218 241
 	}
219 242
 
243
+	os := d.getOsFromFlagsAndStage(stagePlatform)
244
+
220 245
 	// Windows cannot support a container with no base image unless it is LCOW.
221 246
 	if name == api.NoBaseImageSpecifier {
222 247
 		imageImage := &image.Image{}
223 248
 		imageImage.OS = runtime.GOOS
224 249
 		if runtime.GOOS == "windows" {
225
-			optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
226
-			switch optionsOS {
250
+			switch os {
227 251
 			case "windows", "":
228 252
 				return nil, errors.New("Windows does not support FROM scratch")
229 253
 			case "linux":
... ...
@@ -232,23 +256,23 @@ 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", optionsOS)
235
+				return nil, errors.Errorf("operating system %q is not supported", os)
236 236
 			}
237 237
 		}
238 238
 		return builder.Image(imageImage), nil
239 239
 	}
240
-	imageMount, err := d.builder.imageSources.Get(name, localOnly)
240
+	imageMount, err := d.builder.imageSources.Get(name, localOnly, os)
241 241
 	if err != nil {
242 242
 		return nil, err
243 243
 	}
244 244
 	return imageMount.Image(), nil
245 245
 }
246
-func (d *dispatchRequest) getFromImage(shlex *shell.Lex, name string) (builder.Image, error) {
246
+func (d *dispatchRequest) getFromImage(shlex *shell.Lex, name string, stagePlatform string) (builder.Image, error) {
247 247
 	name, err := d.getExpandedImageName(shlex, name)
248 248
 	if err != nil {
249 249
 		return nil, err
250 250
 	}
251
-	return d.getImageOrStage(name)
251
+	return d.getImageOrStage(name, stagePlatform)
252 252
 }
253 253
 
254 254
 func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error {
... ...
@@ -264,8 +288,7 @@ 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
-	baseImageOS := system.ParsePlatform(d.state.operatingSystem).OS
268
-	runConfig.WorkingDir, err = normalizeWorkdir(baseImageOS, runConfig.WorkingDir, c.Path)
267
+	runConfig.WorkingDir, err = normalizeWorkdir(d.state.baseImage.OperatingSystem(), runConfig.WorkingDir, c.Path)
269 268
 	if err != nil {
270 269
 		return err
271 270
 	}
... ...
@@ -281,7 +304,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error {
281 281
 	}
282 282
 
283 283
 	comment := "WORKDIR " + runConfig.WorkingDir
284
-	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, baseImageOS))
284
+	runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.state.baseImage.OperatingSystem()))
285 285
 	containerID, err := d.builder.probeAndCreate(d.state, runConfigWithCommentCmd)
286 286
 	if err != nil || containerID == "" {
287 287
 		return err
... ...
@@ -316,7 +339,7 @@ func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error {
316 316
 		return system.ErrNotSupportedOperatingSystem
317 317
 	}
318 318
 	stateRunConfig := d.state.runConfig
319
-	cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.state.operatingSystem)
319
+	cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.state.baseImage.OperatingSystem())
320 320
 	buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env)
321 321
 
322 322
 	saveCmd := cmdFromArgs
... ...
@@ -397,8 +420,7 @@ func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.S
397 397
 //
398 398
 func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error {
399 399
 	runConfig := d.state.runConfig
400
-	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
401
-	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS)
400
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.state.baseImage.OperatingSystem())
402 401
 	runConfig.Cmd = cmd
403 402
 	// set config as already being escaped, this prevents double escaping on windows
404 403
 	runConfig.ArgsEscaped = true
... ...
@@ -441,8 +463,7 @@ func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand)
441 441
 //
442 442
 func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error {
443 443
 	runConfig := d.state.runConfig
444
-	optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
445
-	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS)
444
+	cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.state.baseImage.OperatingSystem())
446 445
 	runConfig.Entrypoint = cmd
447 446
 	if !d.state.cmdSet {
448 447
 		runConfig.Cmd = nil
... ...
@@ -225,6 +225,7 @@ func TestWorkdir(t *testing.T) {
225 225
 func TestCmd(t *testing.T) {
226 226
 	b := newBuilderWithMockBackend()
227 227
 	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
228
+	sb.state.baseImage = &mockImage{}
228 229
 	command := "./executable"
229 230
 
230 231
 	cmd := &instructions.CmdCommand{
... ...
@@ -282,6 +283,7 @@ func TestHealthcheckCmd(t *testing.T) {
282 282
 func TestEntrypoint(t *testing.T) {
283 283
 	b := newBuilderWithMockBackend()
284 284
 	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
285
+	sb.state.baseImage = &mockImage{}
285 286
 	entrypointCmd := "/usr/sbin/nginx"
286 287
 
287 288
 	cmd := &instructions.EntrypointCommand{
... ...
@@ -357,6 +359,7 @@ func TestStopSignal(t *testing.T) {
357 357
 	}
358 358
 	b := newBuilderWithMockBackend()
359 359
 	sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults())
360
+	sb.state.baseImage = &mockImage{}
360 361
 	signal := "SIGKILL"
361 362
 
362 363
 	cmd := &instructions.StopSignalCommand{
... ...
@@ -37,8 +37,7 @@ import (
37 37
 
38 38
 func dispatch(d dispatchRequest, cmd instructions.Command) (err error) {
39 39
 	if c, ok := cmd.(instructions.PlatformSpecific); ok {
40
-		optionsOS := system.ParsePlatform(d.builder.options.Platform).OS
41
-		err := c.CheckPlatform(optionsOS)
40
+		err := c.CheckPlatform(d.state.baseImage.OperatingSystem())
42 41
 		if err != nil {
43 42
 			return errdefs.InvalidParameter(err)
44 43
 		}
... ...
@@ -6,13 +6,12 @@ import (
6 6
 	"github.com/docker/docker/api/types/backend"
7 7
 	"github.com/docker/docker/builder"
8 8
 	dockerimage "github.com/docker/docker/image"
9
-	"github.com/docker/docker/pkg/system"
10 9
 	"github.com/pkg/errors"
11 10
 	"github.com/sirupsen/logrus"
12 11
 	"golang.org/x/net/context"
13 12
 )
14 13
 
15
-type getAndMountFunc func(string, bool) (builder.Image, builder.ROLayer, error)
14
+type getAndMountFunc func(string, bool, string) (builder.Image, builder.ROLayer, error)
16 15
 
17 16
 // imageSources mounts images and provides a cache for mounted images. It tracks
18 17
 // all images so they can be unmounted at the end of the build.
... ...
@@ -23,7 +22,7 @@ type imageSources struct {
23 23
 }
24 24
 
25 25
 func newImageSources(ctx context.Context, options builderOptions) *imageSources {
26
-	getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ROLayer, error) {
26
+	getAndMount := func(idOrRef string, localOnly bool, osForPull string) (builder.Image, builder.ROLayer, error) {
27 27
 		pullOption := backend.PullOptionNoPull
28 28
 		if !localOnly {
29 29
 			if options.Options.PullParent {
... ...
@@ -32,12 +31,11 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
32 32
 				pullOption = backend.PullOptionPreferLocal
33 33
 			}
34 34
 		}
35
-		optionsPlatform := system.ParsePlatform(options.Options.Platform)
36 35
 		return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{
37 36
 			PullOption: pullOption,
38 37
 			AuthConfig: options.Options.AuthConfigs,
39 38
 			Output:     options.ProgressWriter.Output,
40
-			OS:         optionsPlatform.OS,
39
+			OS:         osForPull,
41 40
 		})
42 41
 	}
43 42
 
... ...
@@ -47,12 +45,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources
47 47
 	}
48 48
 }
49 49
 
50
-func (m *imageSources) Get(idOrRef string, localOnly bool) (*imageMount, error) {
50
+func (m *imageSources) Get(idOrRef string, localOnly bool, osForPull string) (*imageMount, error) {
51 51
 	if im, ok := m.byImageID[idOrRef]; ok {
52 52
 		return im, nil
53 53
 	}
54 54
 
55
-	image, layer, err := m.getImage(idOrRef, localOnly)
55
+	image, layer, err := m.getImage(idOrRef, localOnly, osForPull)
56 56
 	if err != nil {
57 57
 		return nil, err
58 58
 	}
... ...
@@ -3,7 +3,6 @@ package instructions // import "github.com/docker/docker/builder/dockerfile/inst
3 3
 import (
4 4
 	"fmt"
5 5
 	"regexp"
6
-	"runtime"
7 6
 	"sort"
8 7
 	"strconv"
9 8
 	"strings"
... ...
@@ -278,20 +277,16 @@ func parseFrom(req parseRequest) (*Stage, error) {
278 278
 		return nil, err
279 279
 	}
280 280
 	specPlatform := system.ParsePlatform(flPlatform.Value)
281
-	if specPlatform.OS == "" {
282
-		specPlatform.OS = runtime.GOOS
283
-	}
284 281
 	if err := system.ValidatePlatform(specPlatform); err != nil {
285 282
 		return nil, fmt.Errorf("invalid platform %q on FROM", flPlatform.Value)
286 283
 	}
287
-	if !system.IsOSSupported(specPlatform.OS) {
284
+	if specPlatform.OS != "" && !system.IsOSSupported(specPlatform.OS) {
288 285
 		return nil, fmt.Errorf("unsupported platform %q on FROM", flPlatform.Value)
289 286
 	}
290 287
 	if err != nil {
291 288
 		return nil, err
292 289
 	}
293 290
 	code := strings.TrimSpace(req.original)
294
-	fmt.Println("JJH", specPlatform.OS)
295 291
 	return &Stage{
296 292
 		BaseName:        req.args[0],
297 293
 		Name:            stageName,
... ...
@@ -83,8 +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
-	optionsPlatform := system.ParsePlatform(b.options.Platform)
87
-	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, optionsPlatform.OS))
86
+	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, dispatchState.baseImage.OperatingSystem()))
88 87
 	hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
89 88
 	if err != nil || hit {
90 89
 		return err
... ...
@@ -164,16 +163,15 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
164 164
 	commentStr := fmt.Sprintf("%s %s%s in %s ", inst.cmdName, chownComment, srcHash, inst.dest)
165 165
 
166 166
 	// TODO: should this have been using origPaths instead of srcHash in the comment?
167
-	optionsPlatform := system.ParsePlatform(b.options.Platform)
168 167
 	runConfigWithCommentCmd := copyRunConfig(
169 168
 		state.runConfig,
170
-		withCmdCommentString(commentStr, optionsPlatform.OS))
169
+		withCmdCommentString(commentStr, state.baseImage.OperatingSystem()))
171 170
 	hit, err := b.probeCache(state, runConfigWithCommentCmd)
172 171
 	if err != nil || hit {
173 172
 		return err
174 173
 	}
175 174
 
176
-	imageMount, err := b.imageSources.Get(state.imageID, true)
175
+	imageMount, err := b.imageSources.Get(state.imageID, true, state.baseImage.OperatingSystem())
177 176
 	if err != nil {
178 177
 		return errors.Wrapf(err, "failed to get destination image %q", state.imageID)
179 178
 	}
... ...
@@ -184,7 +182,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error
184 184
 	}
185 185
 	defer rwLayer.Release()
186 186
 
187
-	destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, rwLayer, b.options.Platform)
187
+	destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, rwLayer, state.baseImage.OperatingSystem())
188 188
 	if err != nil {
189 189
 		return err
190 190
 	}