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