فهرست منبع

Add deepCopyRunConfig for copying buidler runConfig

Signed-off-by: Daniel Nephin <dnephin@docker.com>
Daniel Nephin 7 سال پیش
والد
کامیت
9bcd5d2574
4فایلهای تغییر یافته به همراه124 افزوده شده و 9 حذف شده
  1. 2 1
      builder/dockerfile/evaluator.go
  2. 43 8
      builder/dockerfile/internals.go
  3. 39 0
      builder/dockerfile/internals_test.go
  4. 40 0
      integration/build/build_test.go

+ 2 - 1
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{}
 	}

+ 43 - 8
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(&copy)
-	}
-	return &copy
-}
-
 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(&copy)
+	}
+	return &copy
+}
+
+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 {

+ 39 - 0
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

+ 40 - 0
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")
+}