Merge pull request #32952 from dnephin/refactor-builder-dispatch-state

[Builder] Move dispatch state to a new struct
This commit is contained in:
Brian Goff 2017-05-05 09:00:57 -04:00 committed by GitHub
commit b106dd8b57
8 changed files with 353 additions and 309 deletions

View file

@ -1,5 +1,10 @@
package dockerfile
import (
"fmt"
"github.com/docker/docker/runconfig/opts"
)
// builtinAllowedBuildArgs is list of built-in allowed build args
// these args are considered transparent and are excluded from the image history.
// Filtering from history is implemented in dispatchers.go
@ -96,6 +101,19 @@ func (b *buildArgs) getAllFromMapping(source map[string]*string) map[string]stri
return m
}
// FilterAllowed returns all allowed args without the filtered args
func (b *buildArgs) FilterAllowed(filter []string) []string {
envs := []string{}
configEnv := opts.ConvertKVStringsToMap(filter)
for key, val := range b.GetAllAllowed() {
if _, ok := configEnv[key]; !ok {
envs = append(envs, fmt.Sprintf("%s=%s", key, val))
}
}
return envs
}
func (b *buildArgs) getBuildArg(key string, mapping map[string]*string) (string, bool) {
defaultValue, exists := mapping[key]
// Return override from options if one is defined

View file

@ -95,20 +95,12 @@ type Builder struct {
source builder.Source
clientCtx context.Context
runConfig *container.Config // runconfig for cmd, run, entrypoint etc.
tmpContainers map[string]struct{}
imageContexts *imageContexts // helper for storing contexts from builds
disableCommit bool
cacheBusted bool
buildArgs *buildArgs
imageCache builder.ImageCache
// TODO: these move to DispatchState
maintainer string
cmdSet bool
noBaseImage bool // A flag to track the use of `scratch` as the base image
image string // imageID
from builder.Image
}
// newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
@ -124,7 +116,6 @@ func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
Stderr: options.ProgressWriter.StderrFormatter,
Output: options.ProgressWriter.Output,
docker: options.Backend,
runConfig: new(container.Config),
tmpContainers: map[string]struct{}{},
buildArgs: newBuildArgs(config.BuildArgs),
}
@ -136,7 +127,6 @@ func (b *Builder) resetImageCache() {
if icb, ok := b.docker.(builder.ImageCacheBuilder); ok {
b.imageCache = icb.MakeImageCache(b.options.CacheFrom)
}
b.noBaseImage = false
b.cacheBusted = false
}
@ -154,59 +144,61 @@ func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*buil
return nil, err
}
imageID, err := b.dispatchDockerfileWithCancellation(dockerfile)
dispatchState, err := b.dispatchDockerfileWithCancellation(dockerfile)
if err != nil {
return nil, err
}
if b.options.Target != "" && !dispatchState.isCurrentStage(b.options.Target) {
return nil, errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
}
b.warnOnUnusedBuildArgs()
if imageID == "" {
if dispatchState.imageID == "" {
return nil, errors.New("No image was generated. Is your Dockerfile empty?")
}
return &builder.Result{ImageID: imageID, FromImage: b.from}, nil
return &builder.Result{ImageID: dispatchState.imageID, FromImage: dispatchState.baseImage}, nil
}
func (b *Builder) dispatchDockerfileWithCancellation(dockerfile *parser.Result) (string, error) {
func (b *Builder) dispatchDockerfileWithCancellation(dockerfile *parser.Result) (*dispatchState, error) {
shlex := NewShellLex(dockerfile.EscapeToken)
state := newDispatchState()
total := len(dockerfile.AST.Children)
var imageID string
var err error
for i, n := range dockerfile.AST.Children {
select {
case <-b.clientCtx.Done():
logrus.Debug("Builder: build cancelled!")
fmt.Fprint(b.Stdout, "Build cancelled")
return "", errors.New("Build cancelled")
return nil, errors.New("Build cancelled")
default:
// Not cancelled yet, keep going...
}
if command.From == n.Value && b.imageContexts.isCurrentTarget(b.options.Target) {
if n.Value == command.From && state.isCurrentStage(b.options.Target) {
break
}
if err := b.dispatch(i, total, n, shlex); err != nil {
opts := dispatchOptions{
state: state,
stepMsg: formatStep(i, total),
node: n,
shlex: shlex,
}
if state, err = b.dispatch(opts); err != nil {
if b.options.ForceRemove {
b.clearTmp()
}
return "", err
return nil, err
}
// TODO: get this from dispatch
imageID = b.image
fmt.Fprintf(b.Stdout, " ---> %s\n", stringid.TruncateID(imageID))
fmt.Fprintf(b.Stdout, " ---> %s\n", stringid.TruncateID(state.imageID))
if b.options.Remove {
b.clearTmp()
}
}
if b.options.Target != "" && !b.imageContexts.isCurrentTarget(b.options.Target) {
return "", errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
}
return imageID, nil
return state, nil
}
func addNodesForLabelOption(dockerfile *parser.Node, labels map[string]string) {
@ -227,12 +219,6 @@ func (b *Builder) warnOnUnusedBuildArgs() {
}
}
// hasFromImage returns true if the builder has processed a `FROM <image>` line
// TODO: move to DispatchState
func (b *Builder) hasFromImage() bool {
return b.image != "" || b.noBaseImage
}
// BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile
// It will:
// - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
@ -249,31 +235,28 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
b := newBuilder(context.Background(), builderOptions{})
result, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
if err != nil {
return nil, err
}
// ensure that the commands are valid
for _, n := range result.AST.Children {
for _, n := range dockerfile.AST.Children {
if !validCommitCommands[n.Value] {
return nil, fmt.Errorf("%s is not a valid change command", n.Value)
}
}
b.runConfig = config
b.Stdout = ioutil.Discard
b.Stderr = ioutil.Discard
b.disableCommit = true
if err := checkDispatchDockerfile(result.AST); err != nil {
if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
return nil, err
}
if err := dispatchFromDockerfile(b, result); err != nil {
return nil, err
}
return b.runConfig, nil
dispatchState := newDispatchState()
dispatchState.runConfig = config
return dispatchFromDockerfile(b, dockerfile, dispatchState)
}
func checkDispatchDockerfile(dockerfile *parser.Node) error {
@ -285,15 +268,21 @@ func checkDispatchDockerfile(dockerfile *parser.Node) error {
return nil
}
func dispatchFromDockerfile(b *Builder, result *parser.Result) error {
func dispatchFromDockerfile(b *Builder, result *parser.Result, dispatchState *dispatchState) (*container.Config, error) {
shlex := NewShellLex(result.EscapeToken)
ast := result.AST
total := len(ast.Children)
for i, n := range ast.Children {
if err := b.dispatch(i, total, n, shlex); err != nil {
return err
opts := dispatchOptions{
state: dispatchState,
stepMsg: formatStep(i, total),
node: n,
shlex: shlex,
}
if _, err := b.dispatch(opts); err != nil {
return nil, err
}
}
return nil
return dispatchState.runConfig, nil
}

View file

@ -47,6 +47,7 @@ func env(req dispatchRequest) error {
return err
}
runConfig := req.state.runConfig
commitMessage := bytes.NewBufferString("ENV")
for j := 0; j < len(req.args); j += 2 {
@ -59,21 +60,21 @@ func env(req dispatchRequest) error {
commitMessage.WriteString(" " + newVar)
gotOne := false
for i, envVar := range req.runConfig.Env {
for i, envVar := range runConfig.Env {
envParts := strings.SplitN(envVar, "=", 2)
compareFrom := envParts[0]
if equalEnvKeys(compareFrom, name) {
req.runConfig.Env[i] = newVar
runConfig.Env[i] = newVar
gotOne = true
break
}
}
if !gotOne {
req.runConfig.Env = append(req.runConfig.Env, newVar)
runConfig.Env = append(runConfig.Env, newVar)
}
}
return req.builder.commit(commitMessage.String())
return req.builder.commit(req.state, commitMessage.String())
}
// MAINTAINER some text <maybe@an.email.address>
@ -89,8 +90,8 @@ func maintainer(req dispatchRequest) error {
}
maintainer := req.args[0]
req.builder.maintainer = maintainer
return req.builder.commit("MAINTAINER " + maintainer)
req.state.maintainer = maintainer
return req.builder.commit(req.state, "MAINTAINER "+maintainer)
}
// LABEL some json data describing the image
@ -111,26 +112,25 @@ func label(req dispatchRequest) error {
}
commitStr := "LABEL"
runConfig := req.state.runConfig
if req.runConfig.Labels == nil {
req.runConfig.Labels = map[string]string{}
if runConfig.Labels == nil {
runConfig.Labels = map[string]string{}
}
for j := 0; j < len(req.args); j++ {
// name ==> req.args[j]
// value ==> req.args[j+1]
if len(req.args[j]) == 0 {
name := req.args[j]
if name == "" {
return errBlankCommandNames("LABEL")
}
newVar := req.args[j] + "=" + req.args[j+1] + ""
commitStr += " " + newVar
value := req.args[j+1]
commitStr += " " + name + "=" + value
req.runConfig.Labels[req.args[j]] = req.args[j+1]
runConfig.Labels[name] = value
j++
}
return req.builder.commit(commitStr)
return req.builder.commit(req.state, commitStr)
}
// ADD foo /path
@ -147,7 +147,7 @@ func add(req dispatchRequest) error {
return err
}
return req.builder.runContextCommand(req.args, true, true, "ADD", nil)
return req.builder.runContextCommand(req, true, true, "ADD", nil)
}
// COPY foo /path
@ -174,13 +174,13 @@ func dispatchCopy(req dispatchRequest) error {
}
}
return req.builder.runContextCommand(req.args, false, false, "COPY", im)
return req.builder.runContextCommand(req, false, false, "COPY", im)
}
// FROM imagename[:tag | @digest] [AS build-stage-name]
//
func from(req dispatchRequest) error {
ctxName, err := parseBuildStageName(req.args)
stageName, err := parseBuildStageName(req.args)
if err != nil {
return err
}
@ -190,21 +190,23 @@ func from(req dispatchRequest) error {
}
req.builder.resetImageCache()
if _, err := req.builder.imageContexts.add(ctxName); err != nil {
req.state.noBaseImage = false
req.state.stageName = stageName
if _, err := req.builder.imageContexts.add(stageName); err != nil {
return err
}
image, err := req.builder.getFromImage(req.shlex, req.args[0])
image, err := req.builder.getFromImage(req.state, req.shlex, req.args[0])
if err != nil {
return err
}
if image != nil {
req.builder.imageContexts.update(image.ImageID(), image.RunConfig())
}
req.builder.from = image
req.state.baseImage = image
req.builder.buildArgs.ResetAllowed()
return req.builder.processImageFrom(image)
return req.builder.processImageFrom(req.state, image)
}
func parseBuildStageName(args []string) (string, error) {
@ -222,7 +224,7 @@ func parseBuildStageName(args []string) (string, error) {
return stageName, nil
}
func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, error) {
func (b *Builder) getFromImage(dispatchState *dispatchState, shlex *ShellLex, name string) (builder.Image, error) {
substitutionArgs := []string{}
for key, value := range b.buildArgs.GetAllMeta() {
substitutionArgs = append(substitutionArgs, key+"="+value)
@ -246,8 +248,8 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err
if runtime.GOOS == "windows" {
return nil, errors.New("Windows does not support FROM scratch")
}
b.image = ""
b.noBaseImage = true
dispatchState.imageID = ""
dispatchState.noBaseImage = true
return nil, nil
}
return pullOrGetImage(b, name)
@ -279,9 +281,10 @@ func onbuild(req dispatchRequest) error {
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
}
runConfig := req.state.runConfig
original := regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(req.original, "")
req.runConfig.OnBuild = append(req.runConfig.OnBuild, original)
return req.builder.commit("ONBUILD " + original)
runConfig.OnBuild = append(runConfig.OnBuild, original)
return req.builder.commit(req.state, "ONBUILD "+original)
}
// WORKDIR /tmp
@ -298,9 +301,10 @@ func workdir(req dispatchRequest) error {
return err
}
runConfig := req.state.runConfig
// This is from the Dockerfile and will not necessarily be in platform
// specific semantics, hence ensure it is converted.
req.runConfig.WorkingDir, err = normaliseWorkdir(req.runConfig.WorkingDir, req.args[0])
runConfig.WorkingDir, err = normaliseWorkdir(runConfig.WorkingDir, req.args[0])
if err != nil {
return err
}
@ -315,9 +319,9 @@ func workdir(req dispatchRequest) error {
return nil
}
comment := "WORKDIR " + req.runConfig.WorkingDir
runConfigWithCommentCmd := copyRunConfig(req.runConfig, withCmdCommentString(comment))
if hit, err := req.builder.probeCache(req.builder.image, runConfigWithCommentCmd); err != nil || hit {
comment := "WORKDIR " + runConfig.WorkingDir
runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment))
if hit, err := req.builder.probeCache(req.state, runConfigWithCommentCmd); err != nil || hit {
return err
}
@ -334,7 +338,7 @@ func workdir(req dispatchRequest) error {
return err
}
return req.builder.commitContainer(container.ID, runConfigWithCommentCmd)
return req.builder.commitContainer(req.state, container.ID, runConfigWithCommentCmd)
}
// RUN some command yo
@ -348,7 +352,7 @@ func workdir(req dispatchRequest) error {
// RUN [ "echo", "hi" ] # echo hi
//
func run(req dispatchRequest) error {
if !req.builder.hasFromImage() {
if !req.state.hasFromImage() {
return errors.New("Please provide a source image with `from` prior to run")
}
@ -356,29 +360,30 @@ func run(req dispatchRequest) error {
return err
}
stateRunConfig := req.state.runConfig
args := handleJSONArgs(req.args, req.attributes)
if !req.attributes["json"] {
args = append(getShell(req.runConfig), args...)
args = append(getShell(stateRunConfig), args...)
}
cmdFromArgs := strslice.StrSlice(args)
buildArgs := req.builder.buildArgsWithoutConfigEnv()
buildArgs := req.builder.buildArgs.FilterAllowed(stateRunConfig.Env)
saveCmd := cmdFromArgs
if len(buildArgs) > 0 {
saveCmd = prependEnvOnCmd(req.builder.buildArgs, buildArgs, cmdFromArgs)
}
runConfigForCacheProbe := copyRunConfig(req.runConfig,
runConfigForCacheProbe := copyRunConfig(stateRunConfig,
withCmd(saveCmd),
withEntrypointOverride(saveCmd, nil))
hit, err := req.builder.probeCache(req.builder.image, runConfigForCacheProbe)
hit, err := req.builder.probeCache(req.state, runConfigForCacheProbe)
if err != nil || hit {
return err
}
runConfig := copyRunConfig(req.runConfig,
runConfig := copyRunConfig(stateRunConfig,
withCmd(cmdFromArgs),
withEnv(append(req.runConfig.Env, buildArgs...)),
withEnv(append(stateRunConfig.Env, buildArgs...)),
withEntrypointOverride(saveCmd, strslice.StrSlice{""}))
// set config as already being escaped, this prevents double escaping on windows
@ -393,7 +398,7 @@ func run(req dispatchRequest) error {
return err
}
return req.builder.commitContainer(cID, runConfigForCacheProbe)
return req.builder.commitContainer(req.state, cID, runConfigForCacheProbe)
}
// Derive the command to use for probeCache() and to commit in this container.
@ -431,22 +436,22 @@ func cmd(req dispatchRequest) error {
return err
}
runConfig := req.state.runConfig
cmdSlice := handleJSONArgs(req.args, req.attributes)
if !req.attributes["json"] {
cmdSlice = append(getShell(req.runConfig), cmdSlice...)
cmdSlice = append(getShell(runConfig), cmdSlice...)
}
req.runConfig.Cmd = strslice.StrSlice(cmdSlice)
runConfig.Cmd = strslice.StrSlice(cmdSlice)
// set config as already being escaped, this prevents double escaping on windows
req.runConfig.ArgsEscaped = true
runConfig.ArgsEscaped = true
if err := req.builder.commit(fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
if err := req.builder.commit(req.state, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
return err
}
if len(req.args) != 0 {
req.builder.cmdSet = true
req.state.cmdSet = true
}
return nil
@ -478,6 +483,7 @@ func healthcheck(req dispatchRequest) error {
if len(req.args) == 0 {
return errAtLeastOneArgument("HEALTHCHECK")
}
runConfig := req.state.runConfig
typ := strings.ToUpper(req.args[0])
args := req.args[1:]
if typ == "NONE" {
@ -485,12 +491,12 @@ func healthcheck(req dispatchRequest) error {
return errors.New("HEALTHCHECK NONE takes no arguments")
}
test := strslice.StrSlice{typ}
req.runConfig.Healthcheck = &container.HealthConfig{
runConfig.Healthcheck = &container.HealthConfig{
Test: test,
}
} else {
if req.runConfig.Healthcheck != nil {
oldCmd := req.runConfig.Healthcheck.Test
if runConfig.Healthcheck != nil {
oldCmd := runConfig.Healthcheck.Test
if len(oldCmd) > 0 && oldCmd[0] != "NONE" {
fmt.Fprintf(req.builder.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd)
}
@ -554,10 +560,10 @@ func healthcheck(req dispatchRequest) error {
healthcheck.Retries = 0
}
req.runConfig.Healthcheck = &healthcheck
runConfig.Healthcheck = &healthcheck
}
return req.builder.commit(fmt.Sprintf("HEALTHCHECK %q", req.runConfig.Healthcheck))
return req.builder.commit(req.state, fmt.Sprintf("HEALTHCHECK %q", runConfig.Healthcheck))
}
// ENTRYPOINT /usr/sbin/nginx
@ -573,27 +579,28 @@ func entrypoint(req dispatchRequest) error {
return err
}
runConfig := req.state.runConfig
parsed := handleJSONArgs(req.args, req.attributes)
switch {
case req.attributes["json"]:
// ENTRYPOINT ["echo", "hi"]
req.runConfig.Entrypoint = strslice.StrSlice(parsed)
runConfig.Entrypoint = strslice.StrSlice(parsed)
case len(parsed) == 0:
// ENTRYPOINT []
req.runConfig.Entrypoint = nil
runConfig.Entrypoint = nil
default:
// ENTRYPOINT echo hi
req.runConfig.Entrypoint = strslice.StrSlice(append(getShell(req.runConfig), parsed[0]))
runConfig.Entrypoint = strslice.StrSlice(append(getShell(runConfig), parsed[0]))
}
// when setting the entrypoint if a CMD was not explicitly set then
// set the command to nil
if !req.builder.cmdSet {
req.runConfig.Cmd = nil
if !req.state.cmdSet {
runConfig.Cmd = nil
}
return req.builder.commit(fmt.Sprintf("ENTRYPOINT %q", req.runConfig.Entrypoint))
return req.builder.commit(req.state, fmt.Sprintf("ENTRYPOINT %q", runConfig.Entrypoint))
}
// EXPOSE 6667/tcp 7000/tcp
@ -612,8 +619,9 @@ func expose(req dispatchRequest) error {
return err
}
if req.runConfig.ExposedPorts == nil {
req.runConfig.ExposedPorts = make(nat.PortSet)
runConfig := req.state.runConfig
if runConfig.ExposedPorts == nil {
runConfig.ExposedPorts = make(nat.PortSet)
}
ports, _, err := nat.ParsePortSpecs(portsTab)
@ -627,14 +635,14 @@ func expose(req dispatchRequest) error {
portList := make([]string, len(ports))
var i int
for port := range ports {
if _, exists := req.runConfig.ExposedPorts[port]; !exists {
req.runConfig.ExposedPorts[port] = struct{}{}
if _, exists := runConfig.ExposedPorts[port]; !exists {
runConfig.ExposedPorts[port] = struct{}{}
}
portList[i] = string(port)
i++
}
sort.Strings(portList)
return req.builder.commit("EXPOSE " + strings.Join(portList, " "))
return req.builder.commit(req.state, "EXPOSE "+strings.Join(portList, " "))
}
// USER foo
@ -651,8 +659,8 @@ func user(req dispatchRequest) error {
return err
}
req.runConfig.User = req.args[0]
return req.builder.commit(fmt.Sprintf("USER %v", req.args))
req.state.runConfig.User = req.args[0]
return req.builder.commit(req.state, fmt.Sprintf("USER %v", req.args))
}
// VOLUME /foo
@ -668,17 +676,18 @@ func volume(req dispatchRequest) error {
return err
}
if req.runConfig.Volumes == nil {
req.runConfig.Volumes = map[string]struct{}{}
runConfig := req.state.runConfig
if runConfig.Volumes == nil {
runConfig.Volumes = map[string]struct{}{}
}
for _, v := range req.args {
v = strings.TrimSpace(v)
if v == "" {
return errors.New("VOLUME specified can not be an empty string")
}
req.runConfig.Volumes[v] = struct{}{}
runConfig.Volumes[v] = struct{}{}
}
return req.builder.commit(fmt.Sprintf("VOLUME %v", req.args))
return req.builder.commit(req.state, fmt.Sprintf("VOLUME %v", req.args))
}
// STOPSIGNAL signal
@ -695,8 +704,8 @@ func stopSignal(req dispatchRequest) error {
return err
}
req.runConfig.StopSignal = sig
return req.builder.commit(fmt.Sprintf("STOPSIGNAL %v", req.args))
req.state.runConfig.StopSignal = sig
return req.builder.commit(req.state, fmt.Sprintf("STOPSIGNAL %v", req.args))
}
// ARG name[=value]
@ -742,11 +751,11 @@ func arg(req dispatchRequest) error {
req.builder.buildArgs.AddArg(name, value)
// Arg before FROM doesn't add a layer
if !req.builder.hasFromImage() {
if !req.state.hasFromImage() {
req.builder.buildArgs.AddMetaArg(name, value)
return nil
}
return req.builder.commit("ARG " + arg)
return req.builder.commit(req.state, "ARG "+arg)
}
// SHELL powershell -command
@ -763,12 +772,12 @@ func shell(req dispatchRequest) error {
return errAtLeastOneArgument("SHELL")
case req.attributes["json"]:
// SHELL ["powershell", "-command"]
req.runConfig.Shell = strslice.StrSlice(shellSlice)
req.state.runConfig.Shell = strslice.StrSlice(shellSlice)
default:
// SHELL powershell -command - not JSON
return errNotJSON("SHELL", req.original)
}
return req.builder.commit(fmt.Sprintf("SHELL %v", shellSlice))
return req.builder.commit(req.state, fmt.Sprintf("SHELL %v", shellSlice))
}
func errAtLeastOneArgument(command string) error {

View file

@ -26,7 +26,7 @@ type commandWithFunction struct {
func withArgs(f dispatcher) func([]string) error {
return func(args []string) error {
return f(dispatchRequest{args: args, runConfig: &container.Config{}})
return f(dispatchRequest{args: args})
}
}
@ -41,14 +41,13 @@ func defaultDispatchReq(builder *Builder, args ...string) dispatchRequest {
builder: builder,
args: args,
flags: NewBFlags(),
runConfig: &container.Config{},
shlex: NewShellLex(parser.DefaultEscapeToken),
state: &dispatchState{runConfig: &container.Config{}},
}
}
func newBuilderWithMockBackend() *Builder {
b := &Builder{
runConfig: &container.Config{},
options: &types.ImageBuildOptions{},
docker: &MockBackend{},
buildArgs: newBuildArgs(make(map[string]*string)),
@ -138,7 +137,7 @@ func TestEnv2Variables(t *testing.T) {
fmt.Sprintf("%s=%s", args[0], args[1]),
fmt.Sprintf("%s=%s", args[2], args[3]),
}
assert.Equal(t, expected, req.runConfig.Env)
assert.Equal(t, expected, req.state.runConfig.Env)
}
func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
@ -146,7 +145,7 @@ func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
args := []string{"var1", "val1"}
req := defaultDispatchReq(b, args...)
req.runConfig.Env = []string{"var1=old", "var2=fromenv"}
req.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
err := env(req)
require.NoError(t, err)
@ -154,16 +153,17 @@ func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
fmt.Sprintf("%s=%s", args[0], args[1]),
"var2=fromenv",
}
assert.Equal(t, expected, req.runConfig.Env)
assert.Equal(t, expected, req.state.runConfig.Env)
}
func TestMaintainer(t *testing.T) {
maintainerEntry := "Some Maintainer <maintainer@example.com>"
b := newBuilderWithMockBackend()
err := maintainer(defaultDispatchReq(b, maintainerEntry))
req := defaultDispatchReq(b, maintainerEntry)
err := maintainer(req)
require.NoError(t, err)
assert.Equal(t, maintainerEntry, b.maintainer)
assert.Equal(t, maintainerEntry, req.state.maintainer)
}
func TestLabel(t *testing.T) {
@ -176,13 +176,14 @@ func TestLabel(t *testing.T) {
err := label(req)
require.NoError(t, err)
require.Contains(t, req.runConfig.Labels, labelName)
assert.Equal(t, req.runConfig.Labels[labelName], labelValue)
require.Contains(t, req.state.runConfig.Labels, labelName)
assert.Equal(t, req.state.runConfig.Labels[labelName], labelValue)
}
func TestFromScratch(t *testing.T) {
b := newBuilderWithMockBackend()
err := from(defaultDispatchReq(b, "scratch"))
req := defaultDispatchReq(b, "scratch")
err := from(req)
if runtime.GOOS == "windows" {
assert.EqualError(t, err, "Windows does not support FROM scratch")
@ -190,8 +191,8 @@ func TestFromScratch(t *testing.T) {
}
require.NoError(t, err)
assert.Equal(t, "", b.image)
assert.Equal(t, true, b.noBaseImage)
assert.Equal(t, "", req.state.imageID)
assert.Equal(t, true, req.state.noBaseImage)
}
func TestFromWithArg(t *testing.T) {
@ -205,11 +206,12 @@ func TestFromWithArg(t *testing.T) {
b.docker.(*MockBackend).getImageOnBuildFunc = getImage
require.NoError(t, arg(defaultDispatchReq(b, "THETAG="+tag)))
err := from(defaultDispatchReq(b, "alpine${THETAG}"))
req := defaultDispatchReq(b, "alpine${THETAG}")
err := from(req)
require.NoError(t, err)
assert.Equal(t, expected, b.image)
assert.Equal(t, expected, b.from.ImageID())
assert.Equal(t, expected, req.state.imageID)
assert.Equal(t, expected, req.state.baseImage.ImageID())
assert.Len(t, b.buildArgs.GetAllAllowed(), 0)
assert.Len(t, b.buildArgs.GetAllMeta(), 1)
}
@ -225,9 +227,10 @@ func TestFromWithUndefinedArg(t *testing.T) {
b.docker.(*MockBackend).getImageOnBuildFunc = getImage
b.options.BuildArgs = map[string]*string{"THETAG": &tag}
err := from(defaultDispatchReq(b, "alpine${THETAG}"))
req := defaultDispatchReq(b, "alpine${THETAG}")
err := from(req)
require.NoError(t, err)
assert.Equal(t, expected, b.image)
assert.Equal(t, expected, req.state.imageID)
}
func TestOnbuildIllegalTriggers(t *testing.T) {
@ -249,11 +252,11 @@ func TestOnbuild(t *testing.T) {
req := defaultDispatchReq(b, "ADD", ".", "/app/src")
req.original = "ONBUILD ADD . /app/src"
req.runConfig = &container.Config{}
req.state.runConfig = &container.Config{}
err := onbuild(req)
require.NoError(t, err)
assert.Equal(t, "ADD . /app/src", req.runConfig.OnBuild[0])
assert.Equal(t, "ADD . /app/src", req.state.runConfig.OnBuild[0])
}
func TestWorkdir(t *testing.T) {
@ -266,7 +269,7 @@ func TestWorkdir(t *testing.T) {
req := defaultDispatchReq(b, workingDir)
err := workdir(req)
require.NoError(t, err)
assert.Equal(t, workingDir, req.runConfig.WorkingDir)
assert.Equal(t, workingDir, req.state.runConfig.WorkingDir)
}
func TestCmd(t *testing.T) {
@ -284,8 +287,8 @@ func TestCmd(t *testing.T) {
expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
}
assert.Equal(t, expectedCommand, req.runConfig.Cmd)
assert.True(t, b.cmdSet)
assert.Equal(t, expectedCommand, req.state.runConfig.Cmd)
assert.True(t, req.state.cmdSet)
}
func TestHealthcheckNone(t *testing.T) {
@ -295,8 +298,8 @@ func TestHealthcheckNone(t *testing.T) {
err := healthcheck(req)
require.NoError(t, err)
require.NotNil(t, req.runConfig.Healthcheck)
assert.Equal(t, []string{"NONE"}, req.runConfig.Healthcheck.Test)
require.NotNil(t, req.state.runConfig.Healthcheck)
assert.Equal(t, []string{"NONE"}, req.state.runConfig.Healthcheck.Test)
}
func TestHealthcheckCmd(t *testing.T) {
@ -307,9 +310,9 @@ func TestHealthcheckCmd(t *testing.T) {
err := healthcheck(req)
require.NoError(t, err)
require.NotNil(t, req.runConfig.Healthcheck)
require.NotNil(t, req.state.runConfig.Healthcheck)
expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
assert.Equal(t, expectedTest, req.runConfig.Healthcheck.Test)
assert.Equal(t, expectedTest, req.state.runConfig.Healthcheck.Test)
}
func TestEntrypoint(t *testing.T) {
@ -319,7 +322,7 @@ func TestEntrypoint(t *testing.T) {
req := defaultDispatchReq(b, entrypointCmd)
err := entrypoint(req)
require.NoError(t, err)
require.NotNil(t, req.runConfig.Entrypoint)
require.NotNil(t, req.state.runConfig.Entrypoint)
var expectedEntrypoint strslice.StrSlice
if runtime.GOOS == "windows" {
@ -327,7 +330,7 @@ func TestEntrypoint(t *testing.T) {
} else {
expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
}
assert.Equal(t, expectedEntrypoint, req.runConfig.Entrypoint)
assert.Equal(t, expectedEntrypoint, req.state.runConfig.Entrypoint)
}
func TestExpose(t *testing.T) {
@ -338,12 +341,12 @@ func TestExpose(t *testing.T) {
err := expose(req)
require.NoError(t, err)
require.NotNil(t, req.runConfig.ExposedPorts)
require.Len(t, req.runConfig.ExposedPorts, 1)
require.NotNil(t, req.state.runConfig.ExposedPorts)
require.Len(t, req.state.runConfig.ExposedPorts, 1)
portsMapping, err := nat.ParsePortSpec(exposedPort)
require.NoError(t, err)
assert.Contains(t, req.runConfig.ExposedPorts, portsMapping[0].Port)
assert.Contains(t, req.state.runConfig.ExposedPorts, portsMapping[0].Port)
}
func TestUser(t *testing.T) {
@ -353,7 +356,7 @@ func TestUser(t *testing.T) {
req := defaultDispatchReq(b, userCommand)
err := user(req)
require.NoError(t, err)
assert.Equal(t, userCommand, req.runConfig.User)
assert.Equal(t, userCommand, req.state.runConfig.User)
}
func TestVolume(t *testing.T) {
@ -365,9 +368,9 @@ func TestVolume(t *testing.T) {
err := volume(req)
require.NoError(t, err)
require.NotNil(t, req.runConfig.Volumes)
assert.Len(t, req.runConfig.Volumes, 1)
assert.Contains(t, req.runConfig.Volumes, exposedVolume)
require.NotNil(t, req.state.runConfig.Volumes)
assert.Len(t, req.state.runConfig.Volumes, 1)
assert.Contains(t, req.state.runConfig.Volumes, exposedVolume)
}
func TestStopSignal(t *testing.T) {
@ -377,7 +380,7 @@ func TestStopSignal(t *testing.T) {
req := defaultDispatchReq(b, signal)
err := stopSignal(req)
require.NoError(t, err)
assert.Equal(t, signal, req.runConfig.StopSignal)
assert.Equal(t, signal, req.state.runConfig.StopSignal)
}
func TestArg(t *testing.T) {
@ -405,7 +408,7 @@ func TestShell(t *testing.T) {
require.NoError(t, err)
expectedShell := strslice.StrSlice([]string{shellCmd})
assert.Equal(t, expectedShell, req.runConfig.Shell)
assert.Equal(t, expectedShell, req.state.runConfig.Shell)
}
func TestParseOptInterval(t *testing.T) {
@ -439,8 +442,9 @@ func TestRunWithBuildArgs(t *testing.T) {
b.buildArgs.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
b.disableCommit = false
runConfig := &container.Config{}
origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
cmdWithShell := strslice.StrSlice(append(getShell(b.runConfig), "echo foo"))
cmdWithShell := strslice.StrSlice(append(getShell(runConfig), "echo foo"))
envVars := []string{"|1", "one=two"}
cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
@ -477,12 +481,10 @@ func TestRunWithBuildArgs(t *testing.T) {
req := defaultDispatchReq(b, "abcdef")
require.NoError(t, from(req))
b.buildArgs.AddArg("one", strPtr("two"))
// TODO: this can be removed with b.runConfig
req.runConfig.Cmd = origCmd
req.args = []string{"echo foo"}
require.NoError(t, run(req))
// Check that runConfig.Cmd has not been modified by run
assert.Equal(t, origCmd, b.runConfig.Cmd)
assert.Equal(t, origCmd, req.state.runConfig.Cmd)
}

View file

@ -25,6 +25,7 @@ import (
"strings"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/builder"
"github.com/docker/docker/builder/dockerfile/command"
"github.com/docker/docker/builder/dockerfile/parser"
"github.com/docker/docker/runconfig/opts"
@ -64,19 +65,19 @@ type dispatchRequest struct {
attributes map[string]bool
flags *BFlags
original string
runConfig *container.Config
shlex *ShellLex
state *dispatchState
}
func newDispatchRequestFromNode(node *parser.Node, builder *Builder, args []string, shlex *ShellLex) dispatchRequest {
func newDispatchRequestFromOptions(options dispatchOptions, builder *Builder, args []string) dispatchRequest {
return dispatchRequest{
builder: builder,
args: args,
attributes: node.Attributes,
original: node.Original,
flags: NewBFlagsWithArgs(node.Flags),
runConfig: builder.runConfig,
shlex: shlex,
attributes: options.node.Attributes,
original: options.node.Original,
flags: NewBFlagsWithArgs(options.node.Flags),
shlex: options.shlex,
state: options.state,
}
}
@ -107,6 +108,10 @@ func init() {
}
}
func formatStep(stepN int, stepTotal int) string {
return fmt.Sprintf("%d/%d", stepN+1, stepTotal)
}
// This method is the entrypoint to all statement handling routines.
//
// Almost all nodes will have this structure:
@ -121,117 +126,144 @@ func init() {
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
// deal with that, at least until it becomes more of a general concern with new
// features.
func (b *Builder) dispatch(stepN int, stepTotal int, node *parser.Node, shlex *ShellLex) error {
func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
node := options.node
cmd := node.Value
upperCasedCmd := strings.ToUpper(cmd)
// To ensure the user is given a decent error message if the platform
// on which the daemon is running does not support a builder command.
if err := platformSupports(strings.ToLower(cmd)); err != nil {
return err
return nil, err
}
strList := []string{}
msg := bytes.NewBufferString(fmt.Sprintf("Step %d/%d : %s", stepN+1, stepTotal, upperCasedCmd))
if len(node.Flags) > 0 {
msg.WriteString(strings.Join(node.Flags, " "))
}
msg := bytes.NewBufferString(fmt.Sprintf("Step %s : %s%s",
options.stepMsg, upperCasedCmd, formatFlags(node.Flags)))
args := []string{}
ast := node
if cmd == "onbuild" {
if ast.Next == nil {
return errors.New("ONBUILD requires at least one argument")
}
ast = ast.Next.Children[0]
strList = append(strList, ast.Value)
msg.WriteString(" " + ast.Value)
if len(ast.Flags) > 0 {
msg.WriteString(" " + strings.Join(ast.Flags, " "))
}
}
msgList := initMsgList(ast)
// Append build args to runConfig environment variables
envs := append(b.runConfig.Env, b.buildArgsWithoutConfigEnv()...)
processFunc := getProcessFunc(shlex, cmd)
for i := 0; ast.Next != nil; i++ {
ast = ast.Next
words, err := processFunc(ast.Value, envs)
if cmd == command.Onbuild {
var err error
ast, args, err = handleOnBuildNode(node, msg)
if err != nil {
return err
return nil, err
}
strList = append(strList, words...)
msgList[i] = ast.Value
}
msg.WriteString(" " + strings.Join(msgList, " "))
runConfigEnv := options.state.runConfig.Env
envs := append(runConfigEnv, b.buildArgs.FilterAllowed(runConfigEnv)...)
processFunc := createProcessWordFunc(options.shlex, cmd, envs)
words, err := getDispatchArgsFromNode(ast, processFunc, msg)
if err != nil {
return nil, err
}
args = append(args, words...)
fmt.Fprintln(b.Stdout, msg.String())
// XXX yes, we skip any cmds that are not valid; the parser should have
// picked these out already.
if f, ok := evaluateTable[cmd]; ok {
if err := f(newDispatchRequestFromNode(node, b, strList, shlex)); err != nil {
return err
f, ok := evaluateTable[cmd]
if !ok {
return nil, fmt.Errorf("unknown instruction: %s", upperCasedCmd)
}
// TODO: return an object instead of setting things on builder
// If the step created a new image set it as the imageID for the
// current runConfig
b.runConfig.Image = b.image
return nil
if err := f(newDispatchRequestFromOptions(options, b, args)); err != nil {
return nil, err
}
options.state.updateRunConfig()
return options.state, nil
}
return fmt.Errorf("Unknown instruction: %s", upperCasedCmd)
type dispatchOptions struct {
state *dispatchState
stepMsg string
node *parser.Node
shlex *ShellLex
}
// count the number of nodes that we are going to traverse first
// allocation of those list a lot when they have a lot of arguments
func initMsgList(cursor *parser.Node) []string {
var n int
for ; cursor.Next != nil; n++ {
cursor = cursor.Next
}
return make([]string, n)
// dispatchState is a data object which is modified by dispatchers
type dispatchState struct {
runConfig *container.Config
maintainer string
cmdSet bool
noBaseImage bool
imageID string
baseImage builder.Image
stageName string
}
type processFunc func(string, []string) ([]string, error)
func newDispatchState() *dispatchState {
return &dispatchState{runConfig: &container.Config{}}
}
func getProcessFunc(shlex *ShellLex, cmd string) processFunc {
func (r *dispatchState) updateRunConfig() {
r.runConfig.Image = r.imageID
}
// hasFromImage returns true if the builder has processed a `FROM <image>` line
func (r *dispatchState) hasFromImage() bool {
return r.imageID != "" || r.noBaseImage
}
func (r *dispatchState) runConfigEnvMapping() map[string]string {
return opts.ConvertKVStringsToMap(r.runConfig.Env)
}
func (r *dispatchState) isCurrentStage(target string) bool {
if target == "" {
return false
}
return strings.EqualFold(r.stageName, target)
}
func handleOnBuildNode(ast *parser.Node, msg *bytes.Buffer) (*parser.Node, []string, error) {
if ast.Next == nil {
return nil, nil, errors.New("ONBUILD requires at least one argument")
}
ast = ast.Next.Children[0]
msg.WriteString(" " + ast.Value + formatFlags(ast.Flags))
return ast, []string{ast.Value}, nil
}
func formatFlags(flags []string) string {
if len(flags) > 0 {
return " " + strings.Join(flags, " ")
}
return ""
}
func getDispatchArgsFromNode(ast *parser.Node, processFunc processWordFunc, msg *bytes.Buffer) ([]string, error) {
args := []string{}
for i := 0; ast.Next != nil; i++ {
ast = ast.Next
words, err := processFunc(ast.Value)
if err != nil {
return nil, err
}
args = append(args, words...)
msg.WriteString(" " + ast.Value)
}
return args, nil
}
type processWordFunc func(string) ([]string, error)
func createProcessWordFunc(shlex *ShellLex, cmd string, envs []string) processWordFunc {
switch {
case !replaceEnvAllowed[cmd]:
return func(word string, _ []string) ([]string, error) {
return func(word string) ([]string, error) {
return []string{word}, nil
}
case allowWordExpansion[cmd]:
return shlex.ProcessWords
return func(word string) ([]string, error) {
return shlex.ProcessWords(word, envs)
}
default:
return func(word string, envs []string) ([]string, error) {
return func(word string) ([]string, error) {
word, err := shlex.ProcessWord(word, envs)
return []string{word}, err
}
}
}
// buildArgsWithoutConfigEnv returns a list of key=value pairs for all the build
// args that are not overriden by runConfig environment variables.
func (b *Builder) buildArgsWithoutConfigEnv() []string {
envs := []string{}
configEnv := b.runConfigEnvMapping()
for key, val := range b.buildArgs.GetAllAllowed() {
if _, ok := configEnv[key]; !ok {
envs = append(envs, fmt.Sprintf("%s=%s", key, val))
}
}
return envs
}
func (b *Builder) runConfigEnvMapping() map[string]string {
return opts.ConvertKVStringsToMap(b.runConfig.Env)
}
// checkDispatch does a simple check for syntax errors of the Dockerfile.
// Because some of the instructions can only be validated through runtime,
// arg, env, etc., this syntax check will not be complete and could not replace

View file

@ -123,7 +123,7 @@ func initDispatchTestCases() []dispatchTestCase {
{
name: "Invalid instruction",
dockerfile: `foo bar`,
expectedError: "Unknown instruction: FOO",
expectedError: "unknown instruction: FOO",
files: nil,
}}
@ -177,13 +177,11 @@ func executeTestCase(t *testing.T, testCase dispatchTestCase) {
t.Fatalf("Error when parsing Dockerfile: %s", err)
}
config := &container.Config{}
options := &types.ImageBuildOptions{
BuildArgs: make(map[string]*string),
}
b := &Builder{
runConfig: config,
options: options,
Stdout: ioutil.Discard,
source: context,
@ -192,7 +190,14 @@ func executeTestCase(t *testing.T, testCase dispatchTestCase) {
shlex := NewShellLex(parser.DefaultEscapeToken)
n := result.AST
err = b.dispatch(0, len(n.Children), n.Children[0], shlex)
state := &dispatchState{runConfig: &container.Config{}}
opts := dispatchOptions{
state: state,
stepMsg: formatStep(0, len(n.Children)),
node: n.Children[0],
shlex: shlex,
}
state, err = b.dispatch(opts)
if err == nil {
t.Fatalf("No error when executing test %s", testCase.name)

View file

@ -23,7 +23,6 @@ type imageContexts struct {
list []*imageMount
byName map[string]*imageMount
cache pathCache
currentName string
}
func (ic *imageContexts) newImageMount(id string) *imageMount {
@ -41,7 +40,6 @@ func (ic *imageContexts) add(name string) (*imageMount, error) {
}
ic.byName[name] = im
}
ic.currentName = name
ic.list = append(ic.list, im)
return im, nil
}
@ -96,13 +94,6 @@ func (ic *imageContexts) unmount() (retErr error) {
return
}
func (ic *imageContexts) isCurrentTarget(target string) bool {
if target == "" {
return false
}
return strings.EqualFold(ic.currentName, target)
}
func (ic *imageContexts) getCache(id, path string) (interface{}, bool) {
if ic.cache != nil {
if id == "" {

View file

@ -35,16 +35,16 @@ import (
"github.com/pkg/errors"
)
func (b *Builder) commit(comment string) error {
func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
if b.disableCommit {
return nil
}
if !b.hasFromImage() {
if !dispatchState.hasFromImage() {
return errors.New("Please provide a source image with `from` prior to commit")
}
runConfigWithCommentCmd := copyRunConfig(b.runConfig, withCmdComment(comment))
hit, err := b.probeCache(b.image, runConfigWithCommentCmd)
runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment))
hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
if err != nil || hit {
return err
}
@ -53,20 +53,21 @@ func (b *Builder) commit(comment string) error {
return err
}
return b.commitContainer(id, runConfigWithCommentCmd)
return b.commitContainer(dispatchState, id, runConfigWithCommentCmd)
}
func (b *Builder) commitContainer(id string, containerConfig *container.Config) error {
// TODO: see if any args can be dropped
func (b *Builder) commitContainer(dispatchState *dispatchState, id string, containerConfig *container.Config) error {
if b.disableCommit {
return nil
}
commitCfg := &backend.ContainerCommitConfig{
ContainerCommitConfig: types.ContainerCommitConfig{
Author: b.maintainer,
Author: dispatchState.maintainer,
Pause: true,
// TODO: this should be done by Commit()
Config: copyRunConfig(b.runConfig),
Config: copyRunConfig(dispatchState.runConfig),
},
ContainerConfig: containerConfig,
}
@ -77,10 +78,8 @@ func (b *Builder) commitContainer(id string, containerConfig *container.Config)
return err
}
// TODO: this function should return imageID and runConfig instead of setting
// then on the builder
b.image = imageID
b.imageContexts.update(imageID, b.runConfig)
dispatchState.imageID = imageID
b.imageContexts.update(imageID, dispatchState.runConfig)
return nil
}
@ -91,7 +90,9 @@ type copyInfo struct {
decompress bool
}
func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string, imageSource *imageMount) error {
// TODO: this needs to be split so that a Builder method doesn't accept req
func (b *Builder) runContextCommand(req dispatchRequest, allowRemote bool, allowLocalDecompression bool, cmdName string, imageSource *imageMount) error {
args := req.args
if len(args) < 2 {
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
}
@ -163,9 +164,9 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
// TODO: should this have been using origPaths instead of srcHash in the comment?
runConfigWithCommentCmd := copyRunConfig(
b.runConfig,
req.state.runConfig,
withCmdCommentString(fmt.Sprintf("%s %s in %s ", cmdName, srcHash, dest)))
if hit, err := b.probeCache(b.image, runConfigWithCommentCmd); err != nil || hit {
if hit, err := b.probeCache(req.state, runConfigWithCommentCmd); err != nil || hit {
return err
}
@ -181,7 +182,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
// Twiddle the destination when it's a relative path - meaning, make it
// relative to the WORKINGDIR
if dest, err = normaliseDest(cmdName, b.runConfig.WorkingDir, dest); err != nil {
if dest, err = normaliseDest(cmdName, req.state.runConfig.WorkingDir, dest); err != nil {
return err
}
@ -191,7 +192,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
}
}
return b.commitContainer(container.ID, runConfigWithCommentCmd)
return b.commitContainer(req.state, container.ID, runConfigWithCommentCmd)
}
type runConfigModifier func(*container.Config)
@ -479,20 +480,20 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression
return copyInfos, nil
}
func (b *Builder) processImageFrom(img builder.Image) error {
func (b *Builder) processImageFrom(dispatchState *dispatchState, img builder.Image) error {
if img != nil {
b.image = img.ImageID()
dispatchState.imageID = img.ImageID()
if img.RunConfig() != nil {
b.runConfig = img.RunConfig()
dispatchState.runConfig = img.RunConfig()
}
}
// Check to see if we have a default PATH, note that windows won't
// have one as it's set by HCS
if system.DefaultPathEnv != "" {
if _, ok := b.runConfigEnvMapping()["PATH"]; !ok {
b.runConfig.Env = append(b.runConfig.Env,
if _, ok := dispatchState.runConfigEnvMapping()["PATH"]; !ok {
dispatchState.runConfig.Env = append(dispatchState.runConfig.Env,
"PATH="+system.DefaultPathEnv)
}
}
@ -503,7 +504,7 @@ func (b *Builder) processImageFrom(img builder.Image) error {
}
// Process ONBUILD triggers if they exist
if nTriggers := len(b.runConfig.OnBuild); nTriggers != 0 {
if nTriggers := len(dispatchState.runConfig.OnBuild); nTriggers != 0 {
word := "trigger"
if nTriggers > 1 {
word = "triggers"
@ -512,21 +513,21 @@ func (b *Builder) processImageFrom(img builder.Image) error {
}
// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
onBuildTriggers := b.runConfig.OnBuild
b.runConfig.OnBuild = []string{}
onBuildTriggers := dispatchState.runConfig.OnBuild
dispatchState.runConfig.OnBuild = []string{}
// Reset stdin settings as all build actions run without stdin
b.runConfig.OpenStdin = false
b.runConfig.StdinOnce = false
dispatchState.runConfig.OpenStdin = false
dispatchState.runConfig.StdinOnce = false
// parse the ONBUILD triggers by invoking the parser
for _, step := range onBuildTriggers {
result, err := parser.Parse(strings.NewReader(step))
dockerfile, err := parser.Parse(strings.NewReader(step))
if err != nil {
return err
}
for _, n := range result.AST.Children {
for _, n := range dockerfile.AST.Children {
if err := checkDispatch(n); err != nil {
return err
}
@ -540,7 +541,7 @@ func (b *Builder) processImageFrom(img builder.Image) error {
}
}
if err := dispatchFromDockerfile(b, result); err != nil {
if _, err := dispatchFromDockerfile(b, dockerfile, dispatchState); err != nil {
return err
}
}
@ -551,12 +552,12 @@ func (b *Builder) processImageFrom(img builder.Image) error {
// If an image is found, probeCache returns `(true, nil)`.
// If no image is found, it returns `(false, nil)`.
// If there is any error, it returns `(false, err)`.
func (b *Builder) probeCache(parentID string, runConfig *container.Config) (bool, error) {
func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) {
c := b.imageCache
if c == nil || b.options.NoCache || b.cacheBusted {
return false, nil
}
cache, err := c.GetCache(parentID, runConfig)
cache, err := c.GetCache(dispatchState.imageID, runConfig)
if err != nil {
return false, err
}
@ -568,16 +569,13 @@ func (b *Builder) probeCache(parentID string, runConfig *container.Config) (bool
fmt.Fprint(b.Stdout, " ---> Using cache\n")
logrus.Debugf("[BUILDER] Use cached version: %s", runConfig.Cmd)
b.image = string(cache)
b.imageContexts.update(b.image, runConfig)
dispatchState.imageID = string(cache)
b.imageContexts.update(dispatchState.imageID, runConfig)
return true, nil
}
func (b *Builder) create(runConfig *container.Config) (string, error) {
if !b.hasFromImage() {
return "", errors.New("Please provide a source image with `from` prior to run")
}
resources := container.Resources{
CgroupParent: b.options.CgroupParent,
CPUShares: b.options.CPUShares,