Fixed merge conflict from master
This commit is contained in:
		
						commit
						aa634e4c5c
					
				|  | @ -12,3 +12,9 @@ COPY ["context/foo", "/tmp/foo" ] | ||||||
| COPY context/b* /baz/ | COPY context/b* /baz/ | ||||||
| COPY context/foo context/bar/ba? /test/ | COPY context/foo context/bar/ba? /test/ | ||||||
| COPY context/arr[[]0].txt /mydir/ | COPY context/arr[[]0].txt /mydir/ | ||||||
|  | COPY context/bar/bat . | ||||||
|  | 
 | ||||||
|  | ENV contextenv ./context | ||||||
|  | COPY ${contextenv}/foo /tmp/foo2 | ||||||
|  | COPY $contextenv/foo /tmp/foo3 | ||||||
|  | COPY $contextenv/* /tmp/${contextenv}/ | ||||||
|  |  | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | FROM gcr.io/distroless/base:latest | ||||||
|  | CMD ["command", "one"] | ||||||
|  | CMD ["command", "two"] | ||||||
|  | CMD echo "hello" | ||||||
|  | 
 | ||||||
|  | ENTRYPOINT ["execute", "something"] | ||||||
|  | ENTRYPOINT ["execute", "entrypoint"] | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | # Copyright 2018 Google, Inc. All rights reserved. | ||||||
|  | # | ||||||
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | # you may not use this file except in compliance with the License. | ||||||
|  | # You may obtain a copy of the License at | ||||||
|  | # | ||||||
|  | #     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | # Unless required by applicable law or agreed to in writing, software | ||||||
|  | # distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | # See the License for the specific language governing permissions and | ||||||
|  | # limitations under the License. | ||||||
|  | 
 | ||||||
|  | FROM gcr.io/google-appengine/debian9 | ||||||
|  | RUN useradd testuser | ||||||
|  | RUN groupadd testgroup | ||||||
|  | USER testuser:testgroup | ||||||
|  | RUN echo "hey" > /tmp/foo | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | FROM gcr.io/google-appengine/debian9:latest | ||||||
|  | COPY context/foo foo | ||||||
|  | WORKDIR /test | ||||||
|  | # Test that this will be appended on to the previous command, to create /test/workdir | ||||||
|  | WORKDIR workdir  | ||||||
|  | COPY context/foo ./currentfoo | ||||||
|  | # Test that the RUN command will happen in the correct directory | ||||||
|  | RUN cp currentfoo newfoo | ||||||
|  | WORKDIR /new/dir | ||||||
|  | ENV dir /another/new/dir | ||||||
|  | WORKDIR $dir/newdir | ||||||
|  | WORKDIR $dir/$doesntexist | ||||||
|  | WORKDIR / | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | [ | ||||||
|  |   { | ||||||
|  |     "Image1": "gcr.io/kbuild-test/docker-test-workdir:latest", | ||||||
|  |     "Image2": "gcr.io/kbuild-test/kbuild-test-workdir:latest", | ||||||
|  |     "DiffType": "File", | ||||||
|  |     "Diff": { | ||||||
|  |       "Adds": null, | ||||||
|  |       "Dels": null, | ||||||
|  |       "Mods": null | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | ] | ||||||
|  | @ -0,0 +1,4 @@ | ||||||
|  | schemaVersion: '2.0.0' | ||||||
|  | metadataTest: | ||||||
|  |   cmd: ["/bin/sh", "-c", "echo \"hello\""] | ||||||
|  |   entrypoint: ["execute", "entrypoint"] | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | schemaVersion: '2.0.0' | ||||||
|  | commandTests: | ||||||
|  | - name: 'whoami' | ||||||
|  |   command: 'whoami' | ||||||
|  |   expectedOutput: ['testuser'] | ||||||
|  |   excludedOutput: ['root'] | ||||||
|  | - name: 'file owner' | ||||||
|  |   command: 'ls' | ||||||
|  |   args: ['-l', '/tmp/foo'] | ||||||
|  |   expectedOutput: ['.*testuser.*', '.*testgroup.*'] | ||||||
|  |   excludedOutput: ['.*root.*'] | ||||||
|  | fileContentTests: | ||||||
|  | - name: "/tmp/foo" | ||||||
|  |   path: "/tmp/foo" | ||||||
|  |   expectedContent: ["hey"] | ||||||
|  | @ -87,6 +87,14 @@ var fileTests = []struct { | ||||||
| 		kbuildContextBucket: true, | 		kbuildContextBucket: true, | ||||||
| 		repo:                "test-bucket-buildcontext", | 		repo:                "test-bucket-buildcontext", | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		description:    "test workdir", | ||||||
|  | 		dockerfilePath: "/workspace/integration_tests/dockerfiles/Dockerfile_test_workdir", | ||||||
|  | 		configPath:     "/workspace/integration_tests/dockerfiles/config_test_workdir.json", | ||||||
|  | 		dockerContext:  buildcontextPath, | ||||||
|  | 		kbuildContext:  buildcontextPath, | ||||||
|  | 		repo:           "test-workdir", | ||||||
|  | 	}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var structureTests = []struct { | var structureTests = []struct { | ||||||
|  | @ -105,6 +113,22 @@ var structureTests = []struct { | ||||||
| 		kbuildContext:         dockerfilesPath, | 		kbuildContext:         dockerfilesPath, | ||||||
| 		structureTestYamlPath: "/workspace/integration_tests/dockerfiles/test_env.yaml", | 		structureTestYamlPath: "/workspace/integration_tests/dockerfiles/test_env.yaml", | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		description:           "test metadata", | ||||||
|  | 		dockerfilePath:        "/workspace/integration_tests/dockerfiles/Dockerfile_test_metadata", | ||||||
|  | 		repo:                  "test-metadata", | ||||||
|  | 		dockerBuildContext:    dockerfilesPath, | ||||||
|  | 		kbuildContext:         dockerfilesPath, | ||||||
|  | 		structureTestYamlPath: "/workspace/integration_tests/dockerfiles/test_metadata.yaml", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		description:           "test user command", | ||||||
|  | 		dockerfilePath:        "/workspace/integration_tests/dockerfiles/Dockerfile_test_user_run", | ||||||
|  | 		repo:                  "test-user", | ||||||
|  | 		dockerBuildContext:    dockerfilesPath, | ||||||
|  | 		kbuildContext:         dockerfilesPath, | ||||||
|  | 		structureTestYamlPath: "/workspace/integration_tests/dockerfiles/test_user.yaml", | ||||||
|  | 	}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type step struct { | type step struct { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,65 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type CmdCommand struct { | ||||||
|  | 	cmd *instructions.CmdCommand | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ExecuteCommand executes the CMD command
 | ||||||
|  | // Argument handling is the same as RUN.
 | ||||||
|  | func (c *CmdCommand) ExecuteCommand(config *manifest.Schema2Config) error { | ||||||
|  | 	logrus.Info("cmd: CMD") | ||||||
|  | 	var newCommand []string | ||||||
|  | 	if c.cmd.PrependShell { | ||||||
|  | 		// This is the default shell on Linux
 | ||||||
|  | 		// TODO: Support shell command here
 | ||||||
|  | 		shell := []string{"/bin/sh", "-c"} | ||||||
|  | 		newCommand = append(shell, strings.Join(c.cmd.CmdLine, " ")) | ||||||
|  | 	} else { | ||||||
|  | 		newCommand = c.cmd.CmdLine | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Infof("Replacing CMD in config with %v", newCommand) | ||||||
|  | 	config.Cmd = newCommand | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FilesToSnapshot returns an empty array since this is a metadata command
 | ||||||
|  | func (c *CmdCommand) FilesToSnapshot() []string { | ||||||
|  | 	return []string{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreatedBy returns some information about the command for the image config history
 | ||||||
|  | func (c *CmdCommand) CreatedBy() string { | ||||||
|  | 	cmd := []string{"CMD"} | ||||||
|  | 	cmdLine := strings.Join(c.cmd.CmdLine, " ") | ||||||
|  | 	if c.cmd.PrependShell { | ||||||
|  | 		// TODO: Support shell command here
 | ||||||
|  | 		shell := []string{"/bin/sh", "-c"} | ||||||
|  | 		appendedShell := append(cmd, shell...) | ||||||
|  | 		return strings.Join(append(appendedShell, cmdLine), " ") | ||||||
|  | 	} | ||||||
|  | 	return strings.Join(append(cmd, cmdLine), " ") | ||||||
|  | } | ||||||
|  | @ -0,0 +1,61 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/GoogleCloudPlatform/k8s-container-builder/testutil" | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/containers/image/pkg/strslice" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var cmdTests = []struct { | ||||||
|  | 	prependShell bool | ||||||
|  | 	cmdLine      []string | ||||||
|  | 	expectedCmd  strslice.StrSlice | ||||||
|  | }{ | ||||||
|  | 	{ | ||||||
|  | 		prependShell: true, | ||||||
|  | 		cmdLine:      []string{"echo", "cmd1"}, | ||||||
|  | 		expectedCmd:  strslice.StrSlice{"/bin/sh", "-c", "echo cmd1"}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		prependShell: false, | ||||||
|  | 		cmdLine:      []string{"echo", "cmd2"}, | ||||||
|  | 		expectedCmd:  strslice.StrSlice{"echo", "cmd2"}, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestExecuteCmd(t *testing.T) { | ||||||
|  | 
 | ||||||
|  | 	cfg := &manifest.Schema2Config{ | ||||||
|  | 		Cmd: nil, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, test := range cmdTests { | ||||||
|  | 		cmd := CmdCommand{ | ||||||
|  | 			&instructions.CmdCommand{ | ||||||
|  | 				ShellDependantCmdLine: instructions.ShellDependantCmdLine{ | ||||||
|  | 					PrependShell: test.prependShell, | ||||||
|  | 					CmdLine:      test.cmdLine, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		err := cmd.ExecuteCommand(cfg) | ||||||
|  | 		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedCmd, cfg.Cmd) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -44,6 +44,16 @@ func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, e | ||||||
| 		return &ExposeCommand{cmd: c}, nil | 		return &ExposeCommand{cmd: c}, nil | ||||||
| 	case *instructions.EnvCommand: | 	case *instructions.EnvCommand: | ||||||
| 		return &EnvCommand{cmd: c}, nil | 		return &EnvCommand{cmd: c}, nil | ||||||
|  | 	case *instructions.WorkdirCommand: | ||||||
|  | 		return &WorkdirCommand{cmd: c}, nil | ||||||
|  | 	case *instructions.CmdCommand: | ||||||
|  | 		return &CmdCommand{cmd: c}, nil | ||||||
|  | 	case *instructions.EntrypointCommand: | ||||||
|  | 		return &EntrypointCommand{cmd: c}, nil | ||||||
|  | 	case *instructions.LabelCommand: | ||||||
|  | 		return &LabelCommand{cmd: c}, nil | ||||||
|  | 	case *instructions.UserCommand: | ||||||
|  | 		return &UserCommand{cmd: c}, nil | ||||||
| 	} | 	} | ||||||
| 	return nil, errors.Errorf("%s is not a supported command", cmd.Name()) | 	return nil, errors.Errorf("%s is not a supported command", cmd.Name()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -39,8 +39,14 @@ func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error { | ||||||
| 	logrus.Infof("cmd: copy %s", srcs) | 	logrus.Infof("cmd: copy %s", srcs) | ||||||
| 	logrus.Infof("dest: %s", dest) | 	logrus.Infof("dest: %s", dest) | ||||||
| 
 | 
 | ||||||
|  | 	// First, resolve any environment replacement
 | ||||||
|  | 	resolvedEnvs, err := util.ResolveEnvironmentReplacementList(c.cmd.SourcesAndDest, config.Env, true) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	dest = resolvedEnvs[len(resolvedEnvs)-1] | ||||||
| 	// Get a map of [src]:[files rooted at src]
 | 	// Get a map of [src]:[files rooted at src]
 | ||||||
| 	srcMap, err := util.ResolveSources(c.cmd.SourcesAndDest, c.buildcontext) | 	srcMap, err := util.ResolveSources(resolvedEnvs, c.buildcontext) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,64 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type EntrypointCommand struct { | ||||||
|  | 	cmd *instructions.EntrypointCommand | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ExecuteCommand handles command processing similar to CMD and RUN,
 | ||||||
|  | func (e *EntrypointCommand) ExecuteCommand(config *manifest.Schema2Config) error { | ||||||
|  | 	logrus.Info("cmd: ENTRYPOINT") | ||||||
|  | 	var newCommand []string | ||||||
|  | 	if e.cmd.PrependShell { | ||||||
|  | 		// This is the default shell on Linux
 | ||||||
|  | 		// TODO: Support shell command here
 | ||||||
|  | 		shell := []string{"/bin/sh", "-c"} | ||||||
|  | 		newCommand = append(shell, strings.Join(e.cmd.CmdLine, " ")) | ||||||
|  | 	} else { | ||||||
|  | 		newCommand = e.cmd.CmdLine | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Infof("Replacing Entrypoint in config with %v", newCommand) | ||||||
|  | 	config.Entrypoint = newCommand | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FilesToSnapshot returns an empty array since this is a metadata command
 | ||||||
|  | func (e *EntrypointCommand) FilesToSnapshot() []string { | ||||||
|  | 	return []string{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreatedBy returns some information about the command for the image config history
 | ||||||
|  | func (e *EntrypointCommand) CreatedBy() string { | ||||||
|  | 	entrypoint := []string{"ENTRYPOINT"} | ||||||
|  | 	cmdLine := strings.Join(e.cmd.CmdLine, " ") | ||||||
|  | 	if e.cmd.PrependShell { | ||||||
|  | 		// TODO: Support shell command here
 | ||||||
|  | 		shell := []string{"/bin/sh", "-c"} | ||||||
|  | 		appendedShell := append(entrypoint, shell...) | ||||||
|  | 		return strings.Join(append(appendedShell, cmdLine), " ") | ||||||
|  | 	} | ||||||
|  | 	return strings.Join(append(entrypoint, cmdLine), " ") | ||||||
|  | } | ||||||
|  | @ -0,0 +1,61 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/GoogleCloudPlatform/k8s-container-builder/testutil" | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/containers/image/pkg/strslice" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var entrypointTests = []struct { | ||||||
|  | 	prependShell bool | ||||||
|  | 	cmdLine      []string | ||||||
|  | 	expectedCmd  strslice.StrSlice | ||||||
|  | }{ | ||||||
|  | 	{ | ||||||
|  | 		prependShell: true, | ||||||
|  | 		cmdLine:      []string{"echo", "cmd1"}, | ||||||
|  | 		expectedCmd:  strslice.StrSlice{"/bin/sh", "-c", "echo cmd1"}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		prependShell: false, | ||||||
|  | 		cmdLine:      []string{"echo", "cmd2"}, | ||||||
|  | 		expectedCmd:  strslice.StrSlice{"echo", "cmd2"}, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestEntrypointExecuteCmd(t *testing.T) { | ||||||
|  | 
 | ||||||
|  | 	cfg := &manifest.Schema2Config{ | ||||||
|  | 		Cmd: nil, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, test := range entrypointTests { | ||||||
|  | 		cmd := EntrypointCommand{ | ||||||
|  | 			&instructions.EntrypointCommand{ | ||||||
|  | 				ShellDependantCmdLine: instructions.ShellDependantCmdLine{ | ||||||
|  | 					PrependShell: test.prependShell, | ||||||
|  | 					CmdLine:      test.cmdLine, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		err := cmd.ExecuteCommand(cfg) | ||||||
|  | 		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedCmd, cfg.Entrypoint) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -17,11 +17,9 @@ limitations under the License. | ||||||
| package commands | package commands | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util" | ||||||
| 	"github.com/containers/image/manifest" | 	"github.com/containers/image/manifest" | ||||||
| 	"github.com/docker/docker/builder/dockerfile/instructions" | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
| 	"github.com/docker/docker/builder/dockerfile/parser" |  | ||||||
| 	"github.com/docker/docker/builder/dockerfile/shell" |  | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | @ -33,26 +31,18 @@ type EnvCommand struct { | ||||||
| 
 | 
 | ||||||
| func (e *EnvCommand) ExecuteCommand(config *manifest.Schema2Config) error { | func (e *EnvCommand) ExecuteCommand(config *manifest.Schema2Config) error { | ||||||
| 	logrus.Info("cmd: ENV") | 	logrus.Info("cmd: ENV") | ||||||
| 	// The dockerfile/shell package handles processing env values
 |  | ||||||
| 	// It handles escape characters and supports expansion from the config.Env array
 |  | ||||||
| 	// Shlex handles some of the following use cases (these and more are tested in integration tests)
 |  | ||||||
| 	// ""a'b'c"" -> "a'b'c"
 |  | ||||||
| 	// "Rex\ The\ Dog \" -> "Rex The Dog"
 |  | ||||||
| 	// "a\"b" -> "a"b"
 |  | ||||||
| 	envString := envToString(e.cmd) |  | ||||||
| 	p, err := parser.Parse(bytes.NewReader([]byte(envString))) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	shlex := shell.NewLex(p.EscapeToken) |  | ||||||
| 	newEnvs := e.cmd.Env | 	newEnvs := e.cmd.Env | ||||||
| 	for index, pair := range newEnvs { | 	for index, pair := range newEnvs { | ||||||
| 		expandedValue, err := shlex.ProcessWord(pair.Value, config.Env) | 		expandedKey, err := util.ResolveEnvironmentReplacement(pair.Key, config.Env, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		expandedValue, err := util.ResolveEnvironmentReplacement(pair.Value, config.Env, false) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		newEnvs[index] = instructions.KeyValuePair{ | 		newEnvs[index] = instructions.KeyValuePair{ | ||||||
| 			Key:   pair.Key, | 			Key:   expandedKey, | ||||||
| 			Value: expandedValue, | 			Value: expandedValue, | ||||||
| 		} | 		} | ||||||
| 		logrus.Infof("Setting environment variable %s=%s", pair.Key, expandedValue) | 		logrus.Infof("Setting environment variable %s=%s", pair.Key, expandedValue) | ||||||
|  | @ -98,14 +88,6 @@ Loop: | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func envToString(cmd *instructions.EnvCommand) string { |  | ||||||
| 	env := []string{"ENV"} |  | ||||||
| 	for _, kvp := range cmd.Env { |  | ||||||
| 		env = append(env, kvp.Key+"="+kvp.Value) |  | ||||||
| 	} |  | ||||||
| 	return strings.Join(env, " ") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // We know that no files have changed, so return an empty array
 | // We know that no files have changed, so return an empty array
 | ||||||
| func (e *EnvCommand) FilesToSnapshot() []string { | func (e *EnvCommand) FilesToSnapshot() []string { | ||||||
| 	return []string{} | 	return []string{} | ||||||
|  |  | ||||||
|  | @ -53,21 +53,39 @@ func TestUpdateEnvConfig(t *testing.T) { | ||||||
| 	updateConfigEnv(newEnvs, cfg) | 	updateConfigEnv(newEnvs, cfg) | ||||||
| 	testutil.CheckErrorAndDeepEqual(t, false, nil, expectedEnvArray, cfg.Env) | 	testutil.CheckErrorAndDeepEqual(t, false, nil, expectedEnvArray, cfg.Env) | ||||||
| } | } | ||||||
|  | func Test_EnvExecute(t *testing.T) { | ||||||
|  | 	cfg := &manifest.Schema2Config{ | ||||||
|  | 		Env: []string{ | ||||||
|  | 			"path=/usr/", | ||||||
|  | 			"home=/root", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| func TestEnvToString(t *testing.T) { | 	envCmd := &EnvCommand{ | ||||||
| 	envCmd := &instructions.EnvCommand{ | 		&instructions.EnvCommand{ | ||||||
| 		Env: []instructions.KeyValuePair{ | 			Env: []instructions.KeyValuePair{ | ||||||
| 			{ | 				{ | ||||||
| 				Key:   "PATH", | 					Key:   "path", | ||||||
| 				Value: "/some/path", | 					Value: "/some/path", | ||||||
| 			}, | 				}, | ||||||
| 			{ | 				{ | ||||||
| 				Key:   "HOME", | 					Key:   "HOME", | ||||||
| 				Value: "/root", | 					Value: "$home", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					Key:   "$path", | ||||||
|  | 					Value: "$home/", | ||||||
|  | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	expectedString := "ENV PATH=/some/path HOME=/root" | 
 | ||||||
| 	actualString := envToString(envCmd) | 	expectedEnvs := []string{ | ||||||
| 	testutil.CheckErrorAndDeepEqual(t, false, nil, expectedString, actualString) | 		"path=/some/path", | ||||||
|  | 		"home=/root", | ||||||
|  | 		"HOME=/root", | ||||||
|  | 		"/usr/=/root/", | ||||||
|  | 	} | ||||||
|  | 	err := envCmd.ExecuteCommand(cfg) | ||||||
|  | 	testutil.CheckErrorAndDeepEqual(t, false, err, expectedEnvs, cfg.Env) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ package commands | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util" | ||||||
| 	"github.com/containers/image/manifest" | 	"github.com/containers/image/manifest" | ||||||
| 	"github.com/docker/docker/builder/dockerfile/instructions" | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | @ -29,25 +30,16 @@ type ExposeCommand struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *ExposeCommand) ExecuteCommand(config *manifest.Schema2Config) error { | func (r *ExposeCommand) ExecuteCommand(config *manifest.Schema2Config) error { | ||||||
| 	return updateExposedPorts(r.cmd.Ports, config) | 	logrus.Info("cmd: EXPOSE") | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func validProtocol(protocol string) bool { |  | ||||||
| 	validProtocols := [2]string{"tcp", "udp"} |  | ||||||
| 	for _, p := range validProtocols { |  | ||||||
| 		if protocol == p { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func updateExposedPorts(ports []string, config *manifest.Schema2Config) error { |  | ||||||
| 	// Grab the currently exposed ports
 | 	// Grab the currently exposed ports
 | ||||||
| 	existingPorts := config.ExposedPorts | 	existingPorts := config.ExposedPorts | ||||||
| 
 |  | ||||||
| 	// Add any new ones in
 | 	// Add any new ones in
 | ||||||
| 	for _, p := range ports { | 	for _, p := range r.cmd.Ports { | ||||||
|  | 		// Resolve any environment variables
 | ||||||
|  | 		p, err := util.ResolveEnvironmentReplacement(p, config.Env, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
| 		// Add the default protocol if one isn't specified
 | 		// Add the default protocol if one isn't specified
 | ||||||
| 		if !strings.Contains(p, "/") { | 		if !strings.Contains(p, "/") { | ||||||
| 			p = p + "/tcp" | 			p = p + "/tcp" | ||||||
|  | @ -64,11 +56,21 @@ func updateExposedPorts(ports []string, config *manifest.Schema2Config) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func validProtocol(protocol string) bool { | ||||||
|  | 	validProtocols := [2]string{"tcp", "udp"} | ||||||
|  | 	for _, p := range validProtocols { | ||||||
|  | 		if protocol == p { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (r *ExposeCommand) FilesToSnapshot() []string { | func (r *ExposeCommand) FilesToSnapshot() []string { | ||||||
| 	return []string{} | 	return []string{} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *ExposeCommand) CreatedBy() string { | func (r *ExposeCommand) CreatedBy() string { | ||||||
| 	s := []string{"/bin/sh", "-c"} | 	s := []string{r.cmd.Name()} | ||||||
| 	return strings.Join(append(s, r.cmd.Ports...), " ") | 	return strings.Join(append(s, r.cmd.Ports...), " ") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ package commands | ||||||
| import ( | import ( | ||||||
| 	"github.com/GoogleCloudPlatform/k8s-container-builder/testutil" | 	"github.com/GoogleCloudPlatform/k8s-container-builder/testutil" | ||||||
| 	"github.com/containers/image/manifest" | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -27,6 +28,10 @@ func TestUpdateExposedPorts(t *testing.T) { | ||||||
| 		ExposedPorts: manifest.Schema2PortSet{ | 		ExposedPorts: manifest.Schema2PortSet{ | ||||||
| 			"8080/tcp": {}, | 			"8080/tcp": {}, | ||||||
| 		}, | 		}, | ||||||
|  | 		Env: []string{ | ||||||
|  | 			"port=udp", | ||||||
|  | 			"num=8085", | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ports := []string{ | 	ports := []string{ | ||||||
|  | @ -34,6 +39,15 @@ func TestUpdateExposedPorts(t *testing.T) { | ||||||
| 		"8081/tcp", | 		"8081/tcp", | ||||||
| 		"8082", | 		"8082", | ||||||
| 		"8083/udp", | 		"8083/udp", | ||||||
|  | 		"8084/$port", | ||||||
|  | 		"$num", | ||||||
|  | 		"$num/$port", | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	exposeCmd := &ExposeCommand{ | ||||||
|  | 		&instructions.ExposeCommand{ | ||||||
|  | 			Ports: ports, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	expectedPorts := manifest.Schema2PortSet{ | 	expectedPorts := manifest.Schema2PortSet{ | ||||||
|  | @ -41,9 +55,12 @@ func TestUpdateExposedPorts(t *testing.T) { | ||||||
| 		"8081/tcp": {}, | 		"8081/tcp": {}, | ||||||
| 		"8082/tcp": {}, | 		"8082/tcp": {}, | ||||||
| 		"8083/udp": {}, | 		"8083/udp": {}, | ||||||
|  | 		"8084/udp": {}, | ||||||
|  | 		"8085/tcp": {}, | ||||||
|  | 		"8085/udp": {}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err := updateExposedPorts(ports, cfg) | 	err := exposeCmd.ExecuteCommand(cfg) | ||||||
| 	testutil.CheckErrorAndDeepEqual(t, false, err, expectedPorts, cfg.ExposedPorts) | 	testutil.CheckErrorAndDeepEqual(t, false, err, expectedPorts, cfg.ExposedPorts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -56,6 +73,12 @@ func TestInvalidProtocol(t *testing.T) { | ||||||
| 		"80/garbage", | 		"80/garbage", | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err := updateExposedPorts(ports, cfg) | 	exposeCmd := &ExposeCommand{ | ||||||
|  | 		&instructions.ExposeCommand{ | ||||||
|  | 			Ports: ports, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err := exposeCmd.ExecuteCommand(cfg) | ||||||
| 	testutil.CheckErrorAndDeepEqual(t, true, err, nil, nil) | 	testutil.CheckErrorAndDeepEqual(t, true, err, nil, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,72 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util" | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type LabelCommand struct { | ||||||
|  | 	cmd *instructions.LabelCommand | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *LabelCommand) ExecuteCommand(config *manifest.Schema2Config) error { | ||||||
|  | 	logrus.Info("cmd: LABEL") | ||||||
|  | 	return updateLabels(r.cmd.Labels, config) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func updateLabels(labels []instructions.KeyValuePair, config *manifest.Schema2Config) error { | ||||||
|  | 	existingLabels := config.Labels | ||||||
|  | 
 | ||||||
|  | 	// Let's unescape values before setting the label
 | ||||||
|  | 	for index, kvp := range labels { | ||||||
|  | 		unescaped, err := util.ResolveEnvironmentReplacement(kvp.Value, []string{}, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		labels[index] = instructions.KeyValuePair{ | ||||||
|  | 			Key:   kvp.Key, | ||||||
|  | 			Value: unescaped, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, kvp := range labels { | ||||||
|  | 		logrus.Infof("Applying label %s=%s", kvp.Key, kvp.Value) | ||||||
|  | 		existingLabels[kvp.Key] = kvp.Value | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	config.Labels = existingLabels | ||||||
|  | 	return nil | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // No files have changed, this command only touches metadata.
 | ||||||
|  | func (r *LabelCommand) FilesToSnapshot() []string { | ||||||
|  | 	return []string{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreatedBy returns some information about the command for the image config history
 | ||||||
|  | func (r *LabelCommand) CreatedBy() string { | ||||||
|  | 	l := []string{r.cmd.Name()} | ||||||
|  | 	for _, kvp := range r.cmd.Labels { | ||||||
|  | 		l = append(l, kvp.String()) | ||||||
|  | 	} | ||||||
|  | 	return strings.Join(l, " ") | ||||||
|  | } | ||||||
|  | @ -0,0 +1,60 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/GoogleCloudPlatform/k8s-container-builder/testutil" | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestUpdateLabels(t *testing.T) { | ||||||
|  | 	cfg := &manifest.Schema2Config{ | ||||||
|  | 		Labels: map[string]string{ | ||||||
|  | 			"foo": "bar", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	labels := []instructions.KeyValuePair{ | ||||||
|  | 		{ | ||||||
|  | 			Key:   "foo", | ||||||
|  | 			Value: "override", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Key:   "bar", | ||||||
|  | 			Value: "baz", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Key:   "multiword", | ||||||
|  | 			Value: "lots\\ of\\ words", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Key:   "backslashes", | ||||||
|  | 			Value: "lots\\\\ of\\\\ words", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	expectedLabels := map[string]string{ | ||||||
|  | 		"foo":         "override", | ||||||
|  | 		"bar":         "baz", | ||||||
|  | 		"multiword":   "lots of words", | ||||||
|  | 		"backslashes": "lots\\ of\\ words", | ||||||
|  | 	} | ||||||
|  | 	updateLabels(labels, cfg) | ||||||
|  | 	testutil.CheckErrorAndDeepEqual(t, false, nil, expectedLabels, cfg.Labels) | ||||||
|  | } | ||||||
|  | @ -22,7 +22,9 @@ import ( | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"syscall" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type RunCommand struct { | type RunCommand struct { | ||||||
|  | @ -44,7 +46,28 @@ func (r *RunCommand) ExecuteCommand(config *manifest.Schema2Config) error { | ||||||
| 	logrus.Infof("args: %s", newCommand[1:]) | 	logrus.Infof("args: %s", newCommand[1:]) | ||||||
| 
 | 
 | ||||||
| 	cmd := exec.Command(newCommand[0], newCommand[1:]...) | 	cmd := exec.Command(newCommand[0], newCommand[1:]...) | ||||||
|  | 	cmd.Dir = config.WorkingDir | ||||||
| 	cmd.Stdout = os.Stdout | 	cmd.Stdout = os.Stdout | ||||||
|  | 	// If specified, run the command as a specific user
 | ||||||
|  | 	if config.User != "" { | ||||||
|  | 		userAndGroup := strings.Split(config.User, ":") | ||||||
|  | 		// uid and gid need to be uint32
 | ||||||
|  | 		uid64, err := strconv.ParseUint(userAndGroup[0], 10, 32) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		uid := uint32(uid64) | ||||||
|  | 		var gid uint32 | ||||||
|  | 		if len(userAndGroup) > 1 { | ||||||
|  | 			gid64, err := strconv.ParseUint(userAndGroup[1], 10, 32) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			gid = uint32(gid64) | ||||||
|  | 		} | ||||||
|  | 		cmd.SysProcAttr = &syscall.SysProcAttr{} | ||||||
|  | 		cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid} | ||||||
|  | 	} | ||||||
| 	return cmd.Run() | 	return cmd.Run() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,95 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util" | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"os/user" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type UserCommand struct { | ||||||
|  | 	cmd *instructions.UserCommand | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *UserCommand) ExecuteCommand(config *manifest.Schema2Config) error { | ||||||
|  | 	logrus.Info("cmd: USER") | ||||||
|  | 	u := r.cmd.User | ||||||
|  | 	userAndGroup := strings.Split(u, ":") | ||||||
|  | 	userStr, err := util.ResolveEnvironmentReplacement(userAndGroup[0], config.Env, false) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	var groupStr string | ||||||
|  | 	if len(userAndGroup) > 1 { | ||||||
|  | 		groupStr, err = util.ResolveEnvironmentReplacement(userAndGroup[1], config.Env, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Lookup by username
 | ||||||
|  | 	userObj, err := user.Lookup(userStr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if _, ok := err.(user.UnknownUserError); ok { | ||||||
|  | 			// Lookup by id
 | ||||||
|  | 			userObj, err = user.LookupId(userStr) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Same dance with groups
 | ||||||
|  | 	var group *user.Group | ||||||
|  | 	if groupStr != "" { | ||||||
|  | 		group, err = user.LookupGroup(groupStr) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if _, ok := err.(user.UnknownGroupError); ok { | ||||||
|  | 				group, err = user.LookupGroupId(groupStr) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	uid := userObj.Uid | ||||||
|  | 	if group != nil { | ||||||
|  | 		uid = uid + ":" + group.Gid | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	logrus.Infof("Setting user to %s", uid) | ||||||
|  | 	config.User = uid | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *UserCommand) FilesToSnapshot() []string { | ||||||
|  | 	return []string{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *UserCommand) CreatedBy() string { | ||||||
|  | 	s := []string{r.cmd.Name(), r.cmd.User} | ||||||
|  | 	return strings.Join(s, " ") | ||||||
|  | } | ||||||
|  | @ -0,0 +1,98 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/GoogleCloudPlatform/k8s-container-builder/testutil" | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var userTests = []struct { | ||||||
|  | 	user        string | ||||||
|  | 	expectedUid string | ||||||
|  | 	shouldError bool | ||||||
|  | }{ | ||||||
|  | 	{ | ||||||
|  | 		user:        "root", | ||||||
|  | 		expectedUid: "0", | ||||||
|  | 		shouldError: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		user:        "0", | ||||||
|  | 		expectedUid: "0", | ||||||
|  | 		shouldError: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		user:        "fakeUser", | ||||||
|  | 		expectedUid: "", | ||||||
|  | 		shouldError: true, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		user:        "root:root", | ||||||
|  | 		expectedUid: "0:0", | ||||||
|  | 		shouldError: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		user:        "0:root", | ||||||
|  | 		expectedUid: "0:0", | ||||||
|  | 		shouldError: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		user:        "root:0", | ||||||
|  | 		expectedUid: "0:0", | ||||||
|  | 		shouldError: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		user:        "0:0", | ||||||
|  | 		expectedUid: "0:0", | ||||||
|  | 		shouldError: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		user:        "root:fakeGroup", | ||||||
|  | 		expectedUid: "", | ||||||
|  | 		shouldError: true, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		user:        "$envuser", | ||||||
|  | 		expectedUid: "0", | ||||||
|  | 		shouldError: false, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		user:        "root:$envgroup", | ||||||
|  | 		expectedUid: "0:0", | ||||||
|  | 		shouldError: false, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestUpdateUser(t *testing.T) { | ||||||
|  | 	for _, test := range userTests { | ||||||
|  | 		cfg := &manifest.Schema2Config{ | ||||||
|  | 			Env: []string{ | ||||||
|  | 				"envuser=root", | ||||||
|  | 				"envgroup=root", | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		cmd := UserCommand{ | ||||||
|  | 			&instructions.UserCommand{ | ||||||
|  | 				User: test.user, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		err := cmd.ExecuteCommand(cfg) | ||||||
|  | 		testutil.CheckErrorAndDeepEqual(t, test.shouldError, err, test.expectedUid, cfg.User) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,58 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util" | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type WorkdirCommand struct { | ||||||
|  | 	cmd           *instructions.WorkdirCommand | ||||||
|  | 	snapshotFiles []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (w *WorkdirCommand) ExecuteCommand(config *manifest.Schema2Config) error { | ||||||
|  | 	logrus.Info("cmd: workdir") | ||||||
|  | 	workdirPath := w.cmd.Path | ||||||
|  | 	resolvedWorkingDir, err := util.ResolveEnvironmentReplacement(workdirPath, config.Env, true) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if filepath.IsAbs(resolvedWorkingDir) { | ||||||
|  | 		config.WorkingDir = resolvedWorkingDir | ||||||
|  | 	} else { | ||||||
|  | 		config.WorkingDir = filepath.Join(config.WorkingDir, resolvedWorkingDir) | ||||||
|  | 	} | ||||||
|  | 	logrus.Infof("Changed working directory to %s", config.WorkingDir) | ||||||
|  | 	w.snapshotFiles = []string{config.WorkingDir} | ||||||
|  | 	return os.MkdirAll(config.WorkingDir, 0755) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FilesToSnapshot returns the workingdir, which should have been created if it didn't already exist
 | ||||||
|  | func (w *WorkdirCommand) FilesToSnapshot() []string { | ||||||
|  | 	return w.snapshotFiles | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreatedBy returns some information about the command for the image config history
 | ||||||
|  | func (w *WorkdirCommand) CreatedBy() string { | ||||||
|  | 	return w.cmd.Name() + " " + w.cmd.Path | ||||||
|  | } | ||||||
|  | @ -0,0 +1,83 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2018 Google LLC | ||||||
|  | 
 | ||||||
|  | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | you may not use this file except in compliance with the License. | ||||||
|  | You may obtain a copy of the License at | ||||||
|  | 
 | ||||||
|  |     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | 
 | ||||||
|  | Unless required by applicable law or agreed to in writing, software | ||||||
|  | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | See the License for the specific language governing permissions and | ||||||
|  | limitations under the License. | ||||||
|  | */ | ||||||
|  | package commands | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/GoogleCloudPlatform/k8s-container-builder/testutil" | ||||||
|  | 	"github.com/containers/image/manifest" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Each test here changes the same WorkingDir field in the config
 | ||||||
|  | // So, some of the tests build off of each other
 | ||||||
|  | // This is needed to make sure WorkingDir handles paths correctly
 | ||||||
|  | // For example, if WORKDIR specifies a non-absolute path, it should be appended to the current WORKDIR
 | ||||||
|  | var workdirTests = []struct { | ||||||
|  | 	path         string | ||||||
|  | 	expectedPath string | ||||||
|  | }{ | ||||||
|  | 	{ | ||||||
|  | 		path:         "/a", | ||||||
|  | 		expectedPath: "/a", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:         "b", | ||||||
|  | 		expectedPath: "/a/b", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:         "c", | ||||||
|  | 		expectedPath: "/a/b/c", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:         "/d", | ||||||
|  | 		expectedPath: "/d", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:         "$path", | ||||||
|  | 		expectedPath: "/d/usr", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:         "$home", | ||||||
|  | 		expectedPath: "/root", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:         "$path/$home", | ||||||
|  | 		expectedPath: "/root/usr/root", | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestWorkdirCommand(t *testing.T) { | ||||||
|  | 
 | ||||||
|  | 	cfg := &manifest.Schema2Config{ | ||||||
|  | 		WorkingDir: "/", | ||||||
|  | 		Env: []string{ | ||||||
|  | 			"path=usr/", | ||||||
|  | 			"home=/root", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, test := range workdirTests { | ||||||
|  | 		cmd := WorkdirCommand{ | ||||||
|  | 			cmd: &instructions.WorkdirCommand{ | ||||||
|  | 				Path: test.path, | ||||||
|  | 			}, | ||||||
|  | 			snapshotFiles: []string{}, | ||||||
|  | 		} | ||||||
|  | 		cmd.ExecuteCommand(cfg) | ||||||
|  | 		testutil.CheckErrorAndDeepEqual(t, false, nil, test.expectedPath, cfg.WorkingDir) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -18,6 +18,8 @@ package util | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/docker/docker/builder/dockerfile/instructions" | 	"github.com/docker/docker/builder/dockerfile/instructions" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/parser" | ||||||
|  | 	"github.com/docker/docker/builder/dockerfile/shell" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"os" | 	"os" | ||||||
|  | @ -25,6 +27,45 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // ResolveEnvironmentReplacement resolves a list of values by calling resolveEnvironmentReplacement
 | ||||||
|  | func ResolveEnvironmentReplacementList(values, envs []string, isFilepath bool) ([]string, error) { | ||||||
|  | 	var resolvedValues []string | ||||||
|  | 	for _, value := range values { | ||||||
|  | 		resolved, err := ResolveEnvironmentReplacement(value, envs, isFilepath) | ||||||
|  | 		logrus.Debugf("Resolved %s to %s", value, resolved) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		resolvedValues = append(resolvedValues, resolved) | ||||||
|  | 	} | ||||||
|  | 	return resolvedValues, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResolveEnvironmentReplacement resolves replacing env variables in some text from envs
 | ||||||
|  | // It takes in a string representation of the command, the value to be resolved, and a list of envs (config.Env)
 | ||||||
|  | // Ex: fp = $foo/newdir, envs = [foo=/foodir], then this should return /foodir/newdir
 | ||||||
|  | // The dockerfile/shell package handles processing env values
 | ||||||
|  | // It handles escape characters and supports expansion from the config.Env array
 | ||||||
|  | // Shlex handles some of the following use cases (these and more are tested in integration tests)
 | ||||||
|  | // ""a'b'c"" -> "a'b'c"
 | ||||||
|  | // "Rex\ The\ Dog \" -> "Rex The Dog"
 | ||||||
|  | // "a\"b" -> "a"b"
 | ||||||
|  | func ResolveEnvironmentReplacement(value string, envs []string, isFilepath bool) (string, error) { | ||||||
|  | 	shlex := shell.NewLex(parser.DefaultEscapeToken) | ||||||
|  | 	fp, err := shlex.ProcessWord(value, envs) | ||||||
|  | 	if !isFilepath { | ||||||
|  | 		return fp, err | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	fp = filepath.Clean(fp) | ||||||
|  | 	if IsDestDir(value) { | ||||||
|  | 		fp = fp + "/" | ||||||
|  | 	} | ||||||
|  | 	return fp, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ContainsWildcards returns true if any entry in paths contains wildcards
 | // ContainsWildcards returns true if any entry in paths contains wildcards
 | ||||||
| func ContainsWildcards(paths []string) bool { | func ContainsWildcards(paths []string) bool { | ||||||
| 	for _, path := range paths { | 	for _, path := range paths { | ||||||
|  |  | ||||||
|  | @ -22,6 +22,88 @@ import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var testEnvReplacement = []struct { | ||||||
|  | 	path         string | ||||||
|  | 	command      string | ||||||
|  | 	envs         []string | ||||||
|  | 	isFilepath   bool | ||||||
|  | 	expectedPath string | ||||||
|  | }{ | ||||||
|  | 	{ | ||||||
|  | 		path:    "/simple/path", | ||||||
|  | 		command: "WORKDIR /simple/path", | ||||||
|  | 		envs: []string{ | ||||||
|  | 			"simple=/path/", | ||||||
|  | 		}, | ||||||
|  | 		isFilepath:   true, | ||||||
|  | 		expectedPath: "/simple/path", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:    "/simple/path/", | ||||||
|  | 		command: "WORKDIR /simple/path/", | ||||||
|  | 		envs: []string{ | ||||||
|  | 			"simple=/path/", | ||||||
|  | 		}, | ||||||
|  | 		isFilepath:   true, | ||||||
|  | 		expectedPath: "/simple/path/", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:    "${a}/b", | ||||||
|  | 		command: "WORKDIR ${a}/b", | ||||||
|  | 		envs: []string{ | ||||||
|  | 			"a=/path/", | ||||||
|  | 			"b=/path2/", | ||||||
|  | 		}, | ||||||
|  | 		isFilepath:   true, | ||||||
|  | 		expectedPath: "/path/b", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:    "/$a/b", | ||||||
|  | 		command: "COPY ${a}/b /c/", | ||||||
|  | 		envs: []string{ | ||||||
|  | 			"a=/path/", | ||||||
|  | 			"b=/path2/", | ||||||
|  | 		}, | ||||||
|  | 		isFilepath:   true, | ||||||
|  | 		expectedPath: "/path/b", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:    "/$a/b/", | ||||||
|  | 		command: "COPY /${a}/b /c/", | ||||||
|  | 		envs: []string{ | ||||||
|  | 			"a=/path/", | ||||||
|  | 			"b=/path2/", | ||||||
|  | 		}, | ||||||
|  | 		isFilepath:   true, | ||||||
|  | 		expectedPath: "/path/b/", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:    "\\$foo", | ||||||
|  | 		command: "COPY \\$foo /quux", | ||||||
|  | 		envs: []string{ | ||||||
|  | 			"foo=/path/", | ||||||
|  | 		}, | ||||||
|  | 		isFilepath:   true, | ||||||
|  | 		expectedPath: "$foo", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		path:    "8080/$protocol", | ||||||
|  | 		command: "EXPOSE 8080/$protocol", | ||||||
|  | 		envs: []string{ | ||||||
|  | 			"protocol=udp", | ||||||
|  | 		}, | ||||||
|  | 		expectedPath: "8080/udp", | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func Test_EnvReplacement(t *testing.T) { | ||||||
|  | 	for _, test := range testEnvReplacement { | ||||||
|  | 		actualPath, err := ResolveEnvironmentReplacement(test.path, test.envs, test.isFilepath) | ||||||
|  | 		testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedPath, actualPath) | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| var buildContextPath = "../../integration_tests/" | var buildContextPath = "../../integration_tests/" | ||||||
| 
 | 
 | ||||||
| var destinationFilepathTests = []struct { | var destinationFilepathTests = []struct { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue