Add "What Is Immutable Infrastructure?" explainer #3680
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: Social Media Review | |
| on: | |
| pull_request: | |
| types: [opened, reopened, ready_for_review, synchronize] | |
| paths: | |
| - 'content/blog/*/index.md' | |
| issue_comment: | |
| types: [created] | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: 'PR number to review' | |
| required: true | |
| type: string | |
| jobs: | |
| social-review: | |
| concurrency: | |
| group: >- | |
| social-review-${{ | |
| github.event.pull_request.number | |
| || github.event.inputs.pr_number | |
| || (github.event_name == 'issue_comment' | |
| && contains(github.event.comment.body, '/social-review') | |
| && github.event.issue.number) | |
| || github.run_id | |
| }} | |
| cancel-in-progress: true | |
| if: >- | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'issue_comment' && | |
| github.event.issue.pull_request && | |
| contains(github.event.comment.body, '/social-review') && | |
| (github.event.comment.author_association == 'MEMBER' || | |
| github.event.comment.author_association == 'OWNER' || | |
| github.event.comment.author_association == 'COLLABORATOR')) || | |
| (github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name == github.repository && | |
| github.event.pull_request.user.login != 'pulumi-bot' && | |
| github.event.pull_request.user.login != 'dependabot[bot]' && | |
| !(github.event.action == 'synchronize' && github.actor == 'claude[bot]')) | |
| runs-on: ubuntu-latest | |
| environment: production | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| id-token: write | |
| actions: read | |
| steps: | |
| - name: Resolve PR info | |
| id: pr-info | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| PR_NUMBER="${{ github.event.inputs.pr_number }}" | |
| elif [ "${{ github.event_name }}" = "issue_comment" ]; then | |
| PR_NUMBER="${{ github.event.issue.number }}" | |
| else | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| fi | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| HEAD_REF="${{ github.event.pull_request.head.ref }}" | |
| BASE_REF="${{ github.event.pull_request.base.ref }}" | |
| else | |
| HEAD_REF=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json headRefName -q .headRefName) | |
| BASE_REF=$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json baseRefName -q .baseRefName) | |
| fi | |
| echo "number=$PR_NUMBER" >> "$GITHUB_OUTPUT" | |
| echo "head_ref=$HEAD_REF" >> "$GITHUB_OUTPUT" | |
| echo "base_ref=$BASE_REF" >> "$GITHUB_OUTPUT" | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ steps.pr-info.outputs.head_ref }} | |
| fetch-depth: 0 | |
| - uses: astral-sh/setup-uv@v7 | |
| - name: Skip if social copy unchanged since prior review | |
| id: gate | |
| if: github.event_name == 'pull_request' && github.event.action == 'synchronize' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| HEAD_REF: ${{ steps.pr-info.outputs.head_ref }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| PR_NUMBER: ${{ steps.pr-info.outputs.number }} | |
| REPO: ${{ github.repository }} | |
| RUN_ID: ${{ github.run_id }} | |
| SERVER_URL: ${{ github.server_url }} | |
| run: | | |
| set -e | |
| # The review verdict lives in the PR comment, not the run conclusion — | |
| # if the social copy hasn't changed since the comment was last updated, | |
| # the verdict still applies. Re-running risks flapping (PASS<->FAIL on | |
| # identical copy) and wastes Claude calls on PRs stuck on a char-limit | |
| # fail (where every push to the same over-limit copy would otherwise | |
| # trigger a fresh review). | |
| # | |
| # Baseline preference order: | |
| # 1. Our edit-in-place comment's footer SHA (most accurate — atomic | |
| # with the comment write, unaffected by later step failures). | |
| # 2. Last successful workflow run on this branch (legacy fallback | |
| # for PRs that pre-date the marker comment). | |
| PRIOR_SHA="" | |
| PRIOR_RUN_ID="" | |
| BASELINE_SOURCE="" | |
| # (1) Try the marker comment first. | |
| # `|| true` on the grep pipeline so a missing footer doesn't kill the | |
| # script under `set -e` — fall through to the run-based baseline instead. | |
| COMMENT_BODY=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" \ | |
| --jq '[.[] | select(.body | startswith("<!-- social-review -->"))] | .[-1].body // empty') | |
| if [ -n "$COMMENT_BODY" ]; then | |
| COMMENT_SHA=$(echo "$COMMENT_BODY" | grep -oE 'commit `[a-f0-9]{7,40}`' | head -1 | grep -oE '[a-f0-9]{7,40}' || true) | |
| if [ -n "$COMMENT_SHA" ]; then | |
| PRIOR_SHA="$COMMENT_SHA" | |
| BASELINE_SOURCE="comment" | |
| echo "Baseline: social-review comment footer SHA $PRIOR_SHA" | |
| fi | |
| fi | |
| # (2) Fall back to last successful run on this branch. | |
| # `gh run list --branch` (and the API's ?branch= query) is unreliable | |
| # for slash-containing branch names — silently returns empty for some | |
| # branches that do have runs. Query the workflow's runs unfiltered and | |
| # filter by head_branch in jq client-side, which always works. | |
| if [ -z "$PRIOR_SHA" ]; then | |
| PRIOR=$(gh api \ | |
| "repos/$REPO/actions/workflows/claude-social-review.yml/runs?per_page=30" \ | |
| --jq "[.workflow_runs[] | select(.head_branch == \"$HEAD_REF\" and .event == \"pull_request\" and .conclusion == \"success\" and .id != $RUN_ID)] | .[0]") | |
| PRIOR_SHA=$(echo "$PRIOR" | jq -r '.head_sha // ""') | |
| PRIOR_RUN_ID=$(echo "$PRIOR" | jq -r '.id // ""') | |
| if [ -n "$PRIOR_SHA" ]; then | |
| BASELINE_SOURCE="run" | |
| echo "Baseline: prior successful run $PRIOR_RUN_ID at SHA $PRIOR_SHA" | |
| fi | |
| fi | |
| if [ -z "$PRIOR_SHA" ]; then | |
| echo "No prior baseline (no marker comment, no successful run) — running review." | |
| echo "should_skip=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Force-push / rebase resilience: if the prior SHA is unreachable in | |
| # the local clone, try to fetch it; if still missing, fall through to | |
| # running the review (degrades to current behaviour, never worse). | |
| if ! git cat-file -e "$PRIOR_SHA" 2>/dev/null; then | |
| git fetch origin "$PRIOR_SHA" --depth=1 2>/dev/null || true | |
| fi | |
| if ! git cat-file -e "$PRIOR_SHA" 2>/dev/null; then | |
| echo "Prior baseline $PRIOR_SHA unreachable (force-push/rebase?) — running review." | |
| echo "should_skip=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| uv run scripts/social/social-review-gate.py \ | |
| --repo . --prior "$PRIOR_SHA" --head "$HEAD_SHA" \ | |
| | tee /tmp/gate.json | |
| SHOULD_SKIP=$(jq -r .should_skip /tmp/gate.json) | |
| REASON=$(jq -r .reason /tmp/gate.json) | |
| if [ "$SHOULD_SKIP" = "true" ]; then | |
| echo "should_skip=true" >> "$GITHUB_OUTPUT" | |
| # Log to the action run instead of posting a PR comment — the comment | |
| # was noisy on active PRs (one per push that didn't touch the social | |
| # block). Anyone wanting a fresh review can still comment `/social-review`. | |
| if [ "$BASELINE_SOURCE" = "comment" ]; then | |
| VERDICT_LINK="The verdict in the existing social-review PR comment still applies." | |
| NOTICE_TAIL="The PR comment from commit $PRIOR_SHA still applies." | |
| else | |
| VERDICT_LINK="The verdict from the [prior run](${SERVER_URL}/${REPO}/actions/runs/${PRIOR_RUN_ID}) still applies." | |
| NOTICE_TAIL="Prior run: ${SERVER_URL}/${REPO}/actions/runs/${PRIOR_RUN_ID}." | |
| fi | |
| echo "::notice::Skipping social media review — $REASON. $NOTICE_TAIL Comment /social-review to force a fresh review." | |
| { | |
| echo "## 🟰 Skipping social media review" | |
| echo "" | |
| echo "**Reason:** $REASON" | |
| echo "" | |
| echo "$VERDICT_LINK" | |
| echo "" | |
| echo "Comment \`/social-review\` on the PR to force a fresh review." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "should_skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Fetch secrets from ESC | |
| if: steps.gate.outputs.should_skip != 'true' | |
| id: esc-secrets | |
| uses: pulumi/esc-action@v3 | |
| - name: Configure AWS Credentials | |
| if: steps.gate.outputs.should_skip != 'true' | |
| id: aws-creds | |
| continue-on-error: true | |
| uses: aws-actions/configure-aws-credentials@v6 | |
| with: | |
| role-to-assume: arn:aws:iam::388588623842:role/ContinuousDelivery | |
| role-session-name: social-review | |
| aws-region: us-west-2 | |
| - if: steps.gate.outputs.should_skip != 'true' | |
| uses: pulumi/actions@v7 | |
| - name: Get social state bucket name | |
| if: steps.gate.outputs.should_skip != 'true' | |
| id: bucket | |
| continue-on-error: true | |
| env: | |
| PULUMI_ACCESS_TOKEN: ${{ steps.esc-secrets.outputs.PULUMI_ACCESS_TOKEN }} | |
| run: | | |
| set -eo pipefail | |
| BUCKET=$(pulumi -C infrastructure stack output socialStateBucketName --stack ${{ vars.PULUMI_STACK_NAME }}) | |
| echo "name=$BUCKET" >> "$GITHUB_OUTPUT" | |
| - name: Configure git auth for private social_core git-dep | |
| if: steps.gate.outputs.should_skip != 'true' | |
| env: | |
| PULUMI_BOT_TOKEN: ${{ steps.esc-secrets.outputs.PULUMI_BOT_TOKEN }} | |
| run: | | |
| git config --global url."https://x-access-token:${PULUMI_BOT_TOKEN}@github.com/".insteadOf "https://github.com/" | |
| - name: Check which posts need social review | |
| if: steps.gate.outputs.should_skip != 'true' | |
| id: check | |
| env: | |
| SOCIAL_STATE_BUCKET: ${{ steps.bucket.outputs.name }} | |
| BASE_REF: origin/${{ steps.pr-info.outputs.base_ref }} | |
| run: | | |
| set +e | |
| uv run scripts/social/schedule-posts.py --check > .social-check-output.txt | |
| EXIT=$? | |
| set -e | |
| if [ -s .social-check-output.txt ]; then | |
| cat .social-check-output.txt | |
| echo "needs_review=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "No blog posts need social media review." | |
| echo "needs_review=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| if [ $EXIT -ne 0 ]; then | |
| echo "has_errors=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Run Claude Social Media Review | |
| if: steps.gate.outputs.should_skip != 'true' && steps.check.outputs.needs_review == 'true' | |
| uses: anthropics/claude-code-action@v1 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| prompt: | | |
| You are running in a CI environment. | |
| Review the social media copy for pull request #${{ steps.pr-info.outputs.number }}. | |
| The social copy and status for each blog post is in `.social-check-output.txt`. Read that file — it contains the actual post text for each platform, plus metadata (character counts, already-posted status). Posts that are already posted or too old have been filtered out. | |
| If you want more context about the blog post itself (to judge whether the social copy accurately represents it), the file path is listed in the output — you can read it. | |
| Review each platform's copy using the rules in `.claude/commands/social-media-review/SKILL.md`. | |
| IMPORTANT: Keep the review short and scannable. Use `#### Platform — PASS` or `#### Platform — FAIL` headings with bullet-pointed reasons. No analysis paragraphs, no rubric citations. A blogger should read it in 30 seconds. | |
| Write the review to `.social-review.md`. Do NOT post a PR comment — a subsequent workflow step handles that. The file's body must: | |
| 1. Start with the literal line `<!-- social-review -->` (HTML comment marker; lets future runs find this comment to update it in place). | |
| 2. Then `## Social Media Review` and the per-platform sections. | |
| 3. End with a footer line: `_Updated for commit \`${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }}\` (short: \`$(echo "${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha }}" | cut -c1-7)\`) at $(date -u '+%Y-%m-%d %H:%M UTC')._` | |
| Use the Write tool. Do not run `gh pr comment`. | |
| claude_args: '--model claude-sonnet-5 --allowed-tools "Read,Glob,Grep,Agent,Write,Bash(python3:*),Bash(gh pr view:*),Bash(gh pr diff:*),Bash(date:*)"' | |
| - name: Find existing social review comment | |
| if: steps.gate.outputs.should_skip != 'true' && steps.check.outputs.needs_review == 'true' | |
| id: find-social-comment | |
| uses: peter-evans/find-comment@v4 | |
| with: | |
| issue-number: ${{ steps.pr-info.outputs.number }} | |
| body-includes: '<!-- social-review -->' | |
| direction: last | |
| - name: Post or update social review comment | |
| if: steps.gate.outputs.should_skip != 'true' && steps.check.outputs.needs_review == 'true' | |
| uses: peter-evans/create-or-update-comment@v5 | |
| with: | |
| issue-number: ${{ steps.pr-info.outputs.number }} | |
| comment-id: ${{ steps.find-social-comment.outputs.comment-id }} | |
| body-path: .social-review.md | |
| edit-mode: replace | |
| - name: Fail if copy exceeds character limits | |
| if: steps.gate.outputs.should_skip != 'true' && steps.check.outputs.has_errors == 'true' | |
| run: | | |
| echo "::error::Social copy exceeds platform character limits. See review comment for details." | |
| exit 1 | |
| env: | |
| ESC_ACTION_OIDC_AUTH: true | |
| ESC_ACTION_OIDC_ORGANIZATION: pulumi | |
| ESC_ACTION_OIDC_REQUESTED_TOKEN_TYPE: urn:pulumi:token-type:access_token:organization | |
| ESC_ACTION_ENVIRONMENT: github-secrets/pulumi-docs | |
| ESC_ACTION_EXPORT_ENVIRONMENT_VARIABLES: false |