Set up this repo as a base image registry. Each image is self-contained in
its own directory under images/ (Dockerfile + README.md + optional test.sh);
CI discovers them by glob, so adding an image needs no workflow changes.
template/ is the skeleton to copy.
The Makefile is the single entry point for both local work and CI, so a green
`make all` locally means a green pipeline.
Workflows:
* lint.yaml — layout check, hadolint, shellcheck
* build.yaml — diffs against the base commit to build only the images that
changed, smoke-tests each one before anything is published, then pushes.
Releases are per-image tags (<image>/vX.Y.Z); main publishes :edge.
First image, node-agent: node:22-alpine plus a GNU userland (Alpine ships
BusyBox, whose applets take narrower flags than scripts and models expect),
helm, kubectl, jq, yq, bind-tools, curl, git, ripgrep, fd and friends.
Nothing is version-pinned — rebuilding is how upstream updates land, and the
published tag is what pins things for consumers.
Its smoke test asserts the deployment contract as well as tool presence: the
image must work non-root, with a read-only root filesystem and all
capabilities dropped, which is how ToolHive runs it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
97 lines
2.8 KiB
Bash
Executable File
97 lines
2.8 KiB
Bash
Executable File
#!/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 (<image>/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:-<none>}" >&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: <image>/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'
|