diff --git a/builder/dockerfile/builder.go b/builder/dockerfile/builder.go index 2a41c1f0c3..d328235a14 100644 --- a/builder/dockerfile/builder.go +++ b/builder/dockerfile/builder.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "io/ioutil" - "runtime" "strings" "time" @@ -104,7 +103,7 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) ( source = src } - os := runtime.GOOS + os := "" apiPlatform := system.ParsePlatform(config.Options.Platform) if apiPlatform.OS != "" { os = apiPlatform.OS diff --git a/builder/dockerfile/dispatchers.go b/builder/dockerfile/dispatchers.go index 38e6f768c4..33e6bd4c23 100644 --- a/builder/dockerfile/dispatchers.go +++ b/builder/dockerfile/dispatchers.go @@ -145,14 +145,14 @@ func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error imageRefOrID = stage.Image localOnly = true } - return d.builder.imageSources.Get(imageRefOrID, localOnly) + return d.builder.imageSources.Get(imageRefOrID, localOnly, d.state.baseImage.OperatingSystem()) } // FROM [--platform=platform] imagename[:tag | @digest] [AS build-stage-name] // func initializeStage(d dispatchRequest, cmd *instructions.Stage) error { d.builder.imageProber.Reset() - image, err := d.getFromImage(d.shlex, cmd.BaseName) + image, err := d.getFromImage(d.shlex, cmd.BaseName, cmd.OperatingSystem) if err != nil { return err } @@ -210,20 +210,44 @@ func (d *dispatchRequest) getExpandedImageName(shlex *shell.Lex, name string) (s } return name, nil } -func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) { + +// getOsFromFlagsAndStage calculates the operating system if we need to pull an image. +// stagePlatform contains the value supplied by optional `--platform=` on +// a current FROM statement. b.builder.options.Platform contains the operating +// system part of the optional flag passed in the API call (or CLI flag +// through `docker build --platform=...`). +func (d *dispatchRequest) getOsFromFlagsAndStage(stagePlatform string) string { + osForPull := "" + // First, take the API platform if nothing provided on FROM + if stagePlatform == "" && d.builder.options.Platform != "" { + osForPull = d.builder.options.Platform + } + // Next, use the FROM flag if that was provided + if osForPull == "" && stagePlatform != "" { + osForPull = stagePlatform + } + // Finally, assume the host OS + if osForPull == "" { + osForPull = runtime.GOOS + } + return osForPull +} + +func (d *dispatchRequest) getImageOrStage(name string, stagePlatform string) (builder.Image, error) { var localOnly bool if im, ok := d.stages.getByName(name); ok { name = im.Image localOnly = true } + os := d.getOsFromFlagsAndStage(stagePlatform) + // Windows cannot support a container with no base image unless it is LCOW. if name == api.NoBaseImageSpecifier { imageImage := &image.Image{} imageImage.OS = runtime.GOOS if runtime.GOOS == "windows" { - optionsOS := system.ParsePlatform(d.builder.options.Platform).OS - switch optionsOS { + switch os { case "windows", "": return nil, errors.New("Windows does not support FROM scratch") case "linux": @@ -232,23 +256,23 @@ func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) { } imageImage.OS = "linux" default: - return nil, errors.Errorf("operating system %q is not supported", optionsOS) + return nil, errors.Errorf("operating system %q is not supported", os) } } return builder.Image(imageImage), nil } - imageMount, err := d.builder.imageSources.Get(name, localOnly) + imageMount, err := d.builder.imageSources.Get(name, localOnly, os) if err != nil { return nil, err } return imageMount.Image(), nil } -func (d *dispatchRequest) getFromImage(shlex *shell.Lex, name string) (builder.Image, error) { +func (d *dispatchRequest) getFromImage(shlex *shell.Lex, name string, stagePlatform string) (builder.Image, error) { name, err := d.getExpandedImageName(shlex, name) if err != nil { return nil, err } - return d.getImageOrStage(name) + return d.getImageOrStage(name, stagePlatform) } func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error { @@ -264,8 +288,7 @@ func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error { func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error { runConfig := d.state.runConfig var err error - baseImageOS := system.ParsePlatform(d.state.operatingSystem).OS - runConfig.WorkingDir, err = normalizeWorkdir(baseImageOS, runConfig.WorkingDir, c.Path) + runConfig.WorkingDir, err = normalizeWorkdir(d.state.baseImage.OperatingSystem(), runConfig.WorkingDir, c.Path) if err != nil { return err } @@ -281,7 +304,7 @@ func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error { } comment := "WORKDIR " + runConfig.WorkingDir - runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, baseImageOS)) + runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, d.state.baseImage.OperatingSystem())) containerID, err := d.builder.probeAndCreate(d.state, runConfigWithCommentCmd) if err != nil || containerID == "" { return err @@ -316,7 +339,7 @@ func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error { return system.ErrNotSupportedOperatingSystem } stateRunConfig := d.state.runConfig - cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.state.operatingSystem) + cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.state.baseImage.OperatingSystem()) buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env) saveCmd := cmdFromArgs @@ -397,8 +420,7 @@ func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.S // func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error { runConfig := d.state.runConfig - optionsOS := system.ParsePlatform(d.builder.options.Platform).OS - cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS) + cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.state.baseImage.OperatingSystem()) runConfig.Cmd = cmd // set config as already being escaped, this prevents double escaping on windows runConfig.ArgsEscaped = true @@ -441,8 +463,7 @@ func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand) // func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error { runConfig := d.state.runConfig - optionsOS := system.ParsePlatform(d.builder.options.Platform).OS - cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS) + cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, d.state.baseImage.OperatingSystem()) runConfig.Entrypoint = cmd if !d.state.cmdSet { runConfig.Cmd = nil diff --git a/builder/dockerfile/dispatchers_test.go b/builder/dockerfile/dispatchers_test.go index 7ad0d8d3c2..d65caf8745 100644 --- a/builder/dockerfile/dispatchers_test.go +++ b/builder/dockerfile/dispatchers_test.go @@ -225,6 +225,7 @@ func TestWorkdir(t *testing.T) { func TestCmd(t *testing.T) { b := newBuilderWithMockBackend() sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) + sb.state.baseImage = &mockImage{} command := "./executable" cmd := &instructions.CmdCommand{ @@ -282,6 +283,7 @@ func TestHealthcheckCmd(t *testing.T) { func TestEntrypoint(t *testing.T) { b := newBuilderWithMockBackend() sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) + sb.state.baseImage = &mockImage{} entrypointCmd := "/usr/sbin/nginx" cmd := &instructions.EntrypointCommand{ @@ -357,6 +359,7 @@ func TestStopSignal(t *testing.T) { } b := newBuilderWithMockBackend() sb := newDispatchRequest(b, '`', nil, newBuildArgs(make(map[string]*string)), newStagesBuildResults()) + sb.state.baseImage = &mockImage{} signal := "SIGKILL" cmd := &instructions.StopSignalCommand{ diff --git a/builder/dockerfile/evaluator.go b/builder/dockerfile/evaluator.go index 74264faf2e..9f747001c7 100644 --- a/builder/dockerfile/evaluator.go +++ b/builder/dockerfile/evaluator.go @@ -37,8 +37,7 @@ import ( func dispatch(d dispatchRequest, cmd instructions.Command) (err error) { if c, ok := cmd.(instructions.PlatformSpecific); ok { - optionsOS := system.ParsePlatform(d.builder.options.Platform).OS - err := c.CheckPlatform(optionsOS) + err := c.CheckPlatform(d.state.baseImage.OperatingSystem()) if err != nil { return errdefs.InvalidParameter(err) } diff --git a/builder/dockerfile/imagecontext.go b/builder/dockerfile/imagecontext.go index 0d4af384af..fd2b942393 100644 --- a/builder/dockerfile/imagecontext.go +++ b/builder/dockerfile/imagecontext.go @@ -6,13 +6,12 @@ import ( "github.com/docker/docker/api/types/backend" "github.com/docker/docker/builder" dockerimage "github.com/docker/docker/image" - "github.com/docker/docker/pkg/system" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/net/context" ) -type getAndMountFunc func(string, bool) (builder.Image, builder.ROLayer, error) +type getAndMountFunc func(string, bool, string) (builder.Image, builder.ROLayer, error) // imageSources mounts images and provides a cache for mounted images. It tracks // all images so they can be unmounted at the end of the build. @@ -23,7 +22,7 @@ type imageSources struct { } func newImageSources(ctx context.Context, options builderOptions) *imageSources { - getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ROLayer, error) { + getAndMount := func(idOrRef string, localOnly bool, osForPull string) (builder.Image, builder.ROLayer, error) { pullOption := backend.PullOptionNoPull if !localOnly { if options.Options.PullParent { @@ -32,12 +31,11 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources pullOption = backend.PullOptionPreferLocal } } - optionsPlatform := system.ParsePlatform(options.Options.Platform) return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{ PullOption: pullOption, AuthConfig: options.Options.AuthConfigs, Output: options.ProgressWriter.Output, - OS: optionsPlatform.OS, + OS: osForPull, }) } @@ -47,12 +45,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources } } -func (m *imageSources) Get(idOrRef string, localOnly bool) (*imageMount, error) { +func (m *imageSources) Get(idOrRef string, localOnly bool, osForPull string) (*imageMount, error) { if im, ok := m.byImageID[idOrRef]; ok { return im, nil } - image, layer, err := m.getImage(idOrRef, localOnly) + image, layer, err := m.getImage(idOrRef, localOnly, osForPull) if err != nil { return nil, err } diff --git a/builder/dockerfile/instructions/parse.go b/builder/dockerfile/instructions/parse.go index 9b94ee3660..6d310eac08 100644 --- a/builder/dockerfile/instructions/parse.go +++ b/builder/dockerfile/instructions/parse.go @@ -3,7 +3,6 @@ package instructions // import "github.com/docker/docker/builder/dockerfile/inst import ( "fmt" "regexp" - "runtime" "sort" "strconv" "strings" @@ -278,20 +277,16 @@ func parseFrom(req parseRequest) (*Stage, error) { return nil, err } specPlatform := system.ParsePlatform(flPlatform.Value) - if specPlatform.OS == "" { - specPlatform.OS = runtime.GOOS - } if err := system.ValidatePlatform(specPlatform); err != nil { return nil, fmt.Errorf("invalid platform %q on FROM", flPlatform.Value) } - if !system.IsOSSupported(specPlatform.OS) { + if specPlatform.OS != "" && !system.IsOSSupported(specPlatform.OS) { return nil, fmt.Errorf("unsupported platform %q on FROM", flPlatform.Value) } if err != nil { return nil, err } code := strings.TrimSpace(req.original) - fmt.Println("JJH", specPlatform.OS) return &Stage{ BaseName: req.args[0], Name: stageName, diff --git a/builder/dockerfile/internals.go b/builder/dockerfile/internals.go index c8b34f8f65..e141a78465 100644 --- a/builder/dockerfile/internals.go +++ b/builder/dockerfile/internals.go @@ -83,8 +83,7 @@ func (b *Builder) commit(dispatchState *dispatchState, comment string) error { return errors.New("Please provide a source image with `from` prior to commit") } - optionsPlatform := system.ParsePlatform(b.options.Platform) - runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, optionsPlatform.OS)) + runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, dispatchState.baseImage.OperatingSystem())) hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd) if err != nil || hit { return err @@ -164,16 +163,15 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error commentStr := fmt.Sprintf("%s %s%s in %s ", inst.cmdName, chownComment, srcHash, inst.dest) // TODO: should this have been using origPaths instead of srcHash in the comment? - optionsPlatform := system.ParsePlatform(b.options.Platform) runConfigWithCommentCmd := copyRunConfig( state.runConfig, - withCmdCommentString(commentStr, optionsPlatform.OS)) + withCmdCommentString(commentStr, state.baseImage.OperatingSystem())) hit, err := b.probeCache(state, runConfigWithCommentCmd) if err != nil || hit { return err } - imageMount, err := b.imageSources.Get(state.imageID, true) + imageMount, err := b.imageSources.Get(state.imageID, true, state.baseImage.OperatingSystem()) if err != nil { return errors.Wrapf(err, "failed to get destination image %q", state.imageID) } @@ -184,7 +182,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error } defer rwLayer.Release() - destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, rwLayer, b.options.Platform) + destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, rwLayer, state.baseImage.OperatingSystem()) if err != nil { return err }