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
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# Work out how one image should be tagged, and whether it should be published.
|
||||
# Prints GITHUB_OUTPUT lines:
|
||||
#
|
||||
# tags=<multiline> every ref to build, one per line
|
||||
# primary=<ref> the ref the smoke test runs against
|
||||
# version=<string> org.opencontainers.image.version
|
||||
# created=<rfc3339> org.opencontainers.image.created
|
||||
# push=true|false whether these refs get published
|
||||
#
|
||||
# Tag scheme:
|
||||
# tag <image>/vX.Y.Z -> :vX.Y.Z :vX.Y :vX :latest published release
|
||||
# push to main -> :edge :main-<sha> tip of main
|
||||
# pull request -> :pr-<n> built, never published
|
||||
# anything else -> :dev-<sha> built, never published
|
||||
set -euo pipefail
|
||||
|
||||
image=${1:?usage: docker-tags.sh <image>}
|
||||
repo="${REGISTRY:?REGISTRY is not set}/${NAMESPACE:?NAMESPACE is not set}/${image}"
|
||||
|
||||
sha=$(git rev-parse --short=7 HEAD)
|
||||
created=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
push=false
|
||||
|
||||
if [ "${GITHUB_REF_TYPE:-}" = tag ]; then
|
||||
version=${GITHUB_REF_NAME##*/}
|
||||
[[ $version =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
|
||||
echo "tag '${GITHUB_REF_NAME}' is not of the form <image>/vX.Y.Z" >&2
|
||||
exit 1
|
||||
}
|
||||
n=${version#v}
|
||||
tags=("$repo:$version" "$repo:v${n%.*}" "$repo:v${n%%.*}" "$repo:latest")
|
||||
push=true
|
||||
elif [ "${GITHUB_EVENT_NAME:-}" = pull_request ]; then
|
||||
version="pr-${PR_NUMBER:-0}"
|
||||
tags=("$repo:$version")
|
||||
elif [ "${GITHUB_REF_NAME:-}" = main ]; then
|
||||
version="edge-$sha"
|
||||
tags=("$repo:edge" "$repo:main-$sha")
|
||||
push=true
|
||||
else
|
||||
version="dev-$sha"
|
||||
tags=("$repo:$version")
|
||||
fi
|
||||
|
||||
printf 'version %s, push %s, tags: %s\n' "$version" "$push" "${tags[*]}" >&2
|
||||
|
||||
echo "tags<<__TAGS__"
|
||||
printf '%s\n' "${tags[@]}"
|
||||
echo "__TAGS__"
|
||||
echo "primary=${tags[0]}"
|
||||
echo "version=$version"
|
||||
echo "created=$created"
|
||||
echo "push=$push"
|
||||
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