Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -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(©) |
|
| 392 |
- } |
|
| 393 |
- return © |
|
| 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(©) |
|
| 472 |
+ } |
|
| 473 |
+ return © |
|
| 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 |
+} |