From d34c6fc6cb7613401c8f2f2c1c0b1435076b50e7 Mon Sep 17 00:00:00 2001 From: Rodrigo Patote <47055922+patote85@users.noreply.github.com> Date: Sun, 20 Sep 2026 20:50:38 -0300 Subject: [PATCH] infra: wire CDK stack (DynamoDB + Lambda + HttpApi) Single stack in cdk/: create table and Lambda together (no from_function_name), grant_read_write_data, TABLE_NAME/PIX_FEE env, HttpApi throttle 100/200. No Cognito (PR3). README section additive only. Do not merge without CAB. --- .gitignore | 21 ++++++++ README.md | 21 ++++++++ cdk.json | 18 +++++++ cdk/app.py | 111 ++++++++++++++++++++++++++++++++++++++++++- cdk/requirements.txt | 2 + 5 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 cdk.json create mode 100644 cdk/requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1451cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# CDK +cdk.out/ +.cdk.staging/ +*.js +!jest.config.js +*.d.ts + +# Python +__pycache__/ +*.py[cod] +.venv/ +venv/ +.env +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ + +# IDE / OS +.idea/ +.vscode/ +.DS_Store diff --git a/README.md b/README.md index 0411cdf..4a8adaf 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,24 @@ PYTHONPATH=. pytest tests/test_exclusao.py -v Usa **moto** (sem AWS real). O pacote `lambda/` importa via `importlib` (nome reservado). CI (GitHub Actions): mesmo comando em Python 3.12 em todo PR/`push` na `main` — sem credenciais AWS no runner. + +--- + +## CDK / Deploy + +Stack único em `cdk/app.py` (ADR-001 PR2): **DynamoDB + Lambda + HTTP API** no mesmo stack — sem `from_function_name`, sem Cognito (PR3). + +- Tabela: PK `cliente_id` / SK `request_id`, on-demand; `grant_read_write_data` na Lambda. +- Env: `TABLE_NAME` (nome da tabela), `PIX_FEE` (default `50.00`). +- Handler: `app.lambda_handler` (código em `lambda/`). +- Throttle HTTP API: **100 rps / burst 200** (espelha `aws-api-gateway-cdk`). +- Rotas: `POST /solicitar-exclusao-cliente`, `GET /status-exclusao/{cliente_id}`, `POST /confirmar-pagamento`, `GET /health`. + +```bash +python3 -m venv .venv && source .venv/bin/activate +pip install -r cdk/requirements.txt +npx aws-cdk@2 synth # requer Node + AWS CDK CLI +# npx aws-cdk@2 deploy # só com CAB / conta lab +``` + +**Nota:** o asset Lambda é o source em `lambda/` — empacotar Powertools (layer ou bundle) fica em follow-up; este PR só faz o wiring IaC. diff --git a/cdk.json b/cdk.json new file mode 100644 index 0000000..4f0aa62 --- /dev/null +++ b/cdk.json @@ -0,0 +1,18 @@ +{ + "app": "python3 cdk/app.py", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "requirements*.txt", + "source.bat", + "**/__pycache__", + "tests" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true + } +} diff --git a/cdk/app.py b/cdk/app.py index 64ebcee..74a4902 100644 --- a/cdk/app.py +++ b/cdk/app.py @@ -1 +1,110 @@ -# CDK stack example - complete in next step \ No newline at end of file +#!/usr/bin/env python3 +"""CDK app — exclusão Pix (ADR-001 PR2). + +Single stack: DynamoDB table + Lambda + HTTP API. +No Cognito (PR3). No from_function_name. +""" +from aws_cdk import ( + App, + CfnOutput, + Duration, + RemovalPolicy, + Stack, + aws_apigatewayv2 as apigwv2, + aws_apigatewayv2_integrations as apigwv2_integrations, + aws_dynamodb as dynamodb, + aws_lambda as _lambda, +) +from constructs import Construct + + +class ExclusaoClienteStack(Stack): + def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: + super().__init__(scope, construct_id, **kwargs) + + table = dynamodb.Table( + self, + "ExclusaoClientesTable", + partition_key=dynamodb.Attribute( + name="cliente_id", type=dynamodb.AttributeType.STRING + ), + sort_key=dynamodb.Attribute( + name="request_id", type=dynamodb.AttributeType.STRING + ), + billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST, + removal_policy=RemovalPolicy.DESTROY, + ) + + # Handler: lambda/app.py → lambda_handler (CI uses Python 3.12) + # Powertools packaging/layer left for follow-up (asset is source only). + fn = _lambda.Function( + self, + "ExclusaoClienteLambda", + runtime=_lambda.Runtime.PYTHON_3_12, + handler="app.lambda_handler", + code=_lambda.Code.from_asset("lambda"), + timeout=Duration.seconds(30), + memory_size=256, + environment={ + "TABLE_NAME": table.table_name, + "PIX_FEE": "50.00", + }, + ) + + table.grant_read_write_data(fn) + + http_api = apigwv2.HttpApi( + self, + "ExclusaoClienteHttpApi", + description="API Gateway para Exclusão de Cliente com Pix", + cors_preflight=apigwv2.CorsPreflightOptions( + allow_origins=["*"], + allow_methods=[apigwv2.CorsHttpMethod.ANY], + ), + create_default_stage=False, + ) + + # Mirror aws-api-gateway-cdk: rate 100 / burst 200 + apigwv2.HttpStage( + self, + "DefaultStage", + http_api=http_api, + stage_name="$default", + auto_deploy=True, + throttle=apigwv2.ThrottleSettings(rate_limit=100, burst_limit=200), + ) + + integration = apigwv2_integrations.HttpLambdaIntegration( + "LambdaIntegration", fn + ) + + http_api.add_routes( + path="/solicitar-exclusao-cliente", + methods=[apigwv2.HttpMethod.POST], + integration=integration, + ) + http_api.add_routes( + path="/status-exclusao/{cliente_id}", + methods=[apigwv2.HttpMethod.GET], + integration=integration, + ) + http_api.add_routes( + path="/confirmar-pagamento", + methods=[apigwv2.HttpMethod.POST], + integration=integration, + ) + http_api.add_routes( + path="/health", + methods=[apigwv2.HttpMethod.GET], + integration=integration, + ) + + CfnOutput(self, "ApiUrl", value=http_api.url or "") + CfnOutput(self, "ApiId", value=http_api.api_id) + CfnOutput(self, "DynamoTableName", value=table.table_name) + CfnOutput(self, "LambdaName", value=fn.function_name) + + +app = App() +ExclusaoClienteStack(app, "ExclusaoClienteStack") +app.synth() diff --git a/cdk/requirements.txt b/cdk/requirements.txt new file mode 100644 index 0000000..1fba38b --- /dev/null +++ b/cdk/requirements.txt @@ -0,0 +1,2 @@ +aws-cdk-lib>=2.170.0,<3 +constructs>=10.0.0,<11