Browse code

Extract commitContainer from commit() to separate the distinct branches

Remove unused arguments to commit.
This will allow us to remove all the runConfig mutate+revert code that
is scattered around builder dispatchers/internals

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2017/04/22 03:11:21
Showing 3 changed files
... ...
@@ -73,7 +73,7 @@ func env(req dispatchRequest) error {
73 73
 		}
74 74
 	}
75 75
 
76
-	return req.builder.commit("", req.runConfig.Cmd, commitMessage.String())
76
+	return req.builder.commit(commitMessage.String())
77 77
 }
78 78
 
79 79
 // MAINTAINER some text <maybe@an.email.address>
... ...
@@ -90,7 +90,7 @@ func maintainer(req dispatchRequest) error {
90 90
 
91 91
 	maintainer := req.args[0]
92 92
 	req.builder.maintainer = maintainer
93
-	return req.builder.commit("", req.runConfig.Cmd, "MAINTAINER "+maintainer)
93
+	return req.builder.commit("MAINTAINER " + maintainer)
94 94
 }
95 95
 
96 96
 // LABEL some json data describing the image
... ...
@@ -130,7 +130,7 @@ func label(req dispatchRequest) error {
130 130
 		req.runConfig.Labels[req.args[j]] = req.args[j+1]
131 131
 		j++
132 132
 	}
133
-	return req.builder.commit("", req.runConfig.Cmd, commitStr)
133
+	return req.builder.commit(commitStr)
134 134
 }
135 135
 
136 136
 // ADD foo /path
... ...
@@ -281,7 +281,7 @@ func onbuild(req dispatchRequest) error {
281 281
 
282 282
 	original := regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(req.original, "")
283 283
 	req.runConfig.OnBuild = append(req.runConfig.OnBuild, original)
284
-	return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("ONBUILD %s", original))
284
+	return req.builder.commit("ONBUILD " + original)
285 285
 }
286 286
 
287 287
 // WORKDIR /tmp
... ...
@@ -321,10 +321,9 @@ func workdir(req dispatchRequest) error {
321 321
 	req.runConfig.Cmd = strslice.StrSlice(append(getShell(req.runConfig), "#(nop) "+comment))
322 322
 	defer func(cmd strslice.StrSlice) { req.runConfig.Cmd = cmd }(cmd)
323 323
 
324
-	if hit, err := req.builder.probeCache(); err != nil {
324
+	// TODO: this should pass a copy of runConfig
325
+	if hit, err := req.builder.probeCache(req.builder.image, req.runConfig); err != nil || hit {
325 326
 		return err
326
-	} else if hit {
327
-		return nil
328 327
 	}
329 328
 
330 329
 	req.runConfig.Image = req.builder.image
... ...
@@ -341,7 +340,7 @@ func workdir(req dispatchRequest) error {
341 341
 		return err
342 342
 	}
343 343
 
344
-	return req.builder.commit(container.ID, cmd, comment)
344
+	return req.builder.commitContainer(container.ID, copyRunConfig(req.runConfig, withCmd(cmd)))
345 345
 }
346 346
 
347 347
 // RUN some command yo
... ...
@@ -402,13 +401,10 @@ func run(req dispatchRequest) error {
402 402
 	}
403 403
 
404 404
 	req.runConfig.Cmd = saveCmd
405
-	hit, err := req.builder.probeCache()
406
-	if err != nil {
405
+	hit, err := req.builder.probeCache(req.builder.image, req.runConfig)
406
+	if err != nil || hit {
407 407
 		return err
408 408
 	}
409
-	if hit {
410
-		return nil
411
-	}
412 409
 
413 410
 	// set Cmd manually, this is special case only for Dockerfiles
414 411
 	req.runConfig.Cmd = config.Cmd
... ...
@@ -419,7 +415,11 @@ func run(req dispatchRequest) error {
419 419
 
420 420
 	logrus.Debugf("[BUILDER] Command to be executed: %v", req.runConfig.Cmd)
421 421
 
422
-	cID, err := req.builder.create()
422
+	// TODO: this was previously in b.create(), why is it necessary?
423
+	req.builder.runConfig.Image = req.builder.image
424
+
425
+	// TODO: should pass a copy of runConfig
426
+	cID, err := req.builder.create(req.runConfig)
423 427
 	if err != nil {
424 428
 		return err
425 429
 	}
... ...
@@ -451,7 +451,7 @@ func run(req dispatchRequest) error {
451 451
 		saveCmd = strslice.StrSlice(append(tmpEnv, saveCmd...))
452 452
 	}
453 453
 	req.runConfig.Cmd = saveCmd
454
-	return req.builder.commit(cID, cmd, "run")
454
+	return req.builder.commitContainer(cID, copyRunConfig(req.runConfig, withCmd(cmd)))
455 455
 }
456 456
 
457 457
 // CMD foo
... ...
@@ -474,7 +474,7 @@ func cmd(req dispatchRequest) error {
474 474
 	// set config as already being escaped, this prevents double escaping on windows
475 475
 	req.runConfig.ArgsEscaped = true
476 476
 
477
-	if err := req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
477
+	if err := req.builder.commit(fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
478 478
 		return err
479 479
 	}
480 480
 
... ...
@@ -590,7 +590,7 @@ func healthcheck(req dispatchRequest) error {
590 590
 		req.runConfig.Healthcheck = &healthcheck
591 591
 	}
592 592
 
593
-	return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("HEALTHCHECK %q", req.runConfig.Healthcheck))
593
+	return req.builder.commit(fmt.Sprintf("HEALTHCHECK %q", req.runConfig.Healthcheck))
594 594
 }
595 595
 
596 596
 // ENTRYPOINT /usr/sbin/nginx
... ...
@@ -626,11 +626,7 @@ func entrypoint(req dispatchRequest) error {
626 626
 		req.runConfig.Cmd = nil
627 627
 	}
628 628
 
629
-	if err := req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("ENTRYPOINT %q", req.runConfig.Entrypoint)); err != nil {
630
-		return err
631
-	}
632
-
633
-	return nil
629
+	return req.builder.commit(fmt.Sprintf("ENTRYPOINT %q", req.runConfig.Entrypoint))
634 630
 }
635 631
 
636 632
 // EXPOSE 6667/tcp 7000/tcp
... ...
@@ -671,7 +667,7 @@ func expose(req dispatchRequest) error {
671 671
 		i++
672 672
 	}
673 673
 	sort.Strings(portList)
674
-	return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
674
+	return req.builder.commit("EXPOSE " + strings.Join(portList, " "))
675 675
 }
676 676
 
677 677
 // USER foo
... ...
@@ -689,7 +685,7 @@ func user(req dispatchRequest) error {
689 689
 	}
690 690
 
691 691
 	req.runConfig.User = req.args[0]
692
-	return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("USER %v", req.args))
692
+	return req.builder.commit(fmt.Sprintf("USER %v", req.args))
693 693
 }
694 694
 
695 695
 // VOLUME /foo
... ...
@@ -715,10 +711,7 @@ func volume(req dispatchRequest) error {
715 715
 		}
716 716
 		req.runConfig.Volumes[v] = struct{}{}
717 717
 	}
718
-	if err := req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("VOLUME %v", req.args)); err != nil {
719
-		return err
720
-	}
721
-	return nil
718
+	return req.builder.commit(fmt.Sprintf("VOLUME %v", req.args))
722 719
 }
723 720
 
724 721
 // STOPSIGNAL signal
... ...
@@ -736,7 +729,7 @@ func stopSignal(req dispatchRequest) error {
736 736
 	}
737 737
 
738 738
 	req.runConfig.StopSignal = sig
739
-	return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("STOPSIGNAL %v", req.args))
739
+	return req.builder.commit(fmt.Sprintf("STOPSIGNAL %v", req.args))
740 740
 }
741 741
 
742 742
 // ARG name[=value]
... ...
@@ -786,7 +779,7 @@ func arg(req dispatchRequest) error {
786 786
 		req.builder.buildArgs.AddMetaArg(name, value)
787 787
 		return nil
788 788
 	}
789
-	return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("ARG %s", arg))
789
+	return req.builder.commit("ARG " + arg)
790 790
 }
791 791
 
792 792
 // SHELL powershell -command
... ...
@@ -808,7 +801,7 @@ func shell(req dispatchRequest) error {
808 808
 		// SHELL powershell -command - not JSON
809 809
 		return errNotJSON("SHELL", req.original)
810 810
 	}
811
-	return req.builder.commit("", req.runConfig.Cmd, fmt.Sprintf("SHELL %v", shellSlice))
811
+	return req.builder.commit(fmt.Sprintf("SHELL %v", shellSlice))
812 812
 }
813 813
 
814 814
 func errAtLeastOneArgument(command string) error {
... ...
@@ -831,15 +824,6 @@ func errTooManyArguments(command string) error {
831 831
 	return fmt.Errorf("Bad input to %s, too many arguments", command)
832 832
 }
833 833
 
834
-// getShell is a helper function which gets the right shell for prefixing the
835
-// shell-form of RUN, ENTRYPOINT and CMD instructions
836
-func getShell(c *container.Config) []string {
837
-	if 0 == len(c.Shell) {
838
-		return append([]string{}, defaultShell[:]...)
839
-	}
840
-	return append([]string{}, c.Shell[:]...)
841
-}
842
-
843 834
 // mountByRef creates an imageMount from a reference. pulling the image if needed.
844 835
 func mountByRef(b *Builder, name string) (*imageMount, error) {
845 836
 	image, err := pullOrGetImage(b, name)
... ...
@@ -36,41 +36,39 @@ import (
36 36
 	"github.com/pkg/errors"
37 37
 )
38 38
 
39
-func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) error {
39
+func (b *Builder) commit(comment string) error {
40 40
 	if b.disableCommit {
41 41
 		return nil
42 42
 	}
43 43
 	if !b.hasFromImage() {
44 44
 		return errors.New("Please provide a source image with `from` prior to commit")
45 45
 	}
46
+	// TODO: why is this set here?
46 47
 	b.runConfig.Image = b.image
47 48
 
48
-	if id == "" {
49
-		cmd := b.runConfig.Cmd
50
-		b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), "#(nop) ", comment))
51
-		defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
52
-
53
-		hit, err := b.probeCache()
54
-		if err != nil {
55
-			return err
56
-		} else if hit {
57
-			return nil
58
-		}
59
-		id, err = b.create()
60
-		if err != nil {
61
-			return err
62
-		}
49
+	runConfigWithCommentCmd := copyRunConfig(b.runConfig, withCmdComment(comment))
50
+	hit, err := b.probeCache(b.image, runConfigWithCommentCmd)
51
+	if err != nil || hit {
52
+		return err
53
+	}
54
+	id, err := b.create(runConfigWithCommentCmd)
55
+	if err != nil {
56
+		return err
63 57
 	}
64 58
 
65
-	// Note: Actually copy the struct
66
-	autoConfig := *b.runConfig
67
-	autoConfig.Cmd = autoCmd
59
+	return b.commitContainer(id, b.runConfig)
60
+}
61
+
62
+func (b *Builder) commitContainer(id string, runConfig *container.Config) error {
63
+	if b.disableCommit {
64
+		return nil
65
+	}
68 66
 
69 67
 	commitCfg := &backend.ContainerCommitConfig{
70 68
 		ContainerCommitConfig: types.ContainerCommitConfig{
71 69
 			Author: b.maintainer,
72 70
 			Pause:  true,
73
-			Config: &autoConfig,
71
+			Config: runConfig,
74 72
 		},
75 73
 	}
76 74
 
... ...
@@ -80,8 +78,10 @@ func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) e
80 80
 		return err
81 81
 	}
82 82
 
83
+	// TODO: this function should return imageID and runConfig instead of setting
84
+	// then on the builder
83 85
 	b.image = imageID
84
-	b.imageContexts.update(imageID, &autoConfig)
86
+	b.imageContexts.update(imageID, runConfig)
85 87
 	return nil
86 88
 }
87 89
 
... ...
@@ -148,11 +148,9 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
148 148
 	// For backwards compat, if there's just one info then use it as the
149 149
 	// cache look-up string, otherwise hash 'em all into one
150 150
 	var srcHash string
151
-	var origPaths string
152 151
 
153 152
 	if len(infos) == 1 {
154 153
 		info := infos[0]
155
-		origPaths = info.path
156 154
 		srcHash = info.hash
157 155
 	} else {
158 156
 		var hashs []string
... ...
@@ -164,17 +162,16 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
164 164
 		hasher := sha256.New()
165 165
 		hasher.Write([]byte(strings.Join(hashs, ",")))
166 166
 		srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil))
167
-		origPaths = strings.Join(origs, " ")
168 167
 	}
169 168
 
170 169
 	cmd := b.runConfig.Cmd
170
+	// TODO: should this have been using origPaths instead of srcHash in the comment?
171 171
 	b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) %s %s in %s ", cmdName, srcHash, dest)))
172 172
 	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
173 173
 
174
-	if hit, err := b.probeCache(); err != nil {
174
+	// TODO: this should pass a copy of runConfig
175
+	if hit, err := b.probeCache(b.image, b.runConfig); err != nil || hit {
175 176
 		return err
176
-	} else if hit {
177
-		return nil
178 177
 	}
179 178
 
180 179
 	container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
... ...
@@ -187,8 +184,6 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
187 187
 	}
188 188
 	b.tmpContainers[container.ID] = struct{}{}
189 189
 
190
-	comment := fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest)
191
-
192 190
 	// Twiddle the destination when it's a relative path - meaning, make it
193 191
 	// relative to the WORKINGDIR
194 192
 	if dest, err = normaliseDest(cmdName, b.runConfig.WorkingDir, dest); err != nil {
... ...
@@ -201,7 +196,44 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
201 201
 		}
202 202
 	}
203 203
 
204
-	return b.commit(container.ID, cmd, comment)
204
+	return b.commitContainer(container.ID, copyRunConfig(b.runConfig, withCmd(cmd)))
205
+}
206
+
207
+type runConfigModifier func(*container.Config)
208
+
209
+func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config {
210
+	copy := *runConfig
211
+	for _, modifier := range modifiers {
212
+		modifier(&copy)
213
+	}
214
+	return &copy
215
+}
216
+
217
+func withCmd(cmd []string) runConfigModifier {
218
+	return func(runConfig *container.Config) {
219
+		runConfig.Cmd = cmd
220
+	}
221
+}
222
+
223
+func withCmdComment(comment string) runConfigModifier {
224
+	return func(runConfig *container.Config) {
225
+		runConfig.Cmd = append(getShell(runConfig), "#(nop) ", comment)
226
+	}
227
+}
228
+
229
+func withEnv(env []string) runConfigModifier {
230
+	return func(runConfig *container.Config) {
231
+		runConfig.Env = env
232
+	}
233
+}
234
+
235
+// getShell is a helper function which gets the right shell for prefixing the
236
+// shell-form of RUN, ENTRYPOINT and CMD instructions
237
+func getShell(c *container.Config) []string {
238
+	if 0 == len(c.Shell) {
239
+		return append([]string{}, defaultShell[:]...)
240
+	}
241
+	return append([]string{}, c.Shell[:]...)
205 242
 }
206 243
 
207 244
 func (b *Builder) download(srcURL string) (remote builder.Source, p string, err error) {
... ...
@@ -498,35 +530,33 @@ func (b *Builder) processImageFrom(img builder.Image) error {
498 498
 // If an image is found, probeCache returns `(true, nil)`.
499 499
 // If no image is found, it returns `(false, nil)`.
500 500
 // If there is any error, it returns `(false, err)`.
501
-func (b *Builder) probeCache() (bool, error) {
501
+func (b *Builder) probeCache(imageID string, runConfig *container.Config) (bool, error) {
502 502
 	c := b.imageCache
503 503
 	if c == nil || b.options.NoCache || b.cacheBusted {
504 504
 		return false, nil
505 505
 	}
506
-	cache, err := c.GetCache(b.image, b.runConfig)
506
+	cache, err := c.GetCache(imageID, runConfig)
507 507
 	if err != nil {
508 508
 		return false, err
509 509
 	}
510 510
 	if len(cache) == 0 {
511
-		logrus.Debugf("[BUILDER] Cache miss: %s", b.runConfig.Cmd)
511
+		logrus.Debugf("[BUILDER] Cache miss: %s", runConfig.Cmd)
512 512
 		b.cacheBusted = true
513 513
 		return false, nil
514 514
 	}
515 515
 
516 516
 	fmt.Fprint(b.Stdout, " ---> Using cache\n")
517
-	logrus.Debugf("[BUILDER] Use cached version: %s", b.runConfig.Cmd)
517
+	logrus.Debugf("[BUILDER] Use cached version: %s", runConfig.Cmd)
518 518
 	b.image = string(cache)
519
-	b.imageContexts.update(b.image, b.runConfig)
519
+	b.imageContexts.update(b.image, runConfig)
520 520
 
521 521
 	return true, nil
522 522
 }
523 523
 
524
-func (b *Builder) create() (string, error) {
524
+func (b *Builder) create(runConfig *container.Config) (string, error) {
525 525
 	if !b.hasFromImage() {
526 526
 		return "", errors.New("Please provide a source image with `from` prior to run")
527 527
 	}
528
-	b.runConfig.Image = b.image
529
-
530 528
 	resources := container.Resources{
531 529
 		CgroupParent: b.options.CgroupParent,
532 530
 		CPUShares:    b.options.CPUShares,
... ...
@@ -551,11 +581,9 @@ func (b *Builder) create() (string, error) {
551 551
 		ExtraHosts: b.options.ExtraHosts,
552 552
 	}
553 553
 
554
-	config := *b.runConfig
555
-
556 554
 	// Create the container
557 555
 	c, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
558
-		Config:     b.runConfig,
556
+		Config:     runConfig,
559 557
 		HostConfig: hostConfig,
560 558
 	})
561 559
 	if err != nil {
... ...
@@ -569,7 +597,7 @@ func (b *Builder) create() (string, error) {
569 569
 	fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(c.ID))
570 570
 
571 571
 	// override the entry point that may have been picked up from the base image
572
-	if err := b.docker.ContainerUpdateCmdOnBuild(c.ID, config.Cmd); err != nil {
572
+	if err := b.docker.ContainerUpdateCmdOnBuild(c.ID, runConfig.Cmd); err != nil {
573 573
 		return "", err
574 574
 	}
575 575
 
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"fmt"
6 6
 	"testing"
7 7
 
8
+	"github.com/docker/docker/api/types/container"
8 9
 	"github.com/docker/docker/builder"
9 10
 	"github.com/docker/docker/builder/remotecontext"
10 11
 	"github.com/docker/docker/pkg/archive"
... ...
@@ -71,3 +72,54 @@ func readAndCheckDockerfile(t *testing.T, testName, contextDir, dockerfilePath,
71 71
 	_, _, err = remotecontext.Detect(context.Background(), "", dockerfilePath, tarStream, nil)
72 72
 	assert.EqualError(t, err, expectedError)
73 73
 }
74
+
75
+func TestCopyRunConfig(t *testing.T) {
76
+	defaultEnv := []string{"foo=1"}
77
+	defaultCmd := []string{"old"}
78
+
79
+	var testcases = []struct {
80
+		doc       string
81
+		modifiers []runConfigModifier
82
+		expected  *container.Config
83
+	}{
84
+		{
85
+			doc:       "Set the command",
86
+			modifiers: []runConfigModifier{withCmd([]string{"new"})},
87
+			expected: &container.Config{
88
+				Cmd: []string{"new"},
89
+				Env: defaultEnv,
90
+			},
91
+		},
92
+		{
93
+			doc:       "Set the command to a comment",
94
+			modifiers: []runConfigModifier{withCmdComment("comment")},
95
+			expected: &container.Config{
96
+				Cmd: append(defaultShell, "#(nop) ", "comment"),
97
+				Env: defaultEnv,
98
+			},
99
+		},
100
+		{
101
+			doc: "Set the command and env",
102
+			modifiers: []runConfigModifier{
103
+				withCmd([]string{"new"}),
104
+				withEnv([]string{"one", "two"}),
105
+			},
106
+			expected: &container.Config{
107
+				Cmd: []string{"new"},
108
+				Env: []string{"one", "two"},
109
+			},
110
+		},
111
+	}
112
+
113
+	for _, testcase := range testcases {
114
+		runConfig := &container.Config{
115
+			Cmd: defaultCmd,
116
+			Env: defaultEnv,
117
+		}
118
+		runConfigCopy := copyRunConfig(runConfig, testcase.modifiers...)
119
+		assert.Equal(t, testcase.expected, runConfigCopy, testcase.doc)
120
+		// Assert the original was not modified
121
+		assert.NotEqual(t, runConfig, runConfigCopy, testcase.doc)
122
+	}
123
+
124
+}