Browse code

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>

Daniel Nephin authored on 2017/04/22 04:08:11
Showing 9 changed files
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"time"
7 7
 
8 8
 	"github.com/docker/docker/api/types"
9
+	"github.com/docker/docker/api/types/container"
9 10
 )
10 11
 
11 12
 // ContainerAttachConfig holds the streams to use when connecting to a container to view logs.
... ...
@@ -97,4 +98,7 @@ type ExecProcessConfig struct {
97 97
 type ContainerCommitConfig struct {
98 98
 	types.ContainerCommitConfig
99 99
 	Changes []string
100
+	// TODO: ContainerConfig is only used by the dockerfile Builder, so remove it
101
+	// once the Builder has been updated to use a different interface
102
+	ContainerConfig *container.Config
100 103
 }
... ...
@@ -243,6 +243,10 @@ func (b *Builder) hasFromImage() bool {
243 243
 //
244 244
 // TODO: Remove?
245 245
 func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
246
+	if len(changes) == 0 {
247
+		return config, nil
248
+	}
249
+
246 250
 	b := newBuilder(context.Background(), builderOptions{})
247 251
 
248 252
 	result, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
... ...
@@ -315,20 +315,18 @@ func workdir(req dispatchRequest) error {
315 315
 		return nil
316 316
 	}
317 317
 
318
-	cmd := req.runConfig.Cmd
319
-	comment := "WORKDIR " + req.runConfig.WorkingDir
320
-	// reset the command for cache detection
321
-	req.runConfig.Cmd = strslice.StrSlice(append(getShell(req.runConfig), "#(nop) "+comment))
322
-	defer func(cmd strslice.StrSlice) { req.runConfig.Cmd = cmd }(cmd)
318
+	// TODO: why is this done here. This seems to be done at random places all over
319
+	// the builder
320
+	req.runConfig.Image = req.builder.image
323 321
 
324
-	// TODO: this should pass a copy of runConfig
325
-	if hit, err := req.builder.probeCache(req.builder.image, req.runConfig); err != nil || hit {
322
+	comment := "WORKDIR " + req.runConfig.WorkingDir
323
+	runConfigWithCommentCmd := copyRunConfig(req.runConfig, withCmdCommentString(comment))
324
+	if hit, err := req.builder.probeCache(req.builder.image, runConfigWithCommentCmd); err != nil || hit {
326 325
 		return err
327 326
 	}
328 327
 
329
-	req.runConfig.Image = req.builder.image
330 328
 	container, err := req.builder.docker.ContainerCreate(types.ContainerCreateConfig{
331
-		Config: req.runConfig,
329
+		Config: runConfigWithCommentCmd,
332 330
 		// Set a log config to override any default value set on the daemon
333 331
 		HostConfig: &container.HostConfig{LogConfig: defaultLogConfig},
334 332
 	})
... ...
@@ -340,7 +338,7 @@ func workdir(req dispatchRequest) error {
340 340
 		return err
341 341
 	}
342 342
 
343
-	return req.builder.commitContainer(container.ID, copyRunConfig(req.runConfig, withCmd(cmd)))
343
+	return req.builder.commitContainer(container.ID, runConfigWithCommentCmd)
344 344
 }
345 345
 
346 346
 // RUN some command yo
... ...
@@ -363,79 +361,57 @@ func run(req dispatchRequest) error {
363 363
 	}
364 364
 
365 365
 	args := handleJSONArgs(req.args, req.attributes)
366
-
367 366
 	if !req.attributes["json"] {
368 367
 		args = append(getShell(req.runConfig), args...)
369 368
 	}
370
-	config := &container.Config{
371
-		Cmd:   strslice.StrSlice(args),
372
-		Image: req.builder.image,
373
-	}
369
+	cmdFromArgs := strslice.StrSlice(args)
370
+	buildArgs := req.builder.buildArgsWithoutConfigEnv()
374 371
 
375
-	// stash the cmd
376
-	cmd := req.runConfig.Cmd
377
-	if len(req.runConfig.Entrypoint) == 0 && len(req.runConfig.Cmd) == 0 {
378
-		req.runConfig.Cmd = config.Cmd
372
+	saveCmd := cmdFromArgs
373
+	if len(buildArgs) > 0 {
374
+		saveCmd = prependEnvOnCmd(req.builder.buildArgs, buildArgs, cmdFromArgs)
379 375
 	}
380 376
 
381
-	// stash the config environment
382
-	env := req.runConfig.Env
383
-
384
-	defer func(cmd strslice.StrSlice) { req.runConfig.Cmd = cmd }(cmd)
385
-	defer func(env []string) { req.runConfig.Env = env }(env)
386
-
387
-	cmdBuildEnv := req.builder.buildArgsWithoutConfigEnv()
388
-
389
-	// derive the command to use for probeCache() and to commit in this container.
390
-	// Note that we only do this if there are any build-time env vars.  Also, we
391
-	// use the special argument "|#" at the start of the args array. This will
392
-	// avoid conflicts with any RUN command since commands can not
393
-	// start with | (vertical bar). The "#" (number of build envs) is there to
394
-	// help ensure proper cache matches. We don't want a RUN command
395
-	// that starts with "foo=abc" to be considered part of a build-time env var.
396
-	saveCmd := config.Cmd
397
-	if len(cmdBuildEnv) > 0 {
398
-		saveCmd = prependEnvOnCmd(req.builder.buildArgs, cmdBuildEnv, saveCmd)
399
-	}
377
+	// TODO: this was previously in b.create(), why is it necessary?
378
+	req.runConfig.Image = req.builder.image
400 379
 
401
-	req.runConfig.Cmd = saveCmd
402
-	hit, err := req.builder.probeCache(req.builder.image, req.runConfig)
380
+	runConfigForCacheProbe := copyRunConfig(req.runConfig, withCmd(saveCmd))
381
+	hit, err := req.builder.probeCache(req.builder.image, runConfigForCacheProbe)
403 382
 	if err != nil || hit {
404 383
 		return err
405 384
 	}
406 385
 
407
-	// set Cmd manually, this is special case only for Dockerfiles
408
-	req.runConfig.Cmd = config.Cmd
409
-	// set build-time environment for 'run'.
410
-	req.runConfig.Env = append(req.runConfig.Env, cmdBuildEnv...)
411
-	// set config as already being escaped, this prevents double escaping on windows
412
-	req.runConfig.ArgsEscaped = true
386
+	runConfig := copyRunConfig(req.runConfig,
387
+		withCmd(cmdFromArgs),
388
+		withEnv(append(req.runConfig.Env, buildArgs...)))
413 389
 
414
-	logrus.Debugf("[BUILDER] Command to be executed: %v", req.runConfig.Cmd)
390
+	// set config as already being escaped, this prevents double escaping on windows
391
+	runConfig.ArgsEscaped = true
415 392
 
416
-	// TODO: this was previously in b.create(), why is it necessary?
417
-	req.builder.runConfig.Image = req.builder.image
393
+	logrus.Debugf("[BUILDER] Command to be executed: %v", runConfig.Cmd)
418 394
 
419
-	// TODO: should pass a copy of runConfig
420
-	cID, err := req.builder.create(req.runConfig)
395
+	cID, err := req.builder.create(runConfig)
421 396
 	if err != nil {
422 397
 		return err
423 398
 	}
424
-
425
-	if err := req.builder.run(cID); err != nil {
399
+	if err := req.builder.run(cID, runConfig.Cmd); err != nil {
426 400
 		return err
427 401
 	}
428 402
 
429
-	// FIXME: this is duplicated with the defer above in this function (i think?)
430
-	// revert to original config environment and set the command string to
431
-	// have the build-time env vars in it (if any) so that future cache look-ups
432
-	// properly match it.
433
-	req.runConfig.Env = env
434
-
435
-	req.runConfig.Cmd = saveCmd
436
-	return req.builder.commitContainer(cID, copyRunConfig(req.runConfig, withCmd(cmd)))
403
+	return req.builder.commitContainer(cID, runConfigForCacheProbe)
437 404
 }
438 405
 
406
+// Derive the command to use for probeCache() and to commit in this container.
407
+// Note that we only do this if there are any build-time env vars.  Also, we
408
+// use the special argument "|#" at the start of the args array. This will
409
+// avoid conflicts with any RUN command since commands can not
410
+// start with | (vertical bar). The "#" (number of build envs) is there to
411
+// help ensure proper cache matches. We don't want a RUN command
412
+// that starts with "foo=abc" to be considered part of a build-time env var.
413
+//
414
+// remove any unreferenced built-in args from the environment variables.
415
+// These args are transparent so resulting image should be the same regardless
416
+// of the value.
439 417
 func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice {
440 418
 	var tmpBuildEnv []string
441 419
 	for _, env := range buildArgVars {
... ...
@@ -5,7 +5,10 @@ import (
5 5
 	"runtime"
6 6
 	"testing"
7 7
 
8
+	"bytes"
9
+	"context"
8 10
 	"github.com/docker/docker/api/types"
11
+	"github.com/docker/docker/api/types/backend"
9 12
 	"github.com/docker/docker/api/types/container"
10 13
 	"github.com/docker/docker/api/types/strslice"
11 14
 	"github.com/docker/docker/builder"
... ...
@@ -49,6 +52,9 @@ func newBuilderWithMockBackend() *Builder {
49 49
 		options:       &types.ImageBuildOptions{},
50 50
 		docker:        &MockBackend{},
51 51
 		buildArgs:     newBuildArgs(make(map[string]*string)),
52
+		tmpContainers: make(map[string]struct{}),
53
+		Stdout:        new(bytes.Buffer),
54
+		clientCtx:     context.Background(),
52 55
 		disableCommit: true,
53 56
 	}
54 57
 	b.imageContexts = &imageContexts{b: b}
... ...
@@ -409,13 +415,71 @@ func TestParseOptInterval(t *testing.T) {
409 409
 		Value:    "50ns",
410 410
 	}
411 411
 	_, err := parseOptInterval(flInterval)
412
-	if err == nil {
413
-		t.Fatalf("Error should be presented for interval %s", flInterval.Value)
414
-	}
412
+	testutil.ErrorContains(t, err, "cannot be less than 1ms")
415 413
 
416 414
 	flInterval.Value = "1ms"
417 415
 	_, err = parseOptInterval(flInterval)
418
-	if err != nil {
419
-		t.Fatalf("Unexpected error: %s", err.Error())
416
+	require.NoError(t, err)
417
+}
418
+
419
+func TestPrependEnvOnCmd(t *testing.T) {
420
+	buildArgs := newBuildArgs(nil)
421
+	buildArgs.AddArg("NO_PROXY", nil)
422
+
423
+	args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
424
+	cmd := []string{"foo", "bar"}
425
+	cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
426
+	expected := strslice.StrSlice([]string{
427
+		"|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
428
+	assert.Equal(t, expected, cmdWithEnv)
429
+}
430
+
431
+func TestRunWithBuildArgs(t *testing.T) {
432
+	b := newBuilderWithMockBackend()
433
+	b.buildArgs.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
434
+	b.disableCommit = false
435
+
436
+	origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
437
+	cmdWithShell := strslice.StrSlice(append(getShell(b.runConfig), "echo foo"))
438
+	envVars := []string{"|1", "one=two"}
439
+	cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
440
+
441
+	imageCache := &mockImageCache{
442
+		getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
443
+			// Check the runConfig.Cmd sent to probeCache()
444
+			assert.Equal(t, cachedCmd, cfg.Cmd)
445
+			return "", nil
446
+		},
420 447
 	}
448
+	b.imageCache = imageCache
449
+
450
+	mockBackend := b.docker.(*MockBackend)
451
+	mockBackend.getImageOnBuildImage = &mockImage{
452
+		id:     "abcdef",
453
+		config: &container.Config{Cmd: origCmd},
454
+	}
455
+	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
456
+		// Check the runConfig.Cmd sent to create()
457
+		assert.Equal(t, cmdWithShell, config.Config.Cmd)
458
+		assert.Contains(t, config.Config.Env, "one=two")
459
+		return container.ContainerCreateCreatedBody{ID: "12345"}, nil
460
+	}
461
+	mockBackend.commitFunc = func(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
462
+		// Check the runConfig.Cmd sent to commit()
463
+		assert.Equal(t, origCmd, cfg.Config.Cmd)
464
+		assert.Equal(t, cachedCmd, cfg.ContainerConfig.Cmd)
465
+		return "", nil
466
+	}
467
+
468
+	req := defaultDispatchReq(b, "abcdef")
469
+	require.NoError(t, from(req))
470
+	b.buildArgs.AddArg("one", strPtr("two"))
471
+	// TODO: this can be removed with b.runConfig
472
+	req.runConfig.Cmd = origCmd
473
+
474
+	req.args = []string{"echo foo"}
475
+	require.NoError(t, run(req))
476
+
477
+	// Check that runConfig.Cmd has not been modified by run
478
+	assert.Equal(t, origCmd, b.runConfig.Cmd)
421 479
 }
... ...
@@ -21,7 +21,6 @@ import (
21 21
 	"github.com/docker/docker/api/types"
22 22
 	"github.com/docker/docker/api/types/backend"
23 23
 	"github.com/docker/docker/api/types/container"
24
-	"github.com/docker/docker/api/types/strslice"
25 24
 	"github.com/docker/docker/builder"
26 25
 	"github.com/docker/docker/builder/dockerfile/parser"
27 26
 	"github.com/docker/docker/builder/remotecontext"
... ...
@@ -56,10 +55,10 @@ func (b *Builder) commit(comment string) error {
56 56
 		return err
57 57
 	}
58 58
 
59
-	return b.commitContainer(id, b.runConfig)
59
+	return b.commitContainer(id, runConfigWithCommentCmd)
60 60
 }
61 61
 
62
-func (b *Builder) commitContainer(id string, runConfig *container.Config) error {
62
+func (b *Builder) commitContainer(id string, containerConfig *container.Config) error {
63 63
 	if b.disableCommit {
64 64
 		return nil
65 65
 	}
... ...
@@ -68,8 +67,9 @@ func (b *Builder) commitContainer(id string, runConfig *container.Config) error
68 68
 		ContainerCommitConfig: types.ContainerCommitConfig{
69 69
 			Author: b.maintainer,
70 70
 			Pause:  true,
71
-			Config: runConfig,
71
+			Config: b.runConfig,
72 72
 		},
73
+		ContainerConfig: containerConfig,
73 74
 	}
74 75
 
75 76
 	// Commit the container
... ...
@@ -81,7 +81,7 @@ func (b *Builder) commitContainer(id string, runConfig *container.Config) error
81 81
 	// TODO: this function should return imageID and runConfig instead of setting
82 82
 	// then on the builder
83 83
 	b.image = imageID
84
-	b.imageContexts.update(imageID, runConfig)
84
+	b.imageContexts.update(imageID, b.runConfig)
85 85
 	return nil
86 86
 }
87 87
 
... ...
@@ -100,6 +100,8 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
100 100
 	// Work in daemon-specific filepath semantics
101 101
 	dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest
102 102
 
103
+	// TODO: why is this done here. This seems to be done at random places all over
104
+	// the builder
103 105
 	b.runConfig.Image = b.image
104 106
 
105 107
 	var infos []copyInfo
... ...
@@ -164,18 +166,16 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
164 164
 		srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil))
165 165
 	}
166 166
 
167
-	cmd := b.runConfig.Cmd
168 167
 	// TODO: should this have been using origPaths instead of srcHash in the comment?
169
-	b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) %s %s in %s ", cmdName, srcHash, dest)))
170
-	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
171
-
172
-	// TODO: this should pass a copy of runConfig
173
-	if hit, err := b.probeCache(b.image, b.runConfig); err != nil || hit {
168
+	runConfigWithCommentCmd := copyRunConfig(
169
+		b.runConfig,
170
+		withCmdCommentString(fmt.Sprintf("%s %s in %s ", cmdName, srcHash, dest)))
171
+	if hit, err := b.probeCache(b.image, runConfigWithCommentCmd); err != nil || hit {
174 172
 		return err
175 173
 	}
176 174
 
177 175
 	container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
178
-		Config: b.runConfig,
176
+		Config: runConfigWithCommentCmd,
179 177
 		// Set a log config to override any default value set on the daemon
180 178
 		HostConfig: &container.HostConfig{LogConfig: defaultLogConfig},
181 179
 	})
... ...
@@ -196,7 +196,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
196 196
 		}
197 197
 	}
198 198
 
199
-	return b.commitContainer(container.ID, copyRunConfig(b.runConfig, withCmd(cmd)))
199
+	return b.commitContainer(container.ID, runConfigWithCommentCmd)
200 200
 }
201 201
 
202 202
 type runConfigModifier func(*container.Config)
... ...
@@ -215,12 +215,24 @@ func withCmd(cmd []string) runConfigModifier {
215 215
 	}
216 216
 }
217 217
 
218
+// withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for
219
+// why there are two almost identical versions of this.
218 220
 func withCmdComment(comment string) runConfigModifier {
219 221
 	return func(runConfig *container.Config) {
220 222
 		runConfig.Cmd = append(getShell(runConfig), "#(nop) ", comment)
221 223
 	}
222 224
 }
223 225
 
226
+// withCmdCommentString exists to maintain compatibility with older versions.
227
+// A few instructions (workdir, copy, add) used a nop comment that is a single arg
228
+// where as all the other instructions used a two arg comment string. This
229
+// function implements the single arg version.
230
+func withCmdCommentString(comment string) runConfigModifier {
231
+	return func(runConfig *container.Config) {
232
+		runConfig.Cmd = append(getShell(runConfig), "#(nop) "+comment)
233
+	}
234
+}
235
+
224 236
 func withEnv(env []string) runConfigModifier {
225 237
 	return func(runConfig *container.Config) {
226 238
 		runConfig.Env = env
... ...
@@ -606,7 +618,7 @@ func (b *Builder) create(runConfig *container.Config) (string, error) {
606 606
 
607 607
 var errCancelled = errors.New("build cancelled")
608 608
 
609
-func (b *Builder) run(cID string) (err error) {
609
+func (b *Builder) run(cID string, cmd []string) (err error) {
610 610
 	attached := make(chan struct{})
611 611
 	errCh := make(chan error)
612 612
 	go func() {
... ...
@@ -660,7 +672,7 @@ func (b *Builder) run(cID string) (err error) {
660 660
 		}
661 661
 		// TODO: change error type, because jsonmessage.JSONError assumes HTTP
662 662
 		return &jsonmessage.JSONError{
663
-			Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(b.runConfig.Cmd, " "), ret),
663
+			Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(cmd, " "), ret),
664 664
 			Code:    ret,
665 665
 		}
666 666
 	}
... ...
@@ -15,13 +15,19 @@ import (
15 15
 
16 16
 // MockBackend implements the builder.Backend interface for unit testing
17 17
 type MockBackend struct {
18
-	getImageOnBuildFunc func(string) (builder.Image, error)
18
+	getImageOnBuildFunc  func(string) (builder.Image, error)
19
+	getImageOnBuildImage *mockImage
20
+	containerCreateFunc  func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
21
+	commitFunc           func(string, *backend.ContainerCommitConfig) (string, error)
19 22
 }
20 23
 
21 24
 func (m *MockBackend) GetImageOnBuild(name string) (builder.Image, error) {
22 25
 	if m.getImageOnBuildFunc != nil {
23 26
 		return m.getImageOnBuildFunc(name)
24 27
 	}
28
+	if m.getImageOnBuildImage != nil {
29
+		return m.getImageOnBuildImage, nil
30
+	}
25 31
 	return &mockImage{id: "theid"}, nil
26 32
 }
27 33
 
... ...
@@ -38,6 +44,9 @@ func (m *MockBackend) ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout
38 38
 }
39 39
 
40 40
 func (m *MockBackend) ContainerCreate(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
41
+	if m.containerCreateFunc != nil {
42
+		return m.containerCreateFunc(config)
43
+	}
41 44
 	return container.ContainerCreateCreatedBody{}, nil
42 45
 }
43 46
 
... ...
@@ -45,7 +54,10 @@ func (m *MockBackend) ContainerRm(name string, config *types.ContainerRmConfig)
45 45
 	return nil
46 46
 }
47 47
 
48
-func (m *MockBackend) Commit(string, *backend.ContainerCommitConfig) (string, error) {
48
+func (m *MockBackend) Commit(cID string, cfg *backend.ContainerCommitConfig) (string, error) {
49
+	if m.commitFunc != nil {
50
+		return m.commitFunc(cID, cfg)
51
+	}
49 52
 	return "", nil
50 53
 }
51 54
 
... ...
@@ -97,3 +109,14 @@ func (i *mockImage) ImageID() string {
97 97
 func (i *mockImage) RunConfig() *container.Config {
98 98
 	return i.config
99 99
 }
100
+
101
+type mockImageCache struct {
102
+	getCacheFunc func(parentID string, cfg *container.Config) (string, error)
103
+}
104
+
105
+func (mic *mockImageCache) GetCache(parentID string, cfg *container.Config) (string, error) {
106
+	if mic.getCacheFunc != nil {
107
+		return mic.getCacheFunc(parentID, cfg)
108
+	}
109
+	return "", nil
110
+}
... ...
@@ -129,6 +129,11 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
129 129
 		return "", err
130 130
 	}
131 131
 
132
+	containerConfig := c.ContainerConfig
133
+	if containerConfig == nil {
134
+		containerConfig = container.Config
135
+	}
136
+
132 137
 	// It is not possible to commit a running container on Windows and on Solaris.
133 138
 	if (runtime.GOOS == "windows" || runtime.GOOS == "solaris") && container.IsRunning() {
134 139
 		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
185 185
 	h := image.History{
186 186
 		Author:     c.Author,
187 187
 		Created:    time.Now().UTC(),
188
-		CreatedBy:  strings.Join(container.Config.Cmd, " "),
188
+		CreatedBy:  strings.Join(containerConfig.Cmd, " "),
189 189
 		Comment:    c.Comment,
190 190
 		EmptyLayer: true,
191 191
 	}
... ...
@@ -204,7 +209,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str
204 204
 			Architecture:    runtime.GOARCH,
205 205
 			OS:              runtime.GOOS,
206 206
 			Container:       container.ID,
207
-			ContainerConfig: *container.Config,
207
+			ContainerConfig: *containerConfig,
208 208
 			Author:          c.Author,
209 209
 			Created:         h.Created,
210 210
 		},
... ...
@@ -1,6 +1,8 @@
1 1
 package cache
2 2
 
3
-import "github.com/docker/docker/api/types/container"
3
+import (
4
+	"github.com/docker/docker/api/types/container"
5
+)
4 6
 
5 7
 // compare two Config struct. Do not compare the "Image" nor "Hostname" fields
6 8
 // If OpenStdin is set, then it differs
... ...
@@ -345,11 +345,8 @@ ONBUILD RUN ["true"]`))
345 345
 
346 346
 	buildImageSuccessfully(c, name2, build.WithDockerfile(fmt.Sprintf(`FROM %s`, name1)))
347 347
 
348
-	out, _ := dockerCmd(c, "run", name2)
349
-	if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) {
350
-		c.Fatalf("did not get echo output from onbuild. Got: %q", out)
351
-	}
352
-
348
+	result := cli.DockerCmd(c, "run", name2)
349
+	result.Assert(c, icmd.Expected{Out: "hello world"})
353 350
 }
354 351
 
355 352
 // FIXME(vdemeester) why we disabled cache here ?
... ...
@@ -4376,12 +4373,8 @@ func (s *DockerSuite) TestBuildTimeArgHistoryExclusions(c *check.C) {
4376 4376
 	if strings.Contains(out, "https_proxy") {
4377 4377
 		c.Fatalf("failed to exclude proxy settings from history!")
4378 4378
 	}
4379
-	if !strings.Contains(out, fmt.Sprintf("%s=%s", envKey, envVal)) {
4380
-		c.Fatalf("explicitly defined ARG %s is not in output", explicitProxyKey)
4381
-	}
4382
-	if !strings.Contains(out, fmt.Sprintf("%s=%s", envKey, envVal)) {
4383
-		c.Fatalf("missing build arguments from output")
4384
-	}
4379
+	result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", envKey, envVal)})
4380
+	result.Assert(c, icmd.Expected{Out: fmt.Sprintf("%s=%s", explicitProxyKey, explicitProxyVal)})
4385 4381
 
4386 4382
 	cacheID := buildImage(imgName + "-two")
4387 4383
 	c.Assert(origID, checker.Equals, cacheID)
... ...
@@ -4576,9 +4569,7 @@ func (s *DockerSuite) TestBuildBuildTimeArgExpansion(c *check.C) {
4576 4576
 	)
4577 4577
 
4578 4578
 	res := inspectField(c, imgName, "Config.WorkingDir")
4579
-	if res != filepath.ToSlash(filepath.Clean(wdVal)) {
4580
-		c.Fatalf("Config.WorkingDir value mismatch. Expected: %s, got: %s", filepath.ToSlash(filepath.Clean(wdVal)), res)
4581
-	}
4579
+	c.Check(res, check.Equals, filepath.ToSlash(wdVal))
4582 4580
 
4583 4581
 	var resArr []string
4584 4582
 	inspectFieldAndUnmarshall(c, imgName, "Config.Env", &resArr)