Files
29b92a58c1 Add container-storage support for Podman and Buildah (#5368)
* feat: add containers-storage support for Podman and Buildah, update tests and documentation

Signed-off-by: Bruce Clark <bruce.clark@mandg.com>

* test: update containers-storage tests to validate package.json and handle missing images

Signed-off-by: Bruce Clark <bruce.clark@mandg.com>

* test: fix rootless containers-storage test setup

Install uidmap for Podman and Buildah CI jobs.
Align temporary rootful and rootless storage paths.
Clean up rootless stores within the builder's user namespace.

Signed-off-by: Bruce Clark <bruce.clark@mandg.com>

* fix: only use containers-storage when explicitly requested

- containers-storage is no longer part of automatic image resolution; it's used only for `--from containers-storage`, the `containers-storage:` scheme, or `default-pull-source: containers-storage` (now an accepted value). Opening a store runs full graph driver init (mounts, locks, store writes), which is too invasive as a probe on every plain image reference.
- rootless stores must be read from inside the builder's user namespace (`podman unshare syft ...`), documented in the README.
- the `containers_image_openpgp` tag is now applied to unit/integration tests and lint, so dev tooling compiles the same provider that ships.
- containers-storage CI runs through `make containers-storage-test` and covers both vfs and overlay stores.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix: exclude cgo btrfs driver and unmount overlay store in cleanup

- cgo-enabled linux builds (`-race` tests, lint) compile go.podman.io's btrfs graph driver, which needs libbtrfs headers. Add `exclude_graphdriver_btrfs` alongside `containers_image_openpgp` everywhere the tag is set; release builds are `CGO_ENABLED=0` and never included it.
- the overlay driver bind-mounts its graphroot inside the rootless user namespace, so the containers-storage test unmounts it before removing the store, and cleanup no longer fails the run.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Bruce Clark <bruce.clark@mandg.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Bruce Clark <bruce.clark@mandg.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-10-05 15:02:37 +00:00

139 lines
5.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# build an image with podman or buildah into an isolated containers-storage store, then verify syft can scan it out
# of that store (for both the vfs and overlay drivers) and that plain image references never touch the store.
#
# usage: SYFT_BINARY_LOCATION=/path/to/syft source-test.sh podman|buildah
set -euo pipefail
builder="${1:?expected podman or buildah}"
case "$builder" in
podman|buildah) ;;
*)
printf 'unsupported image builder: %s\n' "$builder" >&2
exit 2
;;
esac
syft_binary="${SYFT_BINARY_LOCATION:?expected SYFT_BINARY_LOCATION to point at a linux syft binary built with containers-storage support}"
if [[ ! -f "$syft_binary" ]]; then
printf 'syft binary not found at %s\n' "$syft_binary" >&2
exit 1
fi
chmod +x "$syft_binary"
test_dir="$(mktemp -d)"
# the overlay driver bind-mounts its graphroot onto itself (in the builder's user namespace when rootless), which must
# be unmounted before the store can be removed. Cleanup is best-effort so it never masks the test result.
cleanup_store() {
umount -l "$1"/*/graphroot/overlay 2>/dev/null || true
rm -rf "$1"
}
cleanup() {
if [[ "$(id -u)" -eq 0 ]]; then
cleanup_store "$test_dir"
else
"$builder" unshare bash -c "$(declare -f cleanup_store); cleanup_store \"\$1\"" _ "$test_dir"
fi || printf 'warning: unable to fully clean up %s\n' "$test_dir" >&2
}
trap cleanup EXIT
# a rootless store can only be opened from inside the builder's user namespace (as podman/buildah/skopeo do for
# themselves), so rootless users must run syft under "<builder> unshare". Root opens the store directly.
run_syft() {
if [[ "$(id -u)" -eq 0 ]]; then
"$syft_binary" "$@"
else
"$builder" unshare "$syft_binary" "$@"
fi
}
build_context="$test_dir/context"
mkdir -p "$build_context"
cat > "$build_context/package.json" <<'EOF'
{"name":"syft-cs-fixture","version":"1.2.3"}
EOF
cat > "$build_context/Containerfile" <<'EOF'
FROM scratch
COPY package.json /app/node_modules/syft-cs-fixture/package.json
EOF
# note: the image name deliberately avoids the string "containers-storage" so stderr greps below are unambiguous
image_ref="localhost/syft-cs-fixture:latest"
missing_ref="localhost/syft-cs-missing:latest"
for driver in vfs overlay; do
store_dir="$test_dir/$driver"
mkdir -p "$store_dir"
export CONTAINERS_STORAGE_CONF="$store_dir/storage.conf"
cat > "$CONTAINERS_STORAGE_CONF" <<EOF
[storage]
driver = "$driver"
graphroot = "$store_dir/graphroot"
rootless_storage_path = "$store_dir/graphroot"
runroot = "$store_dir/runroot"
EOF
printf 'Building %s with %s (%s driver)\n' "$image_ref" "$builder" "$driver"
case "$builder" in
podman)
podman build --pull=never --tag "$image_ref" "$build_context"
;;
buildah)
buildah build --pull=false --tag "$image_ref" "$build_context"
;;
esac
for scan_mode in from-flag scheme default-pull-source; do
printf 'Testing %s resolution with %s (%s driver)\n' "$scan_mode" "$builder" "$driver"
out="$store_dir/$scan_mode"
case "$scan_mode" in
from-flag)
run_syft -vv --from containers-storage "$image_ref" --output json > "$out.json" 2> "$out.stderr"
;;
scheme)
run_syft -vv "containers-storage:$image_ref" --output json > "$out.json" 2> "$out.stderr"
;;
default-pull-source)
SYFT_SOURCE_IMAGE_DEFAULT_PULL_SOURCE=containers-storage \
run_syft -vv "$image_ref" --output json > "$out.json" 2> "$out.stderr"
;;
esac
# prove the image came out of the store, not from some other provider that happened to answer
if ! grep -q 'copied image from containers-storage' "$out.stderr"; then
printf 'Expected %s scan to resolve via containers-storage\n' "$scan_mode" >&2
cat "$out.stderr" >&2
exit 1
fi
jq -e --arg image_ref "$image_ref" '
.source.type == "image" and
.source.metadata.userInput == $image_ref and
any(.artifacts[]; .name == "syft-cs-fixture" and .version == "1.2.3" and .type == "npm")
' "$out.json" >/dev/null
done
printf 'Testing plain reference skips containers-storage with %s (%s driver)\n' "$builder" "$driver"
# the image only exists in the local store, so automatic resolution (daemons, then registry) must fail without
# ever trying containers-storage
if run_syft "$image_ref" --output json > "$store_dir/plain.json" 2> "$store_dir/plain.stderr"; then
printf 'Expected plain reference scan to fail since the image only exists in containers-storage\n' >&2
exit 1
fi
if grep -q 'containers-storage' "$store_dir/plain.stderr"; then
printf 'Plain reference unexpectedly attempted containers-storage resolution\n' >&2
cat "$store_dir/plain.stderr" >&2
exit 1
fi
printf 'Testing missing image with %s (%s driver)\n' "$builder" "$driver"
if run_syft --from containers-storage "$missing_ref" --output json > "$store_dir/missing.json" 2> "$store_dir/missing.stderr"; then
printf 'Expected the missing containers-storage image scan to fail\n' >&2
exit 1
fi
if ! grep -q 'does not resolve to an image ID' "$store_dir/missing.stderr"; then
printf 'Expected a containers-storage "image not found" error\n' >&2
cat "$store_dir/missing.stderr" >&2
exit 1
fi
done