Skip to content

Deploy PR previews with HarperFast/deploy-action - #797

Draft
dawsontoth wants to merge 2 commits into
mainfrom
ci/preview-deploy-action
Draft

dawsontoth wants to merge 2 commits into
mainfrom
ci/preview-deploy-action

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

⊙ Problem

pr-preview.yaml checks out each PR's code under pull_request_target (fork PRs too, after an approval in preview), builds it in a job that holds HARPER_PREVIEW_USERNAME and HARPER_PREVIEW_PASSWORD, and deploys a pr-<N> component that stays until the PR closes. Previews pile up on the preview host (#695).

HarperFast/deploy-action now does per-PR previews on Harper 5.4 with OIDC only, and Kris suggested running it here so it's exercised on real PRs (HarperFast/deploy-action#1).

💡 Solution

  • preview.yaml is the action's example workflow: a deploy job in the preview environment and a cleanup job in preview-cleanup, each with the fork guard. No checkout, no stored credential, and the action keeps one PR comment and a GitHub Deployment. Previews move from <preview host>/pr-<N> to https://<branch>.app.<cluster domain>/ and expire on their own (7 days by default).
  • preview-build.yaml builds the site. The action's default deploys a PR's commit by reference, but this repo has no Harper config.yaml and the cluster doesn't run a Docusaurus build. So, as the action's README and the CI guide describe for apps that need a build, an unprivileged pull_request run builds the PR's head commit and uploads pr-<N>-build. The deploy job waits for that run, downloads the artifact into site/build, writes its own site/config.yaml (the same static config production uses), and deploys site as a payload with by_ref: false.
  • scripts/preview-pr.mjs (npm run preview:pr) looks for the new build workflow's runs, and serves with base URL /.

The action is pinned to v1.0.0 by its commit, 45fa59b.

🤔 Things to look at

  • Polling. GitHub starts both workflows at once, and the trust policy matches only pull_request_target, so workflow_run can't deploy. The deploy job polls for the build of its exact commit, from a branch of this repository. It deploys nothing if the PR moves on or closes, and fails if the build fails, takes over 30 minutes, or never starts.
  • A PR with merge conflicts gets no preview. GitHub starts no pull_request run for it, so the deploy job fails after 10 minutes and says why. Assemble the v5 reference and open release notes from harper and harper-pro #788 is in that state now. The old workflow built conflicting PRs anyway.
  • Fork PRs get no preview, not one after approval. They still get the build artifact for npm run preview:pr, subject to the repository's fork-approval setting.
  • The application is docs-site, production's component name, so previews are docs-site-pr-<N>. Each gets its own database branch (the default). The site has no tables.
  • actions: read on the deploy job, beyond the example, is for reading the build's runs and downloading its artifact.

📋 Enabling previews (Dawson)

Nothing here is done yet: this PR changes no cluster or repository settings. A real preview needs this PR merged, since pull_request_target runs main's workflow and the trust policies pin main.

Before merging

Steps 3a, 3b and 4 don't depend on the cluster, so they can happen in any order.

  • 1. Harper Pro 5.4.0-alpha.2 or later on the preview cluster, preview.harper-documentation.harperfabric.com (one instance), upgraded in Fabric. Its version isn't checked yet, because the operations API needs a login. alpha.1 has no enable_previews or harper preview setup.
  • 1. A certificate for preview hosts. DNS for *.app.preview.harper-documentation.harperfabric.com resolves, but the instance's certificate (issued Sep 20, expires Dec 19) doesn't cover it, so a preview host's connection is reset. Fabric adds it when the certificate renews on its own, before Dec 19, unless Fabric renews it sooner.
  • 2. harper preview setup app=docs-site, signed in as a super_user, from a checkout of this branch (setup reads .github/workflows/preview.yaml and checks it). login prompts for the target, https://preview.harper-documentation.harperfabric.com:9925/.
    npx -y [email protected] login
    npx -y [email protected] preview setup app=docs-site
    Setup creates the docs-site-ci-preview role and user and both trust policies, and enables previews for docs-site. It also sets the HARPER_PREVIEW_TARGET repository variable, which is separate from today's secret of the same name.
  • 3a. preview environment: allow only main under Deployment branches and tags. Keep its developers reviewers until this merges: main's old pr-preview.yaml builds fork PRs with the stored password, and those reviewers are what gate it.
  • 3b. preview-cleanup environment: create it, with no reviewers and the same main rule.
  • 4. An Actions policy allowing pull_request_target for .github/workflows/preview.yaml. The repository is public, and GitHub blocks the event from December 2, with brownouts on November 9 and 16. The repository has no policies yet. From the action's README:
    curl --fail --silent --show-error --location https://raw.githubusercontent.com/HarperFast/deploy-action/main/examples/preview-actions-policy.json | jq '{name, enforcement, conditions, rules}' | gh api --method POST repos/HarperFast/documentation/actions/policies --header 'X-GitHub-Api-Version: 2026-03-10' --input -

After merging

  • 3c. preview environment: remove the developers reviewers, or every docs PR's preview waits for an approval.
  • A same-repository PR that changes the docs gets a preview that serves, and closing it drops the preview. Then update Verification below.
  • Delete the HARPER_PREVIEW_USERNAME, HARPER_PREVIEW_PASSWORD and HARPER_PREVIEW_TARGET secrets, and the PREVIEW_CLEANUP_APP_ID variable and PREVIEW_CLEANUP_APP_PRIVATE_KEY secret if they're set. Nothing reads them now.
  • Delete the 37 pr-<N> environments.
  • Drop the old pr-<N> components from the preview cluster.

Outside this PR

  • An Actions policy for companion-check.yaml, allowing pull_request_target, schedule and workflow_dispatch. It also runs on pull_request_target, so without a policy it fails during the brownouts and stops on December 2.

✅ Verification

Relates to #695.

🤖 Generated with Claude Code

Replace the hand-written pr-preview.yaml with preview.yaml, the action's
two-job preview workflow (deploy in `preview`, cleanup in
`preview-cleanup`). It authenticates by OIDC only, so the stored
username and password are no longer read, and it never checks out PR
code; fork PRs get no preview.

The site needs a Docusaurus build, which must not run with the preview
identity. preview-build.yaml builds each PR's head commit in an
unprivileged pull_request run and uploads it; the deploy job waits for
that run, downloads the artifact under a config.yaml of its own, and
deploys it as a payload preview at the root of its own host. The local
preview script follows the build workflow's name and base URL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates scripts/preview-pr.mjs to query the "Build PR Preview" workflow instead of "Deploy PR Preview" and sets the DOCUSAURUS_BASE_URL to / instead of a PR-specific path. Feedback points out that using the --paginate flag with gh api and --jq can result in multiple JSON documents being returned, which will cause a SyntaxError when parsed. It is recommended to remove the --paginate flag.

Comment thread scripts/preview-pr.mjs
const runs = JSON.parse(
execSync(
`gh api repos/HarperFast/documentation/actions/runs --paginate -X GET -f branch=${sanitizedBranch} --jq '.workflow_runs | map(select(.conclusion == "success" and .name == "Deploy PR Preview")) | sort_by(.created_at) | reverse | .[0]'`,
`gh api repos/HarperFast/documentation/actions/runs --paginate -X GET -f branch=${sanitizedBranch} --jq '.workflow_runs | map(select(.conclusion == "success" and .name == "Build PR Preview")) | sort_by(.created_at) | reverse | .[0]'`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using --paginate with gh api and --jq can output multiple JSON documents (one per page) if there are multiple pages of workflow runs. When passed to JSON.parse(), this will cause a SyntaxError because it is not a single valid JSON document. Since we only need the latest successful run, which is guaranteed to be on the first page, we should remove the --paginate flag to ensure the output is always a single valid JSON document.

Suggested change
`gh api repos/HarperFast/documentation/actions/runs --paginate -X GET -f branch=${sanitizedBranch} --jq '.workflow_runs | map(select(.conclusion == "success" and .name == "Build PR Preview")) | sort_by(.created_at) | reverse | .[0]'`,
`gh api repos/HarperFast/documentation/actions/runs -X GET -f branch=${sanitizedBranch} --jq '.workflow_runs | map(select(.conclusion == "success" and .name == "Build PR Preview")) | sort_by(.created_at) | reverse | .[0]'`,

Co-Authored-By: Claude Opus 5.5 <[email protected]>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant