Skip to content

Reusable workflows ​

outcrop ships reusable GitHub Actions workflows and composite actions so a service repository only needs three thin caller workflows. Everything authenticates with GitHub OIDC - no AWS access keys are ever stored in GitHub.

Reference them as rpallas/outcrop/.github/workflows/<file>@v1 (floating major) or pin a full tag for reproducibility. The composite actions used by the workflows are documented in .github/actions/README.md.

Prerequisites ​

Repository variables ​

Set under Settings > Secrets and variables > Actions > Variables (plain variables, not secrets):

VariableUsed byDescription
AWS_REGIONallHome region, e.g. eu-west-1.
AWS_DEPLOY_ROLE_ARN_DEVpreview deploy/destroy, service-deployDeploy role for dev (an output of the account baseline).
AWS_DEPLOY_ROLE_ARN_STAGEservice-deployDeploy role for stage.
AWS_DEPLOY_ROLE_ARN_PRODservice-deployDeploy role for prod.
AWS_READONLY_ROLE_ARN_DEVservice-checks (recommended)Read-only role; lets cdk synth resolve SSM lookups and enables cdk diff comments.
AWS_BASELINE_ROLE_ARN_<ENV>account-baseline-deploy (optional)Bootstrap/administration role used to deploy the account baseline itself.
SANDBOX_DEPLOY_ROLE_ARNthis repository onlyDeploy role of the sandbox account used by the examples-* workflows.

service-deploy.yml looks up AWS_DEPLOY_ROLE_ARN_<ENV> (environment name upper-cased, - -> _) for each environment unless the role-arns input provides it. Every role must trust the GitHub OIDC provider for your repository.

GitHub Environments ​

Create GitHub Environments named after your platform environments (dev, stage, prod). The deploy workflows bind each job to the environment of the same name (environment: <env>), so required reviewers, wait timers and branch protection configured on the environment gate the deployment - add required reviewers on prod.

The skip-preview label ​

Create a label named skip-preview (configurable via the skip-label input). Pull requests that carry it do not get a preview environment; the sticky PR comment says so instead.

Workflows ​

service-checks.yml ​

Secret scan (gitleaks), lint, test, cdk synth (uploads cdk.out) and, optionally, a cdk diff comment on pull requests using a read-only role.

InputTypeDefaultDescription
working-directorystring.CDK app directory.
install-working-directorystring"" (= working-directory)Where npm ci runs; pass . in a monorepo.
build-commandstring""Optional build command run after install (e.g. npm run build).
node-versionstring"" (= .nvmrc)Node.js version.
lint-commandstringnpm run lint --if-present
test-commandstringnpm test --if-present
synth-envstringdevEnvironment for synth / diff.
synth-commandstringnpx cdk synth -c env=<synth-env> --quietOverride.
aws-regionstring""Required for the diff job.
readonly-role-arnstring""Read-only role for synth-time lookups and cdk diff. Without it (and without a committed cdk.context.json) the synth step is skipped.
diffbooleanfalsePost cdk diff comment (needs readonly-role-arn, PR event).
runs-onstringubuntu-latest
secret-scanbooleantrueRun gitleaks over the full git history.

Permissions: contents: read, id-token: write, pull-requests: write.

service-preview-deploy.yml ​

Deploys a per-pull-request preview and posts a sticky comment. Call it on pull_request: types: [opened, synchronize, reopened]. Pull requests from forks are skipped.

Steps: preview id (ticket in branch name, e.g. ABC-123 -> abc-123, else pr-<n>), OIDC login, cdk deploy --all -c env=<target-env> -c preview=true -c previewId=<id> -c prNumber=<n>, stack outputs exported as PREVIEW_<Key> environment variables, base URL = first output whose key ends with BaseUrl, optional integration tests (with PREVIEW_ID, PREVIEW_BASE_URL and all outputs in the environment), then the comment (deployed, or failed on error). Concurrency group preview-<repo>-<pr> cancels superseded runs.

InputTypeDefaultDescription
working-directorystring.CDK app directory.
install-working-directorystring"" (= working-directory)Where npm ci runs.
build-commandstring""Optional build command.
node-versionstring""Node.js version (empty = .nvmrc).
aws-regionstringrequired
role-arnstringrequiredDeploy role assumed via OIDC.
target-envstringdevEnvironment hosting previews.
strategystringticket-then-prticket-then-pr, pr or branch-slug.
skip-labelstringskip-previewLabel that skips the preview.
integration-test-commandstring""Run from working-directory after deploy.
stacksstring--allStack names / wildcards.
extra-contextstring""Newline separated key=value context.
runs-onstringubuntu-latest
cli-versionstringlatest@rpallas/outcrop-cli version (npx --yes).
cli-commandstring""Explicit CLI command (local build).
output-prefixstringPREVIEW_Prefix for exported outputs.

Outputs: preview-id, base-url, stack-names. Permissions: contents: read, id-token: write, pull-requests: write.

service-preview-destroy.yml ​

Destroys the preview of a closed pull request and updates the sticky comment to destroyed. Call it on pull_request: types: [closed]. Same concurrency group as the deploy workflow, without cancel-in-progress, so it waits for an in-flight deploy. Skips gracefully when no stacks exist.

Inputs: as service-preview-deploy.yml minus skip-label, integration-test-command and output-prefix. Outputs: preview-id, destroyed. Permissions: contents: read, id-token: write, pull-requests: write.

service-deploy.yml ​

Deploys to an ordered list of environments, one after the other, each gated by the matching GitHub Environment.

InputTypeDefaultDescription
environmentsstringdevOrdered comma separated list, e.g. dev,stage,prod (max 3).
role-arnsstring""Newline separated env=arn pairs; preferred over AWS_DEPLOY_ROLE_ARN_*.
working-directorystring.CDK app directory.
install-working-directorystring"" (= working-directory)Where npm ci runs.
build-commandstring""Optional build command.
node-versionstring""Node.js version (empty = .nvmrc).
aws-regionstringrequired
stacksstring--allStack names / wildcards.
extra-contextstring""Newline separated key=value context.
smoke-test-commandstring""Run after each deploy with outputs and DEPLOY_ENV in the environment.
output-prefixstringSTACK_Prefix for exported outputs.
runs-onstringubuntu-latest
cli-versionstring""CLI version for stack outputs (empty = jq / AWS CLI).
cli-commandstring""Explicit CLI command.

Permissions: contents: read, id-token: write.

Limitation. Reusable workflows cannot create jobs dynamically, so the workflow contains a plan job plus three explicit jobs deploy-1 -> deploy-2 -> deploy-3. At most three environments are supported per call; call the workflow twice for longer pipelines. Job N deploys the N-th environment and is skipped when the list is shorter.

account-baseline-deploy.yml ​

Same plan + three sequential jobs pattern for an account baseline app (cdk deploy --all -c env=<env>). Roles come from the role-arns input or the AWS_BASELINE_ROLE_ARN_<ENV> variables.

InputTypeDefault
environmentsstringdev
role-arnsstring""
working-directorystring.
install-working-directorystring"" (= working-directory)
build-commandstring""
node-versionstring""
aws-regionstringrequired
stacksstring--all
runs-onstringubuntu-latest

Permissions: contents: read, id-token: write.

Dogfooding workflows in this repository ​

examples-preview.yml, examples-preview-destroy.yml and examples-deploy.yml call the local reusable workflows for examples/hello-http, passing install-working-directory: ., build-command: npm run build and cli-command: node ../../packages/outcrop-cli/bin/outcrop.js so the freshly built packages and CLI are exercised. They are skipped unless the repository variable SANDBOX_DEPLOY_ROLE_ARN (and AWS_REGION) is set.

How the actions are resolved ​

The reusable workflows use the composite actions in this repository. Because uses: ./... inside a reusable workflow resolves against the caller's checkout, each job checks out the repository and commit the workflow itself runs from (job.workflow_repository @ job.workflow_sha) into .outcrop and references the actions from there. Workflows and actions are therefore always the same version. The checks job removes that directory again before linting so repository-wide linters do not see it.

Caller workflows for a service repository ​

Copy these three files into .github/workflows/ of the service (the scaffolder generates them).

checks.yml ​

yaml
name: Checks

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write
  pull-requests: write

jobs:
  checks:
    uses: rpallas/outcrop/.github/workflows/service-checks.yml@v1
    with:
      diff: true
      aws-region: ${{ vars.AWS_REGION }}
      readonly-role-arn: ${{ vars.AWS_READONLY_ROLE_ARN_DEV }}
    permissions:
      contents: read
      id-token: write
      pull-requests: write

preview.yml ​

yaml
name: Preview

on:
  pull_request:
    types: [opened, synchronize, reopened, closed]

permissions:
  contents: read
  id-token: write
  pull-requests: write

jobs:
  deploy:
    if: github.event.action != 'closed'
    uses: rpallas/outcrop/.github/workflows/service-preview-deploy.yml@v1
    with:
      aws-region: ${{ vars.AWS_REGION }}
      role-arn: ${{ vars.AWS_DEPLOY_ROLE_ARN_DEV }}
      integration-test-command: npm run test:integration
    permissions:
      contents: read
      id-token: write
      pull-requests: write

  destroy:
    if: github.event.action == 'closed'
    uses: rpallas/outcrop/.github/workflows/service-preview-destroy.yml@v1
    with:
      aws-region: ${{ vars.AWS_REGION }}
      role-arn: ${{ vars.AWS_DEPLOY_ROLE_ARN_DEV }}
    permissions:
      contents: read
      id-token: write
      pull-requests: write

deploy.yml ​

yaml
name: Deploy

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    uses: rpallas/outcrop/.github/workflows/service-deploy.yml@v1
    with:
      environments: dev,stage,prod
      aws-region: ${{ vars.AWS_REGION }}
      smoke-test-command: npm run test:smoke --if-present
    permissions:
      contents: read
      id-token: write

vars are resolved in the calling repository, so AWS_DEPLOY_ROLE_ARN_DEV, _STAGE and _PROD must exist there (or be passed through role-arns).

Security notes ​

  • OIDC only: roles must trust token.actions.githubusercontent.com with a sub condition scoped to your repository (and, for production, to the GitHub Environment).
  • Permissions are declared per job and are minimal; pull-requests: write is only granted where a comment is posted.
  • All untrusted values (branch names, PR numbers, inputs) reach shell scripts through env: variables, never by interpolating ${{ }} into run:.
  • Dependencies are installed with npm ci --ignore-scripts.

Released under the MIT License.