Builder: Plumbing through platform in `FROM` statement

Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
John Howard 2017-10-04 14:26:56 -07:00
parent 7f0c2d23e1
commit 69fa84bc3d
7 changed files with 53 additions and 40 deletions

View File

@ -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

View File

@ -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

View File

@ -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{

View File

@ -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)
}

View File

@ -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
}

View File

@ -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,

View File

@ -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
}