Skip to content

Commit 1127965

Browse files
committed
ci: docker-build lifecycle hardening — PR build-without-push, no-cache, weekly refresh
The image lifecycle was the review's weakest subsystem. Three additions to docker-build.yml, none changing the normal push path: - pull_request trigger scoped to .github/docker/** and this workflow: builds the image to validate a Dockerfile change before merge, WITHOUT pushing (the login step is skipped and push: is false via github.event_name), so a fork PR never touches the packages:write token. params derives the series from github.base_ref on a PR. - workflow_dispatch 'no-cache' boolean input wired to build-push-action, for a from-scratch rebuild that picks up base-image/apt and new gramps patch releases the buildx cache key would otherwise skip. - weekly schedule + a weekly-rebuild fan-out job that dispatches a no-cache rebuild per maintenance branch (permissions: actions: write; existing job guarded off schedule). Documented as inert until the workflows reach the default branch, since scheduled runs fire only from there. CI-MAINTAINER.md gains an 'Image lifecycle' section covering all of the above plus the known one-push image lag (ci.yml pulls the moving tag while the rebuild runs, so an image-affecting push tests against the previous image — re-run the affected jobs after the build; the Dockerfile ruff pin lands one push late for the same reason).
1 parent 92529bb commit 1127965

2 files changed

Lines changed: 94 additions & 4 deletions

File tree

.github/CI-MAINTAINER.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ an unreleased branch see [CONTRIBUTING.md](../CONTRIBUTING.md#work-towards-a-mer
1414
1. [One-time setup when the PR first merges](#one-time-setup-when-the-pr-first-merges)
1515
2. [Creating a new maintenance branch](#creating-a-new-maintenance-branch)
1616
3. [When a Gramps minor release lands on PyPI](#when-a-gramps-minor-release-lands-on-pypi)
17-
4. [Diagnostic log markers](#diagnostic-log-markers)
18-
5. [Optional future-proofing knobs](#optional-future-proofing-knobs)
17+
4. [Image lifecycle](#image-lifecycle)
18+
5. [Diagnostic log markers](#diagnostic-log-markers)
19+
6. [Optional future-proofing knobs](#optional-future-proofing-knobs)
1920

2021
## One-time setup when the PR first merges
2122

@@ -109,6 +110,39 @@ waiting for the next push:
109110

110111
The `::notice::` line in the build log confirms the switch.
111112

113+
## Image lifecycle
114+
115+
`docker-build.yml` owns the `gramps-ci:<suffix>` image. How it rebuilds:
116+
117+
- **On push** to `maintenance/gramps**` — builds and pushes the image
118+
for that branch. This is the normal path.
119+
- **On pull_request** touching `.github/docker/**` or
120+
`docker-build.yml` — builds the image **without pushing** (the login
121+
step is skipped and `push:` is false), so a Dockerfile change is
122+
validated before merge. A fork PR never touches the push credential.
123+
A green PR build proves "the image still builds"; it does **not**
124+
update the tag CI pulls.
125+
- **workflow_dispatch** with **`no-cache: true`** — a from-scratch
126+
rebuild that ignores the buildx layer cache. Use this to pick up a
127+
new base-image (apt security) update or a newly published gramps
128+
patch release that the cache key would otherwise skip.
129+
- **Weekly `schedule`** — the `weekly-rebuild` job fans out a
130+
`no-cache` dispatch to every `maintenance/grampsNN` branch.
131+
**Caveat:** GitHub runs scheduled workflows only from the repo's
132+
**default branch**, so this is inert until these workflows exist
133+
there (upstream's default is `maintenance/gramps61`). That is by
134+
design — remove/adjust it if the default branch never carries the
135+
pipeline.
136+
137+
**Known one-push image lag (not fixed, be aware):** `ci.yml` and
138+
`docker-build.yml` both fire on the same push, and `ci.yml` pulls the
139+
moving `gramps-ci:<suffix>` tag while the rebuild is still running. So a
140+
push that changes the image (e.g. a Dockerfile edit) has its CI run
141+
against the **previous** image. After the `Build Docker Images` run for
142+
that push finishes, **re-run the affected CI jobs** to test against the
143+
new image. For the same reason, the `ruff` version pinned in the
144+
Dockerfile takes effect one push late.
145+
112146
## Diagnostic log markers
113147

114148
When investigating a CI failure, the install-step output in

.github/workflows/docker-build.yml

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,27 @@ on:
77
# in exchange for guaranteeing gramps-ci:<suffix> exists on the
88
# first push to any newly-created maintenance branch.
99
branches: [maintenance/gramps**]
10+
pull_request:
11+
# Build-only validation (no push — see the build step's `push:` and the
12+
# login step's `if:`) when a PR touches the image definition, so a broken
13+
# Dockerfile is caught before it merges instead of on the next push.
14+
branches: [maintenance/gramps**]
15+
paths:
16+
- ".github/docker/**"
17+
- ".github/workflows/docker-build.yml"
18+
schedule:
19+
# Weekly no-cache refresh so base-image (apt security) updates and new
20+
# gramps patch releases enter the image even when nothing changes the
21+
# buildx cache key. Fans out to every maintenance branch (see the
22+
# weekly-rebuild job). NOTE: scheduled runs execute only from the repo's
23+
# DEFAULT branch — inert until these workflows exist there.
24+
- cron: "23 4 * * 1"
1025
workflow_dispatch:
26+
inputs:
27+
no-cache:
28+
description: "Rebuild without the buildx layer cache (fresh base image + gramps)"
29+
type: boolean
30+
default: false
1131

1232
env:
1333
REGISTRY: ghcr.io
@@ -20,11 +40,17 @@ permissions:
2040
jobs:
2141
build-ci:
2242
name: Build gramps-ci
43+
# The schedule event is handled by weekly-rebuild (fan-out); this job runs
44+
# for push / pull_request / workflow_dispatch.
45+
if: github.event_name != 'schedule'
2346
runs-on: ubuntu-latest
2447
steps:
2548
- uses: actions/checkout@v4
2649

2750
- name: Log in to GHCR
51+
# Skip on pull_request: a fork PR's token is read-only and this build
52+
# does not push, so the push credential is never exercised on a PR.
53+
if: github.event_name != 'pull_request'
2854
uses: docker/login-action@v3
2955
with:
3056
registry: ghcr.io
@@ -48,7 +74,7 @@ jobs:
4874
id: params
4975
shell: bash
5076
run: |
51-
ref="${{ github.ref_name }}"
77+
ref="${{ github.base_ref || github.ref_name }}"
5278
suffix="${ref#maintenance/}"
5379
case "$suffix" in
5480
gramps[0-9][0-9]) ;;
@@ -77,11 +103,41 @@ jobs:
77103
uses: docker/build-push-action@v6
78104
with:
79105
context: .github/docker/gramps-ci
80-
push: true
106+
# Push everywhere EXCEPT pull_request, where this is a build-only
107+
# validation that must not touch GHCR.
108+
push: ${{ github.event_name != 'pull_request' }}
81109
tags: ${{ steps.meta.outputs.tags }}
82110
labels: ${{ steps.meta.outputs.labels }}
83111
build-args: |
84112
GRAMPS_SERIES=${{ steps.params.outputs.series }}
85113
GRAMPS_FALLBACK_SHA=${{ steps.params.outputs.fallback_sha }}
114+
# `no-cache` (workflow_dispatch input; empty/false on other events)
115+
# forces a from-scratch rebuild — used by the weekly refresh.
116+
no-cache: ${{ inputs.no-cache == true }}
86117
cache-from: type=gha
87118
cache-to: type=gha,mode=max
119+
120+
weekly-rebuild:
121+
name: Weekly no-cache refresh (fan-out)
122+
# The scheduled event fires only on the default branch; dispatch a no-cache
123+
# rebuild for every maintenance branch that carries this workflow. Inert
124+
# until these workflows reach the default branch — by design, not a bug.
125+
if: github.event_name == 'schedule'
126+
runs-on: ubuntu-latest
127+
permissions:
128+
contents: read
129+
actions: write # gh workflow run
130+
steps:
131+
- name: Dispatch a no-cache rebuild per maintenance branch
132+
env:
133+
GH_TOKEN: ${{ github.token }}
134+
run: |
135+
git ls-remote --heads "https://github.com/${GITHUB_REPOSITORY}.git" \
136+
'refs/heads/maintenance/gramps*' \
137+
| awk -F'refs/heads/' '{print $2}' \
138+
| grep -E '^maintenance/gramps[0-9][0-9]$' \
139+
| while read -r branch; do
140+
gh workflow run docker-build.yml --repo "$GITHUB_REPOSITORY" \
141+
--ref "$branch" -f no-cache=true \
142+
|| echo "::warning::dispatch failed for $branch (workflow not on that branch yet?)"
143+
done

0 commit comments

Comments
 (0)