From e175c161e83ea300853a8ad591b52c377bd50c28 Mon Sep 17 00:00:00 2001 From: Amr Abed <3361565+amrabed@users.noreply.github.com> Date: Thu, 23 Jul 2026 12:26:45 -0400 Subject: [PATCH 1/2] feat: add local deployment support for CDK stacks --- .env.local.example | 5 ++ .gitignore | 9 ++- .vibe/specs/deploy-localstack/design.md | 37 ++++++++++ .vibe/specs/deploy-localstack/plan.md | 70 ++++++++++++++++++ .vibe/specs/deploy-localstack/requirements.md | 29 ++++++++ .vibe/specs/deploy-localstack/tasks.md | 11 +++ .vibe/specs/deploy-localstack/walkthrough.md | 46 ++++++++++++ AGENTS.md | 13 +++- cdk.json | 6 +- compose.yml | 16 +++++ docs/README.md | 38 +++++++++- mise.toml | 55 ++++++++++++-- package.json | 12 ++++ pnpm-lock.yaml | 71 +++++++++++++++++++ 14 files changed, 405 insertions(+), 13 deletions(-) create mode 100644 .env.local.example create mode 100644 .vibe/specs/deploy-localstack/design.md create mode 100644 .vibe/specs/deploy-localstack/plan.md create mode 100644 .vibe/specs/deploy-localstack/requirements.md create mode 100644 .vibe/specs/deploy-localstack/tasks.md create mode 100644 .vibe/specs/deploy-localstack/walkthrough.md create mode 100644 compose.yml create mode 100644 package.json create mode 100644 pnpm-lock.yaml diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..0879020 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,5 @@ +LOCALSTACK_AUTH_TOKEN= + +# Local CDK defaults +AWS_ENDPOINT_URL=http://localhost:4566 +AWS_ENDPOINT_URL_S3=http://s3.localhost:4566 diff --git a/.gitignore b/.gitignore index 910939d..19cee17 100644 --- a/.gitignore +++ b/.gitignore @@ -123,6 +123,7 @@ celerybeat.pid # Environments .env +.env.local .venv env/ venv/ @@ -171,4 +172,10 @@ cython_debug/ devcontainer-lock.json # CDK -cdk.out/ \ No newline at end of file +cdk.out/ + +# Localstack +.localstack/ + +# Node.js +node_modules/ diff --git a/.vibe/specs/deploy-localstack/design.md b/.vibe/specs/deploy-localstack/design.md new file mode 100644 index 0000000..e3d060a --- /dev/null +++ b/.vibe/specs/deploy-localstack/design.md @@ -0,0 +1,37 @@ +# Design: Deploy Lambda Stacks to Local via LocalStack + +## System Architecture + +```mermaid +flowchart TD + CLI["mise run local <stack>"] --> CDK["aws-cdk-local (npx)"] + CDK -->|"http://localhost:4566"| LS["LocalStack Container
(DynamoDB, Lambda, ApiGateway, SQS, etc.)"] +``` + +## Component Design + +### 1. `.env.local.example` & `compose.yml` + +- `.env.local.example`: Template configuration file defining `LOCALSTACK_AUTH_TOKEN`, local AWS credentials, and endpoint URLs. +- `compose.yml`: Standard LocalStack Docker Compose service configuration mapping port `4566` (LocalStack Gateway) and volume mounts for persistence, loaded via `--env-file .env.local`. + +### 2. `mise.toml` + +Adds the following task configurations: + +- `tasks."local:up"`: Launches LocalStack via `docker compose --env-file .env.local up -d --wait localstack`. +- `tasks."local:down"`: Stops LocalStack via `docker compose --env-file .env.local down localstack`. +- `tasks."local:deploy"`: Executes `STACK=$usage_stack npx --package=aws-cdk --package=aws-cdk-local cdklocal deploy --require-approval=never`. + - Aliases: `local`, `dl` + - Depends on: `install-cdk`, `local:up` +- `tasks."local:destroy"`: Executes `STACK=$usage_stack npx --package=aws-cdk --package=aws-cdk-local cdklocal destroy --force`. + - Alias: `Dl` + - Depends on: `install-cdk`, `local:up` +- `tasks.docs-local`: Serves MkDocs documentation (`uv run mkdocs serve`). + +### 3. Documentation (`docs/README.md`, `AGENTS.md`) + +- Instruct copying `.env.local.example` to `.env.local` and obtaining auth token from `app.localstack.cloud`. +- Add LocalStack under Prerequisites and Setup sections. +- Update shell aliases and common commands tables. +- Add step-by-step instructions to start LocalStack and deploy stacks locally. diff --git a/.vibe/specs/deploy-localstack/plan.md b/.vibe/specs/deploy-localstack/plan.md new file mode 100644 index 0000000..5b784f9 --- /dev/null +++ b/.vibe/specs/deploy-localstack/plan.md @@ -0,0 +1,70 @@ +# Implementation Plan - Deploy Lambda Stacks to Local using LocalStack + +Enable local deployment and teardown of AWS CDK Lambda stacks using LocalStack and `aws-cdk-local`. + +## User Review Required + +> [!IMPORTANT] +> Developers must copy `.env.local.example` to `.env.local` and set `LOCALSTACK_AUTH_TOKEN` obtained from [app.localstack.cloud](https://app.localstack.cloud). + +## Proposed Changes + +### Configuration & Infrastructure + +#### [NEW] [compose.yml](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/compose.yml) + +- Add standard LocalStack container definition exposing port `4566`. + +#### [NEW] [.env.local.example](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/.env.local.example) + +- Add template environment file with `LOCALSTACK_AUTH_TOKEN`, local AWS credentials, and endpoint URLs. + +#### [MODIFY] [mise.toml](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/mise.toml) + +- Add shell aliases `local = "mise run local:deploy"`, `dl = "mise run local:deploy"`, and `Dl = "mise run local:destroy"`. +- Rename documentation local serve task to `[tasks.docs-local]`. +- Add `[tasks."local:up"]` to start LocalStack via `docker compose --env-file .env.local up -d --wait localstack`. +- Add `[tasks."local:down"]` to stop LocalStack via `docker compose --env-file .env.local down localstack`. +- Add `[tasks."local:deploy"]` (aliases `local`, `dl`) to deploy stacks locally using `aws-cdk-local`. +- Add `[tasks."local:destroy"]` (alias `Dl`) to destroy stacks locally using `aws-cdk-local`. + +--- + +### Documentation + +#### [MODIFY] [docs/README.md](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/docs/README.md) + +- Update Prerequisites section to include LocalStack / Docker. +- Add step to copy `.env.local.example` to `.env.local` and obtain token from `app.localstack.cloud`. +- Add "Deploy stack to local (LocalStack)" and "Destroy local stack" sub-sections under Infrastructure deployment options. +- Update project structure tree diagram to include `compose.yml` and `.vibe/specs`. + +#### [MODIFY] [AGENTS.md](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/AGENTS.md) + +- Add instructions to setup `.env.local` with token from `app.localstack.cloud`. +- Add `mise run local:deploy`, `mise run local:destroy`, `mise run local:up`, and `mise run local:down` to Common Commands list. + +--- + +### Specifications + +#### [NEW] [.vibe/specs/deploy-localstack/requirements.md](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/.vibe/specs/deploy-localstack/requirements.md) + +#### [NEW] [.vibe/specs/deploy-localstack/design.md](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/.vibe/specs/deploy-localstack/design.md) + +#### [NEW] [.vibe/specs/deploy-localstack/plan.md](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/.vibe/specs/deploy-localstack/plan.md) + +#### [NEW] [.vibe/specs/deploy-localstack/tasks.md](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/.vibe/specs/deploy-localstack/tasks.md) + +#### [NEW] [.vibe/specs/deploy-localstack/walkthrough.md](file:///Users/amrabed/Library/CloudStorage/OneDrive-Personal/code/aws-lambda-templates/.vibe/specs/deploy-localstack/walkthrough.md) + +## Verification Plan + +### Automated Tests + +- Run `mise run lint` (ruff format and check) to confirm code quality and formatting. +- Run `mise run test` to verify pytest suite execution. + +### Manual Verification + +- Test synthesizing / validating localstack CDK command syntax: `STACK=api uv run npx --package=aws-cdk --package=aws-cdk-local cdklocal synth`. diff --git a/.vibe/specs/deploy-localstack/requirements.md b/.vibe/specs/deploy-localstack/requirements.md new file mode 100644 index 0000000..3c294b5 --- /dev/null +++ b/.vibe/specs/deploy-localstack/requirements.md @@ -0,0 +1,29 @@ +# Requirements: Deploy Lambda Stacks to Local via LocalStack + +## Overview + +Enable developers to deploy and tear down AWS CDK Lambda stacks locally using LocalStack without requiring an active AWS cloud account or active AWS credentials. + +## Functional Requirements + +1. **Environment & Auth Token Setup**: + - Provide `.env.local.example` with defaults for LocalStack and local CDK deployment. + - Instruct developers to copy `.env.local.example` to `.env.local` and obtain an auth token from [app.localstack.cloud](https://app.localstack.cloud) to populate `LOCALSTACK_AUTH_TOKEN`. +2. **Local Stack Deployment**: + - Provide a `local:deploy` task in `mise.toml` that deploys any CDK stack defined under `infra/stacks/` to a local LocalStack instance. + - Command syntax: `mise run local:deploy ` (alias `local ` or `dl `). +3. **Local Stack Destruction**: + - Provide a `local:destroy` task in `mise.toml` that destroys a deployed stack from LocalStack. + - Command syntax: `mise run local:destroy ` (alias `Dl `). +4. **LocalStack Container Management**: + - Provide a standard `compose.yml` file configuring LocalStack. + - Provide `local:up` (`mise run local:up`) to start LocalStack with `.env.local`. + - Provide `local:down` (`mise run local:down`) to stop LocalStack. +5. **Documentation Updates**: + - Update `docs/README.md` to document LocalStack setup, `.env.local` configuration, token retrieval from app.localstack.cloud, prerequisites, and usage of `local:deploy`, `local:destroy`, `local:up`, and `local:down`. + - Update `AGENTS.md` to include local deployment environment setup, commands, aliases (`local`, `dl`, `Dl`), and instructions for AI agents. + +## Non-Functional Requirements + +- Maintain backward compatibility with existing cloud CDK deployment tasks (`mise run deploy` and `mise run destroy`). +- Pass all project linting (`mise run lint`) and tests (`mise run test`). diff --git a/.vibe/specs/deploy-localstack/tasks.md b/.vibe/specs/deploy-localstack/tasks.md new file mode 100644 index 0000000..f5bca0b --- /dev/null +++ b/.vibe/specs/deploy-localstack/tasks.md @@ -0,0 +1,11 @@ +# Tasks: Deploy Lambda Stacks to Local via LocalStack + +- [x] Create `compose.yml` for LocalStack container management +- [x] Create `.env.local.example` template environment file +- [x] Add `tasks."local:up"`, `tasks."local:down"`, `tasks."local:deploy"`, and `tasks."local:destroy"` to `mise.toml` +- [x] Add `local`, `dl`, and `Dl` shell aliases to `mise.toml` +- [x] Update `docs/README.md` with `.env.local` setup, app.localstack.cloud token, and deployment instructions +- [x] Update `AGENTS.md` with `.env.local` token guidance, `mise` tasks, and local CDK instructions +- [x] Run `mise run lint` to verify code quality +- [x] Run `mise run test` to verify test suite passes +- [x] Create `walkthrough.md` with complete verification details diff --git a/.vibe/specs/deploy-localstack/walkthrough.md b/.vibe/specs/deploy-localstack/walkthrough.md new file mode 100644 index 0000000..19a002c --- /dev/null +++ b/.vibe/specs/deploy-localstack/walkthrough.md @@ -0,0 +1,46 @@ +# Walkthrough: Deploy Lambda Stacks to Local using LocalStack + +We have added support for deploying and tearing down AWS CDK Lambda stacks locally using LocalStack. + +## Changes Made + +### Configuration & Infrastructure + +- Created `compose.yml` configured to spin up LocalStack (gateway on port 4566). +- Created `.env.local.example` with defaults for local execution and LocalStack auth token configuration. +- Updated `mise.toml`: + - Added `[tasks."local:up"]` task to start LocalStack container with `.env.local`. + - Added `[tasks."local:down"]` task to stop LocalStack container with `.env.local`. + - Added `[tasks."local:deploy"]` task with aliases `local` and `dl` to deploy CDK stacks locally via `aws-cdk-local` (depends on `install-cdk`, `local:up`). + - Added `[tasks."local:destroy"]` task with alias `Dl` to destroy stacks from LocalStack via `aws-cdk-local` (depends on `install-cdk`, `local:up`). + - Renamed documentation local preview task to `[tasks.docs-local]`. + +### Documentation + +- Updated `docs/README.md`: + - Added step to copy `.env.local.example` to `.env.local` and set `LOCALSTACK_AUTH_TOKEN` from [app.localstack.cloud](https://app.localstack.cloud). + - Added Docker under Local environment prerequisites. + - Added "Deploy a stack locally (LocalStack)" and "Destroy a local stack" sections with `local:up`, `local:deploy`, `local:destroy`, and `local:down`. + - Updated Project Structure tree diagram with `compose.yml`. +- Updated `AGENTS.md`: + - Added `.env.local` token guidance, LocalStack & local CDK deployment commands (`mise run local:up`, `mise run local:deploy`, `mise run local:destroy`) and aliases. + +### Specifications + +- Updated `.vibe/specs/deploy-localstack/`: + - `requirements.md` + - `design.md` + - `plan.md` + - `tasks.md` + - `walkthrough.md` + +## Verification Results + +### Automated Verification + +1. `mise run lint`: Format & lint validations passed. +2. `mise run test`: All 71 unit tests passed with 93% coverage. + +### Manual Verification + +- Verified `.env.local.example` content and task execution configuration. diff --git a/AGENTS.md b/AGENTS.md index 3e244ca..0294bb2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,11 @@ mise run precommit # install pre-commit hooks mise run lint # run ruff (check + format) and pyright mise run test # run pytest with coverage mise run docs # build and deploy docs to GitHub Pages -mise run local # serve docs locally +mise run docs-local # serve docs locally +mise run local:up # start LocalStack via Docker Compose +mise run local:deploy # deploy CDK stack to LocalStack (alias: local, dl) +mise run local:destroy # destroy CDK stack from LocalStack (alias: Dl) +mise run local:down # stop LocalStack via Docker Compose ``` ## Code Style @@ -48,6 +52,7 @@ templates/ # main package tests/ # pytest tests docs/ # MkDocs documentation infra/ # AWS CDK infrastructure stacks +compose.yml # LocalStack container configuration ``` ## Renaming the Template @@ -67,7 +72,11 @@ mise run rename --name="my-project" --description="My description" --author="Nam ## Infrastructure -- Define and deploy infrastructure using AWS CDK under the `infra/` folder +- Define infrastructure using AWS CDK under the `infra/` folder. +- Deploy to AWS: `mise run deploy ` (alias `d `). +- Deploy locally using LocalStack: copy `.env.local.example` to `.env.local` and set token from [app.localstack.cloud](https://app.localstack.cloud), then `mise run local:deploy ` (alias `local `, `dl `). +- Destroy locally from LocalStack: `mise run local:destroy ` (alias `Dl `). + ## Testing Guidelines diff --git a/cdk.json b/cdk.json index 8e703da..2bc0551 100644 --- a/cdk.json +++ b/cdk.json @@ -1,4 +1,4 @@ { - "app": "python infra/app.py", - "requireApproval": "never" -} \ No newline at end of file + "app": "python infra/app.py", + "requireApproval": "never" +} diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..57d87c9 --- /dev/null +++ b/compose.yml @@ -0,0 +1,16 @@ +services: + localstack: + container_name: localstack + image: localstack/localstack:latest + env_file: .env.local + ports: + - "4566:4566" + volumes: + - "${LOCALSTACK_VOLUME_DIR:-./.localstack}:/var/lib/localstack" + - "/var/run/docker.sock:/var/run/docker.sock" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 10s diff --git a/docs/README.md b/docs/README.md index ef67518..cf0e28c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,6 +31,7 @@ Templates come pre-wired with: - **Clean Architecture**: Separation of concerns using the Repository pattern for data access - **Data Modeling**: Strong typing and validation using [Pydantic](https://docs.pydantic.dev) - **Infrastructure as Code**: [AWS CDK](https://aws.amazon.com/cdk) stacks +- **Local AWS Deployment**: [LocalStack](https://localstack.cloud) integration for local CDK deployment and testing without an AWS account - **Testing**: Comprehensive [pytest](https://pytest.org) suite with [moto](http://docs.getmoto.org) for AWS mocking and [hypothesis](https://hypothesis.readthedocs.io) for property-based testing - **Code Quality**: [ruff](https://docs.astral.sh/ruff) for linting and formatting, [pyright](https://microsoft.github.io/pyright) for type checking, and test coverage using [coverage](https://coverage.readthedocs.io) - **Dependency Control**: [uv](https://docs.astral.sh/uv/) for dependency management and [Dependabot](https://docs.github.com/en/code-security/dependabot) for automated dependency updates @@ -83,6 +84,7 @@ Parameter | Description ### Local environment - [mise](https://mise.jdx.dev/) +- Docker (for running LocalStack locally) ## Setup @@ -121,8 +123,8 @@ This runs the `new` script, which builds a skeleton for the new template from `. ### Deploy a stack -Infrastructure is defined as AWS CDK stacks under `infra/stacks/`. -The CDK entry point is `infra/app.py`. +Infrastructure is defined as AWS CDK stacks under `infra/stacks/`. +The CDK entry point is `infra/app.py`. ```bash mise run deploy # alias: d @@ -138,6 +140,34 @@ mise run deploy --profile mise run destroy # alias: D ``` +### Deploy a stack locally (LocalStack) +To deploy CDK stacks locally without an AWS account: + +1. Copy `.env.local.example` to `.env.local` and set your auth token from [app.localstack.cloud](https://app.localstack.cloud): +```bash +cp .env.local.example .env.local +``` + +2. Start LocalStack via Docker Compose: +```bash +mise run local:up +``` + +3. Deploy the stack to LocalStack: +```bash +mise run local:deploy # aliases: local , dl +``` + +### Destroy a local stack +```bash +mise run local:destroy # alias: Dl +``` + +To stop the LocalStack container when finished: +```bash +mise run local:down +``` + ### Generating documentation To build and publish the project documentation to GitHub Pages, run: @@ -150,7 +180,7 @@ That pushes the new documentation to the `gh-pages` branch. Make sure GitHub Pag ### Local preview To serve the documentation on a local server, run: ```bash -mise run local +mise run docs-local ``` ## Coding Conventions @@ -182,7 +212,9 @@ mise run local │ └── docs.yml # Workflow to publish documentation ├── .gitignore # Git-ignored file list ├── .pre-commit-config.yaml # Pre-commit configuration file +├── .vibe # Feature specification ├── .vscode # VS Code folder +├── compose.yml # Docker Compose configuration for LocalStack ├── mise.toml # mise configuration and tasks ├── pyproject.toml # Configuration file for different tools ├── mkdocs.yml # MkDocs configuration file diff --git a/mise.toml b/mise.toml index 19ba62e..bb8c1ae 100644 --- a/mise.toml +++ b/mise.toml @@ -20,6 +20,7 @@ l = "mise run lint" t = "mise run test" d = "mise run deploy" D = "mise run destroy" +v = "mise run verify" c = "mise run clean" m = "mise run" @@ -30,6 +31,9 @@ gs = "git status" ga = "git add" gc = "git commit" gp = "git push" +gco = "git checkout" +gb = "git branch" +g = "git" ##### Scripts ##### @@ -53,7 +57,7 @@ flag "--github " help="GitHub username" run = "uv run rename" ##### Dev setup and maintenance ##### - + [tasks.install] description = "Install dependencies and project" run = "uv sync" @@ -91,9 +95,14 @@ uv run coverage xml """ alias = "t" +[tasks.verify] +description = "Run lint and test" +depends = ["lint", "test"] +alias = "v" + [tasks.clean] description = "Clean up build artifacts and temporary files" -run = """rm -rf cdk.out .coverage .pytest_cache .ruff_cache .hypotheses""" +run = """rm -rf cdk.out .coverage coverage.xml .pytest_cache .ruff_cache .hypothesis .localstack node_modules""" alias = "c" ##### Docs ##### @@ -102,13 +111,14 @@ alias = "c" description = "Install documentation dependencies" run = "uv sync --group docs" hide = true +tools = { node = "latest" } [tasks.docs] description = "Build and deploy documentation to GitHub pages" run = "uv run mkdocs gh-deploy --force" depends = ["install-docs"] -[tasks.local] +[tasks.docs-local] description = "Serve documentation on a local server" run = "uv run mkdocs serve" @@ -118,7 +128,6 @@ run = "uv run mkdocs serve" description = "Install CDK dependencies" run = "uv sync --group infra" hide = true -tools = { node = "latest" } [tasks.deploy] description = "Deploy a CDK stack" @@ -139,3 +148,41 @@ flag "-p --profile " help="AWS profile to use" run = "STACK=$usage_stack npx aws-cdk destroy --force ${usage_profile:+--profile $usage_profile}" alias = "D" depends = ["install-cdk"] + +[tasks."local:setup"] +description = "Install CDK dependencies in local environment" +run = "pnpm i" +hide = true +tools = { node = "latest" } +depends = ["install-cdk"] + +[tasks."local:deploy"] +description = "Deploy a CDK stack to LocalStack" +usage = """ +arg "" env=STACK help="CDK stack to deploy" +""" +run = "STACK=$usage_stack pnpm run deploy" +alias = ["local"] +depends = ["local:setup", "local:up"] + +[tasks."local:destroy"] +description = "Destroy a deployed CDK stack from LocalStack" +usage = """ +arg "" env=STACK help="CDK stack to destroy" +""" +run = "STACK=$usage_stack pnpm run destroy" +depends = ["local:setup"] + +[tasks."local:up"] +description = "Start LocalStack via docker compose" +run = "docker compose up -d --wait localstack" + +[tasks."local:down"] +description = "Stop LocalStack via docker compose" +run = "docker compose down localstack" + +[tasks."local:deploy".env] +_.file = ".env.local" + +[tasks."local:destroy".env] +_.file = ".env.local" diff --git a/package.json b/package.json new file mode 100644 index 0000000..113ce07 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "scripts": { + "format": "prettier --write .", + "deploy": "cdklocal bootstrap && cdklocal deploy", + "destroy": "cdklocal destroy --force" + }, + "dependencies": { + "aws-cdk": "^2.1133.0", + "aws-cdk-local": "^3.0.4", + "prettier": "^3.9.6" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..3d392db --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,71 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + aws-cdk: + specifier: ^2.1133.0 + version: 2.1133.0 + aws-cdk-local: + specifier: ^3.0.4 + version: 3.0.4 + prettier: + specifier: ^3.9.6 + version: 3.9.6 + +packages: + aws-cdk-local@3.0.4: + resolution: + { + integrity: sha512-u8c62rfZNGqB9oW+RSW8YAqLLFAcgc5y2aQrgqQTBUVKLTN6r54g/IUbTq/dM1fqvT1GqtylzQjQ8ytACMSGTQ==, + } + hasBin: true + + aws-cdk@2.1133.0: + resolution: + { + integrity: sha512-DGlCBwqxSHTe1u/IoT5WV+Bbd2K3UhwFHsdMqb2nQa1fkJmaQgoNFu0It+p3HxyMWeW+gKsVzT5KcjQPQ6Kzuw==, + } + engines: { node: ">= 18.0.0" } + hasBin: true + + diff@5.2.2: + resolution: + { + integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==, + } + engines: { node: ">=0.3.1" } + + prettier@3.9.6: + resolution: + { + integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==, + } + engines: { node: ">=14" } + hasBin: true + + semver@7.8.5: + resolution: + { + integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==, + } + engines: { node: ">=10" } + hasBin: true + +snapshots: + aws-cdk-local@3.0.4: + dependencies: + diff: 5.2.2 + semver: 7.8.5 + + aws-cdk@2.1133.0: {} + + diff@5.2.2: {} + + prettier@3.9.6: {} + + semver@7.8.5: {} From 77f60a0547784d4f261f4e24fe6980d0d3f7b61c Mon Sep 17 00:00:00 2001 From: Amr Abed <3361565+amrabed@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:37:35 -0400 Subject: [PATCH 2/2] feat: add infrastructure synthesis workflow --- .github/workflows/{check.yml => verify.yml} | 10 +++---- AGENTS.md | 2 ++ docs/README.md | 8 +++++- infra/app.py | 16 +++++++----- infra/stacks/graphql.py | 9 ++++--- mise.toml | 29 ++++++++++++++------- 6 files changed, 47 insertions(+), 27 deletions(-) rename .github/workflows/{check.yml => verify.yml} (60%) diff --git a/.github/workflows/check.yml b/.github/workflows/verify.yml similarity index 60% rename from .github/workflows/check.yml rename to .github/workflows/verify.yml index a7030c9..6d636e9 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/verify.yml @@ -1,19 +1,17 @@ -name: Format, Lint, and Test +name: Verify code and infra on: workflow_call: push: jobs: - check: + verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Install mise uses: jdx/mise-action@v4 - - name: Format and lint - run: mise run lint - - name: Run tests - run: mise run test + - name: Verify code and infra + run: mise run verify env: AWS_DEFAULT_REGION: us-east-1 diff --git a/AGENTS.md b/AGENTS.md index 0294bb2..5e41b6d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,7 @@ mise run precommit # install pre-commit hooks ```bash mise run lint # run ruff (check + format) and pyright mise run test # run pytest with coverage +mise run infra:synth # synthesize CDK stack(s) (alias: synth, s) mise run docs # build and deploy docs to GitHub Pages mise run docs-local # serve docs locally mise run local:up # start LocalStack via Docker Compose @@ -73,6 +74,7 @@ mise run rename --name="my-project" --description="My description" --author="Nam ## Infrastructure - Define infrastructure using AWS CDK under the `infra/` folder. +- Synthesize CloudFormation templates: `mise run infra:synth [stack]` (alias `synth [stack]`, `s [stack]`). - Deploy to AWS: `mise run deploy ` (alias `d `). - Deploy locally using LocalStack: copy `.env.local.example` to `.env.local` and set token from [app.localstack.cloud](https://app.localstack.cloud), then `mise run local:deploy ` (alias `local `, `dl `). - Destroy locally from LocalStack: `mise run local:destroy ` (alias `Dl `). diff --git a/docs/README.md b/docs/README.md index cf0e28c..a39669e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -122,10 +122,16 @@ mise run new # alias: n This runs the `new` script, which builds a skeleton for the new template from `.template`. -### Deploy a stack +### Synthesize infrastructure Infrastructure is defined as AWS CDK stacks under `infra/stacks/`. The CDK entry point is `infra/app.py`. +```bash +mise run infra:synth [stack] # aliases: synth [stack], s [stack] +``` + +### Deploy a stack + ```bash mise run deploy # alias: d ``` diff --git a/infra/app.py b/infra/app.py index f166f46..0eb5ba8 100644 --- a/infra/app.py +++ b/infra/app.py @@ -23,13 +23,15 @@ stack_name = os.environ.get("STACK") -if not stack_name: - sys.exit("Error: STACK environment variable is not set. Valid values: " + ", ".join(STACK_REGISTRY)) +app = App() -if stack_name not in STACK_REGISTRY: - sys.exit(f"Error: unknown stack '{stack_name}'. Valid values: " + ", ".join(STACK_REGISTRY)) +if not stack_name or stack_name == "all": + for stack_class in STACK_REGISTRY.values(): + stack_class(app, stack_class.__name__) +elif stack_name in STACK_REGISTRY: + stack_class = STACK_REGISTRY[stack_name] + stack_class(app, stack_class.__name__) +else: + sys.exit(f"Error: unknown stack '{stack_name}'. Valid values: all, " + ", ".join(STACK_REGISTRY)) -stack_class = STACK_REGISTRY[stack_name] -app = App() -stack_class(app, stack_class.__name__) app.synth() diff --git a/infra/stacks/graphql.py b/infra/stacks/graphql.py index b842ef4..fa818b1 100644 --- a/infra/stacks/graphql.py +++ b/infra/stacks/graphql.py @@ -1,7 +1,8 @@ -from aws_cdk import Expiration, Stack +from aws_cdk import Duration, Expiration, Stack from aws_cdk.aws_appsync import ( ApiKeyConfig, AuthorizationConfig, + AuthorizationMode, AuthorizationType, Definition, GraphqlApi, @@ -46,8 +47,10 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: name="AppSyncDynamodbApi", definition=Definition.from_schema(SchemaFile.from_asset("templates/graphql/schema.graphql")), authorization_config=AuthorizationConfig( - default_authorization=AuthorizationType.API_KEY, - api_key_config=ApiKeyConfig(expires=Expiration.after_days(365)), + default_authorization=AuthorizationMode( + authorization_type=AuthorizationType.API_KEY, + api_key_config=ApiKeyConfig(expires=Expiration.after(Duration.days(365))), + ), ), ) diff --git a/mise.toml b/mise.toml index bb8c1ae..8e5d24b 100644 --- a/mise.toml +++ b/mise.toml @@ -96,8 +96,8 @@ uv run coverage xml alias = "t" [tasks.verify] -description = "Run lint and test" -depends = ["lint", "test"] +description = "Run lint, test, and infra synth" +depends = ["lint", "test", "infra:synth"] alias = "v" [tasks.clean] @@ -124,37 +124,46 @@ run = "uv run mkdocs serve" ##### Infra ##### -[tasks.install-cdk] +[tasks."infra:setup"] description = "Install CDK dependencies" run = "uv sync --group infra" hide = true -[tasks.deploy] +[tasks."infra:synth"] +description = "Synthesize CDK stack(s)" +usage = """ +arg "[stack]" help="CDK stack to synthesize" +""" +run = "STACK=${usage_stack:-all} npx aws-cdk synth" +alias = ["synth", "s"] +depends = ["infra:setup"] + +[tasks."infra:deploy"] description = "Deploy a CDK stack" usage = """ arg "" help="CDK stack to deploy" flag "-p --profile " help="AWS profile to use" """ run = "STACK=$usage_stack npx aws-cdk deploy ${usage_profile:+--profile $usage_profile}" -alias = "d" -depends = ["install-cdk"] +alias = ["d", "deploy"] +depends = ["infra:synth"] -[tasks.destroy] +[tasks."infra:destroy"] description = "Destroy a deployed CDK stack" usage = """ arg "" help="CDK stack to destroy" flag "-p --profile " help="AWS profile to use" """ run = "STACK=$usage_stack npx aws-cdk destroy --force ${usage_profile:+--profile $usage_profile}" -alias = "D" -depends = ["install-cdk"] +alias = ["destroy", "D"] +depends = ["infra:setup"] [tasks."local:setup"] description = "Install CDK dependencies in local environment" run = "pnpm i" hide = true tools = { node = "latest" } -depends = ["install-cdk"] +depends = ["infra:setup"] [tasks."local:deploy"] description = "Deploy a CDK stack to LocalStack"