From 5b18b71501275de502cfc55b51b29f7f1db091d3 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 10:48:57 +0000 Subject: [PATCH] ci: deploy pull request previews from a separate privileged workflow Pull requests from forks have no access to secrets, variables or the AWS OIDC token, so the deploy job in the pull_request workflow fails. Move deployment to a workflow_run workflow that runs on main, downloads the build artifact and deploys it without running pull request code. Fork deploys use a separate preview-fork environment so that they can require reviewer approval. Co-Authored-By: Mattias Buelens --- .github/workflows/_deploy.yml | 29 ++++++-- .github/workflows/_undeploy.yml | 2 - .github/workflows/pull-request-preview.yml | 80 ++++++++++++++++++++++ .github/workflows/pull-request.yml | 33 ++------- 4 files changed, 108 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/pull-request-preview.yml diff --git a/.github/workflows/_deploy.yml b/.github/workflows/_deploy.yml index 0d4afc86420a..35dcd052079e 100644 --- a/.github/workflows/_deploy.yml +++ b/.github/workflows/_deploy.yml @@ -17,6 +17,21 @@ on: description: Name of the built artifact for deployment. required: true type: string + artifact_run_id: + description: ID of the workflow run that uploaded the artifact. Defaults to the current run. + required: false + type: string + default: '' + pr_number: + description: Number of the pull request to comment on with a link to the preview. + required: false + type: number + default: -1 + pr_head_sha: + description: Commit that the pull request preview was built from. + required: false + type: string + default: '' s3_prefix: description: AWS S3 prefix where to store the build. required: true @@ -26,8 +41,6 @@ on: required: true type: string -concurrency: deploy-${{ github.ref }} - jobs: deploy-to-cloudfront: runs-on: ubuntu-latest @@ -36,13 +49,14 @@ jobs: deployment: true url: ${{ inputs.environment_url }} permissions: + actions: read id-token: write pull-requests: write steps: - name: Create app token uses: actions/create-github-app-token@v3 id: app-token - if: ${{ inputs.environment_name == 'preview' }} + if: ${{ inputs.pr_number > 0 }} with: client-id: ${{ vars.THEOPLAYER_BOT_APP_ID }} private-key: ${{ secrets.THEOPLAYER_BOT_PRIVATE_KEY }} @@ -52,6 +66,8 @@ jobs: with: name: ${{ inputs.artifact_name }} path: ${{ github.workspace }}/${{ inputs.artifact_name }} + run-id: ${{ inputs.artifact_run_id || github.run_id }} + github-token: ${{ github.token }} - name: Configure AWS credentials ☁️ uses: aws-actions/configure-aws-credentials@v6 @@ -81,17 +97,18 @@ jobs: - name: Get deployment timestamp πŸ•°οΈ id: timestamp - if: ${{ inputs.environment_name == 'preview' }} + if: ${{ inputs.pr_number > 0 }} run: echo "time=$(date -u '+%Y-%m-%d %H:%M %Z')" >> "$GITHUB_OUTPUT" - name: Add comment to Pull Request with link to preview πŸ•ΈοΈ uses: marocchino/sticky-pull-request-comment@v3 - if: ${{ inputs.environment_name == 'preview' }} + if: ${{ inputs.pr_number > 0 }} with: header: pr-preview + number: ${{ inputs.pr_number }} GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} message: | Documentation preview :---: | :rocket: View preview at
${{ inputs.environment_url }}

- |
Deployed from commit ${{ github.event.pull_request.head.sha }} at ${{ steps.timestamp.outputs.time }}.
+ |
Deployed from commit ${{ inputs.pr_head_sha }} at ${{ steps.timestamp.outputs.time }}.
diff --git a/.github/workflows/_undeploy.yml b/.github/workflows/_undeploy.yml index ac17983f2ce0..4cc8f8d8ec4f 100644 --- a/.github/workflows/_undeploy.yml +++ b/.github/workflows/_undeploy.yml @@ -15,8 +15,6 @@ on: required: true type: string -concurrency: deploy-${{ github.ref }} - jobs: undeploy-from-cloudfront: runs-on: ubuntu-latest diff --git a/.github/workflows/pull-request-preview.yml b/.github/workflows/pull-request-preview.yml new file mode 100644 index 000000000000..32756727532d --- /dev/null +++ b/.github/workflows/pull-request-preview.yml @@ -0,0 +1,80 @@ +name: 'Pull request preview' + +# Deploys the build artifact from "On Pull Requests" to the preview environment. +# This workflow runs in the context of the main branch, so it has access to +# secrets and the AWS OIDC token, even for pull requests from forks. +# It must never check out or run code from the pull request. +# See https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ +on: + workflow_run: + workflows: ['On Pull Requests'] + types: + - completed + pull_request_target: + types: + - closed + +jobs: + pull-request: + name: pull-request + if: >- + github.event_name == 'workflow_run' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.conclusion == 'success' + && github.event.workflow_run.actor.login != 'dependabot[bot]' + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + number: ${{ steps.pr.outputs.number }} + head_sha: ${{ github.event.workflow_run.head_sha }} + is_fork: ${{ github.event.workflow_run.head_repository.full_name != github.repository }} + steps: + - name: Find pull request for commit πŸ” + id: pr + # The workflow_run event does not list pull requests from forks, + # so look up the open pull request whose head is exactly this commit. + run: | + number=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${HEAD_SHA}/pulls" \ + --jq "[.[] | select(.state == \"open\" and .head.sha == \"${HEAD_SHA}\" and .head.repo.full_name == \"${HEAD_REPO}\")] | first | .number") + if [[ -z "$number" || "$number" == "null" ]]; then + echo "No open pull request found with head ${HEAD_REPO}@${HEAD_SHA}, skipping preview." + exit 1 + fi + echo "number=${number}" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ github.token }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }} + + deploy: + name: deploy + needs: [pull-request] + concurrency: preview-pr-${{ needs.pull-request.outputs.number }} + uses: ./.github/workflows/_deploy.yml + secrets: inherit + permissions: + actions: read + id-token: write + pull-requests: write + with: + # Deployments from forks use a separate environment, so that they can require approval. + environment_name: ${{ needs.pull-request.outputs.is_fork == 'true' && 'preview-fork' || 'preview' }} + environment_url: '${{ vars.PREVIEW_CLOUDFRONT_URL }}/pr-${{ needs.pull-request.outputs.number }}/' + artifact_name: dist.zip + artifact_run_id: ${{ github.event.workflow_run.id }} + pr_number: ${{ fromJSON(needs.pull-request.outputs.number) }} + pr_head_sha: ${{ needs.pull-request.outputs.head_sha }} + s3_prefix: 'pr-${{ needs.pull-request.outputs.number }}' + cf_invalidate_path: '/pr-${{ needs.pull-request.outputs.number }}*' + + undeploy: + name: undeploy + if: ${{ github.event_name == 'pull_request_target' && github.triggering_actor != 'dependabot[bot]' }} + concurrency: preview-pr-${{ github.event.number }} + uses: ./.github/workflows/_undeploy.yml + secrets: inherit + with: + environment_name: preview + s3_prefix: 'pr-${{ github.event.number }}' + cf_invalidate_path: '/pr-${{ github.event.number }}*' diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index a07dde8d5c21..3a9fa7e4149b 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -1,5 +1,8 @@ name: 'On Pull Requests' +# This workflow runs on pull requests from forks too, so it has no access to +# secrets, environment variables or the AWS OIDC token. Deployment happens in +# pull-request-preview.yml, which never runs code from the pull request. on: pull_request: types: @@ -7,50 +10,24 @@ on: - reopened - synchronize - ready_for_review - - closed jobs: lint: name: lint - if: ${{ github.event.action != 'closed' }} uses: ./.github/workflows/_lint.yml secrets: inherit build: name: build - if: ${{ github.event.action != 'closed' }} uses: ./.github/workflows/_build.yml with: environment_name: preview # Preview URLs look like this: https://[storage-url]/pr-[number]/ - docusaurus_url: ${{ vars.PREVIEW_CLOUDFRONT_URL }} + # The URL is hardcoded because `vars` are not available to pull requests from forks. + docusaurus_url: 'https://docs-preview.optiview.dolby.com' docusaurus_base_url: '/pr-${{ github.event.number }}/' # Prevent PR previews from being indexed by search engines docusaurus_no_index: 1 # Add an announcement at the top to indicate if this is a preview docusaurus_pr_number: ${{ github.event.number }} docusaurus_pr_url: ${{ github.event.pull_request.html_url }} - - deploy: - name: deploy - needs: [build] - # Do not run on PRs from Dependabot, since they should not need it. - if: ${{ github.event.action != 'closed' && github.triggering_actor != 'dependabot[bot]' }} - uses: ./.github/workflows/_deploy.yml - secrets: inherit - with: - environment_name: preview - environment_url: '${{ vars.PREVIEW_CLOUDFRONT_URL }}/pr-${{ github.event.number }}/' - artifact_name: ${{ needs.build.outputs.artifact_name }} - s3_prefix: 'pr-${{ github.event.number }}' - cf_invalidate_path: '/pr-${{ github.event.number }}*' - - undeploy: - name: undeploy - if: ${{ github.event.action == 'closed' && github.triggering_actor != 'dependabot[bot]' }} - uses: ./.github/workflows/_undeploy.yml - secrets: inherit - with: - environment_name: preview - s3_prefix: 'pr-${{ github.event.number }}' - cf_invalidate_path: '/pr-${{ github.event.number }}*'