Fixed merge conflict from master

This commit is contained in:
Priya Wadhwa 2018-04-02 11:34:04 -07:00
commit aa634e4c5c
No known key found for this signature in database
GPG Key ID: 0D0DAFD8F7AA73AE
27 changed files with 1062 additions and 58 deletions

View File

@ -12,3 +12,9 @@ COPY ["context/foo", "/tmp/foo" ]
COPY context/b* /baz/
COPY context/foo context/bar/ba? /test/
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}/

View File

@ -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"]

View File

@ -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

View File

@ -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 /

View File

@ -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
}
}
]

View File

@ -0,0 +1,4 @@
schemaVersion: '2.0.0'
metadataTest:
cmd: ["/bin/sh", "-c", "echo \"hello\""]
entrypoint: ["execute", "entrypoint"]

View File

@ -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"]

View File

@ -87,6 +87,14 @@ var fileTests = []struct {
kbuildContextBucket: true,
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 {
@ -105,6 +113,22 @@ var structureTests = []struct {
kbuildContext: dockerfilesPath,
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 {

65
pkg/commands/cmd.go Normal file
View File

@ -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), " ")
}

61
pkg/commands/cmd_test.go Normal file
View File

@ -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)
}
}

View File

@ -44,6 +44,16 @@ func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, e
return &ExposeCommand{cmd: c}, nil
case *instructions.EnvCommand:
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())
}

View File

@ -39,8 +39,14 @@ func (c *CopyCommand) ExecuteCommand(config *manifest.Schema2Config) error {
logrus.Infof("cmd: copy %s", srcs)
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]
srcMap, err := util.ResolveSources(c.cmd.SourcesAndDest, c.buildcontext)
srcMap, err := util.ResolveSources(resolvedEnvs, c.buildcontext)
if err != nil {
return err
}

View File

@ -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), " ")
}

View File

@ -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)
}
}

View File

@ -17,11 +17,9 @@ limitations under the License.
package commands
import (
"bytes"
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util"
"github.com/containers/image/manifest"
"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"
"os"
"strings"
@ -33,26 +31,18 @@ type EnvCommand struct {
func (e *EnvCommand) ExecuteCommand(config *manifest.Schema2Config) error {
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
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 {
return err
}
newEnvs[index] = instructions.KeyValuePair{
Key: pair.Key,
Key: expandedKey,
Value: expandedValue,
}
logrus.Infof("Setting environment variable %s=%s", pair.Key, expandedValue)
@ -98,14 +88,6 @@ Loop:
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
func (e *EnvCommand) FilesToSnapshot() []string {
return []string{}

View File

@ -53,21 +53,39 @@ func TestUpdateEnvConfig(t *testing.T) {
updateConfigEnv(newEnvs, cfg)
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 := &instructions.EnvCommand{
Env: []instructions.KeyValuePair{
{
Key: "PATH",
Value: "/some/path",
},
{
Key: "HOME",
Value: "/root",
envCmd := &EnvCommand{
&instructions.EnvCommand{
Env: []instructions.KeyValuePair{
{
Key: "path",
Value: "/some/path",
},
{
Key: "HOME",
Value: "$home",
},
{
Key: "$path",
Value: "$home/",
},
},
},
}
expectedString := "ENV PATH=/some/path HOME=/root"
actualString := envToString(envCmd)
testutil.CheckErrorAndDeepEqual(t, false, nil, expectedString, actualString)
expectedEnvs := []string{
"path=/some/path",
"home=/root",
"HOME=/root",
"/usr/=/root/",
}
err := envCmd.ExecuteCommand(cfg)
testutil.CheckErrorAndDeepEqual(t, false, err, expectedEnvs, cfg.Env)
}

View File

@ -18,6 +18,7 @@ package commands
import (
"fmt"
"github.com/GoogleCloudPlatform/k8s-container-builder/pkg/util"
"github.com/containers/image/manifest"
"github.com/docker/docker/builder/dockerfile/instructions"
"github.com/sirupsen/logrus"
@ -29,25 +30,16 @@ type ExposeCommand struct {
}
func (r *ExposeCommand) ExecuteCommand(config *manifest.Schema2Config) error {
return updateExposedPorts(r.cmd.Ports, config)
}
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 {
logrus.Info("cmd: EXPOSE")
// Grab the currently exposed ports
existingPorts := config.ExposedPorts
// 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
if !strings.Contains(p, "/") {
p = p + "/tcp"
@ -64,11 +56,21 @@ func updateExposedPorts(ports []string, config *manifest.Schema2Config) error {
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 {
return []string{}
}
func (r *ExposeCommand) CreatedBy() string {
s := []string{"/bin/sh", "-c"}
s := []string{r.cmd.Name()}
return strings.Join(append(s, r.cmd.Ports...), " ")
}

View File

@ -19,6 +19,7 @@ package commands
import (
"github.com/GoogleCloudPlatform/k8s-container-builder/testutil"
"github.com/containers/image/manifest"
"github.com/docker/docker/builder/dockerfile/instructions"
"testing"
)
@ -27,6 +28,10 @@ func TestUpdateExposedPorts(t *testing.T) {
ExposedPorts: manifest.Schema2PortSet{
"8080/tcp": {},
},
Env: []string{
"port=udp",
"num=8085",
},
}
ports := []string{
@ -34,6 +39,15 @@ func TestUpdateExposedPorts(t *testing.T) {
"8081/tcp",
"8082",
"8083/udp",
"8084/$port",
"$num",
"$num/$port",
}
exposeCmd := &ExposeCommand{
&instructions.ExposeCommand{
Ports: ports,
},
}
expectedPorts := manifest.Schema2PortSet{
@ -41,9 +55,12 @@ func TestUpdateExposedPorts(t *testing.T) {
"8081/tcp": {},
"8082/tcp": {},
"8083/udp": {},
"8084/udp": {},
"8085/tcp": {},
"8085/udp": {},
}
err := updateExposedPorts(ports, cfg)
err := exposeCmd.ExecuteCommand(cfg)
testutil.CheckErrorAndDeepEqual(t, false, err, expectedPorts, cfg.ExposedPorts)
}
@ -56,6 +73,12 @@ func TestInvalidProtocol(t *testing.T) {
"80/garbage",
}
err := updateExposedPorts(ports, cfg)
exposeCmd := &ExposeCommand{
&instructions.ExposeCommand{
Ports: ports,
},
}
err := exposeCmd.ExecuteCommand(cfg)
testutil.CheckErrorAndDeepEqual(t, true, err, nil, nil)
}

72
pkg/commands/label.go Normal file
View File

@ -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, " ")
}

View File

@ -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)
}

View File

@ -22,7 +22,9 @@ import (
"github.com/sirupsen/logrus"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
)
type RunCommand struct {
@ -44,7 +46,28 @@ func (r *RunCommand) ExecuteCommand(config *manifest.Schema2Config) error {
logrus.Infof("args: %s", newCommand[1:])
cmd := exec.Command(newCommand[0], newCommand[1:]...)
cmd.Dir = config.WorkingDir
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()
}

95
pkg/commands/user.go Normal file
View File

@ -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, " ")
}

98
pkg/commands/user_test.go Normal file
View File

@ -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)
}
}

58
pkg/commands/workdir.go Normal file
View File

@ -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
}

View File

@ -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)
}
}

View File

@ -18,6 +18,8 @@ package util
import (
"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/sirupsen/logrus"
"os"
@ -25,6 +27,45 @@ import (
"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
func ContainsWildcards(paths []string) bool {
for _, path := range paths {

View File

@ -22,6 +22,88 @@ import (
"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 destinationFilepathTests = []struct {