engineering-handbook/cicd.md

122 lines
3.8 KiB
Markdown
Raw Normal View History

# CI/CD Pipelines
## Requirement
Every deployable repo must have a CI/CD pipeline. No manual deploys to production. If it deploys to AWS, it needs a pipeline.
## Platform
GitHub Actions is the standard CI/CD platform. All pipelines use reusable workflows from the `Sea-Haven-Industries/.github` org repo (`.github/workflows/`).
## Workflow Structure
Every repo gets two thin workflow files in `.github/workflows/`:
| File | Trigger | Purpose |
|---|---|---|
| `ci.yaml` | `pull_request` on `main` | Lint, typecheck, test, synth/validate |
| `deploy.yaml` | `push` on `main` | Deploy to AWS |
### CDK Stacks (TypeScript)
```yaml
# .github/workflows/ci.yaml
name: CI
on:
pull_request:
branches: [main]
jobs:
ci:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main
with:
node-version: "24"
# .github/workflows/deploy.yaml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@main
with:
node-version: "24"
secrets:
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
```
### SAM Stacks (Python)
```yaml
# .github/workflows/ci.yaml
name: CI
on:
pull_request:
branches: [main]
jobs:
ci:
uses: Sea-Haven-Industries/.github/.github/workflows/ci-python-sam.yaml@main
# .github/workflows/deploy.yaml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: Sea-Haven-Industries/.github/.github/workflows/cd-sam.yaml@main
with:
stack-name: "your-stack-name"
secrets:
cfn-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
```
## Authentication
Deploy workflows authenticate to AWS via OIDC (no long-lived credentials). Each repo needs:
1. An IAM role named `githubdeploy-<repo-name>` with:
- OIDC trust policy for `token.actions.githubusercontent.com`
- Subject condition: `repo:Sea-Haven-Industries/<repo>:ref:refs/heads/main`
- Inline policy allowing `sts:AssumeRole` on CDK/SAM bootstrap roles
2. A repo secret `AWS_DEPLOY_ROLE_ARN` containing the role ARN
## Node.js Version
Always pass `node-version: "24"` to reusable workflows. Local dev uses Node 24 / npm 11 which generates lockfileVersion 3. The workflow defaults match this, but be explicit to avoid drift.
## Naming
- All workflow files: kebab-case
- Reusable workflow references: `@main` branch
## When to Add a Pipeline
- When creating a new deployable project — the pipeline is part of the initial setup, not a follow-up
- When working on an existing project that lacks one — flag it and add it as part of the current work
A project is not production-ready without CI/CD.
## PR Auto-Labeling
Pull requests are auto-labeled org-wide by a reusable workflow in `.github`. The label rules live once, centrally, inside the reusable workflow itself (written to the runner at execution time), so each repo needs only a short caller and **no per-repo `labeler.yml`**:
```yaml
# .github/workflows/labeler.yml — the per-repo caller
name: Labeler
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
label:
uses: Sea-Haven-Industries/.github/.github/workflows/callable-labeler.yaml@main
```
- The trigger is plain `pull_request`, not `pull_request_target`: private repos take no fork PRs, so the lower-privilege event is sufficient and avoids the pwn-request surface. Because `pull_request` runs the workflow from the merge commit, the Labeler check appears on the PR that first adds the caller — an absent or failed check means a missing permission, not expected behaviour.
- The caller MUST grant all three permissions. Reusable-workflow permissions can only be downgraded from the caller, so omitting `issues: write` (needed to create labels that don't exist yet) or any other grant causes a silent `startup_failure`.
- Adding the caller is part of new-repo provisioning.