build,config: add support for --unsetannotation

Just like `--unsetlabel` add support for `--unsetannotation`.

Closes: https://github.com/containers/buildah/issues/6183

Signed-off-by: flouthoc <flouthoc.git@gmail.com>
This commit is contained in:
flouthoc 2025-06-01 09:16:38 -07:00
parent b8d8cc375f
commit 83acf720d9
No known key found for this signature in database
GPG Key ID: F1993E9E45D69299
11 changed files with 180 additions and 24 deletions

View File

@ -49,6 +49,7 @@ type configResults struct {
volume []string
workingDir string
unsetLabels []string
unsetAnnotations []string
}
func init() {
@ -102,6 +103,7 @@ func init() {
flags.StringSliceVarP(&opts.volume, "volume", "v", []string{}, "add default `volume` path to be created for containers based on image (default [])")
flags.StringVar(&opts.workingDir, "workingdir", "", "set working `directory` for containers based on image")
flags.StringSliceVar(&opts.unsetLabels, "unsetlabel", nil, "remove image configuration label")
flags.StringSliceVar(&opts.unsetAnnotations, "unsetannotation", nil, "remove image configuration annotation")
rootCmd.AddCommand(configCommand)
}
@ -309,6 +311,10 @@ func updateConfig(builder *buildah.Builder, c *cobra.Command, iopts configResult
for _, key := range iopts.unsetLabels {
builder.UnsetLabel(key)
}
// unset annotation if any
for _, key := range iopts.unsetAnnotations {
builder.UnsetAnnotation(key)
}
if c.Flag("workingdir").Changed {
builder.SetWorkDir(iopts.workingDir)
conditionallyAddHistory(builder, c, "/bin/sh -c #(nop) WORKDIR %s", iopts.workingDir)

View File

@ -340,6 +340,8 @@ type BuildOptions struct {
UnsetEnvs []string
// UnsetLabels is a list of labels to not add to final image from base image.
UnsetLabels []string
// UnsetAnnotations is a list of annotations to not add to final image from base image.
UnsetAnnotations []string
// Envs is a list of environment variables to set in the final image.
Envs []string
// OSFeatures specifies operating system features the image requires.

View File

@ -1045,6 +1045,10 @@ include:
"sigpending": maximum number of pending signals (ulimit -i)
"stack": maximum stack size (ulimit -s)
**--unsetannotation** *annotation*
Unset the image annotation, causing the annotation not to be inherited from the base image.
**--unsetenv** *env*
Unset environment variables from the final image.

View File

@ -231,6 +231,10 @@ Note: this setting is not present in the OCIv1 image format, so it is discarded
Set default *stop signal* for container. This signal will be sent when container is stopped, default is SIGINT.
**--unsetannotation** *annotation*
Unset the image annotation, causing the annotation not to be inherited from the base image.
**--unsetlabel** *label*
Unset the image label, causing the label not to be inherited from the base image.

View File

@ -151,6 +151,7 @@ type Executor struct {
logPrefix string
unsetEnvs []string
unsetLabels []string
unsetAnnotations []string
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
buildOutputs []string // Specifies instructions for any custom build output
@ -319,6 +320,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
logPrefix: logPrefix,
unsetEnvs: slices.Clone(options.UnsetEnvs),
unsetLabels: slices.Clone(options.UnsetLabels),
unsetAnnotations: slices.Clone(options.UnsetAnnotations),
buildOutputs: buildOutputs,
osVersion: options.OSVersion,
osFeatures: slices.Clone(options.OSFeatures),
@ -331,6 +333,11 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
compatScratchConfig: options.CompatScratchConfig,
noPivotRoot: options.NoPivotRoot,
}
// sort unsetAnnotations because we will later write these
// values to the history of the image therefore we want to
// make sure that order is always consistent.
slices.Sort(exec.unsetAnnotations)
if exec.err == nil {
exec.err = os.Stderr
}

View File

@ -1284,7 +1284,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
if len(children) == 0 {
// There are no steps.
if s.builder.FromImageID == "" || s.executor.squash || s.executor.confidentialWorkload.Convert || len(s.executor.labels) > 0 || len(s.executor.annotations) > 0 || len(s.executor.unsetEnvs) > 0 || len(s.executor.unsetLabels) > 0 || len(s.executor.sbomScanOptions) > 0 {
if s.builder.FromImageID == "" || s.executor.squash || s.executor.confidentialWorkload.Convert || len(s.executor.labels) > 0 || len(s.executor.annotations) > 0 || len(s.executor.unsetEnvs) > 0 || len(s.executor.unsetLabels) > 0 || len(s.executor.sbomScanOptions) > 0 || len(s.executor.unsetAnnotations) > 0 {
// We either don't have a base image, or we need to
// transform the contents of the base image, or we need
// to make some changes to just the config blob. Whichever
@ -1293,7 +1293,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// No base image means there's nothing to put in a
// layer, so don't create one.
emptyLayer := (s.builder.FromImageID == "")
createdBy, err := s.getCreatedBy(nil, "")
createdBy, err := s.getCreatedBy(nil, "", lastStage)
if err != nil {
return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err)
}
@ -1444,7 +1444,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
if s.executor.timestamp != nil {
timestamp = *s.executor.timestamp
}
createdBy, err := s.getCreatedBy(node, addedContentSummary)
createdBy, err := s.getCreatedBy(node, addedContentSummary, false)
if err != nil {
return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err)
}
@ -1458,7 +1458,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// stage.
if lastStage || imageIsUsedLater {
logCommit(s.output, i)
createdBy, err := s.getCreatedBy(node, addedContentSummary)
createdBy, err := s.getCreatedBy(node, addedContentSummary, lastStage && lastInstruction)
if err != nil {
return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err)
}
@ -1533,7 +1533,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// cacheKey since it will be used either while pulling or pushing the
// cache images.
if needsCacheKey {
cacheKey, err = s.generateCacheKey(ctx, node, addedContentSummary, s.stepRequiresLayer(step))
cacheKey, err = s.generateCacheKey(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage)
if err != nil {
return "", nil, false, fmt.Errorf("failed while generating cache key: %w", err)
}
@ -1561,13 +1561,13 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
addedContentSummary = s.getContentSummaryAfterAddingContent()
// regenerate cache key with updated content summary
if needsCacheKey {
cacheKey, err = s.generateCacheKey(ctx, node, addedContentSummary, s.stepRequiresLayer(step))
cacheKey, err = s.generateCacheKey(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage)
if err != nil {
return "", nil, false, fmt.Errorf("failed while generating cache key: %w", err)
}
}
}
cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step))
cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage)
if err != nil {
return "", nil, false, fmt.Errorf("checking if cached image exists from a previous build: %w", err)
}
@ -1579,7 +1579,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// is ignored and will be automatically logged for --log-level debug
if ref, id, err := s.pullCache(ctx, cacheKey); ref != nil && id != "" && err == nil {
logCachePulled(cacheKey, ref)
cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step))
cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage)
if err != nil {
return "", nil, false, fmt.Errorf("checking if cached image exists from a previous build: %w", err)
}
@ -1611,7 +1611,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
addedContentSummary = s.getContentSummaryAfterAddingContent()
// regenerate cache key with updated content summary
if needsCacheKey {
cacheKey, err = s.generateCacheKey(ctx, node, addedContentSummary, s.stepRequiresLayer(step))
cacheKey, err = s.generateCacheKey(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage)
if err != nil {
return "", nil, false, fmt.Errorf("failed while generating cache key: %w", err)
}
@ -1620,7 +1620,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// Check if there's already an image based on our parent that
// has the same change that we just made.
if checkForLayers && !avoidLookingCache {
cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step))
cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage)
if err != nil {
return "", nil, false, fmt.Errorf("checking if cached image exists from a previous build: %w", err)
}
@ -1633,7 +1633,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// is ignored and will be automatically logged for --log-level debug
if ref, id, err := s.pullCache(ctx, cacheKey); ref != nil && id != "" && err == nil {
logCachePulled(cacheKey, ref)
cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step))
cacheID, err = s.intermediateImageExists(ctx, node, addedContentSummary, s.stepRequiresLayer(step), lastInstruction && lastStage)
if err != nil {
return "", nil, false, fmt.Errorf("checking if cached image exists from a previous build: %w", err)
}
@ -1683,7 +1683,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// We're not going to find any more cache hits, so we
// can stop looking for them.
checkForLayers = false
createdBy, err := s.getCreatedBy(node, addedContentSummary)
createdBy, err := s.getCreatedBy(node, addedContentSummary, lastStage && lastInstruction)
if err != nil {
return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err)
}
@ -1725,7 +1725,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
if lastInstruction && lastStage {
if s.executor.squash || s.executor.confidentialWorkload.Convert || len(s.executor.sbomScanOptions) != 0 {
createdBy, err := s.getCreatedBy(node, addedContentSummary)
createdBy, err := s.getCreatedBy(node, addedContentSummary, lastStage && lastInstruction)
if err != nil {
return "", nil, false, fmt.Errorf("unable to get createdBy for the node: %w", err)
}
@ -1826,7 +1826,7 @@ func historyEntriesEqual(base, derived v1.History) bool {
// that we're comparing.
// Used to verify whether a cache of the intermediate image exists and whether
// to run the build again.
func (s *StageExecutor) historyAndDiffIDsMatch(baseHistory []v1.History, baseDiffIDs []digest.Digest, child *parser.Node, history []v1.History, diffIDs []digest.Digest, addedContentSummary string, buildAddsLayer bool) (bool, error) {
func (s *StageExecutor) historyAndDiffIDsMatch(baseHistory []v1.History, baseDiffIDs []digest.Digest, child *parser.Node, history []v1.History, diffIDs []digest.Digest, addedContentSummary string, buildAddsLayer bool, lastInstruction bool) (bool, error) {
// our history should be as long as the base's, plus one entry for what
// we're doing
if len(history) != len(baseHistory)+1 {
@ -1865,7 +1865,7 @@ func (s *StageExecutor) historyAndDiffIDsMatch(baseHistory []v1.History, baseDif
return false, nil
}
}
createdBy, err := s.getCreatedBy(child, addedContentSummary)
createdBy, err := s.getCreatedBy(child, addedContentSummary, lastInstruction)
if err != nil {
return false, fmt.Errorf("unable to get createdBy for the node: %w", err)
}
@ -1875,15 +1875,25 @@ func (s *StageExecutor) historyAndDiffIDsMatch(baseHistory []v1.History, baseDif
// getCreatedBy returns the command the image at node will be created by. If
// the passed-in CompositeDigester is not nil, it is assumed to have the digest
// information for the content if the node is ADD or COPY.
func (s *StageExecutor) getCreatedBy(node *parser.Node, addedContentSummary string) (string, error) {
//
// This function acts differently if getCreatedBy is invoked by LastStep. For instances
// certain instructions like `removing annotations` does not makes sense for every step
// but only makes sense if the step is last step of a build.
func (s *StageExecutor) getCreatedBy(node *parser.Node, addedContentSummary string, isLastStep bool) (string, error) {
if node == nil {
return "/bin/sh", nil
}
inheritLabels := ""
unsetAnnotations := ""
// If --inherit-label was manually set to false then update history.
if s.executor.inheritLabels == types.OptionalBoolFalse {
inheritLabels = "|inheritLabels=false"
}
if isLastStep {
for _, annotation := range s.executor.unsetAnnotations {
unsetAnnotations += "|unsetAnnotation=" + annotation
}
}
switch strings.ToUpper(node.Value) {
case "ARG":
for _, variable := range strings.Fields(node.Original) {
@ -1892,7 +1902,7 @@ func (s *StageExecutor) getCreatedBy(node *parser.Node, addedContentSummary stri
}
}
buildArgs := s.getBuildArgsKey()
return "/bin/sh -c #(nop) ARG " + buildArgs + inheritLabels, nil
return "/bin/sh -c #(nop) ARG " + buildArgs + inheritLabels + unsetAnnotations, nil
case "RUN":
shArg := ""
buildArgs := s.getBuildArgsResolvedForRun()
@ -1972,16 +1982,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 + inheritLabels
result = result + "/bin/sh -c " + shArg + heredoc + appendCheckSum + inheritLabels + unsetAnnotations
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 + " " + inheritLabels, nil
return "/bin/sh -c #(nop) " + strings.ToUpper(node.Value) + " " + addedContentSummary + " in " + destination.Value + " " + inheritLabels + " " + unsetAnnotations, nil
default:
return "/bin/sh -c #(nop) " + node.Original + inheritLabels, nil
return "/bin/sh -c #(nop) " + node.Original + inheritLabels + unsetAnnotations, nil
}
}
@ -2115,7 +2125,7 @@ func (s *StageExecutor) tagExistingImage(ctx context.Context, cacheID, output st
// generated CacheKey is further used by buildah to lock and decide
// tag for the intermediate image which can be pushed and pulled to/from
// the remote repository.
func (s *StageExecutor) generateCacheKey(ctx context.Context, currNode *parser.Node, addedContentDigest string, buildAddsLayer bool) (string, error) {
func (s *StageExecutor) generateCacheKey(ctx context.Context, currNode *parser.Node, addedContentDigest string, buildAddsLayer bool, lastInstruction bool) (string, error) {
hash := sha256.New()
var baseHistory []v1.History
var diffIDs []digest.Digest
@ -2130,7 +2140,7 @@ func (s *StageExecutor) generateCacheKey(ctx context.Context, currNode *parser.N
fmt.Fprintln(hash, diffIDs[i].String())
}
}
createdBy, err := s.getCreatedBy(currNode, addedContentDigest)
createdBy, err := s.getCreatedBy(currNode, addedContentDigest, lastInstruction)
if err != nil {
return "", err
}
@ -2248,7 +2258,7 @@ func (s *StageExecutor) pullCache(ctx context.Context, cacheKey string) (referen
// intermediateImageExists returns image ID if an intermediate image of currNode exists in the image store from a previous build.
// It verifies this by checking the parent of the top layer of the image and the history.
// If more than one image matches as potiential candidates then priority is given to the most recently built image.
func (s *StageExecutor) intermediateImageExists(ctx context.Context, currNode *parser.Node, addedContentDigest string, buildAddsLayer bool) (string, error) {
func (s *StageExecutor) intermediateImageExists(ctx context.Context, currNode *parser.Node, addedContentDigest string, buildAddsLayer bool, lastInstruction bool) (string, error) {
cacheCandidates := []storage.Image{}
// Get the list of images available in the image store
images, err := s.executor.store.Images()
@ -2315,7 +2325,7 @@ func (s *StageExecutor) intermediateImageExists(ctx context.Context, currNode *p
continue
}
// children + currNode is the point of the Dockerfile we are currently at.
foundMatch, err := s.historyAndDiffIDsMatch(baseHistory, baseDiffIDs, currNode, history, diffIDs, addedContentDigest, buildAddsLayer)
foundMatch, err := s.historyAndDiffIDsMatch(baseHistory, baseDiffIDs, currNode, history, diffIDs, addedContentDigest, buildAddsLayer, lastInstruction)
if err != nil {
return "", err
}
@ -2431,6 +2441,11 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer
annotationk, annotationv, _ := strings.Cut(annotationSpec, "=")
s.builder.SetAnnotation(annotationk, annotationv)
}
if finalInstruction {
for _, key := range s.executor.unsetAnnotations {
s.builder.UnsetAnnotation(key)
}
}
if imageRef != nil {
logName := transports.ImageName(imageRef)
logrus.Debugf("COMMIT %q", logName)

View File

@ -419,6 +419,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
TransientMounts: iopts.Volumes,
UnsetEnvs: iopts.UnsetEnvs,
UnsetLabels: iopts.UnsetLabels,
UnsetAnnotations: iopts.UnsetAnnotations,
}
if iopts.RetryDelay != "" {
options.PullPushRetryDelay, err = time.ParseDuration(iopts.RetryDelay)

View File

@ -116,6 +116,7 @@ type BudResults struct {
RusageLogFile string
UnsetEnvs []string
UnsetLabels []string
UnsetAnnotations []string
Envs []string
OSFeatures []string
OSVersion string
@ -314,6 +315,7 @@ newer: only pull base and SBOM scanner images when newer images exist on the r
fs.String("variant", "", "override the `variant` of the specified image")
fs.StringSliceVar(&flags.UnsetEnvs, "unsetenv", nil, "unset environment variable from final image")
fs.StringSliceVar(&flags.UnsetLabels, "unsetlabel", nil, "unset label when inheriting labels from base image")
fs.StringSliceVar(&flags.UnsetAnnotations, "unsetannotation", nil, "unset annotation when inheriting annotation from base image")
return fs
}
@ -368,6 +370,7 @@ func GetBudFlagsCompletions() commonComp.FlagCompletions {
flagCompletion["timestamp"] = commonComp.AutocompleteNone
flagCompletion["unsetenv"] = commonComp.AutocompleteNone
flagCompletion["unsetlabel"] = commonComp.AutocompleteNone
flagCompletion["unsetannotation"] = commonComp.AutocompleteNone
flagCompletion["variant"] = commonComp.AutocompleteNone
return flagCompletion
}

View File

@ -2670,6 +2670,104 @@ _EOF
expect_output "$want_output"
}
@test "bud and test --unsetannotation" {
base=registry.fedoraproject.org/fedora-minimal
_prefetch $base
target=exp
run_buildah --version
local -a output_fields=($output)
buildah_version=${output_fields[2]}
buildah inspect --format '{{ .ImageAnnotations }}' $base
not_want_output='map[]'
assert "$output" != "$not_want_output" "expected some annotations to be set in base image $base"
annotations=$(buildah inspect --format '{{ range $key, $value := .ImageAnnotations }}{{ $key }} {{end}}' $base)
annotationflags="--annotation hello=world"
for annotation in $annotations; do
if test $annotation != io.buildah.version ; then
annotationflags="$annotationflags --unsetannotation $annotation"
fi
done
run_buildah build $WITH_POLICY_JSON $annotationflags -t $target --from $base $BUDFILES/base-with-labels
# no annotations should be inherited from base image
# and `hello=world` which we just added using cli flag
want_output='map["hello":"world"]'
run_buildah inspect --format '{{printf "%q" .ImageAnnotations}}' $target
expect_output "$want_output"
}
@test "bud and test --unsetannotation with --layers" {
base=registry.fedoraproject.org/fedora-minimal
_prefetch $base
target=exp
run_buildah --version
local -a output_fields=($output)
buildah_version=${output_fields[2]}
buildah inspect --format '{{ .ImageAnnotations }}' $base
not_want_output='map[]'
assert "$output" != "$not_want_output" "expected some annotations to be set in base image $base"
## Build without removing annotations
run_buildah build $WITH_POLICY_JSON --layers --iidfile ${TEST_SCRATCH_DIR}/iid1 -t $target --from $base $BUDFILES/base-with-labels
## Second build must use cache
run_buildah build $WITH_POLICY_JSON --layers --iidfile ${TEST_SCRATCH_DIR}/iid2 -t $target --from $base $BUDFILES/base-with-labels
## Must use cache
expect_output --substring " Using cache"
cmp ${TEST_SCRATCH_DIR}/iid1 ${TEST_SCRATCH_DIR}/iid2
annotations=$(buildah inspect --format '{{ range $key, $value := .ImageAnnotations }}{{ $key }} {{end}}' $base)
annotationflags="--annotation hello=world"
for annotation in $annotations; do
if test $annotation != io.buildah.version ; then
annotationflags="$annotationflags --unsetannotation $annotation"
fi
done
## Since we are unsetting something, this should not use previous image present in the cache.
run_buildah build $WITH_POLICY_JSON --layers $annotationflags -t $target --from $base $BUDFILES/base-with-labels
## should not contain `Using Cache`
#assert "$output" !~ "Using cache"
# no annotations should be inherited from base image
# and `hello=world` which we just added using cli flag
want_output='map["hello":"world"]'
run_buildah inspect --format '{{printf "%q" .ImageAnnotations}}' $target
expect_output "$want_output"
}
@test "bud and test --unsetannotation with only base image" {
base=registry.fedoraproject.org/fedora-minimal
_prefetch $base
target=exp
run_buildah --version
local -a output_fields=($output)
buildah_version=${output_fields[2]}
buildah inspect --format '{{ .ImageAnnotations }}' $base
not_want_output='map[]'
assert "$output" != "$not_want_output" "expected some annotations to be set in base image $base"
annotations=$(buildah inspect --format '{{ range $key, $value := .ImageAnnotations }}{{ $key }} {{end}}' $base)
annotationflags="--annotation hello=world"
for annotation in $annotations; do
if test $annotation != io.buildah.version ; then
annotationflags="$annotationflags --unsetannotation $annotation"
fi
done
run_buildah build $WITH_POLICY_JSON $annotationflags -t $target --from $base $BUDFILES/only-base
want_output='map["hello":"world"]'
run_buildah inspect --format '{{printf "%q" .ImageAnnotations}}' $target
expect_output "$want_output"
}
@test "bud and test inherit-labels" {
base=registry.fedoraproject.org/fedora-minimal
_prefetch $base

View File

@ -0,0 +1 @@
FROM registry.fedoraproject.org/fedora-minimal

View File

@ -96,6 +96,21 @@ function check_matrix() {
assert "$output" == "" "name label should be removed"
}
@test "config --unsetannotation" {
base=registry.fedoraproject.org/fedora-minimal
_prefetch $base
run_buildah from --quiet --pull=false $WITH_POLICY_JSON $base
cid=$output
run_buildah commit $WITH_POLICY_JSON $cid with-name-annotation
run_buildah config --unsetannotation org.opencontainers.image.base.name $cid
run_buildah commit $WITH_POLICY_JSON $cid without-name-annotation
run_buildah inspect --format '{{ index .ImageAnnotations "org.opencontainers.image.base.name"}}' with-name-annotation
assert "$output" != "" "annotation should be set in base image"
run_buildah inspect --format '{{ index .ImageAnnotations "org.opencontainers.image.base.name"}}' without-name-annotation
assert "$output" == "" "name annotation should be removed"
}
@test "config set empty entrypoint doesn't wipe cmd" {
run_buildah from $WITH_POLICY_JSON scratch
cid=$output