Skip to content

Deploy Python docs from main@209369e640ca517fe0014a636632f65cea2704a8 #304

Deploy Python docs from main@209369e640ca517fe0014a636632f65cea2704a8

Deploy Python docs from main@209369e640ca517fe0014a636632f65cea2704a8 #304

Workflow file for this run

name: Docs deployment
run-name: >-
${{ github.event_name == 'workflow_dispatch'
&& format('Deploy Python release docs {0}@{1} from release run {2}.{3}',
inputs.release_version, inputs.release_source_sha, inputs.release_run_id, inputs.release_run_attempt)
|| format('Deploy Python docs from main@{0}', github.sha) }}
on:
push:
branches: [main]
paths:
- 'src/**'
- 'docs/**'
- 'overrides/**'
- 'mkdocs.yml'
- 'pyproject.toml'
- 'scripts/ci/classify_docs_visual_changes.py'
- 'scripts/ci/test-classify-docs-visual-changes.py'
- 'scripts/ci/validate-release-docs-source.py'
- 'scripts/api_reference_release.py'
- 'scripts/check_api_reference_install.py'
- 'scripts/check-docs-analytics.py'
- 'scripts/check-docs-layout.py'
- 'scripts/mkdocs_hooks.py'
- '.github/workflows/docs.yml'
- '.github/workflows/docs-pr.yml'
- '.github/workflows/docs-visual.yml'
workflow_dispatch:
inputs:
release_version:
description: Exact public PyPI version and immutable source tag.
required: true
type: string
release_source_sha:
description: Exact release source commit to render and audit.
required: true
type: string
release_parent_sha:
description: Exact first parent of the release source commit.
required: true
type: string
release_run_id:
description: Authenticated publication workflow run requesting this deployment.
required: true
type: string
release_run_attempt:
description: Exact attempt of the publication workflow run.
required: true
type: string
permissions:
contents: read
concurrency:
group: docs-deployment
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
outputs:
release_ready: ${{ steps.public_install.outputs.release_ready }}
release_version: ${{ steps.source.outputs.release_version }}
source_base_revision: ${{ steps.source.outputs.base_revision }}
source_revision: ${{ steps.source.outputs.revision }}
steps:
- name: Checkout the trusted main-context workflow controller
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
path: controller
ref: ${{ github.sha }}
- name: Checkout the exact documentation source
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
path: candidate
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_source_sha || github.sha }}
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.12"
- name: Validate and record the deployment source identity
id: source
env:
EVENT_BEFORE: ${{ github.event.before }}
EVENT_NAME: ${{ github.event_name }}
RELEASE_PARENT_SHA: ${{ inputs.release_parent_sha }}
RELEASE_RUN_ATTEMPT: ${{ inputs.release_run_attempt }}
RELEASE_RUN_ID: ${{ inputs.release_run_id }}
RELEASE_SOURCE_SHA: ${{ inputs.release_source_sha }}
RELEASE_VERSION: ${{ inputs.release_version }}
WORKFLOW_REF: ${{ github.ref }}
WORKFLOW_SHA: ${{ github.sha }}
run: |
set -euo pipefail
if [ "$WORKFLOW_REF" != refs/heads/main ]; then
printf 'docs deployment workflow must execute from refs/heads/main, got %s\n' "$WORKFLOW_REF" >&2
exit 1
fi
observed_source="$(git -C candidate rev-parse HEAD)"
if [ "$EVENT_NAME" = workflow_dispatch ]; then
for identity in "$RELEASE_RUN_ID" "$RELEASE_RUN_ATTEMPT"; do
if [[ ! "$identity" =~ ^[1-9][0-9]*$ ]]; then
printf 'release workflow run identity is invalid\n' >&2
exit 1
fi
done
python controller/scripts/ci/validate-release-docs-source.py \
--repo-root candidate \
--source-sha "$RELEASE_SOURCE_SHA" \
--parent-sha "$RELEASE_PARENT_SHA" \
--release-version "$RELEASE_VERSION"
source_revision="$RELEASE_SOURCE_SHA"
base_revision="$RELEASE_PARENT_SHA"
release_version="$RELEASE_VERSION"
elif [ "$EVENT_NAME" = push ]; then
if [ "$observed_source" != "$WORKFLOW_SHA" ]; then
printf 'main docs checkout is %s, expected event commit %s\n' "$observed_source" "$WORKFLOW_SHA" >&2
exit 1
fi
source_revision="$observed_source"
base_revision="$EVENT_BEFORE"
release_version="$(python -c \
'import tomllib; print(tomllib.load(open("candidate/pyproject.toml", "rb"))["project"]["version"])')"
else
printf 'docs deployment does not accept event %s\n' "$EVENT_NAME" >&2
exit 1
fi
if [[ ! "$source_revision" =~ ^[0-9a-f]{40}$ ]] ||
[[ ! "$base_revision" =~ ^[0-9a-f]{40}$ ]]; then
printf 'docs deployment source identities must be exact Git object IDs\n' >&2
exit 1
fi
{
printf 'revision=%s\n' "$source_revision"
printf 'base_revision=%s\n' "$base_revision"
printf 'release_version=%s\n' "$release_version"
} >> "$GITHUB_OUTPUT"
- name: Install package + docs deps
working-directory: candidate
run: pip install -e '.[docs]'
- name: Install browser for responsive layout checks
working-directory: candidate
run: python -m playwright install --with-deps chromium
- name: Build site
working-directory: candidate
env:
CLOUDFLARE_WEB_ANALYTICS_TOKEN: ${{ vars.CLOUDFLARE_WEB_ANALYTICS_TOKEN }}
SOURCE_REVISION: ${{ steps.source.outputs.revision }}
run: |
python scripts/ci/test-classify-docs-visual-changes.py
mkdocs build --strict
python scripts/check_api_reference_install.py --site site
python scripts/check-docs-analytics.py site
python scripts/check-docs-layout.py site
if [ "$GITHUB_SERVER_URL" = https://github.com ]; then
if ! printf '%s' "$CLOUDFLARE_WEB_ANALYTICS_TOKEN" | grep -Eq '^[a-f0-9]{32}$'; then
echo 'CLOUDFLARE_WEB_ANALYTICS_TOKEN must be the canonical 32-character site token' >&2
exit 1
fi
sed -i "s/__CLOUDFLARE_WEB_ANALYTICS_TOKEN__/$CLOUDFLARE_WEB_ANALYTICS_TOKEN/" \
docs/javascripts/analytics.js
mkdocs build --strict
python scripts/check_api_reference_install.py --site site
python scripts/check-docs-analytics.py site --require-token
fi
python scripts/check_api_reference_install.py \
--site site \
--source-revision "$SOURCE_REVISION"
- name: Verify the rendered command against public PyPI
id: public_install
if: ${{ github.server_url == 'https://github.com' }}
working-directory: candidate
env:
PUBLISHED_RELEASE: ${{ github.event_name == 'workflow_dispatch' }}
run: |
arguments=(--site site --install)
if [ "$PUBLISHED_RELEASE" = true ]; then
python scripts/check_api_reference_install.py "${arguments[@]}"
else
set +e
python scripts/check_api_reference_install.py \
"${arguments[@]}" \
--install-attempts 1 \
--install-retry-sleep 0 \
--unavailable-exit-code 75
status="$?"
set -e
if [ "$status" -eq 75 ]; then
printf 'release_ready=false\n' >> "$GITHUB_OUTPUT"
printf '::notice title=API reference preserved::The exact SDK release is not yet public on PyPI.\n'
exit 0
fi
if [ "$status" -ne 0 ]; then
exit "$status"
fi
fi
printf 'release_ready=true\n' >> "$GITHUB_OUTPUT"
- name: Upload GitHub Pages artifact
if: >-
github.server_url == 'https://github.com' &&
steps.public_install.outputs.release_ready == 'true'
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
with:
path: candidate/site
visual-evidence:
needs: build
uses: ./.github/workflows/docs-visual.yml # local
with:
source_base_sha: ${{ needs.build.outputs.source_base_revision }}
source_ref: ${{ needs.build.outputs.source_revision }}
permissions:
contents: read
deploy:
needs: [build, visual-evidence]
if: >-
github.server_url == 'https://github.com' &&
github.ref == 'refs/heads/main' &&
needs.build.outputs.release_ready == 'true' &&
(github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
outputs:
page_url: ${{ steps.deployment.outputs.page_url }}
source_revision: ${{ steps.evidence.outputs.source_revision }}
permissions:
contents: read
id-token: write
pages: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
- name: Record the deployed public source revision
id: evidence
env:
DEPLOYED_REVISION: ${{ needs.build.outputs.source_revision }}
DEPLOYMENT_URL: ${{ steps.deployment.outputs.page_url }}
RELEASE_VERSION: ${{ needs.build.outputs.release_version }}
run: |
printf 'source_revision=%s\n' "$DEPLOYED_REVISION" >> "$GITHUB_OUTPUT"
{
printf '## Python API reference deployed\n\n'
printf -- '- SDK version: `%s`\n' "$RELEASE_VERSION"
printf -- '- Source revision: `%s`\n' "$DEPLOYED_REVISION"
printf -- '- Public URL: %s\n' "$DEPLOYMENT_URL"
} >> "$GITHUB_STEP_SUMMARY"
audit-release:
needs: [build, deploy]
if: ${{ github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
steps:
- name: Checkout the trusted main-context workflow controller
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
path: controller
ref: ${{ github.sha }}
- name: Checkout the exact audited release source
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
path: candidate
ref: ${{ needs.build.outputs.source_revision }}
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.12"
- name: Build and exercise the API-reference install path
working-directory: candidate
run: |
pip install -e '.[docs]'
mkdocs build --strict
python ../controller/scripts/check_api_reference_install.py \
--repo-root . \
--site site \
--install
- name: Verify the deployed API-reference release record
working-directory: candidate
env:
DEPLOYED_REVISION: ${{ needs.deploy.outputs.source_revision }}
EXPECTED_REVISION: ${{ needs.build.outputs.source_revision }}
RELEASE_VERSION: ${{ needs.build.outputs.release_version }}
run: |
if [ "$DEPLOYED_REVISION" != "$EXPECTED_REVISION" ]; then
printf 'deployed revision %s does not match release revision %s\n' \
"$DEPLOYED_REVISION" "$EXPECTED_REVISION" >&2
exit 1
fi
python ../controller/scripts/check_api_reference_install.py \
--repo-root . \
--site site \
--source-revision "$EXPECTED_REVISION" \
--verify-deployed-url https://python.durable-workflow.com/release-audit.json
{
printf '## Python API reference release audit passed\n\n'
printf -- '- SDK release: `%s`\n' "$RELEASE_VERSION"
printf -- '- Source revision: `%s`\n' "$EXPECTED_REVISION"
printf -- '- Release evidence: https://python.durable-workflow.com/release-audit.json\n'
} >> "$GITHUB_STEP_SUMMARY"
- name: Verify live docs release audit after PyPI publish
env:
DOCS_RELEASE_AUDIT_ARTIFACT: sdk-python
DOCS_RELEASE_AUDIT_VERSION: ${{ needs.build.outputs.release_version }}
DOCS_RELEASE_AUDIT_EVIDENCE: docs-release-audit-evidence.json
DOCS_RELEASE_AUDIT_HANDOFF: docs-release-audit-handoff.json
DOCS_RELEASE_AUDIT_ENFORCEMENT: advisory
run: controller/scripts/ci/check-docs-release-audit.sh
- name: Upload docs release audit evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: docs-release-audit-evidence-${{ needs.build.outputs.release_version }}
path: |
docs-release-audit-evidence.json
docs-release-audit-handoff.json
if-no-files-found: warn