Browse code

Add deepCopyRunConfig for copying buidler runConfig

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

Daniel Nephin authored on 2017/11/10 06:20:51
Showing 4 changed files
... ...
@@ -214,7 +214,8 @@ func (s *dispatchState) beginStage(stageName string, image builder.Image) {
214 214
 	s.imageID = image.ImageID()
215 215
 
216 216
 	if image.RunConfig() != nil {
217
-		s.runConfig = copyRunConfig(image.RunConfig()) // copy avoids referencing the same instance when 2 stages have the same base
217
+		// copy avoids referencing the same instance when 2 stages have the same base
218
+		s.runConfig = copyRunConfig(image.RunConfig())
218 219
 	} else {
219 220
 		s.runConfig = &container.Config{}
220 221
 	}
... ...
@@ -25,6 +25,7 @@ import (
25 25
 	"github.com/docker/docker/pkg/stringid"
26 26
 	"github.com/docker/docker/pkg/symlink"
27 27
 	"github.com/docker/docker/pkg/system"
28
+	"github.com/docker/go-connections/nat"
28 29
 	lcUser "github.com/opencontainers/runc/libcontainer/user"
29 30
 	"github.com/pkg/errors"
30 31
 )
... ...
@@ -385,14 +386,6 @@ func hashStringSlice(prefix string, slice []string) string {
385 385
 
386 386
 type runConfigModifier func(*container.Config)
387 387
 
388
-func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config {
389
-	copy := *runConfig
390
-	for _, modifier := range modifiers {
391
-		modifier(&copy)
392
-	}
393
-	return &copy
394
-}
395
-
396 388
 func withCmd(cmd []string) runConfigModifier {
397 389
 	return func(runConfig *container.Config) {
398 390
 		runConfig.Cmd = cmd
... ...
@@ -438,6 +431,48 @@ func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier
438 438
 	}
439 439
 }
440 440
 
441
+func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config {
442
+	copy := *runConfig
443
+	copy.Cmd = copyStringSlice(runConfig.Cmd)
444
+	copy.Env = copyStringSlice(runConfig.Env)
445
+	copy.Entrypoint = copyStringSlice(runConfig.Entrypoint)
446
+	copy.OnBuild = copyStringSlice(runConfig.OnBuild)
447
+	copy.Shell = copyStringSlice(runConfig.Shell)
448
+
449
+	if copy.Volumes != nil {
450
+		copy.Volumes = make(map[string]struct{}, len(runConfig.Volumes))
451
+		for k, v := range runConfig.Volumes {
452
+			copy.Volumes[k] = v
453
+		}
454
+	}
455
+
456
+	if copy.ExposedPorts != nil {
457
+		copy.ExposedPorts = make(nat.PortSet, len(runConfig.ExposedPorts))
458
+		for k, v := range runConfig.ExposedPorts {
459
+			copy.ExposedPorts[k] = v
460
+		}
461
+	}
462
+
463
+	if copy.Labels != nil {
464
+		copy.Labels = make(map[string]string, len(runConfig.Labels))
465
+		for k, v := range runConfig.Labels {
466
+			copy.Labels[k] = v
467
+		}
468
+	}
469
+
470
+	for _, modifier := range modifiers {
471
+		modifier(&copy)
472
+	}
473
+	return &copy
474
+}
475
+
476
+func copyStringSlice(orig []string) []string {
477
+	if orig == nil {
478
+		return nil
479
+	}
480
+	return append([]string{}, orig...)
481
+}
482
+
441 483
 // getShell is a helper function which gets the right shell for prefixing the
442 484
 // shell-form of RUN, ENTRYPOINT and CMD instructions
443 485
 func getShell(c *container.Config, os string) []string {
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/builder/remotecontext"
15 15
 	"github.com/docker/docker/pkg/archive"
16 16
 	"github.com/docker/docker/pkg/idtools"
17
+	"github.com/docker/go-connections/nat"
17 18
 	"github.com/stretchr/testify/assert"
18 19
 	"github.com/stretchr/testify/require"
19 20
 )
... ...
@@ -133,6 +134,44 @@ func TestCopyRunConfig(t *testing.T) {
133 133
 
134 134
 }
135 135
 
136
+func fullMutableRunConfig() *container.Config {
137
+	return &container.Config{
138
+		Cmd: []string{"command", "arg1"},
139
+		Env: []string{"env1=foo", "env2=bar"},
140
+		ExposedPorts: nat.PortSet{
141
+			"1000/tcp": {},
142
+			"1001/tcp": {},
143
+		},
144
+		Volumes: map[string]struct{}{
145
+			"one": {},
146
+			"two": {},
147
+		},
148
+		Entrypoint: []string{"entry", "arg1"},
149
+		OnBuild:    []string{"first", "next"},
150
+		Labels: map[string]string{
151
+			"label1": "value1",
152
+			"label2": "value2",
153
+		},
154
+		Shell: []string{"shell", "-c"},
155
+	}
156
+}
157
+
158
+func TestDeepCopyRunConfig(t *testing.T) {
159
+	runConfig := fullMutableRunConfig()
160
+	copy := copyRunConfig(runConfig)
161
+	assert.Equal(t, fullMutableRunConfig(), copy)
162
+
163
+	copy.Cmd[1] = "arg2"
164
+	copy.Env[1] = "env2=new"
165
+	copy.ExposedPorts["10002"] = struct{}{}
166
+	copy.Volumes["three"] = struct{}{}
167
+	copy.Entrypoint[1] = "arg2"
168
+	copy.OnBuild[0] = "start"
169
+	copy.Labels["label3"] = "value3"
170
+	copy.Shell[0] = "sh"
171
+	assert.Equal(t, fullMutableRunConfig(), runConfig)
172
+}
173
+
136 174
 func TestChownFlagParsing(t *testing.T) {
137 175
 	testFiles := map[string]string{
138 176
 		"passwd": `root:x:0:0::/bin:/bin/false
... ...
@@ -6,13 +6,16 @@ import (
6 6
 	"context"
7 7
 	"encoding/json"
8 8
 	"io"
9
+	"io/ioutil"
9 10
 	"strings"
10 11
 	"testing"
11 12
 
12 13
 	"github.com/docker/docker/api/types"
13 14
 	"github.com/docker/docker/api/types/filters"
15
+	"github.com/docker/docker/integration-cli/cli/build/fakecontext"
14 16
 	"github.com/docker/docker/integration/util/request"
15 17
 	"github.com/docker/docker/pkg/jsonmessage"
18
+	"github.com/stretchr/testify/assert"
16 19
 	"github.com/stretchr/testify/require"
17 20
 )
18 21
 
... ...
@@ -129,3 +132,40 @@ func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
129 129
 		}
130 130
 	}
131 131
 }
132
+
133
+func TestBuildMultiStageParentConfig(t *testing.T) {
134
+	dockerfile := `
135
+		FROM busybox AS stage0
136
+		ENV WHO=parent
137
+		WORKDIR /foo
138
+
139
+		FROM stage0
140
+		ENV WHO=sibling1
141
+		WORKDIR sub1
142
+
143
+		FROM stage0
144
+		WORKDIR sub2
145
+	`
146
+	ctx := context.Background()
147
+	source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
148
+	defer source.Close()
149
+
150
+	apiclient := testEnv.APIClient()
151
+	resp, err := apiclient.ImageBuild(ctx,
152
+		source.AsTarReader(t),
153
+		types.ImageBuildOptions{
154
+			Remove:      true,
155
+			ForceRemove: true,
156
+			Tags:        []string{"build1"},
157
+		})
158
+	require.NoError(t, err)
159
+	_, err = io.Copy(ioutil.Discard, resp.Body)
160
+	resp.Body.Close()
161
+	require.NoError(t, err)
162
+
163
+	image, _, err := apiclient.ImageInspectWithRaw(ctx, "build1")
164
+	require.NoError(t, err)
165
+
166
+	assert.Equal(t, "/foo/sub2", image.Config.WorkingDir)
167
+	assert.Contains(t, image.Config.Env, "WHO=parent")
168
+}