mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 08:03:15 +00:00
ci: path-filtered infra CI/CD + dual OIDC roles + prod approval gate (T18) (#15)
* ci: path-filtered infra CI/CD with dual OIDC roles + prod approval gate (T18)
Add the /infra half of the combined-repo pipeline (the Python agent keeps ci.yml):
- ci-infra.yml — PR check on infra/** : tsc + jest + cdk synth via the org
reusable ci-typescript-cdk.yaml (working-directory: infra).
- cd-infra.yml — push to dev/main on infra/** (or dispatch):
* job 'ci' (reusable) is the CI-green precondition (deploy needs: ci).
* deploy-dev (ref=dev, NO environment) → cdk deploy OpenSweDevStack,
assuming githubdeploy-open-swe-infra-dev (OIDC sub ref:refs/heads/dev). AUTO.
* deploy-prod (ref=main, environment: prod) → cdk deploy OpenSweProdStack,
assuming githubdeploy-open-swe-infra-prod (OIDC sub environment:prod). The
'prod' Environment's required reviewer is the manual-approval gate.
Deliberately self-contained (NOT the reusable cd-cdk.yaml) because that runs
'cdk deploy --all' — from a single-env push it would deploy the other env + the
shared IAM stack, breaking the per-env boundary. CD targets one stack per env;
the shared open-swe-iam stack is human-gated (T6), never deployed by CD.
Infra CI is enforced at the DEPLOY boundary (deploy jobs need ci), not as a
branch-protection required check — path-filtering a required check would deadlock
app-only PRs. Documented in infra/README.md along with the post-T6 prerequisites
(repo vars AWS_DEPLOY_ROLE_INFRA_{DEV,PROD}; a 'prod' Environment w/ reviewer).
Not active until the IAM roles are applied (T6) — assuming a nonexistent role
just fails closed. App-side CD (S3 artifact + SSM) is T19.
* fix(infra): commit jest.config.js (was ignored by *.js → infra CI used Babel)
The infra/.gitignore *.js rule (for compiled CDK output) silently swept up the
hand-authored jest.config.js, so it was never committed. Local jest passed (file
present in the working tree) but CI's fresh checkout lacked it → jest fell back to
the default Babel transform → 'Cannot use import statement outside a module' on the
TypeScript test. Surfaced now because T18 is the first workflow to run infra jest
in CI. Negate the ignore for this one file and commit it.
This commit is contained in:
parent
fcbdfb67aa
commit
70319cac0d
5 changed files with 204 additions and 4 deletions
122
.github/workflows/cd-infra.yml
vendored
Normal file
122
.github/workflows/cd-infra.yml
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
name: Infra CD
|
||||
|
||||
# Path-filtered CDK deploy for /infra, per env, OIDC-only (no static keys).
|
||||
#
|
||||
# push to dev → CI (tsc+jest+synth) → deploy OpenSweDevStack (AUTO, CI-green-gated)
|
||||
# push to main → CI → deploy OpenSweProdStack (manual approval: env "prod")
|
||||
#
|
||||
# Why this is NOT the reusable cd-cdk.yaml: that workflow runs `cdk deploy --all`,
|
||||
# which would deploy ALL THREE stacks (incl. the OTHER env + the shared IAM stack)
|
||||
# from a single-env push — breaking the per-env dev/prod boundary. So we target one
|
||||
# stack explicitly per env. (Infra CI still uses the reusable ci-typescript-cdk.)
|
||||
#
|
||||
# The shared IAM stack (open-swe-iam — owns BOTH envs' OIDC deploy roles) is
|
||||
# intentionally NOT deployed here: it is a privileged, human-gated apply (T6), so a
|
||||
# routine dev push can never alter prod's deploy role.
|
||||
#
|
||||
# OIDC subject alignment (must match the per-env trust in infra/lib/config.ts):
|
||||
# - deploy-dev declares NO `environment:` → token sub = repo:…:ref:refs/heads/dev,
|
||||
# which is exactly what githubdeploy-open-swe-infra-dev trusts.
|
||||
# - deploy-prod declares `environment: prod` → token sub = repo:…:environment:prod,
|
||||
# which githubdeploy-open-swe-infra-prod trusts AND which triggers the GitHub
|
||||
# Environment's required-reviewer (manual approval) gate.
|
||||
#
|
||||
# Prerequisites (post-T6, when the roles exist):
|
||||
# - repo variables AWS_DEPLOY_ROLE_INFRA_DEV / AWS_DEPLOY_ROLE_INFRA_PROD = the
|
||||
# githubdeploy-open-swe-infra-<env> role ARNs (open-swe-iam CfnOutputs).
|
||||
# - a GitHub Environment named "prod" with Adam as a required reviewer.
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, main]
|
||||
paths:
|
||||
- "infra/**"
|
||||
- ".github/workflows/cd-infra.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
# one infra deploy per branch at a time; never cancel an in-flight deploy.
|
||||
group: cd-infra-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# CI-green precondition — re-run tsc + jest + synth on the pushed commit before
|
||||
# any deploy. A failure here blocks the deploy jobs (needs: ci).
|
||||
ci:
|
||||
name: Infra CI (pre-deploy)
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main
|
||||
with:
|
||||
node-version: "24"
|
||||
working-directory: infra
|
||||
cache-dependency-path: infra/package-lock.json
|
||||
run-typecheck: true
|
||||
run-tests: true
|
||||
run-cdk-synth: true
|
||||
|
||||
deploy-dev:
|
||||
name: Deploy open-swe-dev
|
||||
needs: ci
|
||||
if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
cache-dependency-path: infra/package-lock.json
|
||||
- name: Install deps
|
||||
working-directory: infra
|
||||
run: npm ci
|
||||
- uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_INFRA_DEV }}
|
||||
aws-region: us-east-1
|
||||
- name: CDK deploy (dev only)
|
||||
working-directory: infra
|
||||
run: npx cdk deploy OpenSweDevStack --require-approval never
|
||||
- name: Stack outputs
|
||||
run: |
|
||||
aws cloudformation describe-stacks --stack-name open-swe-dev \
|
||||
--query 'Stacks[0].Outputs[*].[OutputKey,OutputValue]' --output table
|
||||
|
||||
deploy-prod:
|
||||
name: Deploy open-swe-prod
|
||||
needs: ci
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
# Manual-approval gate: the "prod" Environment requires a reviewer (Adam).
|
||||
# Also makes the OIDC sub …:environment:prod (matches the prod role trust).
|
||||
environment: prod
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
cache-dependency-path: infra/package-lock.json
|
||||
- name: Install deps
|
||||
working-directory: infra
|
||||
run: npm ci
|
||||
- uses: aws-actions/configure-aws-credentials@v6
|
||||
with:
|
||||
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_INFRA_PROD }}
|
||||
aws-region: us-east-1
|
||||
- name: CDK deploy (prod only)
|
||||
working-directory: infra
|
||||
run: npx cdk deploy OpenSweProdStack --require-approval never
|
||||
- name: Stack outputs
|
||||
run: |
|
||||
aws cloudformation describe-stacks --stack-name open-swe-prod \
|
||||
--query 'Stacks[0].Outputs[*].[OutputKey,OutputValue]' --output table
|
||||
26
.github/workflows/ci-infra.yml
vendored
Normal file
26
.github/workflows/ci-infra.yml
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
name: Infra CI
|
||||
|
||||
# Path-filtered CI for the /infra CDK app (TypeScript). The existing "Agent CI"
|
||||
# (ci.yml) covers the Python agent; this adds tsc + jest + cdk synth for /infra so
|
||||
# infra changes are gated on a PR the same way. Runs only when /infra changes.
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "infra/**"
|
||||
- ".github/workflows/ci-infra.yml"
|
||||
|
||||
jobs:
|
||||
infra-ci:
|
||||
name: Infra CI (tsc + jest + synth)
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main
|
||||
with:
|
||||
node-version: "24"
|
||||
working-directory: infra
|
||||
cache-dependency-path: infra/package-lock.json
|
||||
run-typecheck: true
|
||||
run-tests: true
|
||||
run-cdk-synth: true
|
||||
2
infra/.gitignore
vendored
2
infra/.gitignore
vendored
|
|
@ -3,6 +3,8 @@ cdk.out/
|
|||
*.js
|
||||
*.d.ts
|
||||
*.js.map
|
||||
# ...but jest.config.js is hand-authored config, not build output — keep it.
|
||||
!jest.config.js
|
||||
|
||||
# deps
|
||||
node_modules/
|
||||
|
|
|
|||
|
|
@ -240,15 +240,56 @@ npx cdk synth open-swe-prod
|
|||
npm test # jest — naming Aspect
|
||||
```
|
||||
|
||||
## CI/CD (T18 — `.github/workflows/ci-infra.yml` + `cd-infra.yml`)
|
||||
|
||||
Path-filtered, OIDC-only (no static keys). The Python agent keeps its own
|
||||
`ci.yml` ("Agent CI"); these two add the `/infra` half.
|
||||
|
||||
| Workflow | Trigger | Does |
|
||||
|---|---|---|
|
||||
| `ci-infra.yml` | PR touching `infra/**` | `tsc` + `jest` + `cdk synth` (reusable `ci-typescript-cdk.yaml`). |
|
||||
| `cd-infra.yml` | push to `dev`/`main` touching `infra/**`, or dispatch | CI (pre-deploy) → per-env `cdk deploy`. |
|
||||
|
||||
`cd-infra.yml` flow:
|
||||
|
||||
- **push to `dev`** → CI green → **auto** `cdk deploy OpenSweDevStack` (assumes
|
||||
`githubdeploy-open-swe-infra-dev`; the job declares **no** `environment:`, so the
|
||||
OIDC subject is `…:ref:refs/heads/dev` — matching that role's trust).
|
||||
- **push to `main`** → CI green → `cdk deploy OpenSweProdStack` behind the
|
||||
**`prod` GitHub Environment** (required reviewer = Adam). The `environment: prod`
|
||||
declaration both fires the manual-approval gate and makes the OIDC subject
|
||||
`…:environment:prod` — matching `githubdeploy-open-swe-infra-prod`'s trust.
|
||||
|
||||
**Why not the reusable `cd-cdk.yaml`:** it runs `cdk deploy --all`, which from a
|
||||
single-env push would deploy the *other* env + the shared IAM stack — breaking the
|
||||
per-env boundary. So CD targets one stack explicitly per env. The shared
|
||||
`open-swe-iam` stack is **not** deployed by CD (privileged, human-gated — T6).
|
||||
|
||||
**Gating note:** infra CI is enforced at the *deploy* boundary (`cd-infra`'s
|
||||
`deploy-*` jobs `needs: ci`), not as a branch-protection required check —
|
||||
path-filtering a *required* check would deadlock app-only PRs (a skipped required
|
||||
check never satisfies). Making `Infra CI` a required check later needs a
|
||||
skip-aware shim or dropping its path filter.
|
||||
|
||||
**Prerequisites (set post-T6, when the roles exist):**
|
||||
|
||||
- repo **variables** `AWS_DEPLOY_ROLE_INFRA_DEV` / `AWS_DEPLOY_ROLE_INFRA_PROD`
|
||||
= the `githubdeploy-open-swe-infra-<env>` role ARNs (`open-swe-iam` outputs).
|
||||
- a GitHub **Environment** named `prod` with Adam as a required reviewer.
|
||||
|
||||
> App-side CD (CI → S3 artifact → SSM deploy via `githubdeploy-open-swe-app-<env>`)
|
||||
> is **T19**, not here.
|
||||
|
||||
## Deploy ordering (when the gate clears — NOT yet)
|
||||
|
||||
1. **`open-swe-iam` first** — create `githubdeploy-open-swe-infra` + set the repo
|
||||
`AWS_DEPLOY_ROLE_ARN` secret before any infra/secrets CI step (BLOCK#3).
|
||||
1. **`open-swe-iam` first** — apply the IAM stack (T6, human-gated), then set the
|
||||
repo `AWS_DEPLOY_ROLE_INFRA_{DEV,PROD}` variables from its role-ARN outputs and
|
||||
configure the `prod` Environment reviewer (BLOCK#3).
|
||||
2. **Security gate** — T4 GPT-4.1 IAM cross-review + T5 `/sh-security-review` on
|
||||
the synth; resolve every confirmed critical/high.
|
||||
3. **IAM applied** (T6) — only after the gate.
|
||||
4. Env stacks (`open-swe-dev`, then `open-swe-prod`) build out at T12+, prod gated
|
||||
by a GitHub Environment manual approval.
|
||||
4. Env stacks: first `open-swe-dev` (T14, manual validate), then CD auto-deploys
|
||||
dev on push; `open-swe-prod` (T21) behind the `prod` Environment approval.
|
||||
|
||||
## Version policy
|
||||
|
||||
|
|
|
|||
9
infra/jest.config.js
Normal file
9
infra/jest.config.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
testEnvironment: "node",
|
||||
roots: ["<rootDir>/test"],
|
||||
testMatch: ["**/*.test.ts"],
|
||||
preset: "ts-jest",
|
||||
transform: {
|
||||
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "tsconfig.json" }],
|
||||
},
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue