policy-nexus/.forgejo/workflows/publish-image.yaml
tegwick 45c464e1bc
All checks were successful
Build and publish policy-nexus image / build-and-push (push) Successful in 46s
Parse immutable registry digest safely
2026-08-18 13:40:11 +02:00

129 lines
5.7 KiB
YAML

# Uses the estate's tier-2 container-build runner and organization-scoped
# REGISTRY_USER / REGISTRY_TOKEN secrets.
name: Build and publish policy-nexus image
on:
push:
branches:
- main
paths:
- ".forgejo/workflows/publish-image.yaml"
- "Containerfile"
- "deploy/**"
- "publication.json"
- "source-inventory.config.json"
- "source-inventory.json"
- "tests/**"
- "tools/**"
schedule:
# Pull-based freshness audit. A failed source checkout, inventory drift, or
# overdue document is visible as a failed scheduled Forgejo Actions run.
- cron: "17 04 * * *"
workflow_dispatch:
concurrency:
group: policy-nexus-publication
cancel-in-progress: false
env:
REGISTRY: forgejo.coulomb.social
IMAGE_NAME: coulomb/policy-nexus
DOCKER_HOST: tcp://127.0.0.1:2375
jobs:
build-and-push:
runs-on: container-build
steps:
- name: Build, verify, and publish immutable policy artifact
env:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -eu
REF="${GITHUB_SHA:-}"
test "${#REF}" -eq 40
SHORT="${REF:0:7}"
BUILD_ROOT="$(mktemp -d)"
PYTHON_CONTAINER=""
cleanup() {
if [ -n "${PYTHON_CONTAINER}" ] && [ -x "${HOME}/bin/docker" ]; then
"${HOME}/bin/docker" rm -f "${PYTHON_CONTAINER}" >/dev/null 2>&1 || true
fi
rm -rf "${BUILD_ROOT}"
}
trap cleanup EXIT
BUILD_CONTEXT="${BUILD_ROOT}/buildctx"
mkdir -p "${BUILD_CONTEXT}" "${HOME}/bin"
wget -qO "${BUILD_ROOT}/policy-nexus.tar.gz" \
"https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${SHORT}.tar.gz"
tar xzf "${BUILD_ROOT}/policy-nexus.tar.gz" \
-C "${BUILD_CONTEXT}" --strip-components=1
wget -qO- https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \
| tar xz --strip-components=1 -C "${HOME}/bin" docker/docker
export PATH="${HOME}/bin:${PATH}"
docker version
PYTHON_IMAGE="docker.io/library/python@sha256:d09d15e60962ca365d1cd544a48773bac9d33f2fb1b00f2aa0deec78ade7dc31"
PYTHON_CONTAINER="policy-source-fetch-${SHORT}-${GITHUB_RUN_ID:-0}"
docker create --name "${PYTHON_CONTAINER}" "${PYTHON_IMAGE}" sleep 300
docker cp "${BUILD_CONTEXT}/." "${PYTHON_CONTAINER}:/workspace"
docker start "${PYTHON_CONTAINER}"
docker exec --workdir /workspace "${PYTHON_CONTAINER}" \
python3 tools/fetch_sources.py \
--config source-inventory.config.json \
--destination _sources \
--policy-revision "${REF}"
NETKINGDOM_REVISION="$(docker exec "${PYTHON_CONTAINER}" python3 -c 'import json; print(json.load(open("/workspace/_sources/source-lock.json"))["repositories"]["net-kingdom"]["revision"])')"
SOURCE_SET_DIGEST="$(docker exec "${PYTHON_CONTAINER}" python3 -c 'import json; print(json.load(open("/workspace/_sources/source-lock.json"))["source_set_digest"])')"
docker cp "${PYTHON_CONTAINER}:/workspace/_sources" "${BUILD_CONTEXT}/"
docker rm -f "${PYTHON_CONTAINER}"
PYTHON_CONTAINER=""
SOURCE_TAG="source-${SOURCE_SET_DIGEST}"
REVISION_TAG="git-${REF}-sources-${SOURCE_SET_DIGEST:0:16}"
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" \
-u "${REGISTRY_USER}" --password-stdin
IMAGE="${REGISTRY}/${IMAGE_NAME}"
docker build \
--file "${BUILD_CONTEXT}/Containerfile" \
--build-arg "VCS_REVISION=${REF}" \
--build-arg "NETKINGDOM_REVISION=${NETKINGDOM_REVISION}" \
--build-arg "SOURCE_SET_DIGEST=${SOURCE_SET_DIGEST}" \
--tag "${IMAGE}:${SOURCE_TAG}" \
--tag "${IMAGE}:${REVISION_TAG}" \
--tag "${IMAGE}:main" \
"${BUILD_CONTEXT}"
PUSH_OUTPUT="$(docker push "${IMAGE}:${SOURCE_TAG}")"
printf '%s\n' "${PUSH_OUTPUT}"
IMAGE_DIGEST="$(printf '%s\n' "${PUSH_OUTPUT}" | tr '\r' '\n' | awk '{for (i=1; i<=NF; i++) if ($i ~ /^sha256:[a-f0-9]{64}$/) print $i}' | tail -1)"
printf '%s' "${IMAGE_DIGEST}" | grep -Eq '^sha256:[a-f0-9]{64}$'
docker push "${IMAGE}:${REVISION_TAG}"
docker push "${IMAGE}:main"
PUBLICATION_DIGEST=$(docker run --rm --entrypoint sha256sum \
"${IMAGE}:${SOURCE_TAG}" /usr/share/nginx/html/publication-manifest.json \
| awk '{print $1}')
SOURCE_INVENTORY_DIGEST=$(docker run --rm --entrypoint sha256sum \
"${IMAGE}:${SOURCE_TAG}" /usr/share/nginx/html/source-inventory.json \
| awk '{print $1}')
echo "published=${IMAGE}:${SOURCE_TAG}"
echo "image_digest=${IMAGE_DIGEST}"
echo "publication_manifest_digest=${PUBLICATION_DIGEST}"
echo "source_inventory_digest=${SOURCE_INVENTORY_DIGEST}"
echo "source_set_digest=${SOURCE_SET_DIGEST}"
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
{
echo "## policy-nexus release candidate"
echo
echo "The scheduled pull only publishes an immutable candidate; it does not deploy."
echo
echo "- image: \`${IMAGE}@${IMAGE_DIGEST}\`"
echo "- publication manifest: \`${PUBLICATION_DIGEST}\`"
echo "- source inventory: \`${SOURCE_INVENTORY_DIGEST}\`"
echo "- source set: \`${SOURCE_SET_DIGEST}\`"
echo
echo "Promotion requires an explicit paired digest update in rapp-policy-nexus and railiance-apps."
} >> "${GITHUB_STEP_SUMMARY}"
fi