diff --git a/define/build.go b/define/build.go index 330d48570ad..1c3d16e20e7 100644 --- a/define/build.go +++ b/define/build.go @@ -236,6 +236,9 @@ type BuildOptions struct { // ID mapping options to use if we're setting up our own user namespace // when handling RUN instructions. IDMappingOptions *IDMappingOptions + // InheritLabels controls whether or not built images will retain the labels + // which were set in their base images + InheritLabels types.OptionalBool // AddCapabilities is a list of capabilities to add to the default set when // handling RUN instructions. AddCapabilities []string diff --git a/docs/buildah-build.1.md b/docs/buildah-build.1.md index bfec23cd01c..55cc680a619 100644 --- a/docs/buildah-build.1.md +++ b/docs/buildah-build.1.md @@ -497,6 +497,10 @@ Path to an alternative .containerignore (.dockerignore) file. Write the built image's ID to the file. When `--platform` is specified more than once, attempting to use this option will trigger an error. +**--inherit-labels** *bool-value* + +Inherit the labels from the base image or base stages. (default true). + **--ipc** *how* Sets the configuration for IPC namespaces when handling `RUN` instructions. diff --git a/imagebuildah/executor.go b/imagebuildah/executor.go index 999a80b1ea1..70bcc460c77 100644 --- a/imagebuildah/executor.go +++ b/imagebuildah/executor.go @@ -82,6 +82,7 @@ type Executor struct { additionalTags []string log func(format string, args ...any) // can be nil in io.Reader + inheritLabels types.OptionalBool out io.Writer err io.Writer signaturePolicyPath string @@ -261,6 +262,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o err: options.Err, reportWriter: writer, isolation: options.Isolation, + inheritLabels: options.InheritLabels, namespaceOptions: options.NamespaceOptions, configureNetwork: options.ConfigureNetwork, cniPluginPath: options.CNIPluginPath, diff --git a/imagebuildah/stage_executor.go b/imagebuildah/stage_executor.go index b1848311b93..89bc4707fcd 100644 --- a/imagebuildah/stage_executor.go +++ b/imagebuildah/stage_executor.go @@ -1069,6 +1069,11 @@ func (s *StageExecutor) prepare(ctx context.Context, from string, initializeIBCo RootFS: rootfs, } dImage.Config = &dImage.ContainerConfig + if s.executor.inheritLabels == types.OptionalBoolFalse { + // If user has selected `--inherit-labels=false` let's not + // inherit labels from base image. + dImage.Config.Labels = nil + } err = ib.FromImage(&dImage, node) if err != nil { if err2 := builder.Delete(); err2 != nil { @@ -1872,6 +1877,11 @@ func (s *StageExecutor) getCreatedBy(node *parser.Node, addedContentSummary stri if node == nil { return "/bin/sh", nil } + inheritLabels := "" + // If --inherit-label was manually set to false then update history. + if s.executor.inheritLabels == types.OptionalBoolFalse { + inheritLabels = "|inheritLabels=false" + } switch strings.ToUpper(node.Value) { case "ARG": for _, variable := range strings.Fields(node.Original) { @@ -1880,7 +1890,7 @@ func (s *StageExecutor) getCreatedBy(node *parser.Node, addedContentSummary stri } } buildArgs := s.getBuildArgsKey() - return "/bin/sh -c #(nop) ARG " + buildArgs, nil + return "/bin/sh -c #(nop) ARG " + buildArgs + inheritLabels, nil case "RUN": shArg := "" buildArgs := s.getBuildArgsResolvedForRun() @@ -1960,16 +1970,16 @@ func (s *StageExecutor) getCreatedBy(node *parser.Node, addedContentSummary stri if buildArgs != "" { result = result + "|" + strconv.Itoa(len(strings.Split(buildArgs, " "))) + " " + buildArgs + " " } - result = result + "/bin/sh -c " + shArg + heredoc + appendCheckSum + result = result + "/bin/sh -c " + shArg + heredoc + appendCheckSum + inheritLabels return result, nil case "ADD", "COPY": destination := node for destination.Next != nil { destination = destination.Next } - return "/bin/sh -c #(nop) " + strings.ToUpper(node.Value) + " " + addedContentSummary + " in " + destination.Value + " ", nil + return "/bin/sh -c #(nop) " + strings.ToUpper(node.Value) + " " + addedContentSummary + " in " + destination.Value + " " + inheritLabels, nil default: - return "/bin/sh -c #(nop) " + node.Original, nil + return "/bin/sh -c #(nop) " + node.Original + inheritLabels, nil } } diff --git a/pkg/cli/build.go b/pkg/cli/build.go index ee4961a787b..9a81a4a4f79 100644 --- a/pkg/cli/build.go +++ b/pkg/cli/build.go @@ -376,6 +376,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) ( IIDFile: iopts.Iidfile, IgnoreFile: iopts.IgnoreFile, In: stdin, + InheritLabels: types.NewOptionalBool(iopts.InheritLabels), Isolation: isolation, Jobs: &iopts.Jobs, Labels: iopts.Label, diff --git a/pkg/cli/common.go b/pkg/cli/common.go index 6cf088f365c..b52b360a87b 100644 --- a/pkg/cli/common.go +++ b/pkg/cli/common.go @@ -72,6 +72,7 @@ type BudResults struct { Format string From string Iidfile string + InheritLabels bool Label []string LayerLabel []string Logfile string @@ -231,6 +232,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.StringVar(&flags.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry") fs.BoolVar(&flags.Compress, "compress", false, "this is a legacy option, which has no effect on the image") fs.BoolVar(&flags.CompatVolumes, "compat-volumes", false, "preserve the contents of VOLUMEs during RUN instructions") + fs.BoolVar(&flags.InheritLabels, "inherit-labels", true, "inherit the labels from the base image or base stages.") fs.StringArrayVar(&flags.CPPFlags, "cpp-flag", []string{}, "set additional flag to pass to C preprocessor (cpp)") fs.StringVar(&flags.Creds, "creds", "", "use `[username[:password]]` for accessing the registry") fs.StringVarP(&flags.CWOptions, "cw", "", "", "confidential workload `options`") diff --git a/tests/bud.bats b/tests/bud.bats index a562972d92a..bd83d2b916b 100644 --- a/tests/bud.bats +++ b/tests/bud.bats @@ -2670,6 +2670,80 @@ _EOF expect_output "$want_output" } +@test "bud and test inherit-labels" { + base=registry.fedoraproject.org/fedora-minimal + _prefetch $base + _prefetch alpine + run_buildah --version + local -a output_fields=($output) + buildah_version=${output_fields[2]} + run_buildah build $WITH_POLICY_JSON -t exp -f $BUDFILES/base-with-labels/Containerfile + + run_buildah inspect --format '{{ index .Docker.Config.Labels "license"}}' exp + expect_output "MIT" "license must be MIT from fedora base image" + run_buildah inspect --format '{{ index .Docker.Config.Labels "name"}}' exp + expect_output "fedora-minimal" "name must be fedora from base image" + + run_buildah build $WITH_POLICY_JSON --inherit-labels=false --label name=world -t exp -f $BUDFILES/base-with-labels/Containerfile + # no labels should be inherited from base image, only the buildah version label + # and `hello=world` which we just added using cli flag + want_output='map["io.buildah.version":"'$buildah_version'" "name":"world"]' + run_buildah inspect --format '{{printf "%q" .Docker.Config.Labels}}' exp + expect_output "$want_output" + + # Try building another file with multiple layers + run_buildah build $WITH_POLICY_JSON --iidfile ${TEST_SCRATCH_DIR}/id1 --layers -t exp -f $BUDFILES/base-with-labels/Containerfile.layer + run_buildah inspect --format '{{ index .Docker.Config.Labels "license"}}' exp + expect_output "MIT" "license must be MIT from fedora base image" + run_buildah inspect --format '{{ index .Docker.Config.Labels "name"}}' exp + expect_output "world" "name must be world from Containerfile" + + # Now build same file with --inherit-labels=false and verify if we are not using the cache again. + run_buildah build $WITH_POLICY_JSON --layers --inherit-labels=false --iidfile ${TEST_SCRATCH_DIR}/inherit_false_1 -t exp -f $BUDFILES/base-with-labels/Containerfile.layer + # Should not contain `Using cache` at all since + assert "$output" !~ "Using cache" + want_output='map["io.buildah.version":"'$buildah_version'" "name":"world"]' + run_buildah inspect --format '{{printf "%q" .Docker.Config.Labels}}' exp + expect_output "$want_output" + + run_buildah build $WITH_POLICY_JSON --layers --inherit-labels=false --iidfile ${TEST_SCRATCH_DIR}/inherit_false_2 -t exp -f $BUDFILES/base-with-labels/Containerfile.layer + # Should contain `Using cache` + expect_output --substring " Using cache" + want_output='map["io.buildah.version":"'$buildah_version'" "name":"world"]' + run_buildah inspect --format '{{printf "%q" .Docker.Config.Labels}}' exp + expect_output "$want_output" + assert "$(cat ${TEST_SCRATCH_DIR}/inherit_false_1)" = "$(cat ${TEST_SCRATCH_DIR}/inherit_false_2)" "expected image ids to not change" + + # Now build same file with --inherit-labels=true and verify if using the cache + run_buildah build $WITH_POLICY_JSON --iidfile ${TEST_SCRATCH_DIR}/id2 --layers --inherit-labels=true -t exp -f $BUDFILES/base-with-labels/Containerfile.layer + expect_output --substring " Using cache" + run_buildah inspect --format '{{ index .Docker.Config.Labels "license"}}' exp + expect_output "MIT" "license must be MIT from fedora base image" + run_buildah inspect --format '{{ index .Docker.Config.Labels "name"}}' exp + expect_output "world" "name must be world from Containerfile" + # Final image id should be exactly same as the one image which was built in the past. + assert "$(cat ${TEST_SCRATCH_DIR}/id1)" = "$(cat ${TEST_SCRATCH_DIR}/id2)" "expected image ids to not change" + + # Now build same file with --inherit-labels=false and verify if target stage did not inherit any labels from base stage. + run_buildah build $WITH_POLICY_JSON --layers --inherit-labels=false -t exp -f $BUDFILES/base-with-labels/Containerfile.multi-stage + want_output='map["io.buildah.version":"'$buildah_version'"]' + run_buildah inspect --format '{{printf "%q" .Docker.Config.Labels}}' exp + expect_output "$want_output" + + # Now build same file with --inherit-labels=true and verify if target stage inherits labels from the base stage. + run_buildah build $WITH_POLICY_JSON --iidfile ${TEST_SCRATCH_DIR}/id3 --layers --inherit-labels=true -t exp -f $BUDFILES/base-with-labels/Containerfile.multi-stage + want_output='map["io.buildah.version":"'$buildah_version'" "name":"world"]' + run_buildah inspect --format '{{printf "%q" .Docker.Config.Labels}}' exp + expect_output "$want_output" + + # Rebuild again with layers should not build image again at all. + run_buildah build $WITH_POLICY_JSON --iidfile ${TEST_SCRATCH_DIR}/id4 --layers --inherit-labels=true -t exp -f $BUDFILES/base-with-labels/Containerfile.multi-stage + want_output='map["io.buildah.version":"'$buildah_version'" "name":"world"]' + run_buildah inspect --format '{{printf "%q" .Docker.Config.Labels}}' exp + expect_output "$want_output" + assert "$(cat ${TEST_SCRATCH_DIR}/id3)" = "$(cat ${TEST_SCRATCH_DIR}/id4)" "expected image ids to not change" +} + @test "build using intermediate images should not inherit label" { _prefetch alpine diff --git a/tests/bud/base-with-labels/Containerfile.layer b/tests/bud/base-with-labels/Containerfile.layer new file mode 100644 index 00000000000..ba86a21550b --- /dev/null +++ b/tests/bud/base-with-labels/Containerfile.layer @@ -0,0 +1,4 @@ +FROM registry.fedoraproject.org/fedora-minimal +LABEL name world +RUN echo world +RUN echo hello diff --git a/tests/bud/base-with-labels/Containerfile.multi-stage b/tests/bud/base-with-labels/Containerfile.multi-stage new file mode 100644 index 00000000000..00f72ac7687 --- /dev/null +++ b/tests/bud/base-with-labels/Containerfile.multi-stage @@ -0,0 +1,5 @@ +FROM alpine as one +LABEL name world + +FROM one +RUN echo world