diff --git a/README.md b/README.md index dfbd11a5b..63910619d 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ After each command, we append a layer of changed files to the base image (if the The majority of Dockerfile commands can be executed with kaniko, but we're still working on supporting the following commands: * HEALTHCHECK -* ARG Multi-Stage Dockerfiles are also unsupported currently, but will be ready soon. diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 2e5d0a66a..faeafba0a 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -21,9 +21,9 @@ import ( "os" "path/filepath" - "github.com/genuinetools/amicontained/container" - "github.com/GoogleContainerTools/kaniko/pkg/executor" + "github.com/genuinetools/amicontained/container" + "strings" "github.com/GoogleContainerTools/kaniko/pkg/constants" "github.com/GoogleContainerTools/kaniko/pkg/util" @@ -40,6 +40,7 @@ var ( dockerInsecureSkipTLSVerify bool logLevel string force bool + buildArgs buildArg ) func init() { @@ -49,6 +50,7 @@ func init() { RootCmd.PersistentFlags().StringVarP(&destination, "destination", "d", "", "Registry the final image should be pushed to (ex: gcr.io/test/example:latest)") RootCmd.MarkPersistentFlagRequired("destination") RootCmd.PersistentFlags().StringVarP(&snapshotMode, "snapshotMode", "", "full", "Set this flag to change the file attributes inspected during snapshotting") + RootCmd.PersistentFlags().VarP(&buildArgs, "build-arg", "", "This flag allows you to pass in ARG values at build time. Set it repeatedly for multiple values.") RootCmd.PersistentFlags().BoolVarP(&dockerInsecureSkipTLSVerify, "insecure-skip-tls-verify", "", false, "Push to insecure registry ignoring TLS verify") RootCmd.PersistentFlags().StringVarP(&logLevel, "verbosity", "v", constants.DefaultLogLevel, "Log level (debug, info, warn, error, fatal, panic") RootCmd.PersistentFlags().BoolVarP(&force, "force", "", false, "Force building outside of a container") @@ -77,13 +79,33 @@ var RootCmd = &cobra.Command{ logrus.Error(err) os.Exit(1) } - if err := executor.DoBuild(dockerfilePath, srcContext, destination, snapshotMode, dockerInsecureSkipTLSVerify); err != nil { + if err := executor.DoBuild(dockerfilePath, srcContext, destination, snapshotMode, dockerInsecureSkipTLSVerify, buildArgs); err != nil { logrus.Error(err) os.Exit(1) } }, } +type buildArg []string + +// Now, for our new type, implement the two methods of +// the flag.Value interface... +// The first method is String() string +func (b *buildArg) String() string { + return strings.Join(*b, ",") +} + +// The second method is Set(value string) error +func (b *buildArg) Set(value string) error { + logrus.Infof("appending to build args %s", value) + *b = append(*b, value) + return nil +} + +func (b *buildArg) Type() string { + return "Build ARG Type" +} + func checkContained() bool { _, err := container.DetectRuntime() return err == nil diff --git a/integration_tests/dockerfiles/Dockerfile_test_add b/integration_tests/dockerfiles/Dockerfile_test_add index 6fb9d73a7..4b58ee6fa 100644 --- a/integration_tests/dockerfiles/Dockerfile_test_add +++ b/integration_tests/dockerfiles/Dockerfile_test_add @@ -14,5 +14,9 @@ ADD $contextenv/* /tmp/${contextenv}/ ADD context/tars/fil* /tars/ ADD context/tars/file.tar /tars_again +# Test with ARG +ARG file +COPY $file /arg + # Finally, test adding a remote URL, concurrently with a normal file ADD https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v1.4.3/docker-credential-gcr_linux_386-1.4.3.tar.gz context/foo /test/all/ diff --git a/integration_tests/dockerfiles/Dockerfile_test_onbuild b/integration_tests/dockerfiles/Dockerfile_test_onbuild index 2968c0c62..ab145e69e 100644 --- a/integration_tests/dockerfiles/Dockerfile_test_onbuild +++ b/integration_tests/dockerfiles/Dockerfile_test_onbuild @@ -1,6 +1,7 @@ FROM gcr.io/kaniko-test/onbuild-base:latest COPY context/foo foo ENV dir /new/workdir/ -ONBUILD RUN echo "onbuild" > /tmp/onbuild +ARG file +ONBUILD RUN echo "onbuild" > $file ONBUILD RUN echo "onbuild 2" > ${dir} ONBUILD WORKDIR /new/workdir diff --git a/integration_tests/dockerfiles/Dockerfile_test_run b/integration_tests/dockerfiles/Dockerfile_test_run index 66307da87..ee5640f8b 100644 --- a/integration_tests/dockerfiles/Dockerfile_test_run +++ b/integration_tests/dockerfiles/Dockerfile_test_run @@ -17,3 +17,7 @@ RUN echo "hey" > /etc/foo RUN echo "baz" > /etc/baz RUN cp /etc/baz /etc/bar RUN rm /etc/baz + +# Test with ARG +ARG file +RUN echo "run" > $file diff --git a/integration_tests/dockerfiles/Dockerfile_test_scratch b/integration_tests/dockerfiles/Dockerfile_test_scratch index 0fb22d93b..e2a1383ee 100644 --- a/integration_tests/dockerfiles/Dockerfile_test_scratch +++ b/integration_tests/dockerfiles/Dockerfile_test_scratch @@ -1,4 +1,14 @@ FROM scratch -ADD context/foo /foo -ENV hello hello -ADD context/foo /$hello +# First, make sure simple arg replacement works +ARG file +COPY $file /foo +# Check that setting a default value works +ARG file2=context/bar/bat +COPY $file2 /bat +# Check that overriding a default value works +ARG file3=context/bar/baz +COPY $file3 /baz +# Check that setting an ENV will override the ARG +ENV file context/bar/bam/bat +COPY $file /env + diff --git a/integration_tests/dockerfiles/Dockerfile_test_workdir b/integration_tests/dockerfiles/Dockerfile_test_workdir index beb508d2b..9c2794f70 100644 --- a/integration_tests/dockerfiles/Dockerfile_test_workdir +++ b/integration_tests/dockerfiles/Dockerfile_test_workdir @@ -11,3 +11,7 @@ ENV dir /another/new/dir WORKDIR $dir/newdir WORKDIR $dir/$doesntexist WORKDIR / + +# Test with ARG +ARG workdir +WORKDIR $workdir diff --git a/integration_tests/integration_test_yaml.go b/integration_tests/integration_test_yaml.go index d5f7dc2a2..933951672 100644 --- a/integration_tests/integration_test_yaml.go +++ b/integration_tests/integration_test_yaml.go @@ -47,6 +47,7 @@ var fileTests = []struct { kanikoContextBucket bool repo string snapshotMode string + args []string }{ { description: "test extract filesystem", @@ -64,6 +65,9 @@ var fileTests = []struct { dockerContext: dockerfilesPath, kanikoContext: dockerfilesPath, repo: "test-run", + args: []string{ + "file=/file", + }, }, { description: "test run no files changed", @@ -99,6 +103,9 @@ var fileTests = []struct { dockerContext: buildcontextPath, kanikoContext: buildcontextPath, repo: "test-workdir", + args: []string{ + "workdir=/arg/workdir", + }, }, { description: "test volume", @@ -115,6 +122,9 @@ var fileTests = []struct { dockerContext: buildcontextPath, kanikoContext: buildcontextPath, repo: "test-add", + args: []string{ + "file=context/foo", + }, }, { description: "test mv add", @@ -139,6 +149,9 @@ var fileTests = []struct { dockerContext: buildcontextPath, kanikoContext: buildcontextPath, repo: "test-onbuild", + args: []string{ + "file=/tmp/onbuild", + }, }, { description: "test scratch", @@ -147,6 +160,11 @@ var fileTests = []struct { dockerContext: buildcontextPath, kanikoContext: buildcontextPath, repo: "test-scratch", + args: []string{ + "hello=hello-value", + "file=context/foo", + "file3=context/b*", + }, }, } @@ -231,18 +249,23 @@ func main() { Args: []string{"push", onbuildBaseImage}, } y := testyaml{ - Steps: []step{containerDiffStep, containerDiffPermissions, GCSBucketTarBuildContext, uploadTarBuildContext, buildExecutorImage, - buildOnbuildImage, pushOnbuildBase}, + Steps: []step{containerDiffStep, containerDiffPermissions, GCSBucketTarBuildContext, + uploadTarBuildContext, buildExecutorImage, buildOnbuildImage, pushOnbuildBase}, Timeout: "1200s", } for _, test := range fileTests { // First, build the image with docker dockerImageTag := testRepo + dockerPrefix + test.repo + var buildArgs []string + buildArgFlag := "--build-arg" + for _, arg := range test.args { + buildArgs = append(buildArgs, buildArgFlag) + buildArgs = append(buildArgs, arg) + } dockerBuild := step{ Name: dockerImage, - Args: []string{"build", "-t", dockerImageTag, "-f", test.dockerfilePath, test.dockerContext}, + Args: append([]string{"build", "-t", dockerImageTag, "-f", test.dockerfilePath, test.dockerContext}, buildArgs...), } - // Then, buld the image with kaniko kanikoImage := testRepo + kanikoPrefix + test.repo snapshotMode := "" @@ -255,7 +278,7 @@ func main() { } kaniko := step{ Name: executorImage, - Args: []string{"--destination", kanikoImage, "--dockerfile", test.dockerfilePath, contextFlag, test.kanikoContext, snapshotMode}, + Args: append([]string{"--destination", kanikoImage, "--dockerfile", test.dockerfilePath, contextFlag, test.kanikoContext, snapshotMode}, buildArgs...), } // Pull the kaniko image diff --git a/pkg/commands/add.go b/pkg/commands/add.go index f7264afec..b407ef871 100644 --- a/pkg/commands/add.go +++ b/pkg/commands/add.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/docker/docker/builder/dockerfile" "path/filepath" "strings" @@ -41,7 +42,7 @@ type AddCommand struct { // - If dest doesn't end with a slash, the filepath is inferred to be / // 2. If is a local tar archive: // -If is a local tar archive, it is unpacked at the dest, as 'tar -x' would -func (a *AddCommand) ExecuteCommand(config *v1.Config) error { +func (a *AddCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { srcs := a.cmd.SourcesAndDest[:len(a.cmd.SourcesAndDest)-1] dest := a.cmd.SourcesAndDest[len(a.cmd.SourcesAndDest)-1] @@ -49,7 +50,8 @@ func (a *AddCommand) ExecuteCommand(config *v1.Config) error { logrus.Infof("dest: %s", dest) // First, resolve any environment replacement - resolvedEnvs, err := util.ResolveEnvironmentReplacementList(a.cmd.SourcesAndDest, config.Env, true) + replacementEnvs := util.ReplacementEnvs(config, buildArgs) + resolvedEnvs, err := util.ResolveEnvironmentReplacementList(a.cmd.SourcesAndDest, replacementEnvs, true) if err != nil { return err } @@ -101,7 +103,7 @@ func (a *AddCommand) ExecuteCommand(config *v1.Config) error { }, buildcontext: a.buildcontext, } - if err := copyCmd.ExecuteCommand(config); err != nil { + if err := copyCmd.ExecuteCommand(config, buildArgs); err != nil { return err } a.snapshotFiles = append(a.snapshotFiles, copyCmd.snapshotFiles...) diff --git a/pkg/commands/arg.go b/pkg/commands/arg.go new file mode 100644 index 000000000..c8fd2abc5 --- /dev/null +++ b/pkg/commands/arg.go @@ -0,0 +1,46 @@ +/* +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/docker/docker/builder/dockerfile" + "github.com/docker/docker/builder/dockerfile/instructions" + "github.com/google/go-containerregistry/v1" + "github.com/sirupsen/logrus" + "strings" +) + +type ArgCommand struct { + cmd *instructions.ArgCommand +} + +// ExecuteCommand only needs to add this ARG key/value as seen +func (r *ArgCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { + logrus.Info("ARG") + buildArgs.AddArg(r.cmd.Key, r.cmd.Value) + return nil +} + +// FilesToSnapshot returns an empty array since this command only touches metadata. +func (r *ArgCommand) FilesToSnapshot() []string { + return []string{} +} + +// CreatedBy returns some information about the command for the image config history +func (r *ArgCommand) CreatedBy() string { + return strings.Join([]string{r.cmd.Name(), r.cmd.Key}, " ") +} diff --git a/pkg/commands/cmd.go b/pkg/commands/cmd.go index 79f13770e..7e20eff70 100644 --- a/pkg/commands/cmd.go +++ b/pkg/commands/cmd.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/docker/docker/builder/dockerfile" "strings" "github.com/docker/docker/builder/dockerfile/instructions" @@ -30,7 +31,7 @@ type CmdCommand struct { // ExecuteCommand executes the CMD command // Argument handling is the same as RUN. -func (c *CmdCommand) ExecuteCommand(config *v1.Config) error { +func (c *CmdCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: CMD") var newCommand []string if c.cmd.PrependShell { diff --git a/pkg/commands/cmd_test.go b/pkg/commands/cmd_test.go index a8395af69..a969e4e3f 100644 --- a/pkg/commands/cmd_test.go +++ b/pkg/commands/cmd_test.go @@ -16,12 +16,10 @@ limitations under the License. package commands import ( - "testing" - - "github.com/google/go-containerregistry/v1" - "github.com/GoogleContainerTools/kaniko/testutil" "github.com/docker/docker/builder/dockerfile/instructions" + "github.com/google/go-containerregistry/v1" + "testing" ) var cmdTests = []struct { @@ -56,7 +54,7 @@ func TestExecuteCmd(t *testing.T) { }, }, } - err := cmd.ExecuteCommand(cfg) + err := cmd.ExecuteCommand(cfg, nil) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedCmd, cfg.Cmd) } } diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index 0d8e6cafa..fb051e4d3 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/google/go-containerregistry/v1" "github.com/pkg/errors" @@ -28,7 +29,7 @@ type DockerCommand interface { // 1. Making required changes to the filesystem (ex. copying files for ADD/COPY or setting ENV variables) // 2. Updating metadata fields in the config // It should not change the config history. - ExecuteCommand(*v1.Config) error + ExecuteCommand(*v1.Config, *dockerfile.BuildArgs) error // The config history has a "created by" field, should return information about the command CreatedBy() string // A list of files to snapshot, empty for metadata commands or nil if we don't know @@ -63,6 +64,8 @@ func GetCommand(cmd instructions.Command, buildcontext string) (DockerCommand, e return &VolumeCommand{cmd: c}, nil case *instructions.StopSignalCommand: return &StopSignalCommand{cmd: c}, nil + case *instructions.ArgCommand: + return &ArgCommand{cmd: c}, nil case *instructions.MaintainerCommand: logrus.Warnf("%s is deprecated, skipping", cmd.Name()) return nil, nil diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go index 5f3e4f025..499fdae99 100644 --- a/pkg/commands/copy.go +++ b/pkg/commands/copy.go @@ -18,13 +18,15 @@ package commands import ( "github.com/GoogleContainerTools/kaniko/pkg/constants" - "github.com/GoogleContainerTools/kaniko/pkg/util" - "github.com/docker/docker/builder/dockerfile/instructions" - "github.com/google/go-containerregistry/v1" - "github.com/sirupsen/logrus" "os" "path/filepath" "strings" + + "github.com/GoogleContainerTools/kaniko/pkg/util" + "github.com/docker/docker/builder/dockerfile" + "github.com/docker/docker/builder/dockerfile/instructions" + "github.com/google/go-containerregistry/v1" + "github.com/sirupsen/logrus" ) type CopyCommand struct { @@ -33,15 +35,16 @@ type CopyCommand struct { snapshotFiles []string } -func (c *CopyCommand) ExecuteCommand(config *v1.Config) error { +func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { srcs := c.cmd.SourcesAndDest[:len(c.cmd.SourcesAndDest)-1] dest := c.cmd.SourcesAndDest[len(c.cmd.SourcesAndDest)-1] logrus.Infof("cmd: copy %s", srcs) logrus.Infof("dest: %s", dest) + replacementEnvs := util.ReplacementEnvs(config, buildArgs) // First, resolve any environment replacement - resolvedEnvs, err := util.ResolveEnvironmentReplacementList(c.cmd.SourcesAndDest, config.Env, true) + resolvedEnvs, err := util.ResolveEnvironmentReplacementList(c.cmd.SourcesAndDest, replacementEnvs, true) if err != nil { return err } diff --git a/pkg/commands/entrypoint.go b/pkg/commands/entrypoint.go index 7310c2faf..36a405021 100644 --- a/pkg/commands/entrypoint.go +++ b/pkg/commands/entrypoint.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/docker/docker/builder/dockerfile" "strings" "github.com/docker/docker/builder/dockerfile/instructions" @@ -29,7 +30,7 @@ type EntrypointCommand struct { } // ExecuteCommand handles command processing similar to CMD and RUN, -func (e *EntrypointCommand) ExecuteCommand(config *v1.Config) error { +func (e *EntrypointCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: ENTRYPOINT") var newCommand []string if e.cmd.PrependShell { diff --git a/pkg/commands/entrypoint_test.go b/pkg/commands/entrypoint_test.go index 57d28cd87..326bbb0ae 100644 --- a/pkg/commands/entrypoint_test.go +++ b/pkg/commands/entrypoint_test.go @@ -55,7 +55,7 @@ func TestEntrypointExecuteCmd(t *testing.T) { }, }, } - err := cmd.ExecuteCommand(cfg) + err := cmd.ExecuteCommand(cfg, nil) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedCmd, cfg.Entrypoint) } } diff --git a/pkg/commands/env.go b/pkg/commands/env.go index 4e576a3f8..a2d4f93d2 100644 --- a/pkg/commands/env.go +++ b/pkg/commands/env.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/docker/docker/builder/dockerfile" "strings" "github.com/GoogleContainerTools/kaniko/pkg/util" @@ -29,15 +30,16 @@ type EnvCommand struct { cmd *instructions.EnvCommand } -func (e *EnvCommand) ExecuteCommand(config *v1.Config) error { +func (e *EnvCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: ENV") newEnvs := e.cmd.Env + replacementEnvs := util.ReplacementEnvs(config, buildArgs) for index, pair := range newEnvs { - expandedKey, err := util.ResolveEnvironmentReplacement(pair.Key, config.Env, false) + expandedKey, err := util.ResolveEnvironmentReplacement(pair.Key, replacementEnvs, false) if err != nil { return err } - expandedValue, err := util.ResolveEnvironmentReplacement(pair.Value, config.Env, false) + expandedValue, err := util.ResolveEnvironmentReplacement(pair.Value, replacementEnvs, false) if err != nil { return err } diff --git a/pkg/commands/env_test.go b/pkg/commands/env_test.go index a321852eb..79e095cb5 100644 --- a/pkg/commands/env_test.go +++ b/pkg/commands/env_test.go @@ -16,11 +16,12 @@ limitations under the License. package commands import ( - "testing" - + "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" "github.com/GoogleContainerTools/kaniko/testutil" + docker "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/google/go-containerregistry/v1" + "testing" ) func TestUpdateEnvConfig(t *testing.T) { @@ -77,6 +78,10 @@ func Test_EnvExecute(t *testing.T) { Key: "$path", Value: "$home/", }, + { + Key: "$buildArg1", + Value: "$buildArg2", + }, }, }, } @@ -86,7 +91,20 @@ func Test_EnvExecute(t *testing.T) { "home=/root", "HOME=/root", "/usr/=/root/", + "foo=foo2", } - err := envCmd.ExecuteCommand(cfg) + buildArgs := setUpBuildArgs() + err := envCmd.ExecuteCommand(cfg, buildArgs) testutil.CheckErrorAndDeepEqual(t, false, err, expectedEnvs, cfg.Env) } + +func setUpBuildArgs() *docker.BuildArgs { + buildArgs := dockerfile.NewBuildArgs([]string{ + "buildArg1=foo", + "buildArg2=foo2", + }) + buildArgs.AddArg("buildArg1", nil) + d := "default" + buildArgs.AddArg("buildArg2", &d) + return buildArgs +} diff --git a/pkg/commands/expose.go b/pkg/commands/expose.go index b86995d75..dc9ba6d7d 100644 --- a/pkg/commands/expose.go +++ b/pkg/commands/expose.go @@ -18,6 +18,7 @@ package commands import ( "fmt" + "github.com/docker/docker/builder/dockerfile" "strings" "github.com/GoogleContainerTools/kaniko/pkg/util" @@ -30,17 +31,18 @@ type ExposeCommand struct { cmd *instructions.ExposeCommand } -func (r *ExposeCommand) ExecuteCommand(config *v1.Config) error { +func (r *ExposeCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: EXPOSE") // Grab the currently exposed ports existingPorts := config.ExposedPorts if existingPorts == nil { existingPorts = make(map[string]struct{}) } + replacementEnvs := util.ReplacementEnvs(config, buildArgs) // Add any new ones in for _, p := range r.cmd.Ports { // Resolve any environment variables - p, err := util.ResolveEnvironmentReplacement(p, config.Env, false) + p, err := util.ResolveEnvironmentReplacement(p, replacementEnvs, false) if err != nil { return err } diff --git a/pkg/commands/expose_test.go b/pkg/commands/expose_test.go index 4da4c3cde..8e64d47f7 100644 --- a/pkg/commands/expose_test.go +++ b/pkg/commands/expose_test.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" "testing" "github.com/GoogleContainerTools/kaniko/testutil" @@ -60,8 +61,8 @@ func TestUpdateExposedPorts(t *testing.T) { "8085/tcp": {}, "8085/udp": {}, } - - err := exposeCmd.ExecuteCommand(cfg) + buildArgs := dockerfile.NewBuildArgs([]string{}) + err := exposeCmd.ExecuteCommand(cfg, buildArgs) testutil.CheckErrorAndDeepEqual(t, false, err, expectedPorts, cfg.ExposedPorts) } @@ -79,7 +80,7 @@ func TestInvalidProtocol(t *testing.T) { Ports: ports, }, } - - err := exposeCmd.ExecuteCommand(cfg) + buildArgs := dockerfile.NewBuildArgs([]string{}) + err := exposeCmd.ExecuteCommand(cfg, buildArgs) testutil.CheckErrorAndDeepEqual(t, true, err, nil, nil) } diff --git a/pkg/commands/label.go b/pkg/commands/label.go index 7296d4d39..e5fbbe892 100644 --- a/pkg/commands/label.go +++ b/pkg/commands/label.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/docker/docker/builder/dockerfile" "strings" "github.com/GoogleContainerTools/kaniko/pkg/util" @@ -29,24 +30,29 @@ type LabelCommand struct { cmd *instructions.LabelCommand } -func (r *LabelCommand) ExecuteCommand(config *v1.Config) error { +func (r *LabelCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: LABEL") - return updateLabels(r.cmd.Labels, config) + return updateLabels(r.cmd.Labels, config, buildArgs) } -func updateLabels(labels []instructions.KeyValuePair, config *v1.Config) error { +func updateLabels(labels []instructions.KeyValuePair, config *v1.Config, buildArgs *dockerfile.BuildArgs) error { existingLabels := config.Labels if existingLabels == nil { existingLabels = make(map[string]string) } // Let's unescape values before setting the label + replacementEnvs := util.ReplacementEnvs(config, buildArgs) for index, kvp := range labels { - unescaped, err := util.ResolveEnvironmentReplacement(kvp.Value, []string{}, false) + key, err := util.ResolveEnvironmentReplacement(kvp.Key, replacementEnvs, false) + if err != nil { + return err + } + unescaped, err := util.ResolveEnvironmentReplacement(kvp.Value, replacementEnvs, false) if err != nil { return err } labels[index] = instructions.KeyValuePair{ - Key: kvp.Key, + Key: key, Value: unescaped, } } diff --git a/pkg/commands/label_test.go b/pkg/commands/label_test.go index 56a3b8069..4b6d8fff1 100644 --- a/pkg/commands/label_test.go +++ b/pkg/commands/label_test.go @@ -17,11 +17,11 @@ limitations under the License. package commands import ( - "testing" - + "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" "github.com/GoogleContainerTools/kaniko/testutil" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/google/go-containerregistry/v1" + "testing" ) func TestUpdateLabels(t *testing.T) { @@ -48,14 +48,25 @@ func TestUpdateLabels(t *testing.T) { Key: "backslashes", Value: "lots\\\\ of\\\\ words", }, + { + Key: "$label", + Value: "foo", + }, } - expectedLabels := map[string]string{ - "foo": "override", - "bar": "baz", - "multiword": "lots of words", - "backslashes": "lots\\ of\\ words", + arguments := []string{ + "label=build_arg_label", } - updateLabels(labels, cfg) + + buildArgs := dockerfile.NewBuildArgs(arguments) + buildArgs.AddArg("label", nil) + expectedLabels := map[string]string{ + "foo": "override", + "bar": "baz", + "multiword": "lots of words", + "backslashes": "lots\\ of\\ words", + "build_arg_label": "foo", + } + updateLabels(labels, cfg, buildArgs) testutil.CheckErrorAndDeepEqual(t, false, nil, expectedLabels, cfg.Labels) } diff --git a/pkg/commands/onbuild.go b/pkg/commands/onbuild.go index b490e1200..53dce72ca 100644 --- a/pkg/commands/onbuild.go +++ b/pkg/commands/onbuild.go @@ -18,6 +18,7 @@ package commands import ( "github.com/GoogleContainerTools/kaniko/pkg/util" + "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/google/go-containerregistry/v1" "github.com/sirupsen/logrus" @@ -28,10 +29,11 @@ type OnBuildCommand struct { } //ExecuteCommand adds the specified expression in Onbuild to the config -func (o *OnBuildCommand) ExecuteCommand(config *v1.Config) error { +func (o *OnBuildCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: ONBUILD") logrus.Infof("args: %s", o.cmd.Expression) - resolvedExpression, err := util.ResolveEnvironmentReplacement(o.cmd.Expression, config.Env, false) + replacementEnvs := util.ReplacementEnvs(config, buildArgs) + resolvedExpression, err := util.ResolveEnvironmentReplacement(o.cmd.Expression, replacementEnvs, false) if err != nil { return err } diff --git a/pkg/commands/onbuild_test.go b/pkg/commands/onbuild_test.go index 4164823bc..44f304184 100644 --- a/pkg/commands/onbuild_test.go +++ b/pkg/commands/onbuild_test.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" "testing" "github.com/GoogleContainerTools/kaniko/testutil" @@ -62,8 +63,8 @@ func TestExecuteOnbuild(t *testing.T) { Expression: test.expression, }, } - - err := onbuildCmd.ExecuteCommand(cfg) + buildArgs := dockerfile.NewBuildArgs([]string{}) + err := onbuildCmd.ExecuteCommand(cfg, buildArgs) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedArray, cfg.OnBuild) } diff --git a/pkg/commands/run.go b/pkg/commands/run.go index 7eb545f19..de55ed15d 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -17,22 +17,23 @@ limitations under the License. package commands import ( + "github.com/GoogleContainerTools/kaniko/pkg/util" + "github.com/docker/docker/builder/dockerfile" + "github.com/docker/docker/builder/dockerfile/instructions" + "github.com/google/go-containerregistry/v1" + "github.com/sirupsen/logrus" "os" "os/exec" "strconv" "strings" "syscall" - - "github.com/docker/docker/builder/dockerfile/instructions" - "github.com/google/go-containerregistry/v1" - "github.com/sirupsen/logrus" ) type RunCommand struct { cmd *instructions.RunCommand } -func (r *RunCommand) ExecuteCommand(config *v1.Config) error { +func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { var newCommand []string if r.cmd.PrependShell { // This is the default shell on Linux @@ -54,7 +55,8 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config) error { cmd := exec.Command(newCommand[0], newCommand[1:]...) cmd.Dir = config.WorkingDir cmd.Stdout = os.Stdout - cmd.Env = config.Env + replacementEnvs := util.ReplacementEnvs(config, buildArgs) + cmd.Env = replacementEnvs // If specified, run the command as a specific user if config.User != "" { diff --git a/pkg/commands/shell.go b/pkg/commands/shell.go index 89a453914..48e941aae 100644 --- a/pkg/commands/shell.go +++ b/pkg/commands/shell.go @@ -17,11 +17,11 @@ limitations under the License. package commands import ( - "strings" - + "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/google/go-containerregistry/v1" "github.com/sirupsen/logrus" + "strings" ) type ShellCommand struct { @@ -29,7 +29,7 @@ type ShellCommand struct { } // ExecuteCommand handles command processing similar to CMD and RUN, -func (s *ShellCommand) ExecuteCommand(config *v1.Config) error { +func (s *ShellCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: SHELL") var newShell []string diff --git a/pkg/commands/shell_test.go b/pkg/commands/shell_test.go index 524138aab..05d977553 100644 --- a/pkg/commands/shell_test.go +++ b/pkg/commands/shell_test.go @@ -49,7 +49,7 @@ func TestShellExecuteCmd(t *testing.T) { Shell: test.cmdLine, }, } - err := cmd.ExecuteCommand(cfg) + err := cmd.ExecuteCommand(cfg, nil) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedShell, cfg.Shell) } } diff --git a/pkg/commands/stopsignal.go b/pkg/commands/stopsignal.go index 65fb7d42f..fd1988a3f 100644 --- a/pkg/commands/stopsignal.go +++ b/pkg/commands/stopsignal.go @@ -17,13 +17,13 @@ limitations under the License. package commands import ( - "strings" - "github.com/GoogleContainerTools/kaniko/pkg/util" + "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/docker/docker/pkg/signal" "github.com/google/go-containerregistry/v1" "github.com/sirupsen/logrus" + "strings" ) type StopSignalCommand struct { @@ -31,11 +31,12 @@ type StopSignalCommand struct { } // ExecuteCommand handles command processing similar to CMD and RUN, -func (s *StopSignalCommand) ExecuteCommand(config *v1.Config) error { +func (s *StopSignalCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: STOPSIGNAL") // resolve possible environment variables - resolvedEnvs, err := util.ResolveEnvironmentReplacementList([]string{s.cmd.Signal}, config.Env, false) + replacementEnvs := util.ReplacementEnvs(config, buildArgs) + resolvedEnvs, err := util.ResolveEnvironmentReplacementList([]string{s.cmd.Signal}, replacementEnvs, false) if err != nil { return err } diff --git a/pkg/commands/stopsignal_test.go b/pkg/commands/stopsignal_test.go index c6e717f76..cab40077d 100644 --- a/pkg/commands/stopsignal_test.go +++ b/pkg/commands/stopsignal_test.go @@ -16,11 +16,11 @@ limitations under the License. package commands import ( - "testing" - + "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" "github.com/GoogleContainerTools/kaniko/testutil" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/google/go-containerregistry/v1" + "testing" ) var stopsignalTests = []struct { @@ -54,7 +54,8 @@ func TestStopsignalExecuteCmd(t *testing.T) { Signal: test.signal, }, } - err := cmd.ExecuteCommand(cfg) + b := dockerfile.NewBuildArgs([]string{}) + err := cmd.ExecuteCommand(cfg, b) testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedSignal, cfg.StopSignal) } } diff --git a/pkg/commands/user.go b/pkg/commands/user.go index f33ab92de..76fecbb44 100644 --- a/pkg/commands/user.go +++ b/pkg/commands/user.go @@ -17,30 +17,31 @@ limitations under the License. package commands import ( - "os/user" - "strings" - "github.com/GoogleContainerTools/kaniko/pkg/util" + "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/google/go-containerregistry/v1" "github.com/sirupsen/logrus" + "os/user" + "strings" ) type UserCommand struct { cmd *instructions.UserCommand } -func (r *UserCommand) ExecuteCommand(config *v1.Config) error { +func (r *UserCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: USER") u := r.cmd.User userAndGroup := strings.Split(u, ":") - userStr, err := util.ResolveEnvironmentReplacement(userAndGroup[0], config.Env, false) + replacementEnvs := util.ReplacementEnvs(config, buildArgs) + userStr, err := util.ResolveEnvironmentReplacement(userAndGroup[0], replacementEnvs, false) if err != nil { return err } var groupStr string if len(userAndGroup) > 1 { - groupStr, err = util.ResolveEnvironmentReplacement(userAndGroup[1], config.Env, false) + groupStr, err = util.ResolveEnvironmentReplacement(userAndGroup[1], replacementEnvs, false) if err != nil { return err } diff --git a/pkg/commands/user_test.go b/pkg/commands/user_test.go index 308f705ad..f4e1759f3 100644 --- a/pkg/commands/user_test.go +++ b/pkg/commands/user_test.go @@ -16,6 +16,7 @@ limitations under the License. package commands import ( + "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" "testing" "github.com/GoogleContainerTools/kaniko/testutil" @@ -93,7 +94,8 @@ func TestUpdateUser(t *testing.T) { User: test.user, }, } - err := cmd.ExecuteCommand(cfg) + buildArgs := dockerfile.NewBuildArgs([]string{}) + err := cmd.ExecuteCommand(cfg, buildArgs) testutil.CheckErrorAndDeepEqual(t, test.shouldError, err, test.expectedUid, cfg.User) } } diff --git a/pkg/commands/volume.go b/pkg/commands/volume.go index ae3af5526..ac1323d01 100644 --- a/pkg/commands/volume.go +++ b/pkg/commands/volume.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/docker/docker/builder/dockerfile" "os" "strings" @@ -31,10 +32,11 @@ type VolumeCommand struct { snapshotFiles []string } -func (v *VolumeCommand) ExecuteCommand(config *v1.Config) error { +func (v *VolumeCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: VOLUME") volumes := v.cmd.Volumes - resolvedVolumes, err := util.ResolveEnvironmentReplacementList(volumes, config.Env, true) + replacementEnvs := util.ReplacementEnvs(config, buildArgs) + resolvedVolumes, err := util.ResolveEnvironmentReplacementList(volumes, replacementEnvs, true) if err != nil { return err } diff --git a/pkg/commands/volume_test.go b/pkg/commands/volume_test.go index 9eb8c5c5e..9045a531b 100644 --- a/pkg/commands/volume_test.go +++ b/pkg/commands/volume_test.go @@ -16,6 +16,7 @@ limitations under the License. package commands import ( + "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" "testing" "github.com/GoogleContainerTools/kaniko/testutil" @@ -49,7 +50,7 @@ func TestUpdateVolume(t *testing.T) { "/var/lib": {}, "/etc": {}, } - - err := volumeCmd.ExecuteCommand(cfg) + buildArgs := dockerfile.NewBuildArgs([]string{}) + err := volumeCmd.ExecuteCommand(cfg, buildArgs) testutil.CheckErrorAndDeepEqual(t, false, err, expectedVolumes, cfg.Volumes) } diff --git a/pkg/commands/workdir.go b/pkg/commands/workdir.go index 13a4b102b..3014eaac9 100644 --- a/pkg/commands/workdir.go +++ b/pkg/commands/workdir.go @@ -17,6 +17,7 @@ limitations under the License. package commands import ( + "github.com/docker/docker/builder/dockerfile" "os" "path/filepath" @@ -31,10 +32,11 @@ type WorkdirCommand struct { snapshotFiles []string } -func (w *WorkdirCommand) ExecuteCommand(config *v1.Config) error { +func (w *WorkdirCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error { logrus.Info("cmd: workdir") workdirPath := w.cmd.Path - resolvedWorkingDir, err := util.ResolveEnvironmentReplacement(workdirPath, config.Env, true) + replacementEnvs := util.ReplacementEnvs(config, buildArgs) + resolvedWorkingDir, err := util.ResolveEnvironmentReplacement(workdirPath, replacementEnvs, true) if err != nil { return err } diff --git a/pkg/commands/workdir_test.go b/pkg/commands/workdir_test.go index 426cf4718..26fe8f578 100644 --- a/pkg/commands/workdir_test.go +++ b/pkg/commands/workdir_test.go @@ -16,6 +16,7 @@ limitations under the License. package commands import ( + "github.com/GoogleContainerTools/kaniko/pkg/dockerfile" "testing" "github.com/GoogleContainerTools/kaniko/testutil" @@ -78,7 +79,8 @@ func TestWorkdirCommand(t *testing.T) { }, snapshotFiles: []string{}, } - cmd.ExecuteCommand(cfg) + buildArgs := dockerfile.NewBuildArgs([]string{}) + cmd.ExecuteCommand(cfg, buildArgs) testutil.CheckErrorAndDeepEqual(t, false, nil, test.expectedPath, cfg.WorkingDir) } } diff --git a/pkg/dockerfile/buildargs.go b/pkg/dockerfile/buildargs.go new file mode 100644 index 000000000..923d59e67 --- /dev/null +++ b/pkg/dockerfile/buildargs.go @@ -0,0 +1,35 @@ +/* +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 dockerfile + +import ( + d "github.com/docker/docker/builder/dockerfile" + "strings" +) + +func NewBuildArgs(args []string) *d.BuildArgs { + argsFromOptions := make(map[string]*string) + for _, a := range args { + s := strings.Split(a, "=") + if len(s) == 1 { + argsFromOptions[s[0]] = nil + } else { + argsFromOptions[s[0]] = &s[1] + } + } + return d.NewBuildArgs(argsFromOptions) +} diff --git a/pkg/executor/executor.go b/pkg/executor/executor.go index 4fbef060e..f6b1fc3ce 100644 --- a/pkg/executor/executor.go +++ b/pkg/executor/executor.go @@ -44,7 +44,7 @@ import ( "github.com/sirupsen/logrus" ) -func DoBuild(dockerfilePath, srcContext, destination, snapshotMode string, dockerInsecureSkipTLSVerify bool) error { +func DoBuild(dockerfilePath, srcContext, destination, snapshotMode string, dockerInsecureSkipTLSVerify bool, args []string) error { // Parse dockerfile and unpack base image to root d, err := ioutil.ReadFile(dockerfilePath) if err != nil { @@ -108,6 +108,7 @@ func DoBuild(dockerfilePath, srcContext, destination, snapshotMode string, docke if err != nil { return err } + buildArgs := dockerfile.NewBuildArgs(args) // Currently only supports single stage builds for _, stage := range stages { if err := resolveOnBuild(&stage, &imageConfig.Config); err != nil { @@ -121,7 +122,7 @@ func DoBuild(dockerfilePath, srcContext, destination, snapshotMode string, docke if dockerCommand == nil { continue } - if err := dockerCommand.ExecuteCommand(&imageConfig.Config); err != nil { + if err := dockerCommand.ExecuteCommand(&imageConfig.Config, buildArgs); err != nil { return err } // Now, we get the files to snapshot from this command and take the snapshot diff --git a/pkg/util/command_util.go b/pkg/util/command_util.go index 20efd19a3..d58d8c7a4 100644 --- a/pkg/util/command_util.go +++ b/pkg/util/command_util.go @@ -17,9 +17,11 @@ limitations under the License. package util import ( + "github.com/docker/docker/builder/dockerfile" "github.com/docker/docker/builder/dockerfile/instructions" "github.com/docker/docker/builder/dockerfile/parser" "github.com/docker/docker/builder/dockerfile/shell" + "github.com/google/go-containerregistry/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "net/http" @@ -222,3 +224,10 @@ func IsSrcRemoteFileURL(rawurl string) bool { } return true } + +// ReplacementEnvs returns a list of all variables that can be used for +// environment replacement +func ReplacementEnvs(config *v1.Config, buildArgs *dockerfile.BuildArgs) []string { + filtered := buildArgs.FilterAllowed(config.Env) + return append(config.Env, filtered...) +}