Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/buildah-build.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ Instead of building for a set of platforms specified using the **--platform** op
Add an image *annotation* (e.g. annotation=*value*) to the image metadata. Can be used multiple times.
If *annotation* is named, but neither `=` nor a `value` is provided, then the *annotation* is set to an empty value.

If the annotation name is prefixed with "manifest:", the prefix will be stripped from it and the annotation will
be added to the built image(s).

If the annotation name is prefixed with "manifest-descriptor:", and the **--manifest** flag is being used, the prefix will
be stripped from it and the annotation will be set on new entries which are added to the list of instances in the image index
instead of to the image metadata. If the **--manifest** flag is not being used, this will trigger an error.

If the annotation name is prefixed with "index:", and the **--manifest** flag is being used, the prefix will be stripped
from it and the annotation will be set in the image index instead of in the image metadata. If the **--manifest** flag is not being used, this value will trigger an error.

One or more of the "manifest:", "manifest-descriptor:", and "index:" prefixes can be combined
into a comma-separated list, for example as "manifest,manifest-descriptor:".

Note: this information is not present in Docker image formats, so it is discarded when writing images in Docker formats.

**--arch**="ARCH"
Expand Down Expand Up @@ -1125,6 +1138,9 @@ include:

Unset the image annotation, causing the annotation not to be inherited from the base image.

If the annotation name is prefixed with "manifest:", the prefix will be stripped from it and
the rest of the argument will be treated as the name of the annotation.

**--unsetenv** *env*

Unset environment variables from the final image.
Expand Down
4 changes: 4 additions & 0 deletions docs/buildah-commit.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ if it is specified. This option can be specified multiple times.
Add an image *annotation* (e.g. annotation=*value*) to the image metadata. Can be used multiple times.
If *annotation* is named, but neither `=` nor a `value` is provided, then the *annotation* is set to an empty value.

If the annotation name is prefixed with "manifest:", the prefix will be stripped from it.

Note: this information is not present in Docker image formats, so it is discarded when writing images in Docker formats.

**--authfile** *path*
Expand Down Expand Up @@ -371,6 +373,8 @@ Require HTTPS and verification of certificates when talking to container registr

Unset the image annotation, causing the annotation not to be inherited from the base image.

If the annotation name is prefixed with "manifest:", the prefix will be stripped from it.

**--unsetenv** *env*

Unset environment variables from the final image.
Expand Down
14 changes: 14 additions & 0 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,10 +607,24 @@ func (i *containerImageRef) newOCIManifestBuilder() (manifestBuilder, error) {
annotations[v1.AnnotationCreated] = created.UTC().Format(time.RFC3339Nano)
}
for _, k := range i.unsetAnnotations {
levelSpec, key, levelsSpecified := strings.Cut(k, ":")
if levelsSpecified {
k = key
if !slices.Contains(strings.Split(levelSpec, ","), "manifest") {
return nil, fmt.Errorf("can't unset non-manifest (%q) annotation %q", levelSpec, k)
}
}
delete(annotations, k)
}
for _, kv := range i.setAnnotations {
k, v, _ := strings.Cut(kv, "=")
levelSpec, key, levelsSpecified := strings.Cut(k, ":")
if levelsSpecified {
k = key
if !slices.Contains(strings.Split(levelSpec, ","), "manifest") {
return nil, fmt.Errorf("can't set non-manifest (%q) annotation %q", levelSpec, k)
}
}
annotations[k] = v
}
return &ociManifestBuilder{
Expand Down
122 changes: 122 additions & 0 deletions imagebuildah/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,59 @@ type Mount = specs.Mount

type BuildOptions = define.BuildOptions

// selectAnnotations selects annotations that were meant for a particular level, and
// returns them in bare "k=v" form, without the levels (list) prefix
func selectAnnotations(inputs []string, level string, includeNoLevels bool) []string {
if len(inputs) == 0 {
return slices.Clone(inputs)
}
var s []string
for _, kv := range inputs {
k, v, equals := strings.Cut(kv, "=")
levelSpec, key, levelSpecified := strings.Cut(k, ":")
if levelSpecified {
if !slices.Contains(strings.Split(levelSpec, ","), level) {
continue
}
k = key
} else {
if !includeNoLevels {
continue
}
}
if equals {
s = append(s, k+"="+v)
} else {
s = append(s, k)
}
}
return s
}

// requireAnnotationLevels checks if there are any annotations that were meant for levels other than
// those that were passed in, and returns an error if it finds any
func requireAnnotationLevels(inputs []string, allowedLevels []string) error {
if len(inputs) == 0 {
return nil
}
const noLevel = "manifest"
allowedFunc := func(s string) bool { return slices.Contains(allowedLevels, s) }
for _, kv := range inputs {
k, _, _ := strings.Cut(kv, "=")
levelSpec, _, levelSpecified := strings.Cut(k, ":")
if levelSpecified {
if !slices.ContainsFunc(strings.Split(levelSpec, ","), allowedFunc) {
return fmt.Errorf("disallowed annotation level %q in %q: only %q allowed", levelSpec, k, allowedLevels)
}
} else {
if !allowedFunc(noLevel) {
return fmt.Errorf("disallowed unspecified annotation level %q in %q", noLevel, k)
}
}
}
return nil
}

// BuildDockerfiles parses a set of one or more Dockerfiles (which may be
// URLs), creates one or more new Executors, and then runs
// Prepare/Execute/Commit/Delete over the entire set of instructions.
Expand Down Expand Up @@ -188,6 +241,22 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
}
}

if options.Manifest != "" {
if err := requireAnnotationLevels(options.Annotations, []string{"manifest", "manifest-descriptor", "index"}); err != nil {
return "", nil, err
}
if err := requireAnnotationLevels(options.UnsetAnnotations, []string{"manifest"}); err != nil {
return "", nil, err
}
} else {
if err := requireAnnotationLevels(options.Annotations, []string{"manifest"}); err != nil {
return "", nil, err
}
if err := requireAnnotationLevels(options.UnsetAnnotations, []string{"manifest"}); err != nil {
return "", nil, err
}
}

manifestList := options.Manifest
options.Manifest = ""
type instance struct {
Expand Down Expand Up @@ -264,6 +333,8 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
platformOptions.SystemContext = &platformContext
platformOptions.OS = platformContext.OSChoice
platformOptions.Architecture = platformContext.ArchitectureChoice
platformOptions.Annotations = selectAnnotations(options.Annotations, "manifest", true)
platformOptions.UnsetAnnotations = selectAnnotations(options.UnsetAnnotations, "manifest", true)
logPrefix := ""
if len(options.Platforms) > 1 {
logPrefix = "[" + platforms.Format(platformSpec) + "] "
Expand Down Expand Up @@ -358,9 +429,59 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
if err != nil {
return "", nil, err
}
// In case it already exists, pull up its annotations.
inspectData, err := list.Inspect()
if err != nil {
return "", nil, err
}
// If we're adding annotations to the index, add them now.
indexAnnotations := maps.Clone(inspectData.Annotations)
for _, k := range selectAnnotations(options.UnsetAnnotations, "index", false) {
delete(indexAnnotations, k)
}
for _, kv := range selectAnnotations(options.Annotations, "index", false) {
k, v, _ := strings.Cut(kv, "=")
if v != "" {
if indexAnnotations == nil {
indexAnnotations = make(map[string]string)
}
indexAnnotations[k] = v
} else {
delete(indexAnnotations, k)
}
}
err = list.AnnotateInstance("", &libimage.ManifestListAnnotateOptions{
IndexAnnotations: indexAnnotations,
})
if err != nil {
return "", nil, err
}
// Add each instance to the list in turn.
storeTransportName := istorage.Transport.Name()
for _, instance := range instances {
// If we're adding annotations to the instance, build them now.
var instanceAnnotations map[string]string
for _, k := range selectAnnotations(options.UnsetAnnotations, "manifest-descriptor", false) {
delete(instanceAnnotations, k)
}
for _, kv := range selectAnnotations(options.Annotations, "manifest-descriptor", false) {
k, v, _ := strings.Cut(kv, "=")
if v != "" {
if instanceAnnotations == nil {
instanceAnnotations = make(map[string]string)
}
instanceAnnotations[k] = v
} else {
delete(instanceAnnotations, k)
}
}
err = list.AnnotateInstance("", &libimage.ManifestListAnnotateOptions{
IndexAnnotations: indexAnnotations,
})
if err != nil {
return "", nil, err
}
// Add the instance and set the things we want to set in it.
instanceDigest, err := list.Add(ctx, storeTransportName+":"+instance.ID, nil)
if err != nil {
return "", nil, err
Expand All @@ -369,6 +490,7 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
Architecture: instance.Architecture,
OS: instance.OS,
Variant: instance.Variant,
Annotations: instanceAnnotations,
})
if err != nil {
return "", nil, err
Expand Down
91 changes: 91 additions & 0 deletions tests/bud.bats
Original file line number Diff line number Diff line change
Expand Up @@ -8844,3 +8844,94 @@ _EOF
run_buildah build --layers ${contextdir}
run_buildah build ${contextdir}
}

@test "bud-with-annotation-levels" {
_prefetch busybox
local contextdir=${TEST_SCRATCH_DIR}/context
mkdir $contextdir
cat > $contextdir/Dockerfile << EOF
FROM busybox
RUN pwd > pwd.txt
EOF
cat > $contextdir/Dockerfile2 << EOF
FROM localhost/foo
RUN pwd > pwd.txt
EOF
for level in "" manifest: manifest-descriptor: index: ; do
local annotation=a=b
run_buildah build --annotation ${level}${annotation} --annotation image=annotation --annotation manifest:manifest=annotation --manifest foo ${contextdir}
case "$level" in
"manifest:")
run_buildah inspect -t image foo
run jq -r '.ImageAnnotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = ${annotation##*=}
run_buildah manifest inspect foo
run jq -r '.manifests[0].annotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = null
run_buildah manifest inspect foo
run jq -r '.annotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = null
;;
"manifest-descriptor:")
run_buildah inspect -t image foo
run jq -r '.ImageAnnotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = null
run_buildah manifest inspect foo
run jq -r '.manifests[0].annotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = ${annotation##*=}
run_buildah manifest inspect foo
run jq -r '.annotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = null
;;
"index:")
run_buildah inspect -t image foo
run jq -r '.ImageAnnotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = null
run_buildah manifest inspect foo
run jq -r '.manifests[0].annotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = null
run_buildah manifest inspect foo
run jq -r '.annotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = ${annotation##*=}
;;
esac
run_buildah build --unsetannotation ${annotation%%=*} --manifest foo2 -f ${contextdir}/Dockerfile2 ${contextdir}
run_buildah inspect -t image foo2
run jq -r '.ImageAnnotations["'${annotation%%=*}'"]' <<< "$output"
assert $status = 0
assert "$output" = null
run_buildah inspect -t image foo2
run jq -r '.ImageAnnotations["image"]' <<< "$output"
assert $status = 0
assert "$output" = annotation
run_buildah inspect -t image foo2
run jq -r '.ImageAnnotations["manifest"]' <<< "$output"
assert $status = 0
assert "$output" = annotation
run_buildah manifest rm foo2
run_buildah manifest rm foo
done
run_buildah 125 build --annotation manifest-descriptor:a=b ${contextdir}
assert "$output" =~ "disallowed annotation level"
run_buildah 125 build --annotation index:a=b ${contextdir}
assert "$output" =~ "disallowed annotation level"
run_buildah 125 build --unsetannotation index:a=b ${contextdir}
assert "$output" =~ "disallowed annotation level"
run_buildah 125 build --annotation index-descriptor:a=b ${contextdir}
assert "$output" =~ "disallowed annotation level"
run_buildah 125 build --unsetannotation index-descriptor:a=b ${contextdir}
assert "$output" =~ "disallowed annotation level"
run_buildah 125 build --annotation made-up:a=b ${contextdir}
assert "$output" =~ "disallowed annotation level"
run_buildah 125 build --unsetannotation made-up:a=b ${contextdir}
assert "$output" =~ "disallowed annotation level"
}
32 changes: 32 additions & 0 deletions tests/commit.bats
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,35 @@ load helpers
fi
done
}

@test "commit-with-annotation-levels" {
_prefetch busybox
run_buildah from -q busybox
local cid="$output"
for level in "" manifest: ; do
for annotation in a=b c=d ; do
local subdir=${level%:}${annotation%%=*}
run_buildah commit --annotation ${level}${annotation} "$cid" oci:${TEST_SCRATCH_DIR}/$subdir
local manifest=${TEST_SCRATCH_DIR}/$subdir/$(oci_image_manifest ${TEST_SCRATCH_DIR}/$subdir)
run jq -r '.annotations["'${annotation%%=*}'"]' "$manifest"
assert $status -eq 0
echo "$output"
assert "$output" = ${annotation##*=}
run_buildah from --quiet oci:${TEST_SCRATCH_DIR}/$subdir
subcid="$output"
run_buildah commit --unsetannotation ${level}${annotation%%=*} "$subcid" oci:${TEST_SCRATCH_DIR}/cleaned-$subdir
manifest=${TEST_SCRATCH_DIR}/cleaned-$subdir/$(oci_image_manifest ${TEST_SCRATCH_DIR}/cleaned-$subdir)
run jq -r '.annotations["'${annotation%%=*}'"]' "$manifest"
echo "$output"
assert "$output" = null
done
done
run_buildah 125 commit --annotation manifest-descriptor:a=b "$cid" oci:${TEST_SCRATCH_DIR}/nonce
assert "$output" =~ "can't set non-manifest.*annotation"
run_buildah 125 commit --annotation index:a=b "$cid" oci:${TEST_SCRATCH_DIR}/nonce
assert "$output" =~ "can't set non-manifest.*annotation"
run_buildah 125 commit --annotation index-descriptor:a=b "$cid" oci:${TEST_SCRATCH_DIR}/nonce
assert "$output" =~ "can't set non-manifest.*annotation"
run_buildah 125 commit --annotation made-up:a=b "$cid" oci:${TEST_SCRATCH_DIR}/nonce
assert "$output" =~ "can't set non-manifest.*annotation"
}