From a9ceb8f03f7c015a20f6aceba1fa2b5a06002994 Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Wed, 22 Jul 2026 22:24:31 -0700 Subject: [PATCH 1/2] CI: port the dev-branch in-image test step into the reusable build check Backend runs its full hermetic suite, frontend its unit tier, inside the just-built image -- the dev-only feature both app repos carried in their own build.yml (deleted here in favor of the root workflow). --- .forgejo/workflows/build.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 10da8c3..7b8e950 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -1,6 +1,6 @@ name: Build check (reusable) -# Build-only `docker build` sanity check for ONE app domain, reused via +# `docker build` + in-image test check for ONE app domain, reused via # `uses:` by pr-build.yml and the deploy-dev workflows -- the monorepo port of # each app repo's build.yml. Still deliberately NOT a live boot+healthz check: # that was tried in the split repos and repeatedly hit this runner's own @@ -34,3 +34,25 @@ jobs: - name: Build run: docker build -t thermograph-${{ inputs.domain }}:ci ${{ inputs.domain }}/ + + # Run the hermetic test tier INSIDE the image we just built (each image + # ships tests/ + every runtime dep via COPY . /app/): the exact + # interpreter/deps that ship, none of the runner-environment quirks that + # sank the old host-side boot check. Backend runs its full (hermetic) + # suite; frontend runs only its unit tier -- its integration tier + # (frontend/tests/integration/, needs a live backend container) stays a + # local `make`/scripts concern, see frontend/scripts/backend-for-tests.sh. + # requirements-dev only layers pytest on top, so the install is tiny. + # -u 0: the images' default user can't write to site-packages. + # --entrypoint sh: backend's entrypoint is alembic-migrate + uvicorn -- + # without the override the test command is swallowed as entrypoint args + # and the container just boots the server forever (harmless no-op for + # frontend, which has no entrypoint). unset THERMOGRAPH_BASE: the images + # bake the prod root-mount (/), which moves every route off the + # /thermograph prefix the tests are written against. + - name: Run tests in the built image + run: | + target=tests + if [ "${{ inputs.domain }}" = "frontend" ]; then target=tests/unit; fi + docker run --rm -u 0 --entrypoint sh thermograph-${{ inputs.domain }}:ci \ + -c "unset THERMOGRAPH_BASE; pip install -q --no-cache-dir pytest==8.4.1 && python -m pytest $target -q" From 0b46844cf5619f2dd0edad95453c67e88dd2aef2 Mon Sep 17 00:00:00 2001 From: emi Date: Thu, 23 Jul 2026 13:42:28 +0000 Subject: [PATCH 2/2] Key image tags to the last domain-touching commit, not the branch tip (#3) --- .forgejo/workflows/backend-build-push.yml | 14 +++++++++++++- .forgejo/workflows/backend-deploy-dev.yml | 14 +++++++++++--- .forgejo/workflows/backend-deploy.yml | 14 ++++++++++++-- .forgejo/workflows/frontend-build-push.yml | 14 +++++++++++++- .forgejo/workflows/frontend-deploy-dev.yml | 14 +++++++++++--- 5 files changed, 60 insertions(+), 10 deletions(-) diff --git a/.forgejo/workflows/backend-build-push.yml b/.forgejo/workflows/backend-build-push.yml index b502978..514aaca 100644 --- a/.forgejo/workflows/backend-build-push.yml +++ b/.forgejo/workflows/backend-build-push.yml @@ -65,6 +65,12 @@ jobs: IMAGE_PATH: ${{ github.repository }}/backend steps: - uses: actions/checkout@v4 + # fetch-depth 0: the tag is keyed to the LAST COMMIT THAT TOUCHED THIS + # DOMAIN, not the branch tip -- in a path-filtered monorepo the tip is + # often an unrelated domain's commit, and a depth-1 clone can't see + # past it to find the real key. + with: + fetch-depth: 0 - name: Install Docker CLI run: | @@ -77,7 +83,13 @@ jobs: host="${REGISTRY#https://}"; host="${host#http://}" path="$(printf '%s' "$IMAGE_PATH" | tr '[:upper:]' '[:lower:]')" ref="$host/$path" - sha_tag="$ref:sha-${GITHUB_SHA:0:12}" + # Key the tag to the last commit that touched backend/ -- the SAME key + # every backend deploy workflow computes -- so build and deploy always + # agree even when the branch tip is another domain's commit. (A tag + # keyed to the push tip breaks the moment an infra-only commit lands: + # deploys go looking for an image no build ever produced.) + domain_sha="$(git log -1 --format=%H -- backend/)" + sha_tag="$ref:sha-${domain_sha:0:12}" echo "host=$host" >> "$GITHUB_OUTPUT" echo "sha_tag=$sha_tag" >> "$GITHUB_OUTPUT" if [[ "$GITHUB_REF" == refs/tags/v* ]]; then diff --git a/.forgejo/workflows/backend-deploy-dev.yml b/.forgejo/workflows/backend-deploy-dev.yml index cee669a..88227f1 100644 --- a/.forgejo/workflows/backend-deploy-dev.yml +++ b/.forgejo/workflows/backend-deploy-dev.yml @@ -51,12 +51,20 @@ jobs: cancel-in-progress: false steps: - uses: actions/checkout@v4 + # fetch-depth 0: the tag is keyed to the LAST COMMIT THAT TOUCHED THIS + # DOMAIN, not the branch tip -- in a path-filtered monorepo the tip is + # often an unrelated domain's commit, and a depth-1 clone can't see + # past it to find the real key. + with: + fetch-depth: 0 - name: Compute image tag id: tag - # Same 12-hex truncation the beta/prod deploys use -- must match - # backend-build-push.yml's `sha-${GITHUB_SHA:0:12}` tag exactly. - run: echo "tag=sha-${GITHUB_SHA:0:12}" >> "$GITHUB_OUTPUT" + # Keyed to the last commit that touched backend/ -- must match + # backend-build-push.yml's tag key exactly (see its comment). + run: | + domain_sha="$(git log -1 --format=%H -- backend/)" + echo "tag=sha-${domain_sha:0:12}" >> "$GITHUB_OUTPUT" - name: Deploy to the LAN dev server # thermograph-lan is host-native (the runner job runs directly on the diff --git a/.forgejo/workflows/backend-deploy.yml b/.forgejo/workflows/backend-deploy.yml index d2cbbf7..0ad3999 100644 --- a/.forgejo/workflows/backend-deploy.yml +++ b/.forgejo/workflows/backend-deploy.yml @@ -35,9 +35,19 @@ jobs: deploy: runs-on: docker steps: - - name: Compute image tag + - uses: actions/checkout@v4 + # fetch-depth 0: the tag is keyed to the LAST COMMIT THAT TOUCHED THIS + # DOMAIN, not the branch tip -- in a path-filtered monorepo the tip is + # often an unrelated domain's commit, and a depth-1 clone can't see + # past it to find the real key. + with: + fetch-depth: 0 + + - name: Compute image tag (last backend-touching commit) id: tag - run: echo "tag=sha-${GITHUB_SHA:0:12}" >> "$GITHUB_OUTPUT" + run: | + domain_sha="$(git log -1 --format=%H -- backend/)" + echo "tag=sha-${domain_sha:0:12}" >> "$GITHUB_OUTPUT" - name: Deploy backend over SSH uses: https://github.com/appleboy/ssh-action@v1.2.0 diff --git a/.forgejo/workflows/frontend-build-push.yml b/.forgejo/workflows/frontend-build-push.yml index df6f568..cf38190 100644 --- a/.forgejo/workflows/frontend-build-push.yml +++ b/.forgejo/workflows/frontend-build-push.yml @@ -65,6 +65,12 @@ jobs: IMAGE_PATH: ${{ github.repository }}/frontend steps: - uses: actions/checkout@v4 + # fetch-depth 0: the tag is keyed to the LAST COMMIT THAT TOUCHED THIS + # DOMAIN, not the branch tip -- in a path-filtered monorepo the tip is + # often an unrelated domain's commit, and a depth-1 clone can't see + # past it to find the real key. + with: + fetch-depth: 0 - name: Install Docker CLI run: | @@ -77,7 +83,13 @@ jobs: host="${REGISTRY#https://}"; host="${host#http://}" path="$(printf '%s' "$IMAGE_PATH" | tr '[:upper:]' '[:lower:]')" ref="$host/$path" - sha_tag="$ref:sha-${GITHUB_SHA:0:12}" + # Key the tag to the last commit that touched frontend/ -- the SAME key + # every frontend deploy workflow computes -- so build and deploy always + # agree even when the branch tip is another domain's commit. (A tag + # keyed to the push tip breaks the moment an infra-only commit lands: + # deploys go looking for an image no build ever produced.) + domain_sha="$(git log -1 --format=%H -- frontend/)" + sha_tag="$ref:sha-${domain_sha:0:12}" echo "host=$host" >> "$GITHUB_OUTPUT" echo "sha_tag=$sha_tag" >> "$GITHUB_OUTPUT" if [[ "$GITHUB_REF" == refs/tags/v* ]]; then diff --git a/.forgejo/workflows/frontend-deploy-dev.yml b/.forgejo/workflows/frontend-deploy-dev.yml index 2b745a8..0269777 100644 --- a/.forgejo/workflows/frontend-deploy-dev.yml +++ b/.forgejo/workflows/frontend-deploy-dev.yml @@ -51,12 +51,20 @@ jobs: cancel-in-progress: false steps: - uses: actions/checkout@v4 + # fetch-depth 0: the tag is keyed to the LAST COMMIT THAT TOUCHED THIS + # DOMAIN, not the branch tip -- in a path-filtered monorepo the tip is + # often an unrelated domain's commit, and a depth-1 clone can't see + # past it to find the real key. + with: + fetch-depth: 0 - name: Compute image tag id: tag - # Same 12-hex truncation the beta/prod deploys use -- must match - # frontend-build-push.yml's `sha-${GITHUB_SHA:0:12}` tag exactly. - run: echo "tag=sha-${GITHUB_SHA:0:12}" >> "$GITHUB_OUTPUT" + # Keyed to the last commit that touched frontend/ -- must match + # frontend-build-push.yml's tag key exactly (see its comment). + run: | + domain_sha="$(git log -1 --format=%H -- frontend/)" + echo "tag=sha-${domain_sha:0:12}" >> "$GITHUB_OUTPUT" - name: Deploy to the LAN dev server # thermograph-lan is host-native (the runner job runs directly on the