#!/usr/bin/env bash # Decide which images CI should build. Prints GITHUB_OUTPUT lines: # # images=["node-agent",...] JSON array consumed by the build job's matrix # any=true|false whether there is anything to build at all # # Rules, first match wins: # 1. workflow_dispatch naming one image -> that image # 2. tag push (/vX.Y.Z) -> the image named in the tag # 3. shared build plumbing changed -> every image # 4. anything else -> images with changed files # # When the diff base is unknown (first push, force-push, shallow clone) this builds # everything. Rebuilding too much is the safe direction to fail. set -euo pipefail cd "$(dirname "$0")/.." # A change to any of these can affect how every image is built. SHARED_PATHS='^(hack/|Makefile|\.hadolint\.yaml|\.gitea/workflows/)' all_images() { local dockerfile for dockerfile in images/*/Dockerfile; do # Guards against the glob staying literal when there are no images. [ -f "$dockerfile" ] || continue basename "$(dirname "$dockerfile")" done } # Names on stdin -> ["a","b"]. Built by hand so the runner needs no jq. as_json() { local out='' name while IFS= read -r name; do [ -n "$name" ] || continue out="${out:+$out,}\"$name\"" done printf '[%s]' "$out" } emit() { local names=$1 reason=$2 any=false [ -n "$names" ] && any=true echo "selected (${reason}): ${names:-}" >&2 printf 'images=%s\n' "$(printf '%s\n' "$names" | as_json)" printf 'any=%s\n' "$any" exit 0 } require_image() { [ -f "images/$1/Dockerfile" ] || { echo "no such image: images/$1/Dockerfile does not exist" >&2 exit 1 } } # 1. Explicit request via workflow_dispatch. case "${DISPATCH_IMAGE:-}" in '') ;; all) emit "$(all_images)" 'workflow_dispatch: all' ;; *) require_image "$DISPATCH_IMAGE" emit "$DISPATCH_IMAGE" "workflow_dispatch: $DISPATCH_IMAGE" ;; esac # 2. Release tag: /vX.Y.Z. if [ "${GITHUB_REF_TYPE:-}" = tag ]; then image=${GITHUB_REF_NAME%/*} require_image "$image" emit "$image" "tag ${GITHUB_REF_NAME}" fi # 3 and 4 both need a usable diff base. base=${BASE_SHA:-} if [ -z "$base" ] || [[ $base =~ ^0+$ ]] || ! git cat-file -e "${base}^{commit}" 2>/dev/null; then emit "$(all_images)" 'diff base unavailable, building everything' fi changed=$(git diff --name-only "$base" HEAD) if printf '%s\n' "$changed" | grep -qE "$SHARED_PATHS"; then emit "$(all_images)" 'shared build plumbing changed' fi # Map changed paths back to image names, dropping any that no longer exist so a # deleted image directory does not fail the build. selected=$( printf '%s\n' "$changed" | sed -n 's#^images/\([^/]*\)/.*#\1#p' | sort -u | while IFS= read -r i; do [ -f "images/$i/Dockerfile" ] && echo "$i" done ) emit "$selected" 'changed paths'