Initial commit: image registry structure and node-agent
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]>
This commit is contained in:
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/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'
|
||||
Reference in New Issue
Block a user