Files
magnusandClaude Opus 5 7ea25ee4fd
pull-request / validate (pull_request) Successful in 32s
Retry the Harbor manifest copy instead of a probe for it
The companion SHA tag step failed on the last build even though the image had
already been pushed and deployed. Harbor's vulnerability policy blocks manifest
reads while Trivy's scan is Running, and `imagetools create` copies the manifest,
so Harbor answered 412 Precondition Failed.

Two faults compounded. The loop gated on `imagetools inspect`, but a probe passing
is no guarantee the copy will be allowed. And `create` sat outside the condition,
so under the default `bash -e` its first failure aborted the step before `exit 0`
or any further attempt: the log shows one waiting line, then the error, with 58 of
60 retries unused.

Retry the real operation instead, inside the condition so a blocked copy is a
reason to wait rather than fatal. The trade-off is that a permanently failing copy
now takes the full ten minutes to report instead of failing fast; the previous
behaviour failed fast but wrongly, on a transient block.

Also records that only the primary tag matters for rollout, since the Flux image
policy matches the timestamp tag, so a failure in this step costs the audit tag
rather than the deployment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-11 09:56:26 +02:00

96 lines
3.4 KiB
YAML

name: build
on:
push:
branches: [main]
tags: ['v*']
env:
REGISTRY: harbor.pmagnus.cloud
IMAGE: harbor.pmagnus.cloud/btcprice/btcprice
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.13'
# act_runner's Actions cache endpoint is unreachable from job
# containers and adds a five-minute timeout. Dependency install is
# fast enough here without setup-python's cache integration.
- name: Test
run: |
python -m pip install -r requirements-dev.txt
python -m pytest tests/
- name: Generate immutable tags
id: tags
shell: bash
run: |
set -euo pipefail
SHA=$(git rev-parse --short HEAD)
case "$GITHUB_REF" in
refs/tags/v*) VERSION="${GITHUB_REF_NAME#v}" ;;
*) VERSION="main-$(date -u +%Y%m%d%H%M%S)" ;;
esac
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "primary=$IMAGE:$VERSION" >> "$GITHUB_OUTPUT"
echo "qualified=$IMAGE:$VERSION-$SHA" >> "$GITHUB_OUTPUT"
# anvil is not a NetBird peer. Host networking makes BuildKit use the
# runner host's pinned Harbor address; keep this until anvil is enrolled.
- uses: docker/setup-buildx-action@v3
with:
driver-opts: network=host
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.HARBOR_ROBOT_USER }}
password: ${{ secrets.HARBOR_ROBOT_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
# Harbor blocks manifest reads until Trivy has approved the image.
# Push one tag first, then add the companion tag after the scan.
tags: ${{ steps.tags.outputs.primary }}
build-args: |
BUILD_SHA=${{ steps.tags.outputs.sha }}
cache-from: type=registry,ref=${{ env.IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.IMAGE }}:buildcache,mode=max
# The primary tag is already pushed and deployable by the time this runs:
# the image policy selects `main-<timestamp>`, so a failure here costs the
# SHA audit tag, not the rollout.
- name: Wait for Harbor policy, then add SHA tag
shell: bash
env:
PRIMARY: ${{ steps.tags.outputs.primary }}
QUALIFIED: ${{ steps.tags.outputs.qualified }}
run: |
# Harbor's vulnerability policy blocks manifest reads while Trivy's scan
# is still Running, and `imagetools create` copies the manifest. Retry
# that copy itself rather than an `inspect` probe: a probe that passes is
# no guarantee the copy will, and under the default `bash -e` the first
# blocked copy aborted the step before its retries could run.
for attempt in $(seq 1 60); do
if docker buildx imagetools create --tag "$QUALIFIED" "$PRIMARY"; then
echo "tagged $QUALIFIED on attempt $attempt"
exit 0
fi
echo "waiting for Harbor scan/pull policy ($attempt/60)"
sleep 10
done
echo "Harbor did not approve $PRIMARY within 10 minutes" >&2
exit 1
- name: Summary
run: echo "pushed ${{ steps.tags.outputs.primary }} and ${{ steps.tags.outputs.qualified }}"