From 9bcd5d2574fe0c84542d2fa18232c34e2a9c0cac Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 9 Nov 2017 16:20:51 -0500 Subject: [PATCH] Add deepCopyRunConfig for copying buidler runConfig Signed-off-by: Daniel Nephin --- builder/dockerfile/evaluator.go | 3 +- builder/dockerfile/internals.go | 51 +++++++++++++++++++++++----- builder/dockerfile/internals_test.go | 39 +++++++++++++++++++++ integration/build/build_test.go | 40 ++++++++++++++++++++++ 4 files changed, 124 insertions(+), 9 deletions(-) diff --git a/builder/dockerfile/evaluator.go b/builder/dockerfile/evaluator.go index da97a7ff6e..6236a194d3 100644 --- a/builder/dockerfile/evaluator.go +++ b/builder/dockerfile/evaluator.go @@ -214,7 +214,8 @@ func (s *dispatchState) beginStage(stageName string, image builder.Image) { s.imageID = image.ImageID() if image.RunConfig() != nil { - s.runConfig = copyRunConfig(image.RunConfig()) // copy avoids referencing the same instance when 2 stages have the same base + // copy avoids referencing the same instance when 2 stages have the same base + s.runConfig = copyRunConfig(image.RunConfig()) } else { s.runConfig = &container.Config{} } diff --git a/builder/dockerfile/internals.go b/builder/dockerfile/internals.go index 0e08ec25f0..c38f48afc0 100644 --- a/builder/dockerfile/internals.go +++ b/builder/dockerfile/internals.go @@ -25,6 +25,7 @@ import ( "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" + "github.com/docker/go-connections/nat" lcUser "github.com/opencontainers/runc/libcontainer/user" "github.com/pkg/errors" ) @@ -385,14 +386,6 @@ func hashStringSlice(prefix string, slice []string) string { type runConfigModifier func(*container.Config) -func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config { - copy := *runConfig - for _, modifier := range modifiers { - modifier(©) - } - return © -} - func withCmd(cmd []string) runConfigModifier { return func(runConfig *container.Config) { runConfig.Cmd = cmd @@ -438,6 +431,48 @@ func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier } } +func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config { + copy := *runConfig + copy.Cmd = copyStringSlice(runConfig.Cmd) + copy.Env = copyStringSlice(runConfig.Env) + copy.Entrypoint = copyStringSlice(runConfig.Entrypoint) + copy.OnBuild = copyStringSlice(runConfig.OnBuild) + copy.Shell = copyStringSlice(runConfig.Shell) + + if copy.Volumes != nil { + copy.Volumes = make(map[string]struct{}, len(runConfig.Volumes)) + for k, v := range runConfig.Volumes { + copy.Volumes[k] = v + } + } + + if copy.ExposedPorts != nil { + copy.ExposedPorts = make(nat.PortSet, len(runConfig.ExposedPorts)) + for k, v := range runConfig.ExposedPorts { + copy.ExposedPorts[k] = v + } + } + + if copy.Labels != nil { + copy.Labels = make(map[string]string, len(runConfig.Labels)) + for k, v := range runConfig.Labels { + copy.Labels[k] = v + } + } + + for _, modifier := range modifiers { + modifier(©) + } + return © +} + +func copyStringSlice(orig []string) []string { + if orig == nil { + return nil + } + return append([]string{}, orig...) +} + // getShell is a helper function which gets the right shell for prefixing the // shell-form of RUN, ENTRYPOINT and CMD instructions func getShell(c *container.Config, os string) []string { diff --git a/builder/dockerfile/internals_test.go b/builder/dockerfile/internals_test.go index 380d86108e..83a207c455 100644 --- a/builder/dockerfile/internals_test.go +++ b/builder/dockerfile/internals_test.go @@ -14,6 +14,7 @@ import ( "github.com/docker/docker/builder/remotecontext" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/idtools" + "github.com/docker/go-connections/nat" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -133,6 +134,44 @@ func TestCopyRunConfig(t *testing.T) { } +func fullMutableRunConfig() *container.Config { + return &container.Config{ + Cmd: []string{"command", "arg1"}, + Env: []string{"env1=foo", "env2=bar"}, + ExposedPorts: nat.PortSet{ + "1000/tcp": {}, + "1001/tcp": {}, + }, + Volumes: map[string]struct{}{ + "one": {}, + "two": {}, + }, + Entrypoint: []string{"entry", "arg1"}, + OnBuild: []string{"first", "next"}, + Labels: map[string]string{ + "label1": "value1", + "label2": "value2", + }, + Shell: []string{"shell", "-c"}, + } +} + +func TestDeepCopyRunConfig(t *testing.T) { + runConfig := fullMutableRunConfig() + copy := copyRunConfig(runConfig) + assert.Equal(t, fullMutableRunConfig(), copy) + + copy.Cmd[1] = "arg2" + copy.Env[1] = "env2=new" + copy.ExposedPorts["10002"] = struct{}{} + copy.Volumes["three"] = struct{}{} + copy.Entrypoint[1] = "arg2" + copy.OnBuild[0] = "start" + copy.Labels["label3"] = "value3" + copy.Shell[0] = "sh" + assert.Equal(t, fullMutableRunConfig(), runConfig) +} + func TestChownFlagParsing(t *testing.T) { testFiles := map[string]string{ "passwd": `root:x:0:0::/bin:/bin/false diff --git a/integration/build/build_test.go b/integration/build/build_test.go index b04cbbf205..cbaa7dc9bb 100644 --- a/integration/build/build_test.go +++ b/integration/build/build_test.go @@ -6,13 +6,16 @@ import ( "context" "encoding/json" "io" + "io/ioutil" "strings" "testing" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/integration-cli/cli/build/fakecontext" "github.com/docker/docker/integration/util/request" "github.com/docker/docker/pkg/jsonmessage" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -129,3 +132,40 @@ func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) { } } } + +func TestBuildMultiStageParentConfig(t *testing.T) { + dockerfile := ` + FROM busybox AS stage0 + ENV WHO=parent + WORKDIR /foo + + FROM stage0 + ENV WHO=sibling1 + WORKDIR sub1 + + FROM stage0 + WORKDIR sub2 + ` + ctx := context.Background() + source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile)) + defer source.Close() + + apiclient := testEnv.APIClient() + resp, err := apiclient.ImageBuild(ctx, + source.AsTarReader(t), + types.ImageBuildOptions{ + Remove: true, + ForceRemove: true, + Tags: []string{"build1"}, + }) + require.NoError(t, err) + _, err = io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() + require.NoError(t, err) + + image, _, err := apiclient.ImageInspectWithRaw(ctx, "build1") + require.NoError(t, err) + + assert.Equal(t, "/foo/sub2", image.Config.WorkingDir) + assert.Contains(t, image.Config.Env, "WHO=parent") +}