浏览代码

Builder: Plumbing through platform in `FROM` statement

Signed-off-by: John Howard <jhoward@microsoft.com>
John Howard 7 年之前
父节点
当前提交
69fa84bc3d

+ 1 - 2
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

+ 38 - 17
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

+ 3 - 0
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{

+ 1 - 2
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)
 		}

+ 5 - 7
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
 	}

+ 1 - 6
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,

+ 4 - 6
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
 	}