mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(.github/workflows): trigger docs reindex on release.published (DOCS-327) (#26070)
Closes [DOCS-327](https://linear.app/codercom/issue/DOCS-327/trigger-docs-reindex-on-codercoder-releasepublished). ## What Add `release: { types: [published] }` to `.github/workflows/deploy-docs.yaml` so that publishing a stable `vX.Y.Z` GitHub Release on this repo auto-dispatches the docs-sync handler against the corresponding `release/X.Y` branch. The existing `push` and `workflow_dispatch` triggers are unchanged. The `Compute action and ref` step gains a release-event branch that: - Skips prereleases (`github.event.release.prerelease == true`) with a workflow notice. - Matches the tag against `^v([0-9]+)\.([0-9]+)\.[0-9]+$` and translates `v2.35.0` to `release/2.35`. - Falls through with a notice and `exit 0` for any tag that doesn't match the plain semver shape (`v2.35`, `v2.35.0-rc.1`, etc.). Downstream validation, HMAC body construction, and the POST step are unchanged. The POST step gains an `if: steps.input.outputs.action != ''` guard so the two `exit 0` paths skip the POST instead of sending empty `action`/`ref` to the production handler. A new `.github/workflows/test-deploy-docs-release.sh` exercises the release-event bash against the 11 event scenarios in the table below plus 3 regex boundary cases, mirroring the existing `test-deploy-docs-diff.sh` pattern. ## Why Today, every mainline rollover requires a human to dispatch this workflow manually with `action=index, ref=release/X.Y`. We just hit this rotation friction on [DOCS-324](https://linear.app/codercom/issue/DOCS-324/rotate-algolia-indexer-allowlist-for-v234-launch-add-release234-drop) (v2.34 launch) and the resulting empty-search-results incident on `/docs/@v2.34.x/...`. `release.published` is the right cue: it fires exactly when a version becomes user-visible, not when its release branch is cut weeks earlier with possibly-incomplete docs. ## Coupling (important) This change is **intentionally inert until coder.com's `INDEXED_REFS_BY_CORPUS` allowlist becomes self-rotating** (filed under [DOCS-210](https://linear.app/codercom/issue/DOCS-210/automated-docs-index-lifecycle-management)). Until that lands, the handler still rejects new minors with `{action: "skipped", reason: "...not in INDEXED_REFS_BY_CORPUS"}` and this workflow logs the skip. Pre-wiring lets both halves land roughly in parallel so the next release cut after both ship is automatic. Reviewers: feel free to merge this independently. There is no downside to the wiring being live before the allowlist half ships; worst case, every release-publish event creates a no-op workflow run. ## Behavior trace (the cases the bash handles) <details> <summary>11 event scenarios I walked through by hand</summary> | Event | Tag | prerelease | Result | |---|---|---|---| | push to main | n/a | n/a | `index`, `ref=main` (existing) | | push to release/2.34 | n/a | n/a | `index`, `ref=release/2.34` (existing) | | workflow_dispatch index release/2.34 | n/a | n/a | `index`, `ref=release/2.34` (existing) | | workflow_dispatch delete release/2.31 | n/a | n/a | `delete`, `ref=release/2.31` (existing) | | release.published | `v2.35.0` | `false` | `index`, `ref=release/2.35` (new) | | release.published | `v2.35.0-rc.1` | `true` | notice + `exit 0` (new) | | release.published | `v2.35.0-rc.1` | `false` | notice + `exit 0`, regex miss (new) | | release.published | `v2.35` | `false` | notice + `exit 0`, regex miss (new) | | release.published | `release-2.35` | `false` | notice + `exit 0`, regex miss (new) | | release.published | `v0.0.0` | `false` | `index`, `ref=release/0.0` then handler rejects via allowlist (defense in depth) | | release.published | `` (empty) | unset | notice with `<unknown>` + `exit 0` | </details> ## Safety - The handler's allowlist gate still applies; this PR can only cause `{action: "skipped"}` responses until DOCS-210's allowlist-derivation lands. No risk of indexing an unintended ref. - The workflow's existing input validation (`case "$REF" in main|release/*)`) rejects any translation output that isn't `release/<int>.<int>`. Defense in depth in case the regex ever loosens by accident. - [DOCS-121](https://linear.app/codercom/issue/DOCS-121/post-mortem-docs-search-outage-2026-05-12-pr-25049-merge-wiped-docs) self-trigger risk is not present here: the new trigger is `release.published`, not push-on-paths. Workflow file edits cannot induce a release event. - `concurrency: { group: deploy-docs-${{ github.ref }} }` already exists. Release events have `github.ref=refs/tags/vX.Y.Z`, distinct from push events on the same release branch. A theoretical race resolves through the handler's atomic deleteBy+saveObjects. - The POST step now has an `if:` guard that skips downstream calls when the Compute step exits early without writing outputs. Closes the empty-env-var failure mode that coder-agents-review CRF-1 flagged. ## Verification - `actionlint .github/workflows/deploy-docs.yaml` clean. - `make pre-commit-light` clean: `fmt/shfmt`, `fmt/markdown`, `lint/actions/actionlint`, `lint/shellcheck`, `lint/markdown`, `lint/emdash`, `lint/typos`, etc. - `.github/workflows/test-deploy-docs-release.sh`: 14 cases pass (11 scenario table + 3 regex boundary cases). - Bash logic hand-traced through 11 event scenarios (table above). ## Out of scope - Build-time allowlist derivation in coder.com (DOCS-210a, will be filed/PR'd as a sibling change). - Webhook-driven cleanup of aged-out refs ([DOCS-210](https://linear.app/codercom/issue/DOCS-210) parent). - code-server release lifecycle (different repo, code-server's docs corpus stays at `main`). --- _Coder Agents on behalf of @nickvigilante._
This commit is contained in:
@@ -1,9 +1,20 @@
|
||||
name: Update coder.com/docs
|
||||
|
||||
# Triggers updates to the public docs at coder.com/docs whenever this
|
||||
# branch's docs/** content changes. One preflight job (`changes`) feeds
|
||||
# two parallel sibling jobs so that search records, the static cache,
|
||||
# and any new routes register at the same time:
|
||||
# Triggers updates to the public docs at coder.com/docs from three
|
||||
# sources:
|
||||
#
|
||||
# * push to main or release/* (docs/** only): markdown edits land in
|
||||
# search and ISR within seconds.
|
||||
# * release.published: when a stable vX.Y.Z release ships on this
|
||||
# repo, the workflow translates the tag to its release/X.Y branch
|
||||
# and reindexes. Eliminates the manual workflow_dispatch step from
|
||||
# the mainline rotation. Prereleases and non-semver tags are
|
||||
# skipped. See DOCS-327.
|
||||
# * workflow_dispatch: operator-driven, with explicit action and ref.
|
||||
#
|
||||
# One preflight job (`changes`) feeds two parallel sibling jobs so that
|
||||
# search records, the static cache, and any new routes register at the
|
||||
# same time:
|
||||
#
|
||||
# 1. algolia-and-isr: HMAC-signed POST to coder.com/api/algolia-docs-sync.
|
||||
# The handler re-extracts records for the (corpus, ref) pair and
|
||||
@@ -35,6 +46,13 @@ on:
|
||||
# auto-trigger a production reindex; use workflow_dispatch instead.
|
||||
# See DOCS-121 (incident) and DOCS-124 (fix).
|
||||
- "docs/**"
|
||||
release:
|
||||
# Fires when a draft release is published, when a release goes from
|
||||
# prerelease to non-prerelease, or when a release is created already
|
||||
# published. The Compute step below translates the published tag
|
||||
# (vX.Y.Z) into its release/X.Y branch and skips prereleases. See
|
||||
# DOCS-327 for the rotation context that motivated this trigger.
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
@@ -75,7 +93,8 @@ jobs:
|
||||
# paths_json: a JSON array of {path, status} objects, or "[]"
|
||||
# when no markdown changes are eligible for
|
||||
# surgical mode (manifest-only push, an
|
||||
# uncomputable diff, a workflow_dispatch trigger,
|
||||
# uncomputable diff, a non-push event
|
||||
# (workflow_dispatch or release.published),
|
||||
# or a diff that exceeds the surgical-mode cap).
|
||||
# An empty array tells the handler to fall back
|
||||
# to whole-branch reindex.
|
||||
@@ -100,10 +119,11 @@ jobs:
|
||||
# everything for this (corpus, ref)".
|
||||
echo "paths_json=[]" >> "$GITHUB_OUTPUT"
|
||||
}
|
||||
# workflow_dispatch never has a diff range; treat as
|
||||
# "manifest unchanged" so the manual reindex/delete path
|
||||
# doesn't trigger a Vercel rebuild it didn't ask for, and as
|
||||
# whole-branch so a manual reindex is exhaustive.
|
||||
# Non-push events (workflow_dispatch, release.published)
|
||||
# have no diff range; treat as "manifest unchanged" so the
|
||||
# manual or release-triggered reindex doesn't fire a Vercel
|
||||
# rebuild it didn't ask for, and as whole-branch so the
|
||||
# resulting reindex is exhaustive.
|
||||
if [ "$EVENT_NAME" != "push" ]; then
|
||||
echo "manifest_changed=false" >> "$GITHUB_OUTPUT"
|
||||
emit_whole_branch_fallback
|
||||
@@ -247,10 +267,40 @@ jobs:
|
||||
INPUT_ACTION: ${{ inputs.action }}
|
||||
INPUT_REF: ${{ inputs.ref }}
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||
RELEASE_PRERELEASE: ${{ github.event.release.prerelease }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ACTION="${INPUT_ACTION:-index}"
|
||||
REF="${INPUT_REF:-$GITHUB_REF_NAME}"
|
||||
ACTION=""
|
||||
REF=""
|
||||
# release.published path: translate a stable vX.Y.Z tag into
|
||||
# its release/X.Y branch and let the rest of the step
|
||||
# validate. Skip prereleases and any tag that does not match
|
||||
# the plain semver shape; backports (vX.Y.<patch>) are
|
||||
# in-scope because they may carry doc updates worth
|
||||
# reindexing. See DOCS-327. The handler's allowlist gates the
|
||||
# downstream POST, so an unsupported minor still no-ops
|
||||
# rather than reindexing something we did not intend.
|
||||
#
|
||||
# Tested in test-deploy-docs-release.sh. Keep that script in
|
||||
# sync with any changes to this block.
|
||||
if [ "${EVENT_NAME:-}" = "release" ]; then
|
||||
if [ "${RELEASE_PRERELEASE:-false}" = "true" ]; then
|
||||
echo "::notice::Skipping prerelease ${RELEASE_TAG:-<unknown>}; no docs reindex."
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${RELEASE_TAG:-}" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then
|
||||
ACTION="index"
|
||||
REF="release/${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
|
||||
echo "::notice::Release ${RELEASE_TAG} resolved to ref ${REF}."
|
||||
else
|
||||
echo "::notice::Skipping ${RELEASE_TAG:-<unknown>}: not a plain vX.Y.Z release tag."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
ACTION="${ACTION:-${INPUT_ACTION:-index}}"
|
||||
REF="${REF:-${INPUT_REF:-$GITHUB_REF_NAME}}"
|
||||
# Reject newlines/carriage returns in either input. GitHub
|
||||
# Actions parses GITHUB_OUTPUT line-by-line with last-writer-
|
||||
# wins, so a newline in $REF would let an operator dispatch
|
||||
@@ -303,6 +353,14 @@ jobs:
|
||||
echo "ref=$REF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: POST to coder.com docs indexer
|
||||
# Sentinel guard. The Compute step has two release-event
|
||||
# early-exit paths (prerelease skip, non-semver tag skip) that
|
||||
# succeed without writing action/ref to GITHUB_OUTPUT. Without
|
||||
# this guard, the POST would still fire with empty ACTION and
|
||||
# REF env vars, sending stray no-op traffic to the production
|
||||
# handler. The step only writes `action` on the success path,
|
||||
# so its presence is a reliable proceed signal. See DOCS-327.
|
||||
if: steps.input.outputs.action != ''
|
||||
env:
|
||||
ACTION: ${{ steps.input.outputs.action }}
|
||||
REF: ${{ steps.input.outputs.ref }}
|
||||
|
||||
Reference in New Issue
Block a user