diff --git a/cmd/buildah/build.go b/cmd/buildah/build.go index 98d74c65a..be2496209 100644 --- a/cmd/buildah/build.go +++ b/cmd/buildah/build.go @@ -394,6 +394,7 @@ func buildCmd(c *cobra.Command, inputArgs []string, iopts buildOptions) error { Timestamp: timestamp, Platforms: platforms, UnsetEnvs: iopts.UnsetEnvs, + Envs: iopts.Envs, } if iopts.Quiet { options.ReportWriter = ioutil.Discard diff --git a/cmd/buildah/config.go b/cmd/buildah/config.go index bfb790fb6..9a147ea6f 100644 --- a/cmd/buildah/config.go +++ b/cmd/buildah/config.go @@ -226,7 +226,6 @@ func updateConfig(builder *buildah.Builder, c *cobra.Command, iopts configResult } env[1] = os.Expand(env[1], getenv) builder.SetEnv(env[0], env[1]) - case env[0] == "-": builder.ClearEnv() case strings.HasSuffix(env[0], "-"): diff --git a/commit.go b/commit.go index 1da8328f8..ca597e222 100644 --- a/commit.go +++ b/commit.go @@ -102,6 +102,7 @@ type CommitOptions struct { // indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer. OciEncryptLayers *[]int // UnsetEnvs is a list of environments to not add to final image. + // Deprecated: use UnsetEnv() before committing instead. UnsetEnvs []string } diff --git a/define/build.go b/define/build.go index 2cb92e811..d9eb53aeb 100644 --- a/define/build.go +++ b/define/build.go @@ -260,4 +260,6 @@ type BuildOptions struct { AllPlatforms bool // UnsetEnvs is a list of environments to not add to final image. UnsetEnvs []string + // Envs is a list of environment variables to set in the final image. + Envs []string } diff --git a/docs/buildah-build.1.md b/docs/buildah-build.1.md index eb80472cf..671c3dab5 100644 --- a/docs/buildah-build.1.md +++ b/docs/buildah-build.1.md @@ -241,6 +241,14 @@ Set custom DNS options Set custom DNS search domains +**--env** *env[=value]* + +Add a value (e.g. env=*value*) to the built image. Can be used multiple times. +If neither `=` nor a `*value*` are specified, but *env* is set in the current +environment, the value from the current environment will be added to the image. +To remove an environment variable from the built image, use the `--unsetenv` +option. + **--file**, **-f** *Containerfile* Specifies a Containerfile which contains instructions for building the image, @@ -829,6 +837,12 @@ buildah build -f Containerfile.in -t imageName . buildah build --network mynet . +buildah build --env LANG=en_US.UTF-8 -t imageName . + +buildah build --env EDITOR -t imageName . + +buildah build --unsetenv LANG -t imageName . + ### Building an multi-architecture image using the --manifest option (requires emulation software) buildah build --arch arm --manifest myimage /tmp/mysrc diff --git a/docs/buildah-config.1.md b/docs/buildah-config.1.md index 55c752e90..9fe35186d 100644 --- a/docs/buildah-config.1.md +++ b/docs/buildah-config.1.md @@ -77,10 +77,12 @@ ignore the `cmd` value of the container image. However if you use the array form, then the cmd will be appended onto the end of the entrypoint cmd and be executed together. -**--env**, **-e** *env=value* +**--env**, **-e** *env[=value]* Add a value (e.g. env=*value*) to the environment for containers based on any images which will be built using the specified container. Can be used multiple times. +If *env* is named but neither `=` nor a `value` is specified, then the value +will be taken from the current process environment. If *env* has a trailing `-`, then the *env* is removed from the config. If the *env* is set to "-" then all environment variables are removed from the config. @@ -229,6 +231,8 @@ buildah config --volume /usr/myvol- containerID buildah config --port 1234 --port 8080 containerID +buildah config --env 1234=5678 containerID + buildah config --env 1234- containerID ## SEE ALSO diff --git a/imagebuildah/executor.go b/imagebuildah/executor.go index 51bc0bb6a..4525163cd 100644 --- a/imagebuildah/executor.go +++ b/imagebuildah/executor.go @@ -134,6 +134,7 @@ type Executor struct { processLabel string // Shares processLabel of first stage container with containers of other stages in same build mountLabel string // Shares mountLabel of first stage container with containers of other stages in same build buildOutput string // Specifies instructions for any custom build output + envs []string } type imageTypeAndHistoryAndDiffIDs struct { @@ -276,8 +277,9 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o secrets: secrets, sshsources: sshsources, logPrefix: logPrefix, - unsetEnvs: options.UnsetEnvs, + unsetEnvs: append([]string{}, options.UnsetEnvs...), buildOutput: options.BuildOutput, + envs: append([]string{}, options.Envs...), } if exec.err == nil { exec.err = os.Stderr diff --git a/imagebuildah/stage_executor.go b/imagebuildah/stage_executor.go index 3750edfc6..70981ae67 100644 --- a/imagebuildah/stage_executor.go +++ b/imagebuildah/stage_executor.go @@ -1494,6 +1494,28 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer spec := strings.SplitN(envSpec, "=", 2) s.builder.SetEnv(spec[0], spec[1]) } + for _, envSpec := range s.executor.envs { + env := strings.SplitN(envSpec, "=", 2) + if len(env) > 1 { + getenv := func(name string) string { + for _, envvar := range s.builder.Env() { + val := strings.SplitN(envvar, "=", 2) + if len(val) == 2 && val[0] == name { + return val[1] + } + } + logrus.Errorf("error expanding variable %q: no value set in image", name) + return name + } + env[1] = os.Expand(env[1], getenv) + s.builder.SetEnv(env[0], env[1]) + } else { + s.builder.SetEnv(env[0], os.Getenv(env[0])) + } + } + for _, envSpec := range s.executor.unsetEnvs { + s.builder.UnsetEnv(envSpec) + } s.builder.SetCmd(config.Cmd) s.builder.ClearVolumes() for v := range config.Volumes { @@ -1566,7 +1588,6 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer RetryDelay: s.executor.retryPullPushDelay, HistoryTimestamp: s.executor.timestamp, Manifest: s.executor.manifest, - UnsetEnvs: s.executor.unsetEnvs, } // generate build output if s.executor.buildOutput != "" { diff --git a/pkg/cli/common.go b/pkg/cli/common.go index cc57b1e0f..38b9b9025 100644 --- a/pkg/cli/common.go +++ b/pkg/cli/common.go @@ -92,6 +92,7 @@ type BudResults struct { LogRusage bool RusageLogFile string UnsetEnvs []string + Envs []string } // FromAndBugResults represents the results for common flags @@ -194,6 +195,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.StringVar(&flags.Creds, "creds", "", "use `[username[:password]]` for accessing the registry") fs.BoolVarP(&flags.DisableCompression, "disable-compression", "D", true, "don't compress layers by default") fs.BoolVar(&flags.DisableContentTrust, "disable-content-trust", false, "This is a Docker specific option and is a NOOP") + fs.StringArrayVar(&flags.Envs, "env", []string{}, "set environment variable for the image") fs.StringVar(&flags.From, "from", "", "image name used to replace the value in the first FROM instruction in the Containerfile") fs.StringVar(&flags.IgnoreFile, "ignorefile", "", "path to an alternate .dockerignore file") fs.StringSliceVarP(&flags.File, "file", "f", []string{}, "`pathname or URL` of a Dockerfile") @@ -262,6 +264,7 @@ func GetBudFlagsCompletions() commonComp.FlagCompletions { flagCompletion["cache-from"] = commonComp.AutocompleteNone flagCompletion["cert-dir"] = commonComp.AutocompleteDefault flagCompletion["creds"] = commonComp.AutocompleteNone + flagCompletion["env"] = commonComp.AutocompleteNone flagCompletion["file"] = commonComp.AutocompleteDefault flagCompletion["from"] = commonComp.AutocompleteDefault flagCompletion["format"] = commonComp.AutocompleteNone diff --git a/tests/bud.bats b/tests/bud.bats index ce459c61e..2df6b06c2 100644 --- a/tests/bud.bats +++ b/tests/bud.bats @@ -582,6 +582,25 @@ _EOF expect_output "[container=buildah date=tomorrow]" "No Path should be defined" } +@test "bud with --env" { + target=scratch-image + run_buildah build --quiet=false --iidfile ${TEST_SCRATCH_DIR}/output.iid --env PATH $WITH_POLICY_JSON -t ${target} $BUDFILES/from-scratch + iid=$(cat ${TEST_SCRATCH_DIR}/output.iid) + run_buildah inspect --format '{{.Docker.Config.Env}}' $iid + expect_output "[PATH=$PATH]" + + run_buildah build --quiet=false --iidfile ${TEST_SCRATCH_DIR}/output.iid --env PATH=foo $WITH_POLICY_JSON -t ${target} $BUDFILES/from-scratch + iid=$(cat ${TEST_SCRATCH_DIR}/output.iid) + run_buildah inspect --format '{{.Docker.Config.Env}}' $iid + expect_output "[PATH=foo]" + + # --unsetenv takes precedence over --env, since we don't know the relative order of the two + run_buildah build --quiet=false --iidfile ${TEST_SCRATCH_DIR}/output.iid --unsetenv PATH --env PATH=foo --env PATH= $WITH_POLICY_JSON -t ${target} $BUDFILES/from-scratch + iid=$(cat ${TEST_SCRATCH_DIR}/output.iid) + run_buildah inspect --format '{{.Docker.Config.Env}}' $iid + expect_output "[]" +} + @test "build with custom build output and output rootfs to directory" { _prefetch alpine mytmpdir=${TEST_SCRATCH_DIR}/my-dir @@ -659,7 +678,7 @@ _EOF expect_output "" "no base name for untagged base image" } -@test "bud with --tag " { +@test "bud with --tag" { target=scratch-image run_buildah build --quiet=false --tag test1 $WITH_POLICY_JSON -t ${target} $BUDFILES/from-scratch expect_output --substring "Successfully tagged localhost/test1:latest" @@ -669,7 +688,7 @@ _EOF expect_output --substring "Successfully tagged localhost/test2:latest" } -@test "bud with bad --tag " { +@test "bud with bad --tag" { target=scratch-image run_buildah 125 build --quiet=false --tag TEST1 $WITH_POLICY_JSON -t ${target} $BUDFILES/from-scratch expect_output --substring "tag TEST1: invalid reference format: repository name must be lowercase"