Purge Fastly #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Purge Fastly | |
| # **What it does**: Purges Fastly after a deploy and on demand. Soft purge by | |
| # default; can hard purge specific languages, or hard purge the ENTIRE cache. | |
| # **Why we have it**: So that, right after a production deploy, we start afresh, | |
| # and so docs engineering can clear a bad cache state without the Fastly UI. | |
| # **Who does it impact**: Writers and engineers. A full purge impacts all readers, | |
| # origin sees a traffic spike while the cache refills, so it's gated below. | |
| on: | |
| deployment_status: | |
| schedule: | |
| # Daily soft purge of all languages to catch reusables and AUTOTITLE links. | |
| # Intentionally off-peak, around 6-7pm PT. | |
| - cron: '20 2 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| languages: | |
| description: "Languages: Comma separated languages, e.g. 'en,es,ja,pt,zh,ru,fr,ko,de'. Blank = all languages." | |
| required: false | |
| default: 'en' | |
| hard: | |
| description: 'Hard purge: Evict immediately instead of the default soft purge. Use when a soft purge fails to clear stale content.' | |
| type: boolean | |
| required: false | |
| default: false | |
| everything: | |
| description: 'Everything: Hard-purge the entire Fastly cache... every key, all readers. Ignores the languages/hard inputs. To confirm, type exactly: "purge everything". Otherwise leave blank.' | |
| required: false | |
| default: '' | |
| permissions: | |
| contents: read | |
| deployments: read | |
| # Serialize full-cache purges so two can't overlap and leave the cache in an | |
| # unknown state. Every other run (per-deploy, per-language) gets a unique group | |
| # so those never block each other. | |
| concurrency: | |
| group: ${{ (inputs.everything == 'purge everything' && 'purge-fastly-all') || format('purge-fastly-{0}', github.run_id) }} | |
| cancel-in-progress: false | |
| env: | |
| FASTLY_TOKEN: ${{ secrets.FASTLY_TOKEN }} | |
| FASTLY_SERVICE_ID: ${{ secrets.FASTLY_SERVICE_ID }} | |
| jobs: | |
| send-purges: | |
| # Run when workflow_dispatch is the event | |
| # or when deployment_status is the event and it's a successful production deploy. | |
| # NOTE: This workflow triggers on all deployment_status events, | |
| # including staging, but only runs for production. | |
| # Non-production deploys will show as "skipped" - this is expected behavior. | |
| if: >- | |
| ${{ | |
| github.repository == 'github/docs-internal' && | |
| (github.event_name != 'deployment_status' || | |
| github.event.deployment_status.state == 'success' && github.event.deployment_status.environment == 'production') | |
| }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out repo | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Generate GitHub App token | |
| # Lets a scheduled-run failure open a tracking issue in docs-engineering | |
| # (the default GITHUB_TOKEN can't write cross-repo). | |
| id: app-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| app-id: ${{ secrets.DOCS_BOT_APP_ID }} | |
| private-key: ${{ secrets.DOCS_BOT_APP_PRIVATE_KEY }} | |
| owner: github | |
| repositories: docs-engineering | |
| - uses: ./.github/actions/node-npm-setup | |
| - name: Validate confirmation input | |
| # A full-cache purge only triggers on the exact string "purge everything". | |
| # Any other non-empty value (e.g. a typo) would otherwise be silently | |
| # ignored and fall through to a normal soft purge that finishes green, so | |
| # an operator could think they evicted the whole cache when they didn't. | |
| # Fail loudly instead. | |
| env: | |
| EVERYTHING_INPUT: ${{ inputs.everything }} | |
| run: | | |
| if [ -n "$EVERYTHING_INPUT" ] && [ "$EVERYTHING_INPUT" != "purge everything" ]; then | |
| echo "::error::To purge the entire cache, the 'everything' input must be exactly 'purge everything'. Got: '$EVERYTHING_INPUT'. Leave it blank for a normal purge." | |
| exit 1 | |
| fi | |
| - name: Purge Fastly | |
| # Auto post-deploy runs wait for the build, purge English only (temporary), | |
| # and stay soft. The daily scheduled run waits for nothing, purges all | |
| # languages (blank = all), and stays soft. A manual run uses the inputs: | |
| # blank languages = all, the `hard` toggle, or a confirmed full-cache purge. | |
| # | |
| # Raw inputs are passed through the environment and quoted, never spliced | |
| # into the command string, so a value like `en' --everything` can't break | |
| # out of its argument and inject another flag. | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| LANGUAGES_INPUT: ${{ inputs.languages }} | |
| HARD_INPUT: ${{ inputs.hard }} | |
| EVERYTHING_INPUT: ${{ inputs.everything }} | |
| run: | | |
| args=() | |
| if [ "$EVENT_NAME" = "deployment_status" ]; then | |
| args+=(--wait-for-build) | |
| args+=(--languages en) | |
| elif [ -n "$LANGUAGES_INPUT" ]; then | |
| args+=(--languages "$LANGUAGES_INPUT") | |
| fi | |
| if [ "$HARD_INPUT" = "true" ]; then | |
| args+=(--hard) | |
| fi | |
| if [ "$EVERYTHING_INPUT" = "purge everything" ]; then | |
| args+=(--everything) | |
| fi | |
| npm run purge-fastly -- "${args[@]}" | |
| - name: Hard-purge changed English content URLs | |
| # Prod deploys only. The soft purge above just marks `language:en` stale, | |
| # so stale-while-revalidate can keep serving the pre-deploy copy of a | |
| # just-changed page for a while. This evicts the specific English URLs | |
| # whose content/ files changed in this deploy, so the next request fetches | |
| # fresh. By the time the deploy succeeds the old pods are already gone, so | |
| # the refill is deterministically the new content. data/ changes stay | |
| # covered by the soft purge above (too many URLs to enumerate cheaply). | |
| if: ${{ github.event_name == 'deployment_status' }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| HEAD_SHA: ${{ github.event.deployment.sha }} | |
| run: npm run purge-fastly-changed-content | |
| - uses: ./.github/actions/slack-alert | |
| if: ${{ failure() && github.event_name != 'workflow_dispatch' }} | |
| with: | |
| slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} | |
| - uses: ./.github/actions/create-workflow-failure-issue | |
| if: ${{ failure() && github.event_name != 'workflow_dispatch' }} | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} |