Cleanup all the mutate + defer revert of b.runConfig in the builder

Instead of mutating and reverting, just create a copy and pass the copy
around.

Add a unit test for builder dispatcher.run

Fix two test failures

Fix image history by adding a CreatedBy to commit options. Previously the
createdBy field was being created by modifying a reference to the runConfig that
was held from when the container was created.

Fix a test that expected a trailing slash. Previously the runConfig was being
modified by container create. Now that we're creating a copy of runConfig
instead of sharing a reference the runConfig retains the trailing slash.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2017-04-21 15:08:11 -04:00
parent 73abe0c682
commit 9f738cc574
9 changed files with 183 additions and 102 deletions

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
) )
// ContainerAttachConfig holds the streams to use when connecting to a container to view logs. // ContainerAttachConfig holds the streams to use when connecting to a container to view logs.
@ -97,4 +98,7 @@ type ExecProcessConfig struct {
type ContainerCommitConfig struct { type ContainerCommitConfig struct {
types.ContainerCommitConfig types.ContainerCommitConfig
Changes []string Changes []string
// TODO: ContainerConfig is only used by the dockerfile Builder, so remove it
// once the Builder has been updated to use a different interface
ContainerConfig *container.Config
} }

View File

@ -243,6 +243,10 @@ func (b *Builder) hasFromImage() bool {
// //
// TODO: Remove? // TODO: Remove?
func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) { func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
if len(changes) == 0 {
return config, nil
}
b := newBuilder(context.Background(), builderOptions{}) b := newBuilder(context.Background(), builderOptions{})
result, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) result, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))

View File

@ -315,20 +315,18 @@ func workdir(req dispatchRequest) error {
return nil return nil
} }
cmd := req.runConfig.Cmd // TODO: why is this done here. This seems to be done at random places all over
comment := "WORKDIR " + req.runConfig.WorkingDir // the builder
// reset the command for cache detection req.runConfig.Image = req.builder.image
req.runConfig.Cmd = strslice.StrSlice(append(getShell(req.runConfig), "#(nop) "+comment))
defer func(cmd strslice.StrSlice) { req.runConfig.Cmd = cmd }(cmd)
// TODO: this should pass a copy of runConfig comment := "WORKDIR " + req.runConfig.WorkingDir
if hit, err := req.builder.probeCache(req.builder.image, req.runConfig); err != nil || hit { runConfigWithCommentCmd := copyRunConfig(req.runConfig, withCmdCommentString(comment))
if hit, err := req.builder.probeCache(req.builder.image, runConfigWithCommentCmd); err != nil || hit {
return err return err
} }
req.runConfig.Image = req.builder.image
container, err := req.builder.docker.ContainerCreate(types.ContainerCreateConfig{ container, err := req.builder.docker.ContainerCreate(types.ContainerCreateConfig{
Config: req.runConfig, Config: runConfigWithCommentCmd,
// Set a log config to override any default value set on the daemon // Set a log config to override any default value set on the daemon
HostConfig: &container.HostConfig{LogConfig: defaultLogConfig}, HostConfig: &container.HostConfig{LogConfig: defaultLogConfig},
}) })
@ -340,7 +338,7 @@ func workdir(req dispatchRequest) error {
return err return err
} }
return req.builder.commitContainer(container.ID, copyRunConfig(req.runConfig, withCmd(cmd))) return req.builder.commitContainer(container.ID, runConfigWithCommentCmd)
} }
// RUN some command yo // RUN some command yo
@ -363,79 +361,57 @@ func run(req dispatchRequest) error {
} }
args := handleJSONArgs(req.args, req.attributes) args := handleJSONArgs(req.args, req.attributes)
if !req.attributes["json"] { if !req.attributes["json"] {
args = append(getShell(req.runConfig), args...) args = append(getShell(req.runConfig), args...)
} }
config := &container.Config{ cmdFromArgs := strslice.StrSlice(args)
Cmd: strslice.StrSlice(args), buildArgs := req.builder.buildArgsWithoutConfigEnv()
Image: req.builder.image,
saveCmd := cmdFromArgs
if len(buildArgs) > 0 {
saveCmd = prependEnvOnCmd(req.builder.buildArgs, buildArgs, cmdFromArgs)
} }
// stash the cmd // TODO: this was previously in b.create(), why is it necessary?
cmd := req.runConfig.Cmd req.runConfig.Image = req.builder.image
if len(req.runConfig.Entrypoint) == 0 && len(req.runConfig.Cmd) == 0 {
req.runConfig.Cmd = config.Cmd
}
// stash the config environment runConfigForCacheProbe := copyRunConfig(req.runConfig, withCmd(saveCmd))
env := req.runConfig.Env hit, err := req.builder.probeCache(req.builder.image, runConfigForCacheProbe)
defer func(cmd strslice.StrSlice) { req.runConfig.Cmd = cmd }(cmd)
defer func(env []string) { req.runConfig.Env = env }(env)
cmdBuildEnv := req.builder.buildArgsWithoutConfigEnv()
// derive the command to use for probeCache() and to commit in this container.
// Note that we only do this if there are any build-time env vars. Also, we
// use the special argument "|#" at the start of the args array. This will
// avoid conflicts with any RUN command since commands can not
// start with | (vertical bar). The "#" (number of build envs) is there to
// help ensure proper cache matches. We don't want a RUN command
// that starts with "foo=abc" to be considered part of a build-time env var.
saveCmd := config.Cmd
if len(cmdBuildEnv) > 0 {
saveCmd = prependEnvOnCmd(req.builder.buildArgs, cmdBuildEnv, saveCmd)
}
req.runConfig.Cmd = saveCmd
hit, err := req.builder.probeCache(req.builder.image, req.runConfig)
if err != nil || hit { if err != nil || hit {
return err return err
} }
// set Cmd manually, this is special case only for Dockerfiles runConfig := copyRunConfig(req.runConfig,
req.runConfig.Cmd = config.Cmd withCmd(cmdFromArgs),
// set build-time environment for 'run'. withEnv(append(req.runConfig.Env, buildArgs...)))
req.runConfig.Env = append(req.runConfig.Env, cmdBuildEnv...)
// set config as already being escaped, this prevents double escaping on windows // set config as already being escaped, this prevents double escaping on windows
req.runConfig.ArgsEscaped = true runConfig.ArgsEscaped = true
logrus.Debugf("[BUILDER] Command to be executed: %v", req.runConfig.Cmd) logrus.Debugf("[BUILDER] Command to be executed: %v", runConfig.Cmd)
// TODO: this was previously in b.create(), why is it necessary? cID, err := req.builder.create(runConfig)
req.builder.runConfig.Image = req.builder.image
// TODO: should pass a copy of runConfig
cID, err := req.builder.create(req.runConfig)
if err != nil { if err != nil {
return err return err
} }
if err := req.builder.run(cID, runConfig.Cmd); err != nil {
if err := req.builder.run(cID); err != nil {
return err return err
} }
// FIXME: this is duplicated with the defer above in this function (i think?) return req.builder.commitContainer(cID, runConfigForCacheProbe)
// revert to original config environment and set the command string to
// have the build-time env vars in it (if any) so that future cache look-ups
// properly match it.
req.runConfig.Env = env
req.runConfig.Cmd = saveCmd
return req.builder.commitContainer(cID, copyRunConfig(req.runConfig, withCmd(cmd)))
} }
// Derive the command to use for probeCache() and to commit in this container.
// Note that we only do this if there are any build-time env vars. Also, we
// use the special argument "|#" at the start of the args array. This will
// avoid conflicts with any RUN command since commands can not
// start with | (vertical bar). The "#" (number of build envs) is there to
// help ensure proper cache matches. We don't want a RUN command
// that starts with "foo=abc" to be considered part of a build-time env var.
//
// remove any unreferenced built-in args from the environment variables.
// These args are transparent so resulting image should be the same regardless
// of the value.
func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice { func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice {
var tmpBuildEnv []string var tmpBuildEnv []string
for _, env := range buildArgVars { for _, env := range buildArgVars {

View File

@ -5,7 +5,10 @@ import (
"runtime" "runtime"
"testing" "testing"
"bytes"
"context"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice" "github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/builder" "github.com/docker/docker/builder"
@ -49,6 +52,9 @@ func newBuilderWithMockBackend() *Builder {
options: &types.ImageBuildOptions{}, options: &types.ImageBuildOptions{},
docker: &MockBackend{}, docker: &MockBackend{},
buildArgs: newBuildArgs(make(map[string]*string)), buildArgs: newBuildArgs(make(map[string]*string)),
tmpContainers: make(map[string]struct{}),
Stdout: new(bytes.Buffer),
clientCtx: context.Background(),
disableCommit: true, disableCommit: true,
} }
b.imageContexts = &imageContexts{b: b} b.imageContexts = &imageContexts{b: b}
@ -409,13 +415,71 @@ func TestParseOptInterval(t *testing.T) {
Value: "50ns", Value: "50ns",
} }
_, err := parseOptInterval(flInterval) _, err := parseOptInterval(flInterval)
if err == nil { testutil.ErrorContains(t, err, "cannot be less than 1ms")
t.Fatalf("Error should be presented for interval %s", flInterval.Value)
}
flInterval.Value = "1ms" flInterval.Value = "1ms"
_, err = parseOptInterval(flInterval) _, err = parseOptInterval(flInterval)
if err != nil { require.NoError(t, err)
t.Fatalf("Unexpected error: %s", err.Error()) }
}
func TestPrependEnvOnCmd(t *testing.T) {
buildArgs := newBuildArgs(nil)
buildArgs.AddArg("NO_PROXY", nil)
args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
cmd := []string{"foo", "bar"}
cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
expected := strslice.StrSlice([]string{
"|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
assert.Equal(t, expected, cmdWithEnv)
}
func TestRunWithBuildArgs(t *testing.T) {
b := newBuilderWithMockBackend()
b.buildArgs.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
b.disableCommit = false
origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
cmdWithShell := strslice.StrSlice(append(getShell(b.runConfig), "echo foo"))
envVars := []string{"|1", "one=two"}
cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
imageCache := &mockImageCache{
getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
// Check the runConfig.Cmd sent to probeCache()
assert.Equal(t, cachedCmd, cfg.Cmd)
return "", nil
},
}
b.imageCache = imageCache
mockBackend := b.docker.(*MockBackend)
mockBackend.getImageOnBuildImage = &mockImage{
id: "abcdef",
config: &container.Config{Cmd: origCmd},
}
mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
// Check the runConfig.Cmd sent to create()
assert.Equal(t, cmdWithShell, config.Config.Cmd)
assert.Contains(t, config.Config.Env, "one=two")
return container.ContainerCreateCreatedBody{ID: "12345"}, nil
}
mockBackend.commitFunc = func(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
// Check the runConfig.Cmd sent to commit()
assert.Equal(t, origCmd, cfg.Config.Cmd)
assert.Equal(t, cachedCmd, cfg.ContainerConfig.Cmd)
return "", nil
}
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)
} }

View File

@ -21,7 +21,6 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend" "github.com/docker/docker/api/types/backend"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/builder" "github.com/docker/docker/builder"
"github.com/docker/docker/builder/dockerfile/parser" "github.com/docker/docker/builder/dockerfile/parser"
"github.com/docker/docker/builder/remotecontext" "github.com/docker/docker/builder/remotecontext"
@ -56,10 +55,10 @@ func (b *Builder) commit(comment string) error {
return err return err
} }
return b.commitContainer(id, b.runConfig) return b.commitContainer(id, runConfigWithCommentCmd)
} }
func (b *Builder) commitContainer(id string, runConfig *container.Config) error { func (b *Builder) commitContainer(id string, containerConfig *container.Config) error {
if b.disableCommit { if b.disableCommit {
return nil return nil
} }
@ -68,8 +67,9 @@ func (b *Builder) commitContainer(id string, runConfig *container.Config) error
ContainerCommitConfig: types.ContainerCommitConfig{ ContainerCommitConfig: types.ContainerCommitConfig{
Author: b.maintainer, Author: b.maintainer,
Pause: true, Pause: true,
Config: runConfig, Config: b.runConfig,
}, },
ContainerConfig: containerConfig,
} }
// Commit the container // Commit the container
@ -81,7 +81,7 @@ func (b *Builder) commitContainer(id string, runConfig *container.Config) error
// TODO: this function should return imageID and runConfig instead of setting // TODO: this function should return imageID and runConfig instead of setting
// then on the builder // then on the builder
b.image = imageID b.image = imageID
b.imageContexts.update(imageID, runConfig) b.imageContexts.update(imageID, b.runConfig)
return nil return nil
} }
@ -100,6 +100,8 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
// Work in daemon-specific filepath semantics // Work in daemon-specific filepath semantics
dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest
// TODO: why is this done here. This seems to be done at random places all over
// the builder
b.runConfig.Image = b.image b.runConfig.Image = b.image
var infos []copyInfo var infos []copyInfo
@ -164,18 +166,16 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil)) srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil))
} }
cmd := b.runConfig.Cmd
// TODO: should this have been using origPaths instead of srcHash in the comment? // TODO: should this have been using origPaths instead of srcHash in the comment?
b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) %s %s in %s ", cmdName, srcHash, dest))) runConfigWithCommentCmd := copyRunConfig(
defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd) b.runConfig,
withCmdCommentString(fmt.Sprintf("%s %s in %s ", cmdName, srcHash, dest)))
// TODO: this should pass a copy of runConfig if hit, err := b.probeCache(b.image, runConfigWithCommentCmd); err != nil || hit {
if hit, err := b.probeCache(b.image, b.runConfig); err != nil || hit {
return err return err
} }
container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{ container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
Config: b.runConfig, Config: runConfigWithCommentCmd,
// Set a log config to override any default value set on the daemon // Set a log config to override any default value set on the daemon
HostConfig: &container.HostConfig{LogConfig: defaultLogConfig}, HostConfig: &container.HostConfig{LogConfig: defaultLogConfig},
}) })
@ -196,7 +196,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
} }
} }
return b.commitContainer(container.ID, copyRunConfig(b.runConfig, withCmd(cmd))) return b.commitContainer(container.ID, runConfigWithCommentCmd)
} }
type runConfigModifier func(*container.Config) type runConfigModifier func(*container.Config)
@ -215,12 +215,24 @@ func withCmd(cmd []string) runConfigModifier {
} }
} }
// withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for
// why there are two almost identical versions of this.
func withCmdComment(comment string) runConfigModifier { func withCmdComment(comment string) runConfigModifier {
return func(runConfig *container.Config) { return func(runConfig *container.Config) {
runConfig.Cmd = append(getShell(runConfig), "#(nop) ", comment) runConfig.Cmd = append(getShell(runConfig), "#(nop) ", comment)
} }
} }
// withCmdCommentString exists to maintain compatibility with older versions.
// A few instructions (workdir, copy, add) used a nop comment that is a single arg
// where as all the other instructions used a two arg comment string. This
// function implements the single arg version.
func withCmdCommentString(comment string) runConfigModifier {
return func(runConfig *container.Config) {
runConfig.Cmd = append(getShell(runConfig), "#(nop) "+comment)
}
}
func withEnv(env []string) runConfigModifier { func withEnv(env []string) runConfigModifier {
return func(runConfig *container.Config) { return func(runConfig *container.Config) {
runConfig.Env = env runConfig.Env = env
@ -606,7 +618,7 @@ func (b *Builder) create(runConfig *container.Config) (string, error) {
var errCancelled = errors.New("build cancelled") var errCancelled = errors.New("build cancelled")
func (b *Builder) run(cID string) (err error) { func (b *Builder) run(cID string, cmd []string) (err error) {
attached := make(chan struct{}) attached := make(chan struct{})
errCh := make(chan error) errCh := make(chan error)
go func() { go func() {
@ -660,7 +672,7 @@ func (b *Builder) run(cID string) (err error) {
} }
// TODO: change error type, because jsonmessage.JSONError assumes HTTP // TODO: change error type, because jsonmessage.JSONError assumes HTTP
return &jsonmessage.JSONError{ return &jsonmessage.JSONError{
Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(b.runConfig.Cmd, " "), ret), Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(cmd, " "), ret),
Code: ret, Code: ret,
} }
} }

View File

@ -15,13 +15,19 @@ import (
// MockBackend implements the builder.Backend interface for unit testing // MockBackend implements the builder.Backend interface for unit testing
type MockBackend struct { type MockBackend struct {
getImageOnBuildFunc func(string) (builder.Image, error) getImageOnBuildFunc func(string) (builder.Image, error)
getImageOnBuildImage *mockImage
containerCreateFunc func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
commitFunc func(string, *backend.ContainerCommitConfig) (string, error)
} }
func (m *MockBackend) GetImageOnBuild(name string) (builder.Image, error) { func (m *MockBackend) GetImageOnBuild(name string) (builder.Image, error) {
if m.getImageOnBuildFunc != nil { if m.getImageOnBuildFunc != nil {
return m.getImageOnBuildFunc(name) return m.getImageOnBuildFunc(name)
} }
if m.getImageOnBuildImage != nil {
return m.getImageOnBuildImage, nil
}
return &mockImage{id: "theid"}, nil return &mockImage{id: "theid"}, nil
} }
@ -38,6 +44,9 @@ func (m *MockBackend) ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout
} }
func (m *MockBackend) ContainerCreate(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) { func (m *MockBackend) ContainerCreate(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
if m.containerCreateFunc != nil {
return m.containerCreateFunc(config)
}
return container.ContainerCreateCreatedBody{}, nil return container.ContainerCreateCreatedBody{}, nil
} }
@ -45,7 +54,10 @@ func (m *MockBackend) ContainerRm(name string, config *types.ContainerRmConfig)
return nil return nil
} }
func (m *MockBackend) Commit(string, *backend.ContainerCommitConfig) (string, error) { func (m *MockBackend) Commit(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
if m.commitFunc != nil {
return m.commitFunc(cID, cfg)
}
return "", nil return "", nil
} }
@ -97,3 +109,14 @@ func (i *mockImage) ImageID() string {
func (i *mockImage) RunConfig() *container.Config { func (i *mockImage) RunConfig() *container.Config {
return i.config return i.config
} }
type mockImageCache struct {
getCacheFunc func(parentID string, cfg *container.Config) (string, error)
}
func (mic *mockImageCache) GetCache(parentID string, cfg *container.Config) (string, error) {
if mic.getCacheFunc != nil {
return mic.getCacheFunc(parentID, cfg)
}
return "", nil
}

View File

@ -129,6 +129,11 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
return "", err return "", err
} }
containerConfig := c.ContainerConfig
if containerConfig == nil {
containerConfig = container.Config
}
// It is not possible to commit a running container on Windows and on Solaris. // It is not possible to commit a running container on Windows and on Solaris.
if (runtime.GOOS == "windows" || runtime.GOOS == "solaris") && container.IsRunning() { if (runtime.GOOS == "windows" || runtime.GOOS == "solaris") && container.IsRunning() {
return "", errors.Errorf("%+v does not support commit of a running container", runtime.GOOS) return "", errors.Errorf("%+v does not support commit of a running container", runtime.GOOS)
@ -185,7 +190,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
h := image.History{ h := image.History{
Author: c.Author, Author: c.Author,
Created: time.Now().UTC(), Created: time.Now().UTC(),
CreatedBy: strings.Join(container.Config.Cmd, " "), CreatedBy: strings.Join(containerConfig.Cmd, " "),
Comment: c.Comment, Comment: c.Comment,
EmptyLayer: true, EmptyLayer: true,
} }
@ -204,7 +209,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
Architecture: runtime.GOARCH, Architecture: runtime.GOARCH,
OS: runtime.GOOS, OS: runtime.GOOS,
Container: container.ID, Container: container.ID,
ContainerConfig: *container.Config, ContainerConfig: *containerConfig,
Author: c.Author, Author: c.Author,
Created: h.Created, Created: h.Created,
}, },

View File

@ -1,6 +1,8 @@
package cache package cache
import "github.com/docker/docker/api/types/container" import (
"github.com/docker/docker/api/types/container"
)
// compare two Config struct. Do not compare the "Image" nor "Hostname" fields // compare two Config struct. Do not compare the "Image" nor "Hostname" fields
// If OpenStdin is set, then it differs // If OpenStdin is set, then it differs

View File

@ -345,11 +345,8 @@ ONBUILD RUN ["true"]`))
buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf(`FROM %s`, name1))) buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf(`FROM %s`, name1)))
out, _ := dockerCmd(c, "run", name2) result := cli.DockerCmd(c, "run", name2)
if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) { result.Assert(c, icmd.Expected{Out: "hello world"})
c.Fatalf("did not get echo output from onbuild. Got: %q", out)
}
} }
// FIXME(vdemeester) why we disabled cache here ? // FIXME(vdemeester) why we disabled cache here ?
@ -4376,12 +4373,8 @@ func (s *DockerSuite) TestBuildTimeArgHistoryExclusions(c *check.C) {
if strings.Contains(out, "https_proxy") { if strings.Contains(out, "https_proxy") {
c.Fatalf("failed to exclude proxy settings from history!") c.Fatalf("failed to exclude proxy settings from history!")
} }
if !strings.Contains(out, fmt.Sprintf("%s=%s", envKey, envVal)) { result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", envKey, envVal)})
c.Fatalf("explicitly defined ARG %s is not in output", explicitProxyKey) result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", explicitProxyKey, explicitProxyVal)})
}
if !strings.Contains(out, fmt.Sprintf("%s=%s", envKey, envVal)) {
c.Fatalf("missing build arguments from output")
}
cacheID := buildImage(imgName + "-two") cacheID := buildImage(imgName + "-two")
c.Assert(origID, checker.Equals, cacheID) c.Assert(origID, checker.Equals, cacheID)
@ -4576,9 +4569,7 @@ func (s *DockerSuite) TestBuildBuildTimeArgExpansion(c *check.C) {
) )
res := inspectField(c, imgName, "Config.WorkingDir") res := inspectField(c, imgName, "Config.WorkingDir")
if res != filepath.ToSlash(filepath.Clean(wdVal)) { c.Check(res, check.Equals, filepath.ToSlash(wdVal))
c.Fatalf("Config.WorkingDir value mismatch. Expected: %s, got: %s", filepath.ToSlash(filepath.Clean(wdVal)), res)
}
var resArr []string var resArr []string
inspectFieldAndUnmarshall(c, imgName, "Config.Env", &resArr) inspectFieldAndUnmarshall(c, imgName, "Config.Env", &resArr)