Build and deployment

Branches and stacks

Branch Stack Account

develop_new_rules

eventlogger-rules-dev

staging, 605668041275

new-rules-P312

eventlogger-rules-prod

production, 625663093496

A push to either branch deploys its stack. Pull requests to them run CI only. Changes to new-rules-P312 go through reviewed pull requests.

Build

sam build uses BuildMethod: makefile: for each function it calls the Makefile target build-<LogicalId> with ARTIFACTS_DIR set. build-ApiFunction and build-WorkerFunction run the same recipe:

  1. Export the runtime dependencies from uv.lock (uv export --frozen --no-dev).

  2. Install them into the artifact for x86_64-manylinux2014 and Python 3.12 (uv pip install --python-platform …​ --target), so a build on macOS still produces Linux wheels.

  3. Fail if a macOS binary (darwin.so, *.dylib) ended up in the artifact.

  4. Copy src/ into the artifact, so handlers are handlers.<module>.lambda_handler.

samconfig.toml builds in source (build_in_source = true), cached and in parallel. Each artifact is about 51 MB unzipped.

Deploy

make deploy ENV=dev|prod runs sam build and sam deploy --config-env <env>. The same command runs in CI and, for emergencies, from a laptop:

make deploy ENV=dev AWS_PROFILE=dev

GitHub Actions

flowchart LR
  PR["Pull request to develop_new_rules or new-rules-P312"] --> CI
  PUSHDEV["Push to develop_new_rules"] --> DEPLOY["deploy.yml"]
  PUSHPROD["Merge to new-rules-P312"] --> DEPLOY
  DEPLOY --> CI["ci.yml: lock check, lint, typecheck, check-names, test-cov, sam-validate"]
  CI -->|develop_new_rules| DEV["_deploy.yml env=dev"]
  CI -->|new-rules-P312| PROD["_deploy.yml env=prod"]
  DEV --> DEVSTACK[("eventlogger-rules-dev")]
  PROD --> PRODSTACK[("eventlogger-rules-prod")]
File Role

.github/workflows/ci.yml

Runs on pull requests, on demand and as a reusable workflow: uv lock --check, make install, lint, typecheck, check-names, test-cov (uploads coverage.xml), sam-validate

.github/workflows/deploy.yml

Runs on pushes to the two branches and on demand: calls CI, then _deploy.yml for the branch’s environment

.github/workflows/_deploy.yml

Reusable deploy job: checks the AWS account ID, runs make deploy ENV=<env>, writes the stack outputs to the job summary

.github/actions/setup

Shared setup: Python from .python-version, a pinned uv with its cache, and git access to the private dependencies through PYB_GITHUB_TOKEN

Deploy jobs run in the GitHub environment of the same name. concurrency: deploy-<env> allows one deploy per environment at a time and never cancels a running one. All actions are pinned by commit SHA and workflows only get contents: read.

Secrets

Secret Use

PYB_GITHUB_TOKEN

Read access to pybFlaskUtils, EventLoggerPySDK, MessagingEmail

DEV_AWS_ACCESS_KEY_ID, DEV_AWS_SECRET_ACCESS_KEY

Deploys to 605668041275

PROD_AWS_ACCESS_KEY_ID, PROD_AWS_SECRET_ACCESS_KEY

Deploys to 625663093496

These are organisation secrets. The deploy keys need to create and update CloudFormation stacks, IAM roles (CAPABILITY_IAM), Lambda functions and event source mappings, API Gateway, EventBridge rules, SNS subscriptions and the SAM managed S3 bucket. They also need lambda:AddPermission and lambda:RemovePermission on pyb-api-gateway-authorizer:live, because the stack grants RulesApi the right to invoke that shared authorizer.

Follow-up: OIDC deploy roles

The deploy job still uses long-lived organisation access keys. Replace them with short-lived credentials from GitHub’s OIDC provider:

  1. In each account, create the IAM OIDC provider token.actions.githubusercontent.com with audience sts.amazonaws.com, if it doesn’t exist yet.

  2. Create a deploy role per account, for example github-eventlogger-rules-deploy, that trusts only this repository’s environment. Because the deploy job declares environment:, the token’s sub claim is environment-based:

    {
      "Effect": "Allow",
      "Principal": {"Federated": "arn:aws:iam::625663093496:oidc-provider/token.actions.githubusercontent.com"},
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:Payability/EventLoggerApp:environment:prod"
        }
      }
    }

    Use environment:dev and the 605668041275 provider for the dev role. Limit its permissions to the eventlogger-rules-* stacks, their roles and functions, RulesApi and the SAM managed bucket.

  3. In _deploy.yml, add permissions: id-token: write to the deploy job and replace the access key inputs of configure-aws-credentials with role-to-assume: arn:aws:iam::<account>:role/github-eventlogger-rules-deploy.

  4. Remove the AWS_* secrets from deploy.yml and _deploy.yml.

Legacy CodeBuild project

The production account still has the CodeBuild project EventLoggerApp from the Zappa era. Its webhook builds every push with the pushed commit’s buildspec.yml, which the new branches don’t have. Exclude them before the first push: see the cutover runbook.