All checks were successful
build-and-release / build (push) Successful in 1m24s
registry_prune.py sorted candidate tags by the manifest GET response's
Date header - which is just "now", the moment the prune script made
that request - not when the image was actually built. Every tag it
queries in the same prune run lands within the same second, so the
"sort by age" was effectively random. On run #56 this deleted the tag
this same CI run had just pushed (4efe848, digest 60114dea) seconds
after pushing it, and seconds before the gitops step committed a
deployment pointing at that now-deleted digest - the cluster then
sat in ImagePullBackOff since the referenced image no longer existed.
Now reads the real "created" timestamp from the image's config blob
(resolving through a manifest list/index if the tag is multi-platform),
and always protects the current run's own SHA from deletion regardless
of sort order, as defense in depth against any remaining timestamp edge
cases.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
134 lines
5.3 KiB
YAML
134 lines
5.3 KiB
YAML
name: build-and-release
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- high-performance
|
|
|
|
concurrency:
|
|
group: frontend-solid-build-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: docker-ready
|
|
env:
|
|
DOCKER_BUILDKIT: "1"
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Point DOCKER_HOST at this container's own gateway
|
|
run: |
|
|
set -euo pipefail
|
|
# 127.0.0.1 doesn't work: the job container is nested one level
|
|
# inside the runner pod's dind sidecar, so its own loopback isn't
|
|
# the sidecar's. Read the container's actual default-route gateway
|
|
# directly from /proc/net/route instead of relying on `ip`/`route`
|
|
# CLI tools being installed in the job image.
|
|
GATEWAY="$(awk '$2 == "00000000" {print $3}' /proc/net/route | head -1 | \
|
|
sed -E 's/(..)(..)(..)(..)/0x\4 0x\3 0x\2 0x\1/' | \
|
|
{ read -r a b c d; printf '%d.%d.%d.%d' "$a" "$b" "$c" "$d"; })"
|
|
echo "Detected docker host gateway: $GATEWAY"
|
|
echo "DOCKER_HOST=tcp://$GATEWAY:2375" >> "$GITHUB_ENV"
|
|
|
|
- name: Set up Docker Buildx
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in $(seq 1 30); do
|
|
if docker version >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
docker version
|
|
docker buildx create --use --name nxtgauge-builder || docker buildx use nxtgauge-builder
|
|
docker buildx inspect --bootstrap
|
|
|
|
- name: Login to registry
|
|
env:
|
|
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST || 'ci.nxtgauge.com' }}
|
|
REGISTRY_NAMESPACE: ${{ secrets.REGISTRY_NAMESPACE || 'ashwin' }}
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
test -n "$REGISTRY_HOST"
|
|
printf '%s' "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USERNAME" --password-stdin
|
|
|
|
- name: Build and push image
|
|
env:
|
|
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST || 'ci.nxtgauge.com' }}
|
|
REGISTRY_NAMESPACE: ${{ secrets.REGISTRY_NAMESPACE || 'ashwin' }}
|
|
SHA: ${{ github.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
metadata_file="/tmp/frontend-solid-metadata.json"
|
|
image_ref="$REGISTRY_HOST/$REGISTRY_NAMESPACE/nxtgauge-frontend-solid:$SHA"
|
|
|
|
docker buildx build --push \
|
|
--metadata-file "$metadata_file" \
|
|
-f Dockerfile \
|
|
-t "$image_ref" \
|
|
.
|
|
|
|
digest="$(grep -o '"containerimage\.digest"[[:space:]]*:[[:space:]]*"sha256:[^"]*"' "$metadata_file" | grep -o 'sha256:[^"]*')"
|
|
test -n "$digest"
|
|
printf '%s@%s\n' "$REGISTRY_HOST/$REGISTRY_NAMESPACE/nxtgauge-frontend-solid" "$digest" > /tmp/frontend-solid-image-ref.txt
|
|
|
|
- name: Prune old SHA tags
|
|
if: success()
|
|
continue-on-error: true
|
|
env:
|
|
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST || 'ci.nxtgauge.com' }}
|
|
REGISTRY_NAMESPACE: ${{ secrets.REGISTRY_NAMESPACE || 'ashwin' }}
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
SHA: ${{ github.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
python3 .forgejo/scripts/registry_prune.py \
|
|
--registry "$REGISTRY_HOST" \
|
|
--repo "$REGISTRY_NAMESPACE/nxtgauge-frontend-solid" \
|
|
--username "$REGISTRY_USERNAME" \
|
|
--password "$REGISTRY_PASSWORD" \
|
|
--protect "$SHA" \
|
|
--keep 2
|
|
|
|
- name: Update GitOps release
|
|
env:
|
|
GITOPS_GITHUB_OWNER: ${{ secrets.GITOPS_GITHUB_OWNER || 'Traceworks2023' }}
|
|
GITOPS_REPO: ${{ secrets.GITOPS_REPO || 'nxtgauge-gitops' }}
|
|
GITOPS_BRANCH: ${{ secrets.GITOPS_BRANCH || 'main' }}
|
|
# GitHub is the source of truth for nxtgauge-gitops. Push there;
|
|
# the repo's own sync-to-forgejo.yml GitHub Action relays the
|
|
# commit to Forgejo, which is what Flux (flux-system GitRepository)
|
|
# actually watches. Do not push straight to Forgejo here - that
|
|
# bypasses GitHub as source of truth.
|
|
GITOPS_GITHUB_TOKEN: ${{ secrets.GITOPS_GITHUB_TOKEN }}
|
|
SHA: ${{ github.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
test -n "${GITOPS_GITHUB_TOKEN:-}" || { echo "GITOPS_GITHUB_TOKEN is empty"; exit 1; }
|
|
|
|
GITOPS_DIR=$(mktemp -d)
|
|
git clone "https://${GITOPS_GITHUB_OWNER}:${GITOPS_GITHUB_TOKEN}@github.com/${GITOPS_GITHUB_OWNER}/${GITOPS_REPO}.git" "$GITOPS_DIR"
|
|
cd "$GITOPS_DIR"
|
|
git checkout "$GITOPS_BRANCH"
|
|
|
|
image_ref="$(cat /tmp/frontend-solid-image-ref.txt)"
|
|
./scripts/set-app-release.sh frontend-solid "$image_ref"
|
|
|
|
if git diff --quiet; then
|
|
echo "GitOps repo already up to date."
|
|
exit 0
|
|
fi
|
|
|
|
git config user.name "forgejo-actions[bot]"
|
|
git config user.email "forgejo-actions@ci.nxtgauge.com"
|
|
git add apps scripts/set-app-release.sh
|
|
git commit -m "chore(gitops): deploy frontend-solid@${SHA}"
|
|
git push origin "HEAD:${GITOPS_BRANCH}"
|