Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 27 additions & 63 deletions apigen/infra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,26 @@ Region: `us-east-1` (required for CloudFront + ACM).

## The `productionAlias` flag

Defined in `cdk.json` under `context`. Default `false`.
Defined in `cdk.json` under `context`. Currently `true` — the distribution
carries `apiref.phpstan.org` as its alias and uses the CDK-issued ACM cert.

- `false`: distribution carries no aliases, no ACM cert attached (serves on the CF default `*.cloudfront.net` domain). First deploy succeeds while `apiref.phpstan.org` is still owned by the legacy distribution `E37G1C2KWNAPBD`.
- `true`: distribution carries `apiref.phpstan.org` as its alias and uses the new ACM cert. Set after the manual cutover.
It exists for the original cutover (it was `false` for the first deploy so the
distribution could be created while the legacy `E37G1C2KWNAPBD` still owned the
alias). It should stay `true`; only set it back to `false` if you ever need to
detach the alias for a rebuild.

## Out-of-band resources

The Route 53 record for `apiref.phpstan.org` is **not** managed by CDK. The
cutover script UPSERTs the record (via raw `change-resource-record-sets`); CDK
isn't aware of it. Same pattern as apex/www on the main site.
The Route 53 records for `apiref.phpstan.org` are **not** managed by CDK — they
were created directly via `change-resource-record-sets` during the cutover, and
CloudFormation can't UPSERT records that already exist outside its state. If the
distribution's CloudFront domain ever changes (e.g. a recreate), update the
`apiref.phpstan.org` A/AAAA alias records by hand. Same pattern as apex/www on
the main site.

## Local development

```sh
npm ci
npm run check # tsc --noEmit
npm test # vitest: 25 redirect-fn tests + 11 stack assertions
npm run synth # cdk synth --all
npm run diff # cdk diff --all (needs AWS creds for the target account)
```

## One-time bootstrap

The CDK bootstrap roles for the AWS account already exist (created by the
phpstan-dist repo's CDK app). You only need to deploy the OIDC roles stack
once, from a maintainer's laptop with admin AWS credentials:

```sh
npx cdk deploy PhpstanApirefOidcRoles
```
## GitHub repo variables

Note the `InfraDeployRoleArn` output and set the corresponding GitHub repo
variable.

## GitHub repo variables to set (in phpstan/phpstan-src)

After the first deploys, set these under Settings → Secrets and variables → Actions → Variables:
Set under Settings → Secrets and variables → Actions → Variables in `phpstan/phpstan-src`:

| Variable | Value | Used by |
|---|---|---|
Expand All @@ -64,42 +47,23 @@ After the first deploys, set these under Settings → Secrets and variables →
| `APIREF_BUCKET` | `phpstan-apiref-web` | `apiref.yml` |
| `APIREF_DISTRIBUTION_ID` | `DistributionId` output of `PhpstanApirefWebsite` | `apiref.yml` |

## Cutover runbook (legacy → new)

This moves `apiref.phpstan.org` from the legacy distribution `E37G1C2KWNAPBD`
to the new CDK-managed distribution. Expect ~5–10 min of intermittent 403s on
`apiref.phpstan.org` while CloudFront edges propagate the alias swap.

**Pre-cutover (with `productionAlias: false`):**

1. Merge the PR that adds this `apigen/infra/` directory. `apiref-infra.yml` deploys both stacks.
2. Copy bucket contents: `aws s3 sync s3://web-apiref.phpstan.org/ s3://phpstan-apiref-web/` (~334 MB / 13.5k objects).
3. Smoke-test on the new distribution's CF domain (look up `DistributionDomain` output):
```sh
D=$(aws cloudfront get-distribution --id <new-id> --query 'Distribution.DomainName' --output text)
curl -sI "https://$D/" # 301 to /2.2.x/namespace-PHPStan.html
curl -sI "https://$D/2.2.x/namespace-PHPStan.html" # 200
curl -sI "https://$D/1.9.x" # 301 to /1.9.x/namespace-PHPStan.html
curl -sI "https://$D/" | grep -iE 'strict-transport|x-content-type|x-frame|referrer-policy|x-xss'
```
Verify HSTS, XCTO, XFO=SAMEORIGIN, Referrer-Policy present; no X-XSS-Protection.

**Cutover (with `productionAlias: true`):**
## Local development

The sequence (do Route 53 first — we learned the hard way on the main site that CloudFront's `AddAlias` does a DNS sanity check):
```sh
npm ci
npm run check # tsc --noEmit
npm test # vitest: 25 redirect-fn tests + 11 stack assertions
npm run synth # cdk synth --all
npm run diff # cdk diff --all (needs AWS creds for the target account)
```

1. UPSERT Route 53 `apiref.phpstan.org` CNAME → new distribution's CF domain.
2. Wait for Route 53 INSYNC (~30–60s).
3. Detach `apiref.phpstan.org` from `E37G1C2KWNAPBD` via `aws cloudfront update-distribution`.
4. Add `apiref.phpstan.org` to the new distribution (retry every 20s if CloudFront's DNS-check cache is stale).
5. Wait for new distribution `Deployed`.
6. Smoke-test against `https://apiref.phpstan.org/`.
Changes merged to `2.2.x` under `apigen/infra/**` are deployed automatically by
`.github/workflows/apiref-infra.yml`.

Then merge the PR that flips `productionAlias: true` in `cdk.json`. The
workflow's `cdk deploy` is a no-op for the alias (already attached by the
script) and just syncs CFN state.
## Cleanup runbook (legacy resources, when stable for ~1 week)

## Cleanup runbook (when stable for ~1 week)
The cutover from the legacy distribution is done; these legacy resources can be
removed once the new stack has been stable for a sensible cooling-off period:

- Delete CloudFront distribution `E37G1C2KWNAPBD` (disable, wait, delete).
- Delete CloudFront Functions `apiref-phpstan-org-viewer-request` and `secure-headers-response` (the latter has no remaining users after `E37G1C2KWNAPBD` is gone).
Expand Down
2 changes: 1 addition & 1 deletion apigen/infra/cdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
]
},
"context": {
"productionAlias": false,
"productionAlias": true,
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": ["aws"],
Expand Down
Loading