ci: deploy Firestore rules/indexes from the runner, not the VM (server-26#51)
The rules/indexes deploy step already existed here, gated on `command -v firebase` over SSH on the deploy VM. It never found one (no node on the VM), so it silently warned-and-skipped on every single deploy for weeks — PR #124 even auto-closed #13/#51 as if this were fixed, when it wasn't. New standalone deploy-firestore-rules job runs on the Gitea runner itself (always has node), authenticated via a new FIREBASE_TOKEN secret (from `firebase login:ci`) instead of anything pre-installed on the VM. It's independent of the deploy job's health-check/rollback chain on purpose — a rules deploy failure has nothing to roll back and must not trigger that logic. notify-failure now distinguishes which job actually failed so the Discord alert doesn't misreport "production is unchanged" when the app deployed fine and only the rules push failed. Needs FIREBASE_TOKEN added as a Gitea Actions secret before this actually runs — it will fail loudly (by design) until then, which is the whole point: a loud failure beats a silent skip. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
1ffff25cd2
commit
2e67d1bad6
+61
-23
@@ -115,27 +115,14 @@ jobs:
|
|||||||
# Update compose files + mosquitto config
|
# Update compose files + mosquitto config
|
||||||
git pull origin main
|
git pull origin main
|
||||||
|
|
||||||
# server-26#51: Firestore rules + composite indexes had no deploy
|
# server-26#51: Firestore rules/indexes deploy used to be attempted
|
||||||
# path and regressed silently after every fix (the alert_events and
|
# HERE, over SSH, gated on the VM having firebase-tools installed.
|
||||||
# calls(org_id,started_at) indexes among them). The VM runs as the
|
# It never did (no node on the VM), so this silently warned and
|
||||||
# project service account, so firebase-tools authenticates via ADC
|
# skipped on every deploy for weeks -- PR #124 even auto-closed
|
||||||
# with no key file, and infra/firestore/firebase.json pins database
|
# #13/#51 as if it were fixed. Moved to a standalone
|
||||||
# c2-server. Indexes go on additively -- no --force -- so a stray
|
# deploy-firestore-rules job below that runs on the Gitea runner
|
||||||
# edit to firestore.indexes.json can never delete a live index;
|
# itself (which always has node), so it no longer depends on
|
||||||
# rules are a full replace, which is the intent. --non-interactive
|
# anything being pre-installed on this VM.
|
||||||
# means the FIRST run after a drift still needs a one-time manual
|
|
||||||
# `firebase deploy` on the VM to clear pending deletions (it aborts
|
|
||||||
# rather than guess). A failure here warns but does NOT fail the
|
|
||||||
# deploy: a transient Firebase API error must not roll back a good
|
|
||||||
# app build.
|
|
||||||
if command -v firebase >/dev/null 2>&1; then
|
|
||||||
( cd /opt/drb/infra/firestore \
|
|
||||||
&& firebase deploy --only firestore:rules,firestore:indexes \
|
|
||||||
--project ${{ secrets.FIREBASE_PROJECT_ID }} --non-interactive ) \
|
|
||||||
|| echo "WARNING: firestore deploy failed (server-26#51) -- rules/indexes may be stale"
|
|
||||||
else
|
|
||||||
echo "WARNING: firebase CLI not on the VM -- skipped firestore deploy (server-26#51); install once with: npm i -g firebase-tools"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# server-26#65: capture what is actually live BEFORE switching, so
|
# server-26#65: capture what is actually live BEFORE switching, so
|
||||||
# a bad deploy has something concrete to fall back to. This reads
|
# a bad deploy has something concrete to fall back to. This reads
|
||||||
@@ -291,9 +278,48 @@ jobs:
|
|||||||
echo "status=success" >> "$GITHUB_OUTPUT"
|
echo "status=success" >> "$GITHUB_OUTPUT"
|
||||||
echo "rolled_back_to=$PREV_TAG" >> "$GITHUB_OUTPUT"
|
echo "rolled_back_to=$PREV_TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
deploy-firestore-rules:
|
||||||
|
name: Deploy Firestore rules & indexes
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Deliberately independent of the `deploy` job (app containers) and its
|
||||||
|
# health-check/rollback chain above: a rules/indexes deploy failure has
|
||||||
|
# nothing to roll back (there is no previous "build" of a ruleset to
|
||||||
|
# revert to via this pipeline) and must never be conflated with an app
|
||||||
|
# deploy failure by triggering that job's rollback logic. This job
|
||||||
|
# failing is its own, separate red run -- picked up by notify-failure
|
||||||
|
# below -- not a signal to touch the running containers.
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Deploy firestore rules and indexes
|
||||||
|
env:
|
||||||
|
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
# server-26#51: this used to run over SSH on the deploy VM, gated
|
||||||
|
# on the VM having firebase-tools installed. It never did, so it
|
||||||
|
# silently warned-and-skipped on every single deploy for weeks.
|
||||||
|
# Running it here instead means the only prerequisite is a secret
|
||||||
|
# -- FIREBASE_TOKEN, from `firebase login:ci` -- rather than
|
||||||
|
# something installed by hand on a machine this pipeline doesn't
|
||||||
|
# otherwise touch. A missing token now fails this job LOUDLY
|
||||||
|
# (picked up by notify-failure) instead of a buried warning line
|
||||||
|
# nobody reads in the app deploy's logs.
|
||||||
|
if [ -z "$FIREBASE_TOKEN" ]; then
|
||||||
|
echo "FIREBASE_TOKEN secret is not set -- cannot deploy Firestore rules/indexes." >&2
|
||||||
|
echo "Generate one with 'firebase login:ci' and add it as a Gitea Actions secret." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
npm install -g firebase-tools
|
||||||
|
cd infra/firestore
|
||||||
|
firebase deploy --only firestore:rules,firestore:indexes \
|
||||||
|
--project ${{ secrets.FIREBASE_PROJECT_ID }} \
|
||||||
|
--token "$FIREBASE_TOKEN" --non-interactive
|
||||||
|
|
||||||
notify-failure:
|
notify-failure:
|
||||||
name: Report a failed deploy
|
name: Report a failed deploy
|
||||||
needs: [build, deploy]
|
needs: [build, deploy, deploy-firestore-rules]
|
||||||
if: failure()
|
if: failure()
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -310,6 +336,8 @@ jobs:
|
|||||||
SHA: ${{ gitea.sha }}
|
SHA: ${{ gitea.sha }}
|
||||||
ROLLBACK_STATUS: ${{ needs.deploy.outputs.rollback_status }}
|
ROLLBACK_STATUS: ${{ needs.deploy.outputs.rollback_status }}
|
||||||
ROLLBACK_SHA: ${{ needs.deploy.outputs.rollback_sha }}
|
ROLLBACK_SHA: ${{ needs.deploy.outputs.rollback_sha }}
|
||||||
|
DEPLOY_RESULT: ${{ needs.deploy.result }}
|
||||||
|
RULES_RESULT: ${{ needs.deploy-firestore-rules.result }}
|
||||||
run: |
|
run: |
|
||||||
if [ -z "$WEBHOOK" ]; then
|
if [ -z "$WEBHOOK" ]; then
|
||||||
echo "DEPLOY_ALERT_WEBHOOK is not set - skipping notification."
|
echo "DEPLOY_ALERT_WEBHOOK is not set - skipping notification."
|
||||||
@@ -321,6 +349,16 @@ jobs:
|
|||||||
run_url = os.environ["RUN_URL"]
|
run_url = os.environ["RUN_URL"]
|
||||||
status = os.environ.get("ROLLBACK_STATUS", "")
|
status = os.environ.get("ROLLBACK_STATUS", "")
|
||||||
rollback_sha = os.environ.get("ROLLBACK_SHA", "")
|
rollback_sha = os.environ.get("ROLLBACK_SHA", "")
|
||||||
|
deploy_result = os.environ.get("DEPLOY_RESULT", "")
|
||||||
|
rules_result = os.environ.get("RULES_RESULT", "")
|
||||||
|
|
||||||
|
# deploy-firestore-rules runs independent of the app deploy/rollback
|
||||||
|
# chain (see its own job comment), so its failure needs its own
|
||||||
|
# branch here -- otherwise this fell through to the generic "Build
|
||||||
|
# failed before any deploy was attempted" text even when the app
|
||||||
|
# deployed fine and only the Firestore rules/indexes push failed.
|
||||||
|
if deploy_result != "failure" and rules_result == "failure":
|
||||||
|
detail = "App deploy succeeded; Firestore rules/indexes deploy FAILED (server-26#51). Rules may be stale — check FIREBASE_TOKEN and the job log."
|
||||||
|
|
||||||
# server-26#65: the old text here unconditionally claimed
|
# server-26#65: the old text here unconditionally claimed
|
||||||
# "production is still running the previous build" -- true only
|
# "production is still running the previous build" -- true only
|
||||||
@@ -329,7 +367,7 @@ jobs:
|
|||||||
# class of bug the correlator instrumentation exists to catch), or
|
# class of bug the correlator instrumentation exists to catch), or
|
||||||
# once the deploy job's own rollback path has run. Say what
|
# once the deploy job's own rollback path has run. Say what
|
||||||
# actually happened instead.
|
# actually happened instead.
|
||||||
if status == "success":
|
elif status == "success":
|
||||||
detail = "Automatic rollback to `%s` succeeded. Production is back on the previous good build." % rollback_sha[:8]
|
detail = "Automatic rollback to `%s` succeeded. Production is back on the previous good build." % rollback_sha[:8]
|
||||||
elif status == "failed":
|
elif status == "failed":
|
||||||
detail = ("Automatic rollback to `%s` FAILED. Production state is UNKNOWN -- "
|
detail = ("Automatic rollback to `%s` FAILED. Production state is UNKNOWN -- "
|
||||||
|
|||||||
Reference in New Issue
Block a user