#!/usr/bin/env bash # # CI's last act. # # Resolves the digest each service's commit-SHA tag points at, writes those digests into # deploy/chart/values.yaml, and commits. That commit is the deploy: ArgoCD is watching # master and picks it up. This script does not, and must not, talk to the cluster. # # Called by .gitea/workflows/ci.yaml on master only. Runnable by hand for a re-bump: # REGISTRY=gitea.oci-oci.duckdns.org IMAGE_NS=gitea_admin IMAGE_TAG= ./scripts/bump-digests.sh # # -e a failed inspect must not lead to committing a stale digest # -u an unset REGISTRY would silently resolve the wrong image # -o pipefail the digest comes out of a pipe; without this, a failing inspect that pipes # into a successful grep exits 0 and writes garbage set -euo pipefail : "${REGISTRY:?REGISTRY must be set}" : "${IMAGE_NS:?IMAGE_NS must be set}" : "${IMAGE_TAG:?IMAGE_TAG must be set (the commit sha the images were built from)}" SERVICES=(api worker reconciler) CHART_VALUES="deploy/chart/values.yaml" # yq, pinned by digest. Not python+pyyaml: a yaml round-trip strips every comment in # values.yaml, and those comments are the only thing explaining why the digests are there. # yq edits in place and leaves the rest of the file alone. YQ_IMAGE="mikefarah/yq:4.44.6@sha256:b1d117c609ba990436ad1649299e2f6c378f62cb562caf30b6f2fb6144713422" WORKDIR="$(mktemp -d)" cleanup() { rm -rf "${WORKDIR}" } trap cleanup EXIT yq() { docker run --rm -v "${PWD}:/work" -w /work -u "$(id -u):$(id -g)" "${YQ_IMAGE}" "$@" } echo "==> resolving digests for tag ${IMAGE_TAG}" for svc in "${SERVICES[@]}"; do image="${REGISTRY}/${IMAGE_NS}/svcforge-${svc}" digest="$(docker buildx imagetools inspect "${image}:${IMAGE_TAG}" --format '{{.Manifest.Digest}}')" # Defence against a silently empty inspect. Without this, `yq` would happily write an # empty digest and the chart's own guard would fail the release later, further from # the cause. if [[ ! "${digest}" =~ ^sha256:[0-9a-f]{64}$ ]]; then echo "!! ${svc}: refusing to write a non-digest: '${digest}'" >&2 exit 1 fi echo " ${svc} -> ${digest}" echo "${digest}" > "${WORKDIR}/${svc}.digest" done echo "==> writing ${CHART_VALUES}" for svc in "${SERVICES[@]}"; do digest="$(cat "${WORKDIR}/${svc}.digest")" # env(...) rather than string interpolation: a digest is attacker-controlled only in # theory, but yq expression injection is not a thing worth leaving open. DIGEST="${digest}" yq -i ".image.${svc}.digest = strenv(DIGEST)" "${CHART_VALUES}" done if git diff --quiet -- "${CHART_VALUES}"; then echo "==> no digest changed; nothing to commit" exit 0 fi echo "==> committing" git config user.name "svcforge-ci" git config user.email "ci@svcforge.invalid" git add "${CHART_VALUES}" git commit -m "ci: bump image digests to ${IMAGE_TAG} Built and scanned by ${IMAGE_TAG}. ArgoCD syncs from this commit. [skip ci]" git push origin HEAD:master echo "==> done. ArgoCD owns it from here."