#!/usr/bin/env bash # Proves that Ekklesia's image is the one generic platform artifact ADR 0048 requires: # # - it contains zero feature modules: no /opt/ekklesia/modules, no module template, and no # ekklesia.module.json anywhere outside installed dependencies; # - its module storage mount point, /var/lib/ekklesia/modules, exists, is root-owned, and is empty; # - EKKLESIA_MODULES_DIR names that directory, in the image configuration and in a running process; # - a checkout with a populated modules/ directory builds the same image: the runtime file system # of both builds is identical, and no module from that directory enters it. # # bash scripts/check-image.sh builds both images from this checkout # EKKLESIA_CHECK_IMAGE= bash scripts/check-image.sh checks one existing image's contents # # Both builds use a copy of the files Git would track (committed, staged, or untracked but not # ignored), so they differ in exactly one thing: the second context also has modules/example. Images # are built and inspected locally only; nothing is pushed, published, or logged in to. set -Eeuo pipefail repository_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" cd "$repository_root" # shellcheck source=scripts/lib/ci-project.sh source "$repository_root/scripts/lib/ci-project.sh" command -v docker >/dev/null || { echo "check-image: docker is required." >&2 exit 1 } project="$(ci_project_name image)" modules_directory="/var/lib/ekklesia/modules" work="$(mktemp -d)" built_images=() cleanup() { local status=$? trap - EXIT INT TERM for built in "${built_images[@]}"; do docker image rm --force "$built" >/dev/null 2>&1 || true done rm -rf "$work" || true exit "$status" } trap cleanup EXIT INT TERM step() { echo "==> $*" } fail() { echo "check-image: $*" >&2 exit 1 } # check_contents : every content guarantee, inside the image and in its configuration. check_contents() { local image="$1" configured configured="$(docker image inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$image")" || fail "could not inspect ${image}." grep -qx "EKKLESIA_MODULES_DIR=${modules_directory}" <<<"$configured" || fail "the image does not set EKKLESIA_MODULES_DIR=${modules_directory}." # As root, so a directory the runtime user cannot read cannot hide a manifest. No network. docker run --rm --network none --user 0:0 --entrypoint sh "$image" -ec ' fail() { echo "check-image: $*" >&2; exit 1; } [ ! -e /opt/ekklesia/modules ] || fail "/opt/ekklesia/modules exists in the image." [ ! -e /opt/ekklesia/templates ] || fail "the module template is in the image." [ -d /var/lib/ekklesia/modules ] || fail "/var/lib/ekklesia/modules is missing." [ "$(stat -c %U:%G:%a /var/lib/ekklesia/modules)" = "root:root:755" ] || fail "/var/lib/ekklesia/modules is not root-owned with mode 755." [ -z "$(ls -A /var/lib/ekklesia/modules)" ] || fail "/var/lib/ekklesia/modules is not empty." manifests="$(find / -xdev -name ekklesia.module.json -not -path "*/node_modules/*" 2>/dev/null)" [ -z "$manifests" ] || fail "a module manifest is in the image: ${manifests}" [ "$EKKLESIA_MODULES_DIR" = /var/lib/ekklesia/modules ] || fail "a process in the image does not see EKKLESIA_MODULES_DIR." ' || fail "${image} is not the generic platform image." echo "ok ${image}: no modules, no template, empty root-owned module storage, EKKLESIA_MODULES_DIR set" } # runtime_listing : every path the platform and its module storage consist of, with its # type, mode, and size, in a stable order. Two builds that differ here differ in content. runtime_listing() { docker run --rm --network none --user 0:0 --entrypoint sh "$1" -ec \ 'find /opt/ekklesia /var/lib/ekklesia -xdev -printf "%y %m %s %p\n" | LC_ALL=C sort' } if [[ -n "${EKKLESIA_CHECK_IMAGE:-}" ]]; then check_contents "$EKKLESIA_CHECK_IMAGE" exit 0 fi command -v git >/dev/null || fail "git is required to copy the build context." # context_copy : the files Git would track, and nothing it ignores. context_copy() { mkdir -p "$1" git ls-files -z --cached --others --exclude-standard | tar --null --files-from=- --create --file=- | tar -C "$1" -xf - } tag_base="ekklesia-ci:${GITHUB_SHA:-local}-${GITHUB_RUN_ID:-0}-${GITHUB_RUN_ATTEMPT:-1}-" generic_image="${tag_base}generic-$$" populated_image="${tag_base}populated-$$" # build_image build_image() { built_images+=("$2") DOCKER_BUILDKIT=1 docker build --quiet --file "$1/deploy/Dockerfile" \ --build-arg REVISION="${GITHUB_SHA:-}" \ --label "${EKKLESIA_CI_LABEL}=${project}" --label "${EKKLESIA_CI_CREATED_LABEL}=$(date +%s)" \ --tag "$2" "$1" >/dev/null || fail "the image did not build from ${1##*/}." } step "Building the platform image from this checkout" context_copy "$work/generic" [[ ! -e "$work/generic/modules" ]] || fail "this checkout itself has a modules/ directory." build_image "$work/generic" "$generic_image" check_contents "$generic_image" step "Building again from the same checkout with a populated modules/ directory" context_copy "$work/populated" mkdir -p "$work/populated/modules" cp -R "$work/populated/templates/module" "$work/populated/modules/example" printf 'must never reach an image\n' >"$work/populated/modules/example/canary.txt" build_image "$work/populated" "$populated_image" check_contents "$populated_image" step "Comparing the two images' runtime file systems" runtime_listing "$generic_image" >"$work/generic.listing" runtime_listing "$populated_image" >"$work/populated.listing" [[ -s "$work/generic.listing" ]] || fail "could not list the image's file system." if ! diff -u "$work/generic.listing" "$work/populated.listing" >"$work/listing.diff"; then head -40 "$work/listing.diff" >&2 fail "a populated modules/ directory changed the image." fi echo "ok both builds have the identical runtime file system ($(wc -l <"$work/generic.listing") paths)" if [[ "$(docker image inspect --format '{{json .RootFS.Layers}}' "$generic_image")" == \ "$(docker image inspect --format '{{json .RootFS.Layers}}' "$populated_image")" ]]; then echo "ok both builds share every layer" else echo "note the two builds' layers differ (a cold build cache); their file systems do not" fi echo "Generic platform image check passed."