Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ jobs:
runs-on: ubuntu-latest
# Every write here is the app token's.
permissions: {}
outputs:
prerelease: ${{ steps.tag.outputs.prerelease }}
steps:
# The app token, not GITHUB_TOKEN: a release published with the built-in
# token starts no workflow run, and the Community Applications template
Expand Down Expand Up @@ -283,3 +285,31 @@ jobs:
--notes-file release-notes.md \
--verify-tag \
"$FLAG"

# The documentation site generates its changelog, its development page and its
# release badge from this repository at build time. Its host builds only on a
# push to its own repository, so without this it keeps serving the previous
# release until something unrelated is pushed there.
docs-rebuild:
needs: release-page
if: needs.release-page.outputs.prerelease == 'false'
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Request a documentation site rebuild
env:
HOOK: ${{ secrets.DOCS_DEPLOY_HOOK_URL }}
run: |
set -euo pipefail
if [ -z "$HOOK" ]; then
echo 'DOCS_DEPLOY_HOOK_URL is not set. Nothing requested.'
exit 0
fi
# Never print the hook. The URL is the credential.
code=$(curl --silent --output /dev/null --write-out '%{http_code}' \
--request POST --max-time 30 "$HOOK")
if [ "$code" -lt 200 ] || [ "$code" -ge 300 ]; then
echo "The deploy hook answered HTTP $code."
exit 1
fi
echo 'Documentation site rebuild requested.'
21 changes: 19 additions & 2 deletions api/test/release-automation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ test('the release workflows grant the built-in token nothing', () => {
assert.deepEqual(release.jobs['release-page'].permissions, {});
});

test('a stable release asks the documentation site to rebuild', () => {
const doc = yaml.load(wf('release.yml'));
const job = doc.jobs['docs-rebuild'];
assert.ok(job, 'release.yml has no docs-rebuild job');
assert.equal(job.needs, 'release-page', 'the site must not describe a release page that failed');
assert.deepEqual(job.permissions, {}, 'the rebuild needs no scope on this repository');
assert.match(String(job.if), /prerelease == 'false'/, 'an rc tag must not move the site');
assert.equal(doc.jobs['release-page'].outputs?.prerelease, '${{ steps.tag.outputs.prerelease }}');
const script = job.steps.map(s => s.run || '').join('\n');
assert.match(script, /DOCS_DEPLOY_HOOK_URL is not set/, 'a missing hook must not fail the release');
assert.doesNotMatch(script, /echo\s+"?\$HOOK/, 'the hook URL is a credential and must not be printed');
});

test('the release runs one at a time', () => {
for (const f of ['release-prep.yml', 'release-tag.yml']) {
const doc = yaml.load(wf(f));
Expand All @@ -70,10 +83,14 @@ test('the release runs one at a time', () => {

test('docs/releasing.md documents the app secrets the workflows read', () => {
const doc = fs.readFileSync(path.join(ROOT, 'docs', 'releasing.md'), 'utf8');
for (const secret of ['RELEASE_APP_CLIENT_ID', 'RELEASE_APP_PRIVATE_KEY']) {
for (const [secret, file] of [
['RELEASE_APP_CLIENT_ID', 'release-prep.yml'],
['RELEASE_APP_PRIVATE_KEY', 'release-prep.yml'],
['DOCS_DEPLOY_HOOK_URL', 'release.yml'],
]) {
assert.ok(doc.includes(secret), `${secret} is read by a workflow and not documented`);
assert.ok(
wf('release-prep.yml').includes(secret),
wf(file).includes(secret),
`${secret} is documented but no longer read; the doc would send someone to create a secret nothing uses`,
);
}
Expand Down
18 changes: 18 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ App** on the account that owns the repository:
The app needs no ruleset bypass. It opens pull requests rather than pushing to
the default branch, and the ruleset covers branches, not tags.

## The documentation site

The site at https://stackyard.sandobserver.com builds its changelog, its
development page and its release badge from this repository. Its host builds
only on a push to its own repository, so a release here does not update it on
its own.

The `docs-rebuild` job asks the host to build after a stable release. It reads
one secret, `DOCS_DEPLOY_HOOK_URL`, added under **Settings > Secrets and
variables > Actions**. Create the hook in the Cloudflare Pages project under
**Settings > Builds and deployments > Deploy hooks**, pointed at `main`.

The URL is the credential. Anyone holding it can trigger a build. It is never
printed in a log.

Without the secret the job reports that nothing was requested and the release
still succeeds. An rc tag never triggers a rebuild.

## When a release build fails

The tag stays and nothing publishes. Fix `main`, delete the tag locally and on
Expand Down