mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-09-30 07:13:13 +00:00
feat(infra): add HCP Terraform for procurement-ingest import-in-place
This commit is contained in:
parent
5b3e20bd35
commit
0ba3600177
33 changed files with 5026 additions and 1 deletions
32
.github/workflows/ci-terraform.yaml
vendored
Normal file
32
.github/workflows/ci-terraform.yaml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
name: Terraform CI
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "terraform/**"
|
||||||
|
- ".github/workflows/ci-terraform.yaml"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
terraform:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: terraform
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
|
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
|
||||||
|
with:
|
||||||
|
terraform_version: "1.9.8"
|
||||||
|
|
||||||
|
- name: Terraform fmt
|
||||||
|
run: terraform fmt -check -recursive
|
||||||
|
|
||||||
|
- name: Terraform init
|
||||||
|
run: terraform init -backend=false
|
||||||
|
|
||||||
|
- name: Terraform validate
|
||||||
|
run: terraform validate
|
||||||
|
|
@ -335,9 +335,12 @@ The canonical map of Sea Haven's AWS infrastructure lives in Confluence. This pr
|
||||||
|
|
||||||
## CI/CD
|
## CI/CD
|
||||||
|
|
||||||
|
> **PLAT-86 (in progress):** CDK CD is soft-frozen (`deploy.yaml` disabled on GitHub) while ownership moves to HCP Terraform workspace `procurement-ingest-prod`. Terraform lives under `terraform/`; import map under `docs/plat-86/`. HCP becomes the sole mutate path after import + green Manual apply; then `deploy.yaml` / `cd-cdk.yaml` are retired. Post-apply smoke still targets `po-email-processor`, `workorder-email-processor`, and `procurement-api` via `scripts/post-deploy-smoke.sh`.
|
||||||
|
|
||||||
GitHub Actions with reusable workflows from `Sea-Haven-Industries/.github` (all pinned to a commit SHA of `main`):
|
GitHub Actions with reusable workflows from `Sea-Haven-Industries/.github` (all pinned to a commit SHA of `main`):
|
||||||
- **CI** (`ci.yaml`, PR to `main`): linting + `cdk synth` via `ci-python-sam.yaml`. `cdk synth`'s Docker-bundled asset build for `po-email-processor` and `workorder-email-processor` mounts the widened `../lambdas` asset root (Phase 2, see [Deploy-Pipeline Guards](#deploy-pipeline-guards-phase-0)) as build context, not just each function's own subdirectory — the `exclude` list on both `from_asset` calls strips local-only `__pycache__`/`package/` (and `tests/`) cruft from that wider mount's source fingerprint, so CI's asset hash matches a clean local checkout, and each function's scoped `cp` glob copies only its own pipeline's `*.py` into the zip. (The exclude does not, and under `SOURCE` hashing cannot, keep the *other* pipeline's tracked source out of the fingerprint (see the PO bundling note above on `SOURCE` hashing) — but that source is identical in CI and local, so it does not cause hash divergence.)
|
- **CI** (`ci.yaml`, PR to `main`): linting + `cdk synth` via `ci-python-sam.yaml`. `cdk synth`'s Docker-bundled asset build for `po-email-processor` and `workorder-email-processor` mounts the widened `../lambdas` asset root (Phase 2, see [Deploy-Pipeline Guards](#deploy-pipeline-guards-phase-0)) as build context, not just each function's own subdirectory — the `exclude` list on both `from_asset` calls strips local-only `__pycache__`/`package/` (and `tests/`) cruft from that wider mount's source fingerprint, so CI's asset hash matches a clean local checkout, and each function's scoped `cp` glob copies only its own pipeline's `*.py` into the zip. (The exclude does not, and under `SOURCE` hashing cannot, keep the *other* pipeline's tracked source out of the fingerprint (see the PO bundling note above on `SOURCE` hashing) — but that source is identical in CI and local, so it does not cause hash divergence.)
|
||||||
- **CD** (`deploy.yaml`, push to `main`): CDK deploy via `cd-cdk.yaml` (OIDC auth), followed by the synchronous `post-deploy-script: scripts/post-deploy-smoke.sh` healthcheck gate (see [Deploy-Pipeline Guards](#deploy-pipeline-guards-phase-0)) — `cd-cdk.yaml`'s `stack-name` input only accepts one stack, so the smoke script itself enumerates `po-email-processor`, `workorder-email-processor`, and `procurement-api`
|
- **Terraform CI** (`ci-terraform.yaml`, PR to `main` when `terraform/**` changes): `fmt -check`, `init -backend=false`, `validate`.
|
||||||
|
- **CD** (`deploy.yaml`, push to `main`): CDK deploy via `cd-cdk.yaml` (OIDC auth), followed by the synchronous `post-deploy-script: scripts/post-deploy-smoke.sh` healthcheck gate (see [Deploy-Pipeline Guards](#deploy-pipeline-guards-phase-0)) — `cd-cdk.yaml`'s `stack-name` input only accepts one stack, so the smoke script itself enumerates `po-email-processor`, `workorder-email-processor`, and `procurement-api`. **Soft-frozen for PLAT-86** (workflow disabled until HCP cutover seals).
|
||||||
- Plus dependency review and PR labeler workflows on every PR
|
- Plus dependency review and PR labeler workflows on every PR
|
||||||
|
|
||||||
Branch protection on `main` — all changes through PR.
|
Branch protection on `main` — all changes through PR.
|
||||||
|
|
|
||||||
374
docs/plat-86/WorkorderIngestStack-resources.json
Normal file
374
docs/plat-86/WorkorderIngestStack-resources.json
Normal file
|
|
@ -0,0 +1,374 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"LogicalId": "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-BucketNotificationsHandler050-fBtoo7x6Azy3",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-BucketNotificationsHandler050a-7KxpBKmlpFSf",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "CDKMetadata",
|
||||||
|
"Type": "AWS::CDK::Metadata",
|
||||||
|
"PhysicalId": "a85b0320-86dc-11f1-b0cf-0eccc7521e19",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "CommentsTableBBDBF0A8",
|
||||||
|
"Type": "AWS::DynamoDB::Table",
|
||||||
|
"PhysicalId": "WorkOrderComments",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucket843A740F",
|
||||||
|
"Type": "AWS::S3::Bucket",
|
||||||
|
"PhysicalId": "workorder-ingest-emails-011934824531",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucketAllowBucketNotificationsToworkorderingestEmailProcessor5B0B85DABA138779",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-EmailBucketAllowBucketNotificationsToworkorderingestEmailProcessor-VuairMrX6Qir",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucketNotificationsDDD5BEEA",
|
||||||
|
"Type": "Custom::S3BucketNotifications",
|
||||||
|
"PhysicalId": "EmailBucketNotificationsDDD5BEEA",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucketNotificationsHandlerPolicy7D16942E",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "Worko-Email-YLl5HvyOvIg2",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucketPolicyBBC68546",
|
||||||
|
"Type": "AWS::S3::BucketPolicy",
|
||||||
|
"PhysicalId": "workorder-ingest-emails-011934824531",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessor218EC076",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "workorder-email-processor",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorAiFallbackRejectedAlarm71D1F2CE",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-email-processor-ai-fallback-rejected",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorDlqA753DED5",
|
||||||
|
"Type": "AWS::SQS::Queue",
|
||||||
|
"PhysicalId": "https://sqs.us-east-1.amazonaws.com/011934824531/WorkorderIngestStack-EmailProcessorDlqA753DED5-qCTHrsoEucas",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorDlqMessagesAlarmED574757",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-email-processor-dlq-messages",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorDlqPolicyB24007F0",
|
||||||
|
"Type": "AWS::SQS::QueuePolicy",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-EmailProcessorDlqPolicyB24007F0-15VK27UU5Q19",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorDurationAlarmF7530BED",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-email-processor-duration",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorErrorsAlarmC1BF0D18",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-email-processor-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorLogGroup8A79A2C8",
|
||||||
|
"Type": "AWS::Logs::LogGroup",
|
||||||
|
"PhysicalId": "/aws/lambda/workorder-email-processor",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorSenderAuthRejectedAlarm3D9534B7",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-email-processor-sender-auth-rejected",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorSenderAuthRejectedFilterC0DAEDFE",
|
||||||
|
"Type": "AWS::Logs::MetricFilter",
|
||||||
|
"PhysicalId": "EmailProcessorSenderAuthRejectedFilterC0DAEDFE-Wkbow1VNDeDM",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorServiceRole637381CE",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-EmailProcessorServiceRole63738-5l9OZXQLVvJn",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorServiceRoleDefaultPolicy0B15DAB8",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "Worko-Email-8PjqeVLLDLRv",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorTemplateFallbackRateAlarmF9B0866A",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-email-processor-template-fallback-rate",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorThrottlesAlarmE102F9E9",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-email-processor-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ExistingRuleSetWorkorderEmailRuleEA29F845",
|
||||||
|
"Type": "AWS::SES::ReceiptRule",
|
||||||
|
"PhysicalId": "ExistingRuleSetWorkorderEmailRuleEA29F845-PKtaDBvIg61a",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterCD1F4C3E",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "workorder-shoc-emitter",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterDurationAlarm3F755835",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-shoc-emitter-duration",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterDynamoDBEventSourceworkorderingestCommentsTableFD97F35C58A19C1A",
|
||||||
|
"Type": "AWS::Lambda::EventSourceMapping",
|
||||||
|
"PhysicalId": "da37d4a0-3086-4929-9ace-6eca5c20cd07",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterDynamoDBEventSourceworkorderingestWorkOrdersTable004CD4AC38D8BED2",
|
||||||
|
"Type": "AWS::Lambda::EventSourceMapping",
|
||||||
|
"PhysicalId": "cc149f4d-5375-4820-8fb8-c514accd3a85",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterErrorsAlarmCE2B702A",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-shoc-emitter-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterFailuresMessagesAlarmEC9798F9",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-shoc-emitter-failures-messages",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterFailuresQueue11D7C8E0",
|
||||||
|
"Type": "AWS::SQS::Queue",
|
||||||
|
"PhysicalId": "https://sqs.us-east-1.amazonaws.com/011934824531/workorder-shoc-emitter-failures",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterFailuresQueuePolicy0D60571C",
|
||||||
|
"Type": "AWS::SQS::QueuePolicy",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-ShocEmitterFailuresQueuePolicy0D60571C-2AMBNJh0la3L",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterIteratorAgeAlarm90B55822",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-shoc-emitter-iterator-age",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterLogGroup80BDE724",
|
||||||
|
"Type": "AWS::Logs::LogGroup",
|
||||||
|
"PhysicalId": "/aws/lambda/workorder-shoc-emitter",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterRejectedMessagesAlarmAE9AFCDD",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-shoc-emitter-rejected-messages",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterRejectedQueueD754F41E",
|
||||||
|
"Type": "AWS::SQS::Queue",
|
||||||
|
"PhysicalId": "https://sqs.us-east-1.amazonaws.com/011934824531/workorder-shoc-emitter-rejected",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterRejectedQueuePolicy4B088C0D",
|
||||||
|
"Type": "AWS::SQS::QueuePolicy",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-ShocEmitterRejectedQueuePolicy4B088C0D-vzXOuhHaGfNy",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterServiceRoleA0CA0C74",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-ShocEmitterServiceRoleA0CA0C74-LlFfZ6F9f6sr",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterServiceRoleDefaultPolicy5DE45717",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "Worko-ShocE-0txSITC4MbcW",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocEmitterThrottlesAlarm5503E07A",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-shoc-emitter-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocHmacRotatorD4073E6B",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "workorder-shoc-hmac-rotator",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocHmacRotatorDurationAlarm0725D01A",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-shoc-hmac-rotator-duration",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocHmacRotatorErrorsAlarm55E7BCC3",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-shoc-hmac-rotator-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocHmacRotatorInvokeN0a2GKfZP0JmDqDEVhhu6A0TUv3NyNbk4YMFKNc3085C896",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-ShocHmacRotatorInvokeN0a2GKfZP0JmDqDEVhhu6A0TUv3NyNbk4YMFKNc3085C8-kJaePEGWmM3r",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocHmacRotatorLogGroup1D109CC5",
|
||||||
|
"Type": "AWS::Logs::LogGroup",
|
||||||
|
"PhysicalId": "/aws/lambda/workorder-shoc-hmac-rotator",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocHmacRotatorServiceRole7F2B6917",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-ShocHmacRotatorServiceRole7F2B-uKqx4ccGQTk5",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocHmacRotatorServiceRoleDefaultPolicy492EC50B",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "Worko-ShocH-B5ATRJ8co1Hc",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocHmacRotatorThrottlesAlarm9963F2A3",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "workorder-shoc-hmac-rotator-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocWebhookHmacKey87DD3EC7",
|
||||||
|
"Type": "AWS::KMS::Key",
|
||||||
|
"PhysicalId": "d10fd1f0-a61a-4405-8568-85e9fd11ba18",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocWebhookHmacKeyAliasC83A07B2",
|
||||||
|
"Type": "AWS::KMS::Alias",
|
||||||
|
"PhysicalId": "alias/workorder-ingest-shoc-webhook-kms",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocWebhookHmacSecretA6CF6356",
|
||||||
|
"Type": "AWS::SecretsManager::Secret",
|
||||||
|
"PhysicalId": "arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocWebhookHmacSecretPolicy2B0A7DAC",
|
||||||
|
"Type": "AWS::SecretsManager::ResourcePolicy",
|
||||||
|
"PhysicalId": "arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ShocWebhookHmacSecretRotationA6130D74",
|
||||||
|
"Type": "AWS::SecretsManager::RotationSchedule",
|
||||||
|
"PhysicalId": "arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUI258D87A5",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "workorder-web-ui",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUILogGroup8F43ABDC",
|
||||||
|
"Type": "AWS::Logs::LogGroup",
|
||||||
|
"PhysicalId": "/aws/lambda/workorder-web-ui",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUIServiceRoleB1B395BE",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "WorkorderIngestStack-WebUIServiceRoleB1B395BE-0kRzDlAiLZVn",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUIServiceRoleDefaultPolicy8B510141",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "Worko-WebUI-TVYFGuD5kBVZ",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WorkOrderCommentsSystemErrorsAlarm4CD72F33",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "WorkOrderComments-system-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WorkOrderCommentsThrottlesAlarmA10AEFD9",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "WorkOrderComments-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WorkOrdersTable515B4C61",
|
||||||
|
"Type": "AWS::DynamoDB::Table",
|
||||||
|
"PhysicalId": "WorkOrders",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WorkOrdersTableSystemErrorsAlarm117925DB",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "WorkOrders-system-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WorkOrdersTableThrottlesAlarmDACF2500",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "WorkOrders-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
}
|
||||||
|
]
|
||||||
52
docs/plat-86/import-map.md
Normal file
52
docs/plat-86/import-map.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# PLAT-86 import map (seahaven-prod `011934824531` / us-east-1)
|
||||||
|
|
||||||
|
Frozen from live `DescribeStackResources` on 2026-08-06. CFN resource total: **164** (46 + 62 + 56). Estimated Terraform resources after expansion: ~220–320 — under HCP free-tier **500**.
|
||||||
|
|
||||||
|
Workspace: one `procurement-ingest-prod` covering all three former stacks.
|
||||||
|
|
||||||
|
## Out-of-band (data source / do not recreate)
|
||||||
|
|
||||||
|
| Dependency | Value |
|
||||||
|
|---|---|
|
||||||
|
| SES ruleset | `INBOUND_MAIL` |
|
||||||
|
| SNS | `arn:aws:sns:us-east-1:011934824531:site-alerts` |
|
||||||
|
| SSM CMK | `/seahaven/dynamodb/cmk-arn` → `arn:aws:kms:us-east-1:011934824531:key/be5fa4cb-c546-40fe-a13d-c7bec79f5d12` |
|
||||||
|
| SSM ACM | `/procurement-api/custom-domain/certificate-arn` → `arn:aws:acm:us-east-1:011934824531:certificate/9eac4c03-6850-49f1-baa1-6c4e7fffd1c7` |
|
||||||
|
| Secret (imported shell) | `arn:aws:secretsmanager:us-east-1:011934824531:secret:procurement-ingest/web-ui-auth-token-ApAMmr` |
|
||||||
|
| Mgmt Route53 | `procurement-api.seahaven.com` alias — stays OOB |
|
||||||
|
|
||||||
|
## CFN disposal disposition
|
||||||
|
|
||||||
|
| Class | Disposition on CFN stack delete |
|
||||||
|
|---|---|
|
||||||
|
| DynamoDB tables, email S3 buckets, Lambda log groups | **RETAIN** (must already be TF-owned; never delete) |
|
||||||
|
| Named SQS (`workorder-shoc-emitter-*`), SHOC secret/KMS, API GW, Lambdas, alarms, SES receipt rules | TF-owned; remove from CFN via retain-on-delete or deletion_policy before stack delete |
|
||||||
|
| CDK custom resources (`Custom::S3BucketNotifications`, `BucketNotificationsHandler` Lambda/role) | **Destroy with CFN** — replaced by native `aws_s3_bucket_notification` |
|
||||||
|
| CDK Metadata | Destroy with CFN |
|
||||||
|
| CDK-generated IAM roles at path `/` | After Lambda repoint to `/tf-managed/`, delete with CFN or sweep |
|
||||||
|
|
||||||
|
## Key physical IDs to preserve
|
||||||
|
|
||||||
|
- Lambdas: `po-email-processor`, `po-web-ui`, `po-ingest-site-extractor`, `workorder-email-processor`, `workorder-web-ui`, `workorder-shoc-emitter`, `workorder-shoc-hmac-rotator`, `procurement-api`
|
||||||
|
- Tables: `purchase-orders`, `verified-sites`, `pending-site-review`, `WorkOrders`, `WorkOrderComments` (PascalCase kept for import)
|
||||||
|
- Buckets: `po-ingest-emails-011934824531`, `workorder-ingest-emails-011934824531`
|
||||||
|
- Queues: `workorder-shoc-emitter-failures`, `workorder-shoc-emitter-rejected`, plus CDK-named DLQs
|
||||||
|
- Domain: `procurement-api.seahaven.com` (mgmt Route53 OOB)
|
||||||
|
- API REST id: `mvul1efda2`
|
||||||
|
- SHOC KMS: alias `workorder-ingest-shoc-webhook-kms` (key id in live inventory JSON only)
|
||||||
|
- Secret: `workorder-ingest/shoc-webhook-hmac`
|
||||||
|
|
||||||
|
## Cross-account pins (must stay byte-stable)
|
||||||
|
|
||||||
|
- API resource policy principal: `arn:aws:iam::396287094661:role/shoc-backend-dev`
|
||||||
|
- SHOC KMS decrypt: exact ARN + `kms:ViaService` for Secrets Manager
|
||||||
|
- Webhook URL: `https://api.dev.seahaven.com/api/webhooks/work-orders`
|
||||||
|
|
||||||
|
## Raw dumps
|
||||||
|
|
||||||
|
- [`po-ingest-resources.json`](po-ingest-resources.json)
|
||||||
|
- [`WorkorderIngestStack-resources.json`](WorkorderIngestStack-resources.json)
|
||||||
|
- [`procurement-api-resources.json`](procurement-api-resources.json)
|
||||||
|
- [`raw-inventory.tsv`](raw-inventory.tsv)
|
||||||
|
|
||||||
|
Import blocks: [`terraform/imports.tf`](../../terraform/imports.tf)
|
||||||
278
docs/plat-86/po-ingest-resources.json
Normal file
278
docs/plat-86/po-ingest-resources.json
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"LogicalId": "BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "po-ingest-BucketNotificationsHandler050a0587b75445-eYKw7CRdcWxS",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "po-ingest-BucketNotificationsHandler050a0587b754454-kAyd3v80XLIz",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "CDKMetadata",
|
||||||
|
"Type": "AWS::CDK::Metadata",
|
||||||
|
"PhysicalId": "e287be30-86dc-11f1-a551-0affcf7dfc53",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucket843A740F",
|
||||||
|
"Type": "AWS::S3::Bucket",
|
||||||
|
"PhysicalId": "po-ingest-emails-011934824531",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucketAllowBucketNotificationsTopoingestEmailProcessor21BB6E7689B27E99",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "po-ingest-EmailBucketAllowBucketNotificationsTopoingestEmailProcessor21BB6E7689B27E99-2uvEuiUCEDgr",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucketNotificationsDDD5BEEA",
|
||||||
|
"Type": "Custom::S3BucketNotifications",
|
||||||
|
"PhysicalId": "EmailBucketNotificationsDDD5BEEA",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucketNotificationsHandlerPolicy7D16942E",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "po-in-Email-eep3xPAZrcL7",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailBucketPolicyBBC68546",
|
||||||
|
"Type": "AWS::S3::BucketPolicy",
|
||||||
|
"PhysicalId": "po-ingest-emails-011934824531",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessor218EC076",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "po-email-processor",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorAiFallbackRejectedAlarm71D1F2CE",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-email-processor-ai-fallback-rejected",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorDlqA753DED5",
|
||||||
|
"Type": "AWS::SQS::Queue",
|
||||||
|
"PhysicalId": "https://sqs.us-east-1.amazonaws.com/011934824531/po-ingest-EmailProcessorDlqA753DED5-Mn33HvhsDPEu",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorDlqMessagesAlarmED574757",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-email-processor-dlq-messages",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorDlqPolicyB24007F0",
|
||||||
|
"Type": "AWS::SQS::QueuePolicy",
|
||||||
|
"PhysicalId": "po-ingest-EmailProcessorDlqPolicyB24007F0-ZBgpMipDtvH3",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorDurationAlarmF7530BED",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-email-processor-duration",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorErrorsAlarmC1BF0D18",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-email-processor-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorLogGroup8A79A2C8",
|
||||||
|
"Type": "AWS::Logs::LogGroup",
|
||||||
|
"PhysicalId": "/aws/lambda/po-email-processor",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorSenderAuthRejectedAlarm3D9534B7",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-email-processor-sender-auth-rejected",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorSenderAuthRejectedFilterC0DAEDFE",
|
||||||
|
"Type": "AWS::Logs::MetricFilter",
|
||||||
|
"PhysicalId": "EmailProcessorSenderAuthRejectedFilterC0DAEDFE-3K6wBb003iDv",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorServiceRole637381CE",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "po-ingest-EmailProcessorServiceRole637381CE-hG1KGKCIivCd",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorServiceRoleDefaultPolicy0B15DAB8",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "po-in-Email-BbPUnOcJ71or",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorTemplateFallbackRateAlarmF9B0866A",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-email-processor-template-fallback-rate",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "EmailProcessorThrottlesAlarmE102F9E9",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-email-processor-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ExistingRuleSetPoEmailRuleAC8E9C87",
|
||||||
|
"Type": "AWS::SES::ReceiptRule",
|
||||||
|
"PhysicalId": "ExistingRuleSetPoEmailRuleAC8E9C87-qwGDj9lBoL1G",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "PendingSiteReviewTableBF01B687",
|
||||||
|
"Type": "AWS::DynamoDB::Table",
|
||||||
|
"PhysicalId": "pending-site-review",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "PendingSiteReviewTableSystemErrorsAlarm36BA58D4",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "pending-site-review-system-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "PendingSiteReviewTableThrottlesAlarm15FB6DD5",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "pending-site-review-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "PurchaseOrdersTable491A23F2",
|
||||||
|
"Type": "AWS::DynamoDB::Table",
|
||||||
|
"PhysicalId": "purchase-orders",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "PurchaseOrdersTableSystemErrorsAlarmBC7F000F",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "purchase-orders-system-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "PurchaseOrdersTableThrottlesAlarmE5ACE81B",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "purchase-orders-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "SiteExtractor84CD7B81",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "po-ingest-site-extractor",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "SiteExtractorDurationAlarm3D503607",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-ingest-site-extractor-duration",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "SiteExtractorDynamoDBEventSourcepoingestPurchaseOrdersTable8DBC21215DEFB913",
|
||||||
|
"Type": "AWS::Lambda::EventSourceMapping",
|
||||||
|
"PhysicalId": "64fbee1f-d9c3-4cfd-aced-19b9d29940f8",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "SiteExtractorErrorsAlarm5DCF80DA",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-ingest-site-extractor-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "SiteExtractorLogGroupD475D9DD",
|
||||||
|
"Type": "AWS::Logs::LogGroup",
|
||||||
|
"PhysicalId": "/aws/lambda/po-ingest-site-extractor",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "SiteExtractorServiceRole11491F4D",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "po-ingest-SiteExtractorServiceRole11491F4D-X3aKoS1Rwq1W",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "SiteExtractorServiceRoleDefaultPolicy81255274",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "po-in-SiteE-JJJqSNBTUCT4",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "SiteExtractorThrottlesAlarm63246CEB",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-ingest-site-extractor-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "VerifiedSitesTable23AD7F59",
|
||||||
|
"Type": "AWS::DynamoDB::Table",
|
||||||
|
"PhysicalId": "verified-sites",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "VerifiedSitesTableSystemErrorsAlarm0408CCA4",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "verified-sites-system-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "VerifiedSitesTableThrottlesAlarmEC141C84",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "verified-sites-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUI258D87A5",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "po-web-ui",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUILogGroup8F43ABDC",
|
||||||
|
"Type": "AWS::Logs::LogGroup",
|
||||||
|
"PhysicalId": "/aws/lambda/po-web-ui",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUIServiceRoleB1B395BE",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "po-ingest-WebUIServiceRoleB1B395BE-UVNhr9c8b4YR",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUIServiceRoleDefaultPolicy8B510141",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "po-in-WebUI-jVyc9nvGbIx6",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUiDurationAlarm1A1A227D",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-web-ui-duration",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "WebUiThrottlesAlarmA33C4B72",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "po-web-ui-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
}
|
||||||
|
]
|
||||||
338
docs/plat-86/procurement-api-resources.json
Normal file
338
docs/plat-86/procurement-api-resources.json
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"LogicalId": "CDKMetadata",
|
||||||
|
"Type": "AWS::CDK::Metadata",
|
||||||
|
"PhysicalId": "472324e0-86ee-11f1-9b27-0afff03a5535",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApi187041AB",
|
||||||
|
"Type": "AWS::Lambda::Function",
|
||||||
|
"PhysicalId": "procurement-api",
|
||||||
|
"Status": "UPDATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApi5xxAlarmE69DCA02",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "procurement-api-5xx",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApiDomain857EE934",
|
||||||
|
"Type": "AWS::ApiGateway::DomainName",
|
||||||
|
"PhysicalId": "procurement-api.seahaven.com",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApiDomainMapprocurementapiProcurementRestApi7C05134920F11EB5",
|
||||||
|
"Type": "AWS::ApiGateway::BasePathMapping",
|
||||||
|
"PhysicalId": "procurement-api.seahaven.com|(none)",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApiDurationAlarmBEC61762",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "procurement-api-duration",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApiErrorsAlarmFA6549F2",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "procurement-api-errors",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApiLogGroupD8265E36",
|
||||||
|
"Type": "AWS::Logs::LogGroup",
|
||||||
|
"PhysicalId": "/aws/lambda/procurement-api",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApiServiceRoleD696D662",
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementApiServiceRoleD696D662-W2RyELHYFwBL",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApiServiceRoleDefaultPolicy1953BD71",
|
||||||
|
"Type": "AWS::IAM::Policy",
|
||||||
|
"PhysicalId": "procu-Procu-3dauci6ha7Eg",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementApiThrottlesAlarm7F98A1AD",
|
||||||
|
"Type": "AWS::CloudWatch::Alarm",
|
||||||
|
"PhysicalId": "procurement-api-throttles",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiDD8C37D6",
|
||||||
|
"Type": "AWS::ApiGateway::RestApi",
|
||||||
|
"PhysicalId": "mvul1efda2",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiDeployment526FECC37a13ca46329dbf433cb1289dad9c6539",
|
||||||
|
"Type": "AWS::ApiGateway::Deployment",
|
||||||
|
"PhysicalId": "uax9sq",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiDeploymentStageprodD4833AB9",
|
||||||
|
"Type": "AWS::ApiGateway::Stage",
|
||||||
|
"PhysicalId": "prod",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApidocsBC7A2DEC",
|
||||||
|
"Type": "AWS::ApiGateway::Resource",
|
||||||
|
"PhysicalId": "k4vl85",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApidocsGET1935E06E",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|k4vl85|GET",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApidocsGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETdocsFCC4F675",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApidocsGETApiPermissionTestprocurementapiProcurementRest-JkMEaKq9d2lY",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApidocsGETApiPermissionprocurementapiProcurementRestApi7C051349GETdocs00B2D21A",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApidocsGETApiPermissionprocurementapiProcurementRestApi7-fVn42qrClCjF",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiopenapijson945C2370",
|
||||||
|
"Type": "AWS::ApiGateway::Resource",
|
||||||
|
"PhysicalId": "xos715",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiopenapijsonGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETopenapijson078A24BA",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiopenapijsonGETApiPermissionTestprocurementapiProcurem-VPxSN2DPhc4W",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiopenapijsonGETApiPermissionprocurementapiProcurementRestApi7C051349GETopenapijson86A01B87",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiopenapijsonGETApiPermissionprocurementapiProcurementR-I2fDmtO8vw2w",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiopenapijsonGETD1B98CF4",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|xos715|GET",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApipurchaseordersE380B6C5",
|
||||||
|
"Type": "AWS::ApiGateway::Resource",
|
||||||
|
"PhysicalId": "pfn6qm",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApipurchaseordersGET1A2C4DD8",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|pfn6qm|GET",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApipurchaseordersGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETpurchaseordersFF5C7CF2",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApipurchaseordersGETApiPermissionTestprocurementapiProcu-FPgglqrEyKO4",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApipurchaseordersGETApiPermissionprocurementapiProcurementRestApi7C051349GETpurchaseordersC47C57EA",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApipurchaseordersGETApiPermissionprocurementapiProcureme-Db7wxlMFIJ1T",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApipurchaseorderspoNumber11A73C81",
|
||||||
|
"Type": "AWS::ApiGateway::Resource",
|
||||||
|
"PhysicalId": "nclnn3",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApipurchaseorderspoNumberGET7331738A",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|nclnn3|GET",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApipurchaseorderspoNumberGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETpurchaseorderspoNumberC6737C34",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApipurchaseorderspoNumberGETApiPermissionTestprocurement-SxEdwssencAN",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApipurchaseorderspoNumberGETApiPermissionprocurementapiProcurementRestApi7C051349GETpurchaseorderspoNumber8DEED34E",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApipurchaseorderspoNumberGETApiPermissionprocurementapiP-qRCfwLHa2pa6",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiverifiedsites4516F10B",
|
||||||
|
"Type": "AWS::ApiGateway::Resource",
|
||||||
|
"PhysicalId": "vyst7e",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiverifiedsitesGET81D69959",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|vyst7e|GET",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiverifiedsitesGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETverifiedsitesA7C29C4E",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiverifiedsitesGETApiPermissionTestprocurementapiProcur-1QHpXCoVpyKN",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiverifiedsitesGETApiPermissionprocurementapiProcurementRestApi7C051349GETverifiedsites16531E92",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiverifiedsitesGETApiPermissionprocurementapiProcuremen-b5nhaktRwejs",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiverifiedsitessiteCode48F86D1E",
|
||||||
|
"Type": "AWS::ApiGateway::Resource",
|
||||||
|
"PhysicalId": "rmaawy",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiverifiedsitessiteCodeGET2EE78735",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|rmaawy|GET",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiverifiedsitessiteCodeGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETverifiedsitessiteCode63EDC375",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiverifiedsitessiteCodeGETApiPermissionTestprocurementa-2PI3jCYWgXPU",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiverifiedsitessiteCodeGETApiPermissionprocurementapiProcurementRestApi7C051349GETverifiedsitessiteCode6457DF41",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiverifiedsitessiteCodeGETApiPermissionprocurementapiPr-QQ5mW4HnxKAr",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersD3A24F7A",
|
||||||
|
"Type": "AWS::ApiGateway::Resource",
|
||||||
|
"PhysicalId": "h5iu4f",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersGET62D5D82A",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|h5iu4f|GET",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETworkorders23C66297",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersGETApiPermissionTestprocurementapiProcureme-Ed6sN9siVtQv",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersGETApiPermissionprocurementapiProcurementRestApi7C051349GETworkorders067BAE83",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersGETApiPermissionprocurementapiProcurementRe-CdkJhuPaVDJv",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdE28A1181",
|
||||||
|
"Type": "AWS::ApiGateway::Resource",
|
||||||
|
"PhysicalId": "4uk6uh",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETworkordersworkOrderId0451CF71",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersworkOrderIdGETApiPermissionTestprocurementa-iZ1Jmq8djJRs",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdGETApiPermissionprocurementapiProcurementRestApi7C051349GETworkordersworkOrderId6619DA0E",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersworkOrderIdGETApiPermissionprocurementapiPr-nbjmJdg8o0nc",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdGETFEEB7CE4",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|4uk6uh|GET",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdPATCH7361320B",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|4uk6uh|PATCH",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdPATCHApiPermissionTestprocurementapiProcurementRestApi7C051349PATCHworkordersworkOrderIdC15330EB",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersworkOrderIdPATCHApiPermissionTestprocuremen-BJYqqytadslV",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdPATCHApiPermissionprocurementapiProcurementRestApi7C051349PATCHworkordersworkOrderId4DF98886",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersworkOrderIdPATCHApiPermissionprocurementapi-O70SvTPEoe0h",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdcomments5C278482",
|
||||||
|
"Type": "AWS::ApiGateway::Resource",
|
||||||
|
"PhysicalId": "gfx0te",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdcommentsGET032FCBD8",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|gfx0te|GET",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdcommentsGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETworkordersworkOrderIdcomments9BD68D42",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersworkOrderIdcommentsGETApiPermissionTestproc-AbYwSYiRKjxQ",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdcommentsGETApiPermissionprocurementapiProcurementRestApi7C051349GETworkordersworkOrderIdcommentsACDBEBB8",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersworkOrderIdcommentsGETApiPermissionprocurem-uxRIv0NP6hKD",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdcommentsPOST98588435",
|
||||||
|
"Type": "AWS::ApiGateway::Method",
|
||||||
|
"PhysicalId": "mvul1efda2|gfx0te|POST",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdcommentsPOSTApiPermissionTestprocurementapiProcurementRestApi7C051349POSTworkordersworkOrderIdcomments2194642A",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersworkOrderIdcommentsPOSTApiPermissionTestpro-Ci7eIdxeqWFP",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"LogicalId": "ProcurementRestApiworkordersworkOrderIdcommentsPOSTApiPermissionprocurementapiProcurementRestApi7C051349POSTworkordersworkOrderIdcommentsFBA9AF05",
|
||||||
|
"Type": "AWS::Lambda::Permission",
|
||||||
|
"PhysicalId": "procurement-api-ProcurementRestApiworkordersworkOrderIdcommentsPOSTApiPermissionprocure-VQGemxZkkIu1",
|
||||||
|
"Status": "CREATE_COMPLETE"
|
||||||
|
}
|
||||||
|
]
|
||||||
164
docs/plat-86/raw-inventory.tsv
Normal file
164
docs/plat-86/raw-inventory.tsv
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
po-ingest AWS::Lambda::Function BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691 po-ingest-BucketNotificationsHandler050a0587b75445-eYKw7CRdcWxS
|
||||||
|
po-ingest AWS::IAM::Role BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC po-ingest-BucketNotificationsHandler050a0587b754454-kAyd3v80XLIz
|
||||||
|
po-ingest AWS::CDK::Metadata CDKMetadata e287be30-86dc-11f1-a551-0affcf7dfc53
|
||||||
|
po-ingest AWS::S3::Bucket EmailBucket843A740F po-ingest-emails-011934824531
|
||||||
|
po-ingest AWS::Lambda::Permission EmailBucketAllowBucketNotificationsTopoingestEmailProcessor21BB6E7689B27E99 po-ingest-EmailBucketAllowBucketNotificationsTopoingestEmailProcessor21BB6E7689B27E99-2uvEuiUCEDgr
|
||||||
|
po-ingest Custom::S3BucketNotifications EmailBucketNotificationsDDD5BEEA EmailBucketNotificationsDDD5BEEA
|
||||||
|
po-ingest AWS::IAM::Policy EmailBucketNotificationsHandlerPolicy7D16942E po-in-Email-eep3xPAZrcL7
|
||||||
|
po-ingest AWS::S3::BucketPolicy EmailBucketPolicyBBC68546 po-ingest-emails-011934824531
|
||||||
|
po-ingest AWS::Lambda::Function EmailProcessor218EC076 po-email-processor
|
||||||
|
po-ingest AWS::CloudWatch::Alarm EmailProcessorAiFallbackRejectedAlarm71D1F2CE po-email-processor-ai-fallback-rejected
|
||||||
|
po-ingest AWS::SQS::Queue EmailProcessorDlqA753DED5 https://sqs.us-east-1.amazonaws.com/011934824531/po-ingest-EmailProcessorDlqA753DED5-Mn33HvhsDPEu
|
||||||
|
po-ingest AWS::CloudWatch::Alarm EmailProcessorDlqMessagesAlarmED574757 po-email-processor-dlq-messages
|
||||||
|
po-ingest AWS::SQS::QueuePolicy EmailProcessorDlqPolicyB24007F0 po-ingest-EmailProcessorDlqPolicyB24007F0-ZBgpMipDtvH3
|
||||||
|
po-ingest AWS::CloudWatch::Alarm EmailProcessorDurationAlarmF7530BED po-email-processor-duration
|
||||||
|
po-ingest AWS::CloudWatch::Alarm EmailProcessorErrorsAlarmC1BF0D18 po-email-processor-errors
|
||||||
|
po-ingest AWS::Logs::LogGroup EmailProcessorLogGroup8A79A2C8 /aws/lambda/po-email-processor
|
||||||
|
po-ingest AWS::CloudWatch::Alarm EmailProcessorSenderAuthRejectedAlarm3D9534B7 po-email-processor-sender-auth-rejected
|
||||||
|
po-ingest AWS::Logs::MetricFilter EmailProcessorSenderAuthRejectedFilterC0DAEDFE EmailProcessorSenderAuthRejectedFilterC0DAEDFE-3K6wBb003iDv
|
||||||
|
po-ingest AWS::IAM::Role EmailProcessorServiceRole637381CE po-ingest-EmailProcessorServiceRole637381CE-hG1KGKCIivCd
|
||||||
|
po-ingest AWS::IAM::Policy EmailProcessorServiceRoleDefaultPolicy0B15DAB8 po-in-Email-BbPUnOcJ71or
|
||||||
|
po-ingest AWS::CloudWatch::Alarm EmailProcessorTemplateFallbackRateAlarmF9B0866A po-email-processor-template-fallback-rate
|
||||||
|
po-ingest AWS::CloudWatch::Alarm EmailProcessorThrottlesAlarmE102F9E9 po-email-processor-throttles
|
||||||
|
po-ingest AWS::SES::ReceiptRule ExistingRuleSetPoEmailRuleAC8E9C87 ExistingRuleSetPoEmailRuleAC8E9C87-qwGDj9lBoL1G
|
||||||
|
po-ingest AWS::DynamoDB::Table PendingSiteReviewTableBF01B687 pending-site-review
|
||||||
|
po-ingest AWS::CloudWatch::Alarm PendingSiteReviewTableSystemErrorsAlarm36BA58D4 pending-site-review-system-errors
|
||||||
|
po-ingest AWS::CloudWatch::Alarm PendingSiteReviewTableThrottlesAlarm15FB6DD5 pending-site-review-throttles
|
||||||
|
po-ingest AWS::DynamoDB::Table PurchaseOrdersTable491A23F2 purchase-orders
|
||||||
|
po-ingest AWS::CloudWatch::Alarm PurchaseOrdersTableSystemErrorsAlarmBC7F000F purchase-orders-system-errors
|
||||||
|
po-ingest AWS::CloudWatch::Alarm PurchaseOrdersTableThrottlesAlarmE5ACE81B purchase-orders-throttles
|
||||||
|
po-ingest AWS::Lambda::Function SiteExtractor84CD7B81 po-ingest-site-extractor
|
||||||
|
po-ingest AWS::CloudWatch::Alarm SiteExtractorDurationAlarm3D503607 po-ingest-site-extractor-duration
|
||||||
|
po-ingest AWS::Lambda::EventSourceMapping SiteExtractorDynamoDBEventSourcepoingestPurchaseOrdersTable8DBC21215DEFB913 64fbee1f-d9c3-4cfd-aced-19b9d29940f8
|
||||||
|
po-ingest AWS::CloudWatch::Alarm SiteExtractorErrorsAlarm5DCF80DA po-ingest-site-extractor-errors
|
||||||
|
po-ingest AWS::Logs::LogGroup SiteExtractorLogGroupD475D9DD /aws/lambda/po-ingest-site-extractor
|
||||||
|
po-ingest AWS::IAM::Role SiteExtractorServiceRole11491F4D po-ingest-SiteExtractorServiceRole11491F4D-X3aKoS1Rwq1W
|
||||||
|
po-ingest AWS::IAM::Policy SiteExtractorServiceRoleDefaultPolicy81255274 po-in-SiteE-JJJqSNBTUCT4
|
||||||
|
po-ingest AWS::CloudWatch::Alarm SiteExtractorThrottlesAlarm63246CEB po-ingest-site-extractor-throttles
|
||||||
|
po-ingest AWS::DynamoDB::Table VerifiedSitesTable23AD7F59 verified-sites
|
||||||
|
po-ingest AWS::CloudWatch::Alarm VerifiedSitesTableSystemErrorsAlarm0408CCA4 verified-sites-system-errors
|
||||||
|
po-ingest AWS::CloudWatch::Alarm VerifiedSitesTableThrottlesAlarmEC141C84 verified-sites-throttles
|
||||||
|
po-ingest AWS::Lambda::Function WebUI258D87A5 po-web-ui
|
||||||
|
po-ingest AWS::Logs::LogGroup WebUILogGroup8F43ABDC /aws/lambda/po-web-ui
|
||||||
|
po-ingest AWS::IAM::Role WebUIServiceRoleB1B395BE po-ingest-WebUIServiceRoleB1B395BE-UVNhr9c8b4YR
|
||||||
|
po-ingest AWS::IAM::Policy WebUIServiceRoleDefaultPolicy8B510141 po-in-WebUI-jVyc9nvGbIx6
|
||||||
|
po-ingest AWS::CloudWatch::Alarm WebUiDurationAlarm1A1A227D po-web-ui-duration
|
||||||
|
po-ingest AWS::CloudWatch::Alarm WebUiThrottlesAlarmA33C4B72 po-web-ui-throttles
|
||||||
|
WorkorderIngestStack AWS::Lambda::Function BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691 WorkorderIngestStack-BucketNotificationsHandler050-fBtoo7x6Azy3
|
||||||
|
WorkorderIngestStack AWS::IAM::Role BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC WorkorderIngestStack-BucketNotificationsHandler050a-7KxpBKmlpFSf
|
||||||
|
WorkorderIngestStack AWS::CDK::Metadata CDKMetadata a85b0320-86dc-11f1-b0cf-0eccc7521e19
|
||||||
|
WorkorderIngestStack AWS::DynamoDB::Table CommentsTableBBDBF0A8 WorkOrderComments
|
||||||
|
WorkorderIngestStack AWS::S3::Bucket EmailBucket843A740F workorder-ingest-emails-011934824531
|
||||||
|
WorkorderIngestStack AWS::Lambda::Permission EmailBucketAllowBucketNotificationsToworkorderingestEmailProcessor5B0B85DABA138779 WorkorderIngestStack-EmailBucketAllowBucketNotificationsToworkorderingestEmailProcessor-VuairMrX6Qir
|
||||||
|
WorkorderIngestStack Custom::S3BucketNotifications EmailBucketNotificationsDDD5BEEA EmailBucketNotificationsDDD5BEEA
|
||||||
|
WorkorderIngestStack AWS::IAM::Policy EmailBucketNotificationsHandlerPolicy7D16942E Worko-Email-YLl5HvyOvIg2
|
||||||
|
WorkorderIngestStack AWS::S3::BucketPolicy EmailBucketPolicyBBC68546 workorder-ingest-emails-011934824531
|
||||||
|
WorkorderIngestStack AWS::Lambda::Function EmailProcessor218EC076 workorder-email-processor
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm EmailProcessorAiFallbackRejectedAlarm71D1F2CE workorder-email-processor-ai-fallback-rejected
|
||||||
|
WorkorderIngestStack AWS::SQS::Queue EmailProcessorDlqA753DED5 https://sqs.us-east-1.amazonaws.com/011934824531/WorkorderIngestStack-EmailProcessorDlqA753DED5-qCTHrsoEucas
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm EmailProcessorDlqMessagesAlarmED574757 workorder-email-processor-dlq-messages
|
||||||
|
WorkorderIngestStack AWS::SQS::QueuePolicy EmailProcessorDlqPolicyB24007F0 WorkorderIngestStack-EmailProcessorDlqPolicyB24007F0-15VK27UU5Q19
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm EmailProcessorDurationAlarmF7530BED workorder-email-processor-duration
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm EmailProcessorErrorsAlarmC1BF0D18 workorder-email-processor-errors
|
||||||
|
WorkorderIngestStack AWS::Logs::LogGroup EmailProcessorLogGroup8A79A2C8 /aws/lambda/workorder-email-processor
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm EmailProcessorSenderAuthRejectedAlarm3D9534B7 workorder-email-processor-sender-auth-rejected
|
||||||
|
WorkorderIngestStack AWS::Logs::MetricFilter EmailProcessorSenderAuthRejectedFilterC0DAEDFE EmailProcessorSenderAuthRejectedFilterC0DAEDFE-Wkbow1VNDeDM
|
||||||
|
WorkorderIngestStack AWS::IAM::Role EmailProcessorServiceRole637381CE WorkorderIngestStack-EmailProcessorServiceRole63738-5l9OZXQLVvJn
|
||||||
|
WorkorderIngestStack AWS::IAM::Policy EmailProcessorServiceRoleDefaultPolicy0B15DAB8 Worko-Email-8PjqeVLLDLRv
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm EmailProcessorTemplateFallbackRateAlarmF9B0866A workorder-email-processor-template-fallback-rate
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm EmailProcessorThrottlesAlarmE102F9E9 workorder-email-processor-throttles
|
||||||
|
WorkorderIngestStack AWS::SES::ReceiptRule ExistingRuleSetWorkorderEmailRuleEA29F845 ExistingRuleSetWorkorderEmailRuleEA29F845-PKtaDBvIg61a
|
||||||
|
WorkorderIngestStack AWS::Lambda::Function ShocEmitterCD1F4C3E workorder-shoc-emitter
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm ShocEmitterDurationAlarm3F755835 workorder-shoc-emitter-duration
|
||||||
|
WorkorderIngestStack AWS::Lambda::EventSourceMapping ShocEmitterDynamoDBEventSourceworkorderingestCommentsTableFD97F35C58A19C1A da37d4a0-3086-4929-9ace-6eca5c20cd07
|
||||||
|
WorkorderIngestStack AWS::Lambda::EventSourceMapping ShocEmitterDynamoDBEventSourceworkorderingestWorkOrdersTable004CD4AC38D8BED2 cc149f4d-5375-4820-8fb8-c514accd3a85
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm ShocEmitterErrorsAlarmCE2B702A workorder-shoc-emitter-errors
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm ShocEmitterFailuresMessagesAlarmEC9798F9 workorder-shoc-emitter-failures-messages
|
||||||
|
WorkorderIngestStack AWS::SQS::Queue ShocEmitterFailuresQueue11D7C8E0 https://sqs.us-east-1.amazonaws.com/011934824531/workorder-shoc-emitter-failures
|
||||||
|
WorkorderIngestStack AWS::SQS::QueuePolicy ShocEmitterFailuresQueuePolicy0D60571C WorkorderIngestStack-ShocEmitterFailuresQueuePolicy0D60571C-2AMBNJh0la3L
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm ShocEmitterIteratorAgeAlarm90B55822 workorder-shoc-emitter-iterator-age
|
||||||
|
WorkorderIngestStack AWS::Logs::LogGroup ShocEmitterLogGroup80BDE724 /aws/lambda/workorder-shoc-emitter
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm ShocEmitterRejectedMessagesAlarmAE9AFCDD workorder-shoc-emitter-rejected-messages
|
||||||
|
WorkorderIngestStack AWS::SQS::Queue ShocEmitterRejectedQueueD754F41E https://sqs.us-east-1.amazonaws.com/011934824531/workorder-shoc-emitter-rejected
|
||||||
|
WorkorderIngestStack AWS::SQS::QueuePolicy ShocEmitterRejectedQueuePolicy4B088C0D WorkorderIngestStack-ShocEmitterRejectedQueuePolicy4B088C0D-vzXOuhHaGfNy
|
||||||
|
WorkorderIngestStack AWS::IAM::Role ShocEmitterServiceRoleA0CA0C74 WorkorderIngestStack-ShocEmitterServiceRoleA0CA0C74-LlFfZ6F9f6sr
|
||||||
|
WorkorderIngestStack AWS::IAM::Policy ShocEmitterServiceRoleDefaultPolicy5DE45717 Worko-ShocE-0txSITC4MbcW
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm ShocEmitterThrottlesAlarm5503E07A workorder-shoc-emitter-throttles
|
||||||
|
WorkorderIngestStack AWS::Lambda::Function ShocHmacRotatorD4073E6B workorder-shoc-hmac-rotator
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm ShocHmacRotatorDurationAlarm0725D01A workorder-shoc-hmac-rotator-duration
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm ShocHmacRotatorErrorsAlarm55E7BCC3 workorder-shoc-hmac-rotator-errors
|
||||||
|
WorkorderIngestStack AWS::Lambda::Permission ShocHmacRotatorInvokeN0a2GKfZP0JmDqDEVhhu6A0TUv3NyNbk4YMFKNc3085C896 WorkorderIngestStack-ShocHmacRotatorInvokeN0a2GKfZP0JmDqDEVhhu6A0TUv3NyNbk4YMFKNc3085C8-kJaePEGWmM3r
|
||||||
|
WorkorderIngestStack AWS::Logs::LogGroup ShocHmacRotatorLogGroup1D109CC5 /aws/lambda/workorder-shoc-hmac-rotator
|
||||||
|
WorkorderIngestStack AWS::IAM::Role ShocHmacRotatorServiceRole7F2B6917 WorkorderIngestStack-ShocHmacRotatorServiceRole7F2B-uKqx4ccGQTk5
|
||||||
|
WorkorderIngestStack AWS::IAM::Policy ShocHmacRotatorServiceRoleDefaultPolicy492EC50B Worko-ShocH-B5ATRJ8co1Hc
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm ShocHmacRotatorThrottlesAlarm9963F2A3 workorder-shoc-hmac-rotator-throttles
|
||||||
|
WorkorderIngestStack AWS::KMS::Key ShocWebhookHmacKey87DD3EC7 d10fd1f0-a61a-4405-8568-85e9fd11ba18
|
||||||
|
WorkorderIngestStack AWS::KMS::Alias ShocWebhookHmacKeyAliasC83A07B2 alias/workorder-ingest-shoc-webhook-kms
|
||||||
|
WorkorderIngestStack AWS::SecretsManager::Secret ShocWebhookHmacSecretA6CF6356 arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB
|
||||||
|
WorkorderIngestStack AWS::SecretsManager::ResourcePolicy ShocWebhookHmacSecretPolicy2B0A7DAC arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB
|
||||||
|
WorkorderIngestStack AWS::SecretsManager::RotationSchedule ShocWebhookHmacSecretRotationA6130D74 arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB
|
||||||
|
WorkorderIngestStack AWS::Lambda::Function WebUI258D87A5 workorder-web-ui
|
||||||
|
WorkorderIngestStack AWS::Logs::LogGroup WebUILogGroup8F43ABDC /aws/lambda/workorder-web-ui
|
||||||
|
WorkorderIngestStack AWS::IAM::Role WebUIServiceRoleB1B395BE WorkorderIngestStack-WebUIServiceRoleB1B395BE-0kRzDlAiLZVn
|
||||||
|
WorkorderIngestStack AWS::IAM::Policy WebUIServiceRoleDefaultPolicy8B510141 Worko-WebUI-TVYFGuD5kBVZ
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm WorkOrderCommentsSystemErrorsAlarm4CD72F33 WorkOrderComments-system-errors
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm WorkOrderCommentsThrottlesAlarmA10AEFD9 WorkOrderComments-throttles
|
||||||
|
WorkorderIngestStack AWS::DynamoDB::Table WorkOrdersTable515B4C61 WorkOrders
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm WorkOrdersTableSystemErrorsAlarm117925DB WorkOrders-system-errors
|
||||||
|
WorkorderIngestStack AWS::CloudWatch::Alarm WorkOrdersTableThrottlesAlarmDACF2500 WorkOrders-throttles
|
||||||
|
procurement-api AWS::CDK::Metadata CDKMetadata 472324e0-86ee-11f1-9b27-0afff03a5535
|
||||||
|
procurement-api AWS::Lambda::Function ProcurementApi187041AB procurement-api
|
||||||
|
procurement-api AWS::CloudWatch::Alarm ProcurementApi5xxAlarmE69DCA02 procurement-api-5xx
|
||||||
|
procurement-api AWS::ApiGateway::DomainName ProcurementApiDomain857EE934 procurement-api.seahaven.com
|
||||||
|
procurement-api AWS::ApiGateway::BasePathMapping ProcurementApiDomainMapprocurementapiProcurementRestApi7C05134920F11EB5 procurement-api.seahaven.com|(none)
|
||||||
|
procurement-api AWS::CloudWatch::Alarm ProcurementApiDurationAlarmBEC61762 procurement-api-duration
|
||||||
|
procurement-api AWS::CloudWatch::Alarm ProcurementApiErrorsAlarmFA6549F2 procurement-api-errors
|
||||||
|
procurement-api AWS::Logs::LogGroup ProcurementApiLogGroupD8265E36 /aws/lambda/procurement-api
|
||||||
|
procurement-api AWS::IAM::Role ProcurementApiServiceRoleD696D662 procurement-api-ProcurementApiServiceRoleD696D662-W2RyELHYFwBL
|
||||||
|
procurement-api AWS::IAM::Policy ProcurementApiServiceRoleDefaultPolicy1953BD71 procu-Procu-3dauci6ha7Eg
|
||||||
|
procurement-api AWS::CloudWatch::Alarm ProcurementApiThrottlesAlarm7F98A1AD procurement-api-throttles
|
||||||
|
procurement-api AWS::ApiGateway::RestApi ProcurementRestApiDD8C37D6 mvul1efda2
|
||||||
|
procurement-api AWS::ApiGateway::Deployment ProcurementRestApiDeployment526FECC37a13ca46329dbf433cb1289dad9c6539 uax9sq
|
||||||
|
procurement-api AWS::ApiGateway::Stage ProcurementRestApiDeploymentStageprodD4833AB9 prod
|
||||||
|
procurement-api AWS::ApiGateway::Resource ProcurementRestApidocsBC7A2DEC k4vl85
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApidocsGET1935E06E mvul1efda2|k4vl85|GET
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApidocsGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETdocsFCC4F675 procurement-api-ProcurementRestApidocsGETApiPermissionTestprocurementapiProcurementRest-JkMEaKq9d2lY
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApidocsGETApiPermissionprocurementapiProcurementRestApi7C051349GETdocs00B2D21A procurement-api-ProcurementRestApidocsGETApiPermissionprocurementapiProcurementRestApi7-fVn42qrClCjF
|
||||||
|
procurement-api AWS::ApiGateway::Resource ProcurementRestApiopenapijson945C2370 xos715
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiopenapijsonGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETopenapijson078A24BA procurement-api-ProcurementRestApiopenapijsonGETApiPermissionTestprocurementapiProcurem-VPxSN2DPhc4W
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiopenapijsonGETApiPermissionprocurementapiProcurementRestApi7C051349GETopenapijson86A01B87 procurement-api-ProcurementRestApiopenapijsonGETApiPermissionprocurementapiProcurementR-I2fDmtO8vw2w
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApiopenapijsonGETD1B98CF4 mvul1efda2|xos715|GET
|
||||||
|
procurement-api AWS::ApiGateway::Resource ProcurementRestApipurchaseordersE380B6C5 pfn6qm
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApipurchaseordersGET1A2C4DD8 mvul1efda2|pfn6qm|GET
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApipurchaseordersGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETpurchaseordersFF5C7CF2 procurement-api-ProcurementRestApipurchaseordersGETApiPermissionTestprocurementapiProcu-FPgglqrEyKO4
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApipurchaseordersGETApiPermissionprocurementapiProcurementRestApi7C051349GETpurchaseordersC47C57EA procurement-api-ProcurementRestApipurchaseordersGETApiPermissionprocurementapiProcureme-Db7wxlMFIJ1T
|
||||||
|
procurement-api AWS::ApiGateway::Resource ProcurementRestApipurchaseorderspoNumber11A73C81 nclnn3
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApipurchaseorderspoNumberGET7331738A mvul1efda2|nclnn3|GET
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApipurchaseorderspoNumberGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETpurchaseorderspoNumberC6737C34 procurement-api-ProcurementRestApipurchaseorderspoNumberGETApiPermissionTestprocurement-SxEdwssencAN
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApipurchaseorderspoNumberGETApiPermissionprocurementapiProcurementRestApi7C051349GETpurchaseorderspoNumber8DEED34E procurement-api-ProcurementRestApipurchaseorderspoNumberGETApiPermissionprocurementapiP-qRCfwLHa2pa6
|
||||||
|
procurement-api AWS::ApiGateway::Resource ProcurementRestApiverifiedsites4516F10B vyst7e
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApiverifiedsitesGET81D69959 mvul1efda2|vyst7e|GET
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiverifiedsitesGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETverifiedsitesA7C29C4E procurement-api-ProcurementRestApiverifiedsitesGETApiPermissionTestprocurementapiProcur-1QHpXCoVpyKN
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiverifiedsitesGETApiPermissionprocurementapiProcurementRestApi7C051349GETverifiedsites16531E92 procurement-api-ProcurementRestApiverifiedsitesGETApiPermissionprocurementapiProcuremen-b5nhaktRwejs
|
||||||
|
procurement-api AWS::ApiGateway::Resource ProcurementRestApiverifiedsitessiteCode48F86D1E rmaawy
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApiverifiedsitessiteCodeGET2EE78735 mvul1efda2|rmaawy|GET
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiverifiedsitessiteCodeGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETverifiedsitessiteCode63EDC375 procurement-api-ProcurementRestApiverifiedsitessiteCodeGETApiPermissionTestprocurementa-2PI3jCYWgXPU
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiverifiedsitessiteCodeGETApiPermissionprocurementapiProcurementRestApi7C051349GETverifiedsitessiteCode6457DF41 procurement-api-ProcurementRestApiverifiedsitessiteCodeGETApiPermissionprocurementapiPr-QQ5mW4HnxKAr
|
||||||
|
procurement-api AWS::ApiGateway::Resource ProcurementRestApiworkordersD3A24F7A h5iu4f
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApiworkordersGET62D5D82A mvul1efda2|h5iu4f|GET
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETworkorders23C66297 procurement-api-ProcurementRestApiworkordersGETApiPermissionTestprocurementapiProcureme-Ed6sN9siVtQv
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersGETApiPermissionprocurementapiProcurementRestApi7C051349GETworkorders067BAE83 procurement-api-ProcurementRestApiworkordersGETApiPermissionprocurementapiProcurementRe-CdkJhuPaVDJv
|
||||||
|
procurement-api AWS::ApiGateway::Resource ProcurementRestApiworkordersworkOrderIdE28A1181 4uk6uh
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersworkOrderIdGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETworkordersworkOrderId0451CF71 procurement-api-ProcurementRestApiworkordersworkOrderIdGETApiPermissionTestprocurementa-iZ1Jmq8djJRs
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersworkOrderIdGETApiPermissionprocurementapiProcurementRestApi7C051349GETworkordersworkOrderId6619DA0E procurement-api-ProcurementRestApiworkordersworkOrderIdGETApiPermissionprocurementapiPr-nbjmJdg8o0nc
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApiworkordersworkOrderIdGETFEEB7CE4 mvul1efda2|4uk6uh|GET
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApiworkordersworkOrderIdPATCH7361320B mvul1efda2|4uk6uh|PATCH
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersworkOrderIdPATCHApiPermissionTestprocurementapiProcurementRestApi7C051349PATCHworkordersworkOrderIdC15330EB procurement-api-ProcurementRestApiworkordersworkOrderIdPATCHApiPermissionTestprocuremen-BJYqqytadslV
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersworkOrderIdPATCHApiPermissionprocurementapiProcurementRestApi7C051349PATCHworkordersworkOrderId4DF98886 procurement-api-ProcurementRestApiworkordersworkOrderIdPATCHApiPermissionprocurementapi-O70SvTPEoe0h
|
||||||
|
procurement-api AWS::ApiGateway::Resource ProcurementRestApiworkordersworkOrderIdcomments5C278482 gfx0te
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApiworkordersworkOrderIdcommentsGET032FCBD8 mvul1efda2|gfx0te|GET
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersworkOrderIdcommentsGETApiPermissionTestprocurementapiProcurementRestApi7C051349GETworkordersworkOrderIdcomments9BD68D42 procurement-api-ProcurementRestApiworkordersworkOrderIdcommentsGETApiPermissionTestproc-AbYwSYiRKjxQ
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersworkOrderIdcommentsGETApiPermissionprocurementapiProcurementRestApi7C051349GETworkordersworkOrderIdcommentsACDBEBB8 procurement-api-ProcurementRestApiworkordersworkOrderIdcommentsGETApiPermissionprocurem-uxRIv0NP6hKD
|
||||||
|
procurement-api AWS::ApiGateway::Method ProcurementRestApiworkordersworkOrderIdcommentsPOST98588435 mvul1efda2|gfx0te|POST
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersworkOrderIdcommentsPOSTApiPermissionTestprocurementapiProcurementRestApi7C051349POSTworkordersworkOrderIdcomments2194642A procurement-api-ProcurementRestApiworkordersworkOrderIdcommentsPOSTApiPermissionTestpro-Ci7eIdxeqWFP
|
||||||
|
procurement-api AWS::Lambda::Permission ProcurementRestApiworkordersworkOrderIdcommentsPOSTApiPermissionprocurementapiProcurementRestApi7C051349POSTworkordersworkOrderIdcommentsFBA9AF05 procurement-api-ProcurementRestApiworkordersworkOrderIdcommentsPOSTApiPermissionprocure-VQGemxZkkIu1
|
||||||
|
11
terraform/.gitignore
vendored
Normal file
11
terraform/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
build/
|
||||||
|
.terraform/
|
||||||
|
*.tfstate
|
||||||
|
*.tfstate.*
|
||||||
|
*.tfvars
|
||||||
|
!terraform.tfvars.example
|
||||||
|
crash.log
|
||||||
|
override.tf
|
||||||
|
override.tf.json
|
||||||
|
*_override.tf
|
||||||
|
*_override.tf.json
|
||||||
74
terraform/.terraform.lock.hcl
generated
Normal file
74
terraform/.terraform.lock.hcl
generated
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
# This file is maintained automatically by "terraform init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/archive" {
|
||||||
|
version = "2.8.0"
|
||||||
|
constraints = "~> 2.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:WB6H5ksIZiyq1lQlD/PWeh+tn4FLsbSjVnRW3+4xe2Y=",
|
||||||
|
"h1:cMBtvdHEgvTmglbioVehZCaOIPocumu9+vlGw5Dsaro=",
|
||||||
|
"h1:jdmKm+xl6ZcQrijxapnZ94RVuz/G4vk7hsIa1N0VT5Q=",
|
||||||
|
"h1:oRWx6ZDIdlNBF90L8TzV9X7pQ+P403hoCDiBfmUfhC0=",
|
||||||
|
"zh:0d14713fdc259fb377d0b899ad3c650a34194bd52194c863303ef22a65a580e2",
|
||||||
|
"zh:369b56040c7a8085d04e7e8ffac1e2b321a3170e502f788819bc34b868ec016f",
|
||||||
|
"zh:4d1a3b983ed6af5a52bfe12794674ae55cbadfa6021b37106ade68b433ad216a",
|
||||||
|
"zh:5c547549e26e083573c78a966ca68ce6d7df6bb8f3948f66a575f07da46b74ea",
|
||||||
|
"zh:6de093e62a975eb19a5e3017ce38e6e3cb639c17b79648d2000e0a8348f0e997",
|
||||||
|
"zh:7267936c2cdbc448efeb594d73e6b56a53d6a7ae14fe88cdd2a4133adc3302f0",
|
||||||
|
"zh:7482f023050ed426b4b45116e1761643bc33b1fd4ce4a6fab207ae2571f35940",
|
||||||
|
"zh:76bbd93b234e5a2927d98b511d86565700f549b570871a194c35f944b96cefb7",
|
||||||
|
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||||
|
"zh:c6afc4bc1f002bac9c173007dd4da05fde788cd14c2916089f958c33fedb0dfa",
|
||||||
|
"zh:d3ba40bd806a3a08e9237dece679193c99afb2085de6b45d7f5d1f673cfcd368",
|
||||||
|
"zh:e1ad7ded53ecd6f0e5b473a3b44eae2b2e885653a56050ab583d387332be02e4",
|
||||||
|
"zh:e93e78575ce82be6084cc153c24ba8f385dc8d6880888ee66e918460c870953d",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/aws" {
|
||||||
|
version = "6.58.0"
|
||||||
|
constraints = "~> 6.57"
|
||||||
|
hashes = [
|
||||||
|
"h1:1im6ypdeXLXq3sHElserTE62qmIqNiHLAyC3R5EnFFw=",
|
||||||
|
"h1:2kpake4zZKRX5437QVIRU3qFYH6Bjw/QE1fgVCPOrUg=",
|
||||||
|
"h1:OWl47Bo8Vzlf5srTUCmA6v4kvQGfah/P1joRtIYUUMc=",
|
||||||
|
"h1:UFot9S97tuAPvjKvoxm08sDG/gKYdDK+lMwsZKtLieY=",
|
||||||
|
"zh:1221253beee5629fb503d79cebc9bc661279cbc4be5d01db9ab4c1b702108250",
|
||||||
|
"zh:132bd0925bdc4b72446ac750b7ccb1e19b9ba8fbb6df57b2c1423314d2195d4f",
|
||||||
|
"zh:18cda250b9e82b753808715893c8927f132273c00ffae7a697d65ac1cb577e48",
|
||||||
|
"zh:204c944f1fb7f440a335bb2083c9691a9d1f677aea9701025dd5816aee41f0ba",
|
||||||
|
"zh:2dc41df289f2b10a01e650cdd73699955f0ab0645d09cfb114a8cd0f4cc4ede7",
|
||||||
|
"zh:345633dfa9a234659d52aadd126e6dce658518c3ab5cbf6d871221287ed5ec56",
|
||||||
|
"zh:4dadcced73e742903158bc9838936d911f3fa4c2c37b5591c1a28f8f2a1902a6",
|
||||||
|
"zh:5bc60cc2b8c093da98b211d9f6c21c9ecec0f21b944d9ecbe3961fba33086e80",
|
||||||
|
"zh:6cc8f084938b0033a9c0c910989919dad6b1683e76e0afa1a5c604e39f398a75",
|
||||||
|
"zh:7db214647f79de9a033b5dfd6cbfaa42d53c4d056b32cb3acc7ad99306dd548a",
|
||||||
|
"zh:9078589ec881cee7ed9403af262c98ff257fb3e1baae72ff6429a398b1c730af",
|
||||||
|
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||||
|
"zh:bd5bce6aec4d4922b1127b8575688bd4bc4279670ee28d198ede404709826c7c",
|
||||||
|
"zh:cd900ecf56d21023873898b06e40234f3f4d350f2343b7d9b980d6c5cb604fae",
|
||||||
|
"zh:dbe93b276a84421026b956c3c5b4eb8897da6cbb54b93cb89f5d6ebbd30805ca",
|
||||||
|
"zh:f6b6c7bb2dbf04ee085e5c22f7a65b3ccaebf368ed95584dc0dfee8a22771056",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/external" {
|
||||||
|
version = "2.4.0"
|
||||||
|
constraints = "~> 2.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:AmY6ZeIvqoTT5ZjzD+P49PeQH6Va1QLMkX+7MUQfYoA=",
|
||||||
|
"zh:0772afb42b658468ac5e15df33bf2080456f8f0b8ab163bfe9c50d2b2ea02135",
|
||||||
|
"zh:0ac31a9aaa43dfcff5944b791596cdc94e153348e4bb4642282d034dff548134",
|
||||||
|
"zh:32d8492b1bdcc956ca3c6d00c6392d0a83942ff11d4820c7ee63ca6796e06950",
|
||||||
|
"zh:3c0482e894429f528ce6655a76ab0d8a9f7c0dacc6c828865e1515d4a7dbb852",
|
||||||
|
"zh:61e68100b4db2f930b31491f23c602126382fd5e51252be1b551f0e17f8ddbee",
|
||||||
|
"zh:6d60f615a0ad85eb962c9eb94f25e3eba7a72684ce276ba5dfb23f36b295a8f8",
|
||||||
|
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||||
|
"zh:9ced2745eb5f1346203027d2dd7bf856ad1d279a25730ff7dbc6eec187aaca0c",
|
||||||
|
"zh:a8378558a177d43f55aa0d79d4fae91a704695122a1b109668c1daa8fb76f09d",
|
||||||
|
"zh:aadd98086133d3ebea67437d56512fdcc6dfb3bd34dfc23f276c0db9272e27b4",
|
||||||
|
"zh:beff701b653841e70441978137768f54e7dc6c27e7bf12a4589087f01f5bbcee",
|
||||||
|
"zh:c91c2223b29fdbc0044d20e1936ccc051d010727a13f2ff1e75e51f09bff33a3",
|
||||||
|
"zh:d491f9c2d32a39dc4031628469ae7c8aec0074312a7c1f0286b173cdcf854a54",
|
||||||
|
]
|
||||||
|
}
|
||||||
328
terraform/api.tf
Normal file
328
terraform/api.tf
Normal file
|
|
@ -0,0 +1,328 @@
|
||||||
|
locals {
|
||||||
|
api_policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Sid = "ShocBackendDevDataRead"
|
||||||
|
Effect = "Allow"
|
||||||
|
Principal = {
|
||||||
|
AWS = local.shoc_consumer_role_arn
|
||||||
|
}
|
||||||
|
Action = "execute-api:Invoke"
|
||||||
|
Resource = [
|
||||||
|
"arn:aws:execute-api:${var.aws_region}:${local.account_id}:${local.api_rest_api_id}/${local.api_stage_name}/GET/work-orders",
|
||||||
|
"arn:aws:execute-api:${var.aws_region}:${local.account_id}:${local.api_rest_api_id}/${local.api_stage_name}/GET/work-orders/*",
|
||||||
|
"arn:aws:execute-api:${var.aws_region}:${local.account_id}:${local.api_rest_api_id}/${local.api_stage_name}/GET/purchase-orders",
|
||||||
|
"arn:aws:execute-api:${var.aws_region}:${local.account_id}:${local.api_rest_api_id}/${local.api_stage_name}/GET/purchase-orders/*",
|
||||||
|
"arn:aws:execute-api:${var.aws_region}:${local.account_id}:${local.api_rest_api_id}/${local.api_stage_name}/GET/verified-sites",
|
||||||
|
"arn:aws:execute-api:${var.aws_region}:${local.account_id}:${local.api_rest_api_id}/${local.api_stage_name}/GET/verified-sites/*",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sid = "DocsTokenGatedRoutes"
|
||||||
|
Effect = "Allow"
|
||||||
|
Principal = { AWS = "*" }
|
||||||
|
Action = "execute-api:Invoke"
|
||||||
|
Resource = [
|
||||||
|
"arn:aws:execute-api:${var.aws_region}:${local.account_id}:${local.api_rest_api_id}/${local.api_stage_name}/GET/docs",
|
||||||
|
"arn:aws:execute-api:${var.aws_region}:${local.account_id}:${local.api_rest_api_id}/${local.api_stage_name}/GET/openapi.json",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Live resource IDs (import). parent_key null => API root.
|
||||||
|
api_resources = {
|
||||||
|
work_orders = {
|
||||||
|
id = "h5iu4f"
|
||||||
|
parent_key = null
|
||||||
|
path_part = "work-orders"
|
||||||
|
}
|
||||||
|
work_order_id = {
|
||||||
|
id = "4uk6uh"
|
||||||
|
parent_key = "work_orders"
|
||||||
|
path_part = "{workOrderId}"
|
||||||
|
}
|
||||||
|
work_order_comments = {
|
||||||
|
id = "gfx0te"
|
||||||
|
parent_key = "work_order_id"
|
||||||
|
path_part = "comments"
|
||||||
|
}
|
||||||
|
purchase_orders = {
|
||||||
|
id = "pfn6qm"
|
||||||
|
parent_key = null
|
||||||
|
path_part = "purchase-orders"
|
||||||
|
}
|
||||||
|
po_number = {
|
||||||
|
id = "nclnn3"
|
||||||
|
parent_key = "purchase_orders"
|
||||||
|
path_part = "{poNumber}"
|
||||||
|
}
|
||||||
|
verified_sites = {
|
||||||
|
id = "vyst7e"
|
||||||
|
parent_key = null
|
||||||
|
path_part = "verified-sites"
|
||||||
|
}
|
||||||
|
site_code = {
|
||||||
|
id = "rmaawy"
|
||||||
|
parent_key = "verified_sites"
|
||||||
|
path_part = "{siteCode}"
|
||||||
|
}
|
||||||
|
docs = {
|
||||||
|
id = "k4vl85"
|
||||||
|
parent_key = null
|
||||||
|
path_part = "docs"
|
||||||
|
}
|
||||||
|
openapi_json = {
|
||||||
|
id = "xos715"
|
||||||
|
parent_key = null
|
||||||
|
path_part = "openapi.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api_iam_methods = {
|
||||||
|
work_orders_get = { resource = "work_orders", method = "GET" }
|
||||||
|
work_order_id_get = { resource = "work_order_id", method = "GET" }
|
||||||
|
work_order_id_patch = { resource = "work_order_id", method = "PATCH" }
|
||||||
|
work_order_comments_get = { resource = "work_order_comments", method = "GET" }
|
||||||
|
work_order_comments_post = { resource = "work_order_comments", method = "POST" }
|
||||||
|
purchase_orders_get = { resource = "purchase_orders", method = "GET" }
|
||||||
|
po_number_get = { resource = "po_number", method = "GET" }
|
||||||
|
verified_sites_get = { resource = "verified_sites", method = "GET" }
|
||||||
|
site_code_get = { resource = "site_code", method = "GET" }
|
||||||
|
}
|
||||||
|
|
||||||
|
api_none_methods = {
|
||||||
|
docs_get = { resource = "docs", method = "GET" }
|
||||||
|
openapi_json_get = { resource = "openapi_json", method = "GET" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_log_group" "procurement_api" {
|
||||||
|
name = "/aws/lambda/procurement-api"
|
||||||
|
retention_in_days = 60
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_function" "procurement_api" {
|
||||||
|
function_name = "procurement-api"
|
||||||
|
role = aws_iam_role.procurement_api.arn
|
||||||
|
handler = "handler.handler"
|
||||||
|
runtime = "python3.12"
|
||||||
|
architectures = ["arm64"]
|
||||||
|
memory_size = 256
|
||||||
|
timeout = 30
|
||||||
|
|
||||||
|
s3_bucket = aws_s3_bucket.artifacts.id
|
||||||
|
s3_key = aws_s3_object.lambda["procurement_api"].key
|
||||||
|
source_code_hash = data.archive_file.lambda["procurement_api"].output_base64sha256
|
||||||
|
|
||||||
|
environment {
|
||||||
|
variables = {
|
||||||
|
VERIFIED_SITES_TABLE = aws_dynamodb_table.verified_sites.name
|
||||||
|
WORK_ORDERS_TABLE = aws_dynamodb_table.work_orders.name
|
||||||
|
COMMENTS_TABLE = aws_dynamodb_table.work_order_comments.name
|
||||||
|
PO_TABLE = aws_dynamodb_table.purchase_orders.name
|
||||||
|
WEB_UI_AUTH_TOKEN_SECRET_ARN = var.web_ui_auth_token_secret_arn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
aws_s3_object.lambda,
|
||||||
|
aws_cloudwatch_log_group.procurement_api,
|
||||||
|
aws_iam_role_policy_attachment.procurement_api_basic,
|
||||||
|
aws_iam_role_policy.procurement_api_ddb,
|
||||||
|
aws_iam_role_policy.procurement_api_kms,
|
||||||
|
aws_iam_role_policy.procurement_api_secrets,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_rest_api" "procurement" {
|
||||||
|
name = "procurement-api"
|
||||||
|
description = "Read API over procurement-ingest work orders + purchase orders; token-gated OpenAPI docs at /docs"
|
||||||
|
policy = local.api_policy
|
||||||
|
|
||||||
|
endpoint_configuration {
|
||||||
|
types = ["REGIONAL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_resource" "work_orders" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
parent_id = aws_api_gateway_rest_api.procurement.root_resource_id
|
||||||
|
path_part = "work-orders"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_resource" "work_order_id" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
parent_id = aws_api_gateway_resource.work_orders.id
|
||||||
|
path_part = "{workOrderId}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_resource" "work_order_comments" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
parent_id = aws_api_gateway_resource.work_order_id.id
|
||||||
|
path_part = "comments"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_resource" "purchase_orders" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
parent_id = aws_api_gateway_rest_api.procurement.root_resource_id
|
||||||
|
path_part = "purchase-orders"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_resource" "po_number" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
parent_id = aws_api_gateway_resource.purchase_orders.id
|
||||||
|
path_part = "{poNumber}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_resource" "verified_sites" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
parent_id = aws_api_gateway_rest_api.procurement.root_resource_id
|
||||||
|
path_part = "verified-sites"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_resource" "site_code" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
parent_id = aws_api_gateway_resource.verified_sites.id
|
||||||
|
path_part = "{siteCode}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_resource" "docs" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
parent_id = aws_api_gateway_rest_api.procurement.root_resource_id
|
||||||
|
path_part = "docs"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_resource" "openapi_json" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
parent_id = aws_api_gateway_rest_api.procurement.root_resource_id
|
||||||
|
path_part = "openapi.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
api_resource_ids = {
|
||||||
|
work_orders = aws_api_gateway_resource.work_orders.id
|
||||||
|
work_order_id = aws_api_gateway_resource.work_order_id.id
|
||||||
|
work_order_comments = aws_api_gateway_resource.work_order_comments.id
|
||||||
|
purchase_orders = aws_api_gateway_resource.purchase_orders.id
|
||||||
|
po_number = aws_api_gateway_resource.po_number.id
|
||||||
|
verified_sites = aws_api_gateway_resource.verified_sites.id
|
||||||
|
site_code = aws_api_gateway_resource.site_code.id
|
||||||
|
docs = aws_api_gateway_resource.docs.id
|
||||||
|
openapi_json = aws_api_gateway_resource.openapi_json.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_method" "iam" {
|
||||||
|
for_each = local.api_iam_methods
|
||||||
|
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
resource_id = local.api_resource_ids[each.value.resource]
|
||||||
|
http_method = each.value.method
|
||||||
|
authorization = "AWS_IAM"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_method" "none" {
|
||||||
|
for_each = local.api_none_methods
|
||||||
|
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
resource_id = local.api_resource_ids[each.value.resource]
|
||||||
|
http_method = each.value.method
|
||||||
|
authorization = "NONE"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_integration" "iam" {
|
||||||
|
for_each = local.api_iam_methods
|
||||||
|
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
resource_id = local.api_resource_ids[each.value.resource]
|
||||||
|
http_method = aws_api_gateway_method.iam[each.key].http_method
|
||||||
|
integration_http_method = "POST"
|
||||||
|
type = "AWS_PROXY"
|
||||||
|
uri = aws_lambda_function.procurement_api.invoke_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_integration" "none" {
|
||||||
|
for_each = local.api_none_methods
|
||||||
|
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
resource_id = local.api_resource_ids[each.value.resource]
|
||||||
|
http_method = aws_api_gateway_method.none[each.key].http_method
|
||||||
|
integration_http_method = "POST"
|
||||||
|
type = "AWS_PROXY"
|
||||||
|
uri = aws_lambda_function.procurement_api.invoke_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_permission" "api_gateway" {
|
||||||
|
statement_id = "AllowAPIGatewayInvoke"
|
||||||
|
action = "lambda:InvokeFunction"
|
||||||
|
function_name = aws_lambda_function.procurement_api.function_name
|
||||||
|
principal = "apigateway.amazonaws.com"
|
||||||
|
source_arn = "${aws_api_gateway_rest_api.procurement.execution_arn}/*/*"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_deployment" "procurement" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
|
||||||
|
triggers = {
|
||||||
|
redeployment = sha1(jsonencode({
|
||||||
|
resources = local.api_resources
|
||||||
|
iam = local.api_iam_methods
|
||||||
|
none = local.api_none_methods
|
||||||
|
policy = local.api_policy
|
||||||
|
lambda_hash = data.archive_file.lambda["procurement_api"].output_base64sha256
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
create_before_destroy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
aws_api_gateway_integration.iam,
|
||||||
|
aws_api_gateway_integration.none,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_stage" "prod" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
deployment_id = aws_api_gateway_deployment.procurement.id
|
||||||
|
stage_name = local.api_stage_name
|
||||||
|
|
||||||
|
xray_tracing_enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_method_settings" "prod_all" {
|
||||||
|
rest_api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
stage_name = aws_api_gateway_stage.prod.stage_name
|
||||||
|
method_path = "*/*"
|
||||||
|
|
||||||
|
settings {
|
||||||
|
metrics_enabled = false
|
||||||
|
logging_level = "OFF"
|
||||||
|
data_trace_enabled = false
|
||||||
|
throttling_burst_limit = 100
|
||||||
|
throttling_rate_limit = 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_domain_name" "procurement" {
|
||||||
|
domain_name = local.api_domain_name
|
||||||
|
regional_certificate_arn = data.aws_ssm_parameter.procurement_api_cert_arn.value
|
||||||
|
security_policy = "TLS_1_2"
|
||||||
|
|
||||||
|
endpoint_configuration {
|
||||||
|
types = ["REGIONAL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_api_gateway_base_path_mapping" "procurement" {
|
||||||
|
api_id = aws_api_gateway_rest_api.procurement.id
|
||||||
|
stage_name = aws_api_gateway_stage.prod.stage_name
|
||||||
|
domain_name = aws_api_gateway_domain_name.procurement.domain_name
|
||||||
|
}
|
||||||
73
terraform/api_alarms.tf
Normal file
73
terraform/api_alarms.tf
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "procurement_api_errors" {
|
||||||
|
alarm_name = "procurement-api-errors"
|
||||||
|
alarm_description = "procurement-api Lambda raised (bundle/init failures; the handler catches request errors, so any signal here is structural)"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Errors"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.procurement_api.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "procurement_api_throttles" {
|
||||||
|
alarm_name = "procurement-api-throttles"
|
||||||
|
alarm_description = "procurement-api Lambda throttled"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Throttles"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.procurement_api.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "procurement_api_duration" {
|
||||||
|
alarm_name = "procurement-api-duration"
|
||||||
|
alarm_description = "procurement-api p99 duration >= 22.5s (75% of the 30s timeout; scans degrading toward timeout)"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 3
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "Duration"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
extended_statistic = "p99"
|
||||||
|
threshold = 22500
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.procurement_api.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "procurement_api_5xx" {
|
||||||
|
alarm_name = "procurement-api-5xx"
|
||||||
|
alarm_description = "procurement-api gateway 5XX responses"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "5XXError"
|
||||||
|
namespace = "AWS/ApiGateway"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
ApiName = "procurement-api"
|
||||||
|
Stage = local.api_stage_name
|
||||||
|
}
|
||||||
|
}
|
||||||
55
terraform/artifacts.tf
Normal file
55
terraform/artifacts.tf
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# HCP plan and apply run on separate workers. archive_file paths from plan are
|
||||||
|
# not on the apply worker, so zip bytes are carried in the plan via
|
||||||
|
# content_base64 and uploaded to S3 at apply time for Lambda to consume.
|
||||||
|
#
|
||||||
|
# Package build runs during plan via external data (local-exec provisioners
|
||||||
|
# only run on apply; archive_file needs build/ present at plan time).
|
||||||
|
|
||||||
|
data "external" "package_build" {
|
||||||
|
program = ["bash", "${path.module}/build_packages_external.sh"]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket" "artifacts" {
|
||||||
|
bucket = "procurement-ingest-artifacts-${local.account_id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_public_access_block" "artifacts" {
|
||||||
|
bucket = aws_s3_bucket.artifacts.id
|
||||||
|
|
||||||
|
block_public_acls = true
|
||||||
|
block_public_policy = true
|
||||||
|
ignore_public_acls = true
|
||||||
|
restrict_public_buckets = true
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
lambda_packages = {
|
||||||
|
po_email_processor = "po-email-processor.zip"
|
||||||
|
po_web_ui = "po-web-ui.zip"
|
||||||
|
po_site_extractor = "po-ingest-site-extractor.zip"
|
||||||
|
wo_email_processor = "workorder-email-processor.zip"
|
||||||
|
wo_web_ui = "workorder-web-ui.zip"
|
||||||
|
wo_shoc_emitter = "workorder-shoc-emitter.zip"
|
||||||
|
wo_shoc_hmac_rotator = "workorder-shoc-hmac-rotator.zip"
|
||||||
|
procurement_api = "procurement-api.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data "archive_file" "lambda" {
|
||||||
|
for_each = local.lambda_packages
|
||||||
|
|
||||||
|
type = "zip"
|
||||||
|
source_dir = "${path.module}/build/${each.key}"
|
||||||
|
output_path = "${path.module}/build/${each.value}"
|
||||||
|
|
||||||
|
depends_on = [data.external.package_build]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_object" "lambda" {
|
||||||
|
for_each = local.lambda_packages
|
||||||
|
|
||||||
|
bucket = aws_s3_bucket.artifacts.id
|
||||||
|
key = each.value
|
||||||
|
content_base64 = filebase64(data.archive_file.lambda[each.key].output_path)
|
||||||
|
source_hash = data.archive_file.lambda[each.key].output_base64sha256
|
||||||
|
}
|
||||||
152
terraform/build_packages.sh
Executable file
152
terraform/build_packages.sh
Executable file
|
|
@ -0,0 +1,152 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Package Lambda zips for HCP plan/apply. Runs on the Terraform worker.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
BUILD="${ROOT}/build"
|
||||||
|
SRC="$(cd "${ROOT}/../lambdas" && pwd)"
|
||||||
|
|
||||||
|
# Copy only regular files that resolve inside SRC (no symlink escape).
|
||||||
|
copy_src_file() {
|
||||||
|
local rel="$1"
|
||||||
|
local dest="$2"
|
||||||
|
local src_path="${SRC}/${rel}"
|
||||||
|
|
||||||
|
if [[ -L "${src_path}" ]]; then
|
||||||
|
echo "error: refusing symlink source: ${src_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -f "${src_path}" ]]; then
|
||||||
|
echo "error: missing regular file: ${src_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local resolved
|
||||||
|
resolved="$(cd "$(dirname "${src_path}")" && pwd)/$(basename "${src_path}")"
|
||||||
|
case "${resolved}" in
|
||||||
|
"${SRC}"/*) ;;
|
||||||
|
*)
|
||||||
|
echo "error: path escapes src tree: ${resolved}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "${dest}")"
|
||||||
|
cp -P "${src_path}" "${dest}"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_py_dir() {
|
||||||
|
local rel_dir="$1"
|
||||||
|
local dest_dir="$2"
|
||||||
|
local f
|
||||||
|
mkdir -p "${dest_dir}"
|
||||||
|
for f in "${SRC}/${rel_dir}"/*.py; do
|
||||||
|
[[ -f "${f}" ]] || continue
|
||||||
|
copy_src_file "${rel_dir}/$(basename "${f}")" "${dest_dir}/$(basename "${f}")"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_shared_all() {
|
||||||
|
local dest_dir="$1"
|
||||||
|
local f
|
||||||
|
for f in "${SRC}/shared"/*.py; do
|
||||||
|
[[ -f "${f}" ]] || continue
|
||||||
|
copy_src_file "shared/$(basename "${f}")" "${dest_dir}/$(basename "${f}")"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_pip_install() {
|
||||||
|
local dest_dir="$1"
|
||||||
|
local req="${dest_dir}/requirements.txt"
|
||||||
|
if [[ ! -f "${req}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local deps
|
||||||
|
deps="$(grep -Ev '^[[:space:]]*(#|$)' "${req}" || true)"
|
||||||
|
if [[ -z "${deps}" ]]; then
|
||||||
|
rm -f "${req}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# boto3-only pins are Dependabot anchors; runtime provides boto3.
|
||||||
|
if ! printf '%s\n' "${deps}" | grep -Eqv '^[[:space:]]*boto3([=<!> ]|$)'; then
|
||||||
|
rm -f "${req}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 -m pip install \
|
||||||
|
--quiet \
|
||||||
|
--disable-pip-version-check \
|
||||||
|
-r "${req}" \
|
||||||
|
-t "${dest_dir}/" \
|
||||||
|
--platform manylinux2014_aarch64 \
|
||||||
|
--implementation cp \
|
||||||
|
--python-version 3.12 \
|
||||||
|
--only-binary=:all: \
|
||||||
|
--upgrade
|
||||||
|
|
||||||
|
rm -rf "${dest_dir}/boto3" "${dest_dir}/botocore" \
|
||||||
|
"${dest_dir}/s3transfer" "${dest_dir}/jmespath" \
|
||||||
|
"${dest_dir}/"*.dist-info 2>/dev/null || true
|
||||||
|
rm -f "${req}"
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -rf "${BUILD}"
|
||||||
|
mkdir -p \
|
||||||
|
"${BUILD}/po_email_processor" \
|
||||||
|
"${BUILD}/po_web_ui" \
|
||||||
|
"${BUILD}/po_site_extractor" \
|
||||||
|
"${BUILD}/wo_email_processor" \
|
||||||
|
"${BUILD}/wo_web_ui" \
|
||||||
|
"${BUILD}/wo_shoc_emitter" \
|
||||||
|
"${BUILD}/wo_shoc_hmac_rotator" \
|
||||||
|
"${BUILD}/procurement_api"
|
||||||
|
|
||||||
|
copy_py_dir "po/email_processor" "${BUILD}/po_email_processor"
|
||||||
|
copy_shared_all "${BUILD}/po_email_processor"
|
||||||
|
if [[ -f "${SRC}/po/email_processor/requirements.txt" ]]; then
|
||||||
|
copy_src_file "po/email_processor/requirements.txt" "${BUILD}/po_email_processor/requirements.txt"
|
||||||
|
fi
|
||||||
|
maybe_pip_install "${BUILD}/po_email_processor"
|
||||||
|
|
||||||
|
copy_py_dir "po/web_ui" "${BUILD}/po_web_ui"
|
||||||
|
copy_src_file "shared/web_ui_auth.py" "${BUILD}/po_web_ui/web_ui_auth.py"
|
||||||
|
if [[ -f "${SRC}/po/web_ui/requirements.txt" ]]; then
|
||||||
|
copy_src_file "po/web_ui/requirements.txt" "${BUILD}/po_web_ui/requirements.txt"
|
||||||
|
fi
|
||||||
|
maybe_pip_install "${BUILD}/po_web_ui"
|
||||||
|
|
||||||
|
copy_py_dir "po/site_extractor" "${BUILD}/po_site_extractor"
|
||||||
|
if [[ -f "${SRC}/po/site_extractor/requirements.txt" ]]; then
|
||||||
|
copy_src_file "po/site_extractor/requirements.txt" "${BUILD}/po_site_extractor/requirements.txt"
|
||||||
|
fi
|
||||||
|
maybe_pip_install "${BUILD}/po_site_extractor"
|
||||||
|
|
||||||
|
copy_py_dir "wo/email_processor" "${BUILD}/wo_email_processor"
|
||||||
|
copy_shared_all "${BUILD}/wo_email_processor"
|
||||||
|
if [[ -f "${SRC}/wo/email_processor/requirements.txt" ]]; then
|
||||||
|
copy_src_file "wo/email_processor/requirements.txt" "${BUILD}/wo_email_processor/requirements.txt"
|
||||||
|
fi
|
||||||
|
maybe_pip_install "${BUILD}/wo_email_processor"
|
||||||
|
|
||||||
|
copy_py_dir "wo/web_ui" "${BUILD}/wo_web_ui"
|
||||||
|
copy_src_file "shared/web_ui_auth.py" "${BUILD}/wo_web_ui/web_ui_auth.py"
|
||||||
|
if [[ -f "${SRC}/wo/web_ui/requirements.txt" ]]; then
|
||||||
|
copy_src_file "wo/web_ui/requirements.txt" "${BUILD}/wo_web_ui/requirements.txt"
|
||||||
|
fi
|
||||||
|
maybe_pip_install "${BUILD}/wo_web_ui"
|
||||||
|
|
||||||
|
copy_py_dir "wo/shoc_emitter" "${BUILD}/wo_shoc_emitter"
|
||||||
|
|
||||||
|
copy_py_dir "wo/shoc_hmac_rotator" "${BUILD}/wo_shoc_hmac_rotator"
|
||||||
|
|
||||||
|
copy_py_dir "api" "${BUILD}/procurement_api"
|
||||||
|
for f in openapi.json docs.html redoc.standalone.js fonts.css; do
|
||||||
|
copy_src_file "api/${f}" "${BUILD}/procurement_api/${f}"
|
||||||
|
done
|
||||||
|
copy_src_file "shared/web_ui_auth.py" "${BUILD}/procurement_api/web_ui_auth.py"
|
||||||
|
if [[ -f "${SRC}/api/requirements.txt" ]]; then
|
||||||
|
copy_src_file "api/requirements.txt" "${BUILD}/procurement_api/requirements.txt"
|
||||||
|
fi
|
||||||
|
maybe_pip_install "${BUILD}/procurement_api"
|
||||||
25
terraform/build_packages_external.sh
Executable file
25
terraform/build_packages_external.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Terraform external data source entrypoint. Stdout must be JSON only.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
"${ROOT}/build_packages.sh" >&2
|
||||||
|
|
||||||
|
hash="$(
|
||||||
|
{
|
||||||
|
find -P \
|
||||||
|
"${ROOT}/build/po_email_processor" \
|
||||||
|
"${ROOT}/build/po_web_ui" \
|
||||||
|
"${ROOT}/build/po_site_extractor" \
|
||||||
|
"${ROOT}/build/wo_email_processor" \
|
||||||
|
"${ROOT}/build/wo_web_ui" \
|
||||||
|
"${ROOT}/build/wo_shoc_emitter" \
|
||||||
|
"${ROOT}/build/wo_shoc_hmac_rotator" \
|
||||||
|
"${ROOT}/build/procurement_api" \
|
||||||
|
-type f -print0 2>/dev/null \
|
||||||
|
| sort -z \
|
||||||
|
| xargs -0 sha256sum
|
||||||
|
} | sha256sum | awk '{print $1}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
printf '{"status":"ok","hash":"%s"}\n' "${hash}"
|
||||||
658
terraform/iam.tf
Normal file
658
terraform/iam.tf
Normal file
|
|
@ -0,0 +1,658 @@
|
||||||
|
data "aws_iam_policy_document" "lambda_assume" {
|
||||||
|
statement {
|
||||||
|
effect = "Allow"
|
||||||
|
actions = ["sts:AssumeRole"]
|
||||||
|
|
||||||
|
principals {
|
||||||
|
type = "Service"
|
||||||
|
identifiers = ["lambda.amazonaws.com"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
bedrock_invoke_policy_json = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"bedrock:InvokeModel",
|
||||||
|
"bedrock:InvokeModelWithResponseStream",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
"arn:aws:bedrock:${var.aws_region}:${local.account_id}:inference-profile/${local.bedrock_model_id}",
|
||||||
|
"arn:aws:bedrock:${var.aws_region}::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||||
|
"arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||||
|
"arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
dynamodb_cmk_rw_policy_json = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"kms:Decrypt",
|
||||||
|
"kms:DescribeKey",
|
||||||
|
"kms:Encrypt",
|
||||||
|
"kms:GenerateDataKey*",
|
||||||
|
"kms:ReEncrypt*",
|
||||||
|
]
|
||||||
|
Resource = [data.aws_ssm_parameter.dynamodb_cmk_arn.value]
|
||||||
|
Condition = {
|
||||||
|
StringEquals = {
|
||||||
|
"kms:ViaService" = "dynamodb.${var.aws_region}.amazonaws.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
dynamodb_cmk_read_policy_json = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"kms:Decrypt",
|
||||||
|
"kms:DescribeKey",
|
||||||
|
]
|
||||||
|
Resource = [data.aws_ssm_parameter.dynamodb_cmk_arn.value]
|
||||||
|
Condition = {
|
||||||
|
StringEquals = {
|
||||||
|
"kms:ViaService" = "dynamodb.${var.aws_region}.amazonaws.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
web_ui_auth_secret_policy_json = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = ["secretsmanager:GetSecretValue"]
|
||||||
|
Resource = [var.web_ui_auth_token_secret_arn]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# po-email-processor
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
resource "aws_iam_role" "po_email_processor" {
|
||||||
|
name = "po-email-processor"
|
||||||
|
path = "/tf-managed/"
|
||||||
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||||||
|
permissions_boundary = local.boundary_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy_attachment" "po_email_processor_basic" {
|
||||||
|
role = aws_iam_role.po_email_processor.name
|
||||||
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_email_processor_s3" {
|
||||||
|
name = "s3-read-emails"
|
||||||
|
role = aws_iam_role.po_email_processor.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"s3:GetObject",
|
||||||
|
"s3:GetObjectVersion",
|
||||||
|
"s3:ListBucket",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
aws_s3_bucket.po_emails.arn,
|
||||||
|
"${aws_s3_bucket.po_emails.arn}/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_email_processor_ddb" {
|
||||||
|
name = "dynamodb-rw"
|
||||||
|
role = aws_iam_role.po_email_processor.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"dynamodb:BatchGetItem",
|
||||||
|
"dynamodb:BatchWriteItem",
|
||||||
|
"dynamodb:ConditionCheckItem",
|
||||||
|
"dynamodb:DeleteItem",
|
||||||
|
"dynamodb:DescribeTable",
|
||||||
|
"dynamodb:GetItem",
|
||||||
|
"dynamodb:PutItem",
|
||||||
|
"dynamodb:Query",
|
||||||
|
"dynamodb:Scan",
|
||||||
|
"dynamodb:UpdateItem",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
aws_dynamodb_table.purchase_orders.arn,
|
||||||
|
"${aws_dynamodb_table.purchase_orders.arn}/index/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_email_processor_kms" {
|
||||||
|
name = "dynamodb-cmk"
|
||||||
|
role = aws_iam_role.po_email_processor.id
|
||||||
|
policy = local.dynamodb_cmk_rw_policy_json
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_email_processor_bedrock" {
|
||||||
|
name = "bedrock-invoke"
|
||||||
|
role = aws_iam_role.po_email_processor.id
|
||||||
|
policy = local.bedrock_invoke_policy_json
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_email_processor_dlq" {
|
||||||
|
name = "sqs-dlq-send"
|
||||||
|
role = aws_iam_role.po_email_processor.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = ["sqs:SendMessage"]
|
||||||
|
Resource = [aws_sqs_queue.po_email_processor_dlq.arn]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# po-web-ui
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
resource "aws_iam_role" "po_web_ui" {
|
||||||
|
name = "po-web-ui"
|
||||||
|
path = "/tf-managed/"
|
||||||
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||||||
|
permissions_boundary = local.boundary_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy_attachment" "po_web_ui_basic" {
|
||||||
|
role = aws_iam_role.po_web_ui.name
|
||||||
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_web_ui_ddb" {
|
||||||
|
name = "dynamodb-read"
|
||||||
|
role = aws_iam_role.po_web_ui.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"dynamodb:BatchGetItem",
|
||||||
|
"dynamodb:ConditionCheckItem",
|
||||||
|
"dynamodb:DescribeTable",
|
||||||
|
"dynamodb:GetItem",
|
||||||
|
"dynamodb:Query",
|
||||||
|
"dynamodb:Scan",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
aws_dynamodb_table.purchase_orders.arn,
|
||||||
|
"${aws_dynamodb_table.purchase_orders.arn}/index/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_web_ui_kms" {
|
||||||
|
name = "dynamodb-cmk"
|
||||||
|
role = aws_iam_role.po_web_ui.id
|
||||||
|
policy = local.dynamodb_cmk_read_policy_json
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_web_ui_secrets" {
|
||||||
|
name = "secretsmanager-get"
|
||||||
|
role = aws_iam_role.po_web_ui.id
|
||||||
|
policy = local.web_ui_auth_secret_policy_json
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# po-site-extractor
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
resource "aws_iam_role" "po_site_extractor" {
|
||||||
|
name = "po-site-extractor"
|
||||||
|
path = "/tf-managed/"
|
||||||
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||||||
|
permissions_boundary = local.boundary_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy_attachment" "po_site_extractor_basic" {
|
||||||
|
role = aws_iam_role.po_site_extractor.name
|
||||||
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_site_extractor_ddb" {
|
||||||
|
name = "dynamodb-rw-sites"
|
||||||
|
role = aws_iam_role.po_site_extractor.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"dynamodb:BatchGetItem",
|
||||||
|
"dynamodb:BatchWriteItem",
|
||||||
|
"dynamodb:ConditionCheckItem",
|
||||||
|
"dynamodb:DeleteItem",
|
||||||
|
"dynamodb:DescribeTable",
|
||||||
|
"dynamodb:GetItem",
|
||||||
|
"dynamodb:PutItem",
|
||||||
|
"dynamodb:Query",
|
||||||
|
"dynamodb:Scan",
|
||||||
|
"dynamodb:UpdateItem",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
aws_dynamodb_table.verified_sites.arn,
|
||||||
|
"${aws_dynamodb_table.verified_sites.arn}/index/*",
|
||||||
|
aws_dynamodb_table.pending_site_review.arn,
|
||||||
|
"${aws_dynamodb_table.pending_site_review.arn}/index/*",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"dynamodb:DescribeStream",
|
||||||
|
"dynamodb:GetRecords",
|
||||||
|
"dynamodb:GetShardIterator",
|
||||||
|
"dynamodb:ListStreams",
|
||||||
|
]
|
||||||
|
Resource = [aws_dynamodb_table.purchase_orders.stream_arn]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = ["dynamodb:DescribeTable"]
|
||||||
|
Resource = [aws_dynamodb_table.purchase_orders.arn]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "po_site_extractor_kms" {
|
||||||
|
name = "dynamodb-cmk"
|
||||||
|
role = aws_iam_role.po_site_extractor.id
|
||||||
|
policy = local.dynamodb_cmk_read_policy_json
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# workorder-email-processor
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
resource "aws_iam_role" "wo_email_processor" {
|
||||||
|
name = "workorder-email-processor"
|
||||||
|
path = "/tf-managed/"
|
||||||
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||||||
|
permissions_boundary = local.boundary_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy_attachment" "wo_email_processor_basic" {
|
||||||
|
role = aws_iam_role.wo_email_processor.name
|
||||||
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_email_processor_s3" {
|
||||||
|
name = "s3-read-emails"
|
||||||
|
role = aws_iam_role.wo_email_processor.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"s3:GetObject",
|
||||||
|
"s3:GetObjectVersion",
|
||||||
|
"s3:ListBucket",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
aws_s3_bucket.wo_emails.arn,
|
||||||
|
"${aws_s3_bucket.wo_emails.arn}/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_email_processor_ddb" {
|
||||||
|
name = "dynamodb-rw"
|
||||||
|
role = aws_iam_role.wo_email_processor.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"dynamodb:BatchGetItem",
|
||||||
|
"dynamodb:BatchWriteItem",
|
||||||
|
"dynamodb:ConditionCheckItem",
|
||||||
|
"dynamodb:DeleteItem",
|
||||||
|
"dynamodb:DescribeTable",
|
||||||
|
"dynamodb:GetItem",
|
||||||
|
"dynamodb:PutItem",
|
||||||
|
"dynamodb:Query",
|
||||||
|
"dynamodb:Scan",
|
||||||
|
"dynamodb:UpdateItem",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
aws_dynamodb_table.work_orders.arn,
|
||||||
|
"${aws_dynamodb_table.work_orders.arn}/index/*",
|
||||||
|
aws_dynamodb_table.work_order_comments.arn,
|
||||||
|
"${aws_dynamodb_table.work_order_comments.arn}/index/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_email_processor_bedrock" {
|
||||||
|
name = "bedrock-invoke"
|
||||||
|
role = aws_iam_role.wo_email_processor.id
|
||||||
|
policy = local.bedrock_invoke_policy_json
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_email_processor_dlq" {
|
||||||
|
name = "sqs-dlq-send"
|
||||||
|
role = aws_iam_role.wo_email_processor.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = ["sqs:SendMessage"]
|
||||||
|
Resource = [aws_sqs_queue.wo_email_processor_dlq.arn]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# workorder-web-ui
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
resource "aws_iam_role" "wo_web_ui" {
|
||||||
|
name = "workorder-web-ui"
|
||||||
|
path = "/tf-managed/"
|
||||||
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||||||
|
permissions_boundary = local.boundary_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy_attachment" "wo_web_ui_basic" {
|
||||||
|
role = aws_iam_role.wo_web_ui.name
|
||||||
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_web_ui_ddb" {
|
||||||
|
name = "dynamodb-read"
|
||||||
|
role = aws_iam_role.wo_web_ui.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"dynamodb:BatchGetItem",
|
||||||
|
"dynamodb:ConditionCheckItem",
|
||||||
|
"dynamodb:DescribeTable",
|
||||||
|
"dynamodb:GetItem",
|
||||||
|
"dynamodb:Query",
|
||||||
|
"dynamodb:Scan",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
aws_dynamodb_table.work_orders.arn,
|
||||||
|
"${aws_dynamodb_table.work_orders.arn}/index/*",
|
||||||
|
aws_dynamodb_table.work_order_comments.arn,
|
||||||
|
"${aws_dynamodb_table.work_order_comments.arn}/index/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_web_ui_secrets" {
|
||||||
|
name = "secretsmanager-get"
|
||||||
|
role = aws_iam_role.wo_web_ui.id
|
||||||
|
policy = local.web_ui_auth_secret_policy_json
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# workorder-shoc-emitter
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
resource "aws_iam_role" "wo_shoc_emitter" {
|
||||||
|
name = "workorder-shoc-emitter"
|
||||||
|
path = "/tf-managed/"
|
||||||
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||||||
|
permissions_boundary = local.boundary_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy_attachment" "wo_shoc_emitter_basic" {
|
||||||
|
role = aws_iam_role.wo_shoc_emitter.name
|
||||||
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_shoc_emitter_streams" {
|
||||||
|
name = "dynamodb-stream-read"
|
||||||
|
role = aws_iam_role.wo_shoc_emitter.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"dynamodb:DescribeStream",
|
||||||
|
"dynamodb:GetRecords",
|
||||||
|
"dynamodb:GetShardIterator",
|
||||||
|
"dynamodb:ListStreams",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
aws_dynamodb_table.work_orders.stream_arn,
|
||||||
|
aws_dynamodb_table.work_order_comments.stream_arn,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = ["dynamodb:DescribeTable"]
|
||||||
|
Resource = [
|
||||||
|
aws_dynamodb_table.work_orders.arn,
|
||||||
|
aws_dynamodb_table.work_order_comments.arn,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_shoc_emitter_secrets" {
|
||||||
|
name = "secretsmanager-get"
|
||||||
|
role = aws_iam_role.wo_shoc_emitter.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"]
|
||||||
|
Resource = [var.shoc_hmac_secret_arn]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_shoc_emitter_kms" {
|
||||||
|
name = "shoc-hmac-kms"
|
||||||
|
role = aws_iam_role.wo_shoc_emitter.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = ["kms:Decrypt"]
|
||||||
|
Resource = [aws_kms_key.shoc_webhook.arn]
|
||||||
|
Condition = {
|
||||||
|
StringEquals = {
|
||||||
|
"kms:ViaService" = "secretsmanager.${var.aws_region}.amazonaws.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_shoc_emitter_sqs" {
|
||||||
|
name = "sqs-send"
|
||||||
|
role = aws_iam_role.wo_shoc_emitter.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = ["sqs:SendMessage"]
|
||||||
|
Resource = [
|
||||||
|
aws_sqs_queue.shoc_emitter_rejected.arn,
|
||||||
|
aws_sqs_queue.shoc_emitter_failures.arn,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# workorder-shoc-hmac-rotator
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
resource "aws_iam_role" "wo_shoc_hmac_rotator" {
|
||||||
|
name = "workorder-shoc-hmac-rotator"
|
||||||
|
path = "/tf-managed/"
|
||||||
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||||||
|
permissions_boundary = local.boundary_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy_attachment" "wo_shoc_hmac_rotator_basic" {
|
||||||
|
role = aws_iam_role.wo_shoc_hmac_rotator.name
|
||||||
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_shoc_hmac_rotator_secrets" {
|
||||||
|
name = "secretsmanager-rotate"
|
||||||
|
role = aws_iam_role.wo_shoc_hmac_rotator.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"secretsmanager:DescribeSecret",
|
||||||
|
"secretsmanager:GetSecretValue",
|
||||||
|
"secretsmanager:PutSecretValue",
|
||||||
|
"secretsmanager:UpdateSecretVersionStage",
|
||||||
|
]
|
||||||
|
Resource = [var.shoc_hmac_secret_arn]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "wo_shoc_hmac_rotator_kms" {
|
||||||
|
name = "shoc-hmac-kms"
|
||||||
|
role = aws_iam_role.wo_shoc_hmac_rotator.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"kms:Decrypt",
|
||||||
|
"kms:Encrypt",
|
||||||
|
"kms:GenerateDataKey*",
|
||||||
|
"kms:ReEncrypt*",
|
||||||
|
]
|
||||||
|
Resource = [aws_kms_key.shoc_webhook.arn]
|
||||||
|
Condition = {
|
||||||
|
StringEquals = {
|
||||||
|
"kms:ViaService" = "secretsmanager.${var.aws_region}.amazonaws.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# procurement-api
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
resource "aws_iam_role" "procurement_api" {
|
||||||
|
name = "procurement-api"
|
||||||
|
path = "/tf-managed/"
|
||||||
|
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||||||
|
permissions_boundary = local.boundary_arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy_attachment" "procurement_api_basic" {
|
||||||
|
role = aws_iam_role.procurement_api.name
|
||||||
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "procurement_api_ddb" {
|
||||||
|
name = "dynamodb-read"
|
||||||
|
role = aws_iam_role.procurement_api.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Action = [
|
||||||
|
"dynamodb:BatchGetItem",
|
||||||
|
"dynamodb:ConditionCheckItem",
|
||||||
|
"dynamodb:DescribeTable",
|
||||||
|
"dynamodb:GetItem",
|
||||||
|
"dynamodb:Query",
|
||||||
|
"dynamodb:Scan",
|
||||||
|
]
|
||||||
|
Resource = [
|
||||||
|
aws_dynamodb_table.purchase_orders.arn,
|
||||||
|
"${aws_dynamodb_table.purchase_orders.arn}/index/*",
|
||||||
|
aws_dynamodb_table.verified_sites.arn,
|
||||||
|
"${aws_dynamodb_table.verified_sites.arn}/index/*",
|
||||||
|
aws_dynamodb_table.work_orders.arn,
|
||||||
|
"${aws_dynamodb_table.work_orders.arn}/index/*",
|
||||||
|
aws_dynamodb_table.work_order_comments.arn,
|
||||||
|
"${aws_dynamodb_table.work_order_comments.arn}/index/*",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "procurement_api_kms" {
|
||||||
|
name = "dynamodb-cmk"
|
||||||
|
role = aws_iam_role.procurement_api.id
|
||||||
|
policy = local.dynamodb_cmk_read_policy_json
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_iam_role_policy" "procurement_api_secrets" {
|
||||||
|
name = "secretsmanager-get"
|
||||||
|
role = aws_iam_role.procurement_api.id
|
||||||
|
policy = local.web_ui_auth_secret_policy_json
|
||||||
|
}
|
||||||
595
terraform/imports.tf
Normal file
595
terraform/imports.tf
Normal file
|
|
@ -0,0 +1,595 @@
|
||||||
|
# Import map: docs/plat-86/import-map.md (seahaven-prod, 2026-08-06).
|
||||||
|
# IAM roles are NOT imported — new /tf-managed/ roles replace CDK roles on cutover.
|
||||||
|
|
||||||
|
# --- DynamoDB ---
|
||||||
|
import {
|
||||||
|
to = aws_dynamodb_table.purchase_orders
|
||||||
|
id = "purchase-orders"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_dynamodb_table.verified_sites
|
||||||
|
id = "verified-sites"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_dynamodb_table.pending_site_review
|
||||||
|
id = "pending-site-review"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_dynamodb_table.work_orders
|
||||||
|
id = "WorkOrders"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_dynamodb_table.work_order_comments
|
||||||
|
id = "WorkOrderComments"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- S3 email buckets ---
|
||||||
|
import {
|
||||||
|
to = aws_s3_bucket.po_emails
|
||||||
|
id = "po-ingest-emails-011934824531"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_s3_bucket.wo_emails
|
||||||
|
id = "workorder-ingest-emails-011934824531"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- SQS ---
|
||||||
|
import {
|
||||||
|
to = aws_sqs_queue.po_email_processor_dlq
|
||||||
|
id = "https://sqs.us-east-1.amazonaws.com/011934824531/po-ingest-EmailProcessorDlqA753DED5-Mn33HvhsDPEu"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_sqs_queue.wo_email_processor_dlq
|
||||||
|
id = "https://sqs.us-east-1.amazonaws.com/011934824531/WorkorderIngestStack-EmailProcessorDlqA753DED5-qCTHrsoEucas"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_sqs_queue.shoc_emitter_failures
|
||||||
|
id = "https://sqs.us-east-1.amazonaws.com/011934824531/workorder-shoc-emitter-failures"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_sqs_queue.shoc_emitter_rejected
|
||||||
|
id = "https://sqs.us-east-1.amazonaws.com/011934824531/workorder-shoc-emitter-rejected"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Lambdas ---
|
||||||
|
import {
|
||||||
|
to = aws_lambda_function.po_email_processor
|
||||||
|
id = "po-email-processor"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_lambda_function.po_web_ui
|
||||||
|
id = "po-web-ui"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_lambda_function.po_site_extractor
|
||||||
|
id = "po-ingest-site-extractor"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_lambda_function.wo_email_processor
|
||||||
|
id = "workorder-email-processor"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_lambda_function.wo_web_ui
|
||||||
|
id = "workorder-web-ui"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_lambda_function.wo_shoc_emitter
|
||||||
|
id = "workorder-shoc-emitter"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_lambda_function.wo_shoc_hmac_rotator
|
||||||
|
id = "workorder-shoc-hmac-rotator"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_lambda_function.procurement_api
|
||||||
|
id = "procurement-api"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Event source mappings ---
|
||||||
|
import {
|
||||||
|
to = aws_lambda_event_source_mapping.po_site_extractor
|
||||||
|
id = "64fbee1f-d9c3-4cfd-aced-19b9d29940f8"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_lambda_event_source_mapping.wo_shoc_emitter_work_orders
|
||||||
|
id = "cc149f4d-5375-4820-8fb8-c514accd3a85"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_lambda_event_source_mapping.wo_shoc_emitter_comments
|
||||||
|
id = "da37d4a0-3086-4929-9ace-6eca5c20cd07"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Log groups ---
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_group.po_email_processor
|
||||||
|
id = "/aws/lambda/po-email-processor"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_group.po_web_ui
|
||||||
|
id = "/aws/lambda/po-web-ui"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_group.po_site_extractor
|
||||||
|
id = "/aws/lambda/po-ingest-site-extractor"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_group.wo_email_processor
|
||||||
|
id = "/aws/lambda/workorder-email-processor"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_group.wo_web_ui
|
||||||
|
id = "/aws/lambda/workorder-web-ui"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_group.wo_shoc_emitter
|
||||||
|
id = "/aws/lambda/workorder-shoc-emitter"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_group.wo_shoc_hmac_rotator
|
||||||
|
id = "/aws/lambda/workorder-shoc-hmac-rotator"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_group.procurement_api
|
||||||
|
id = "/aws/lambda/procurement-api"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- SES receipt rules ---
|
||||||
|
import {
|
||||||
|
to = aws_ses_receipt_rule.po_email
|
||||||
|
id = "INBOUND_MAIL:ExistingRuleSetPoEmailRuleAC8E9C87-qwGDj9lBoL1G"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_ses_receipt_rule.wo_email
|
||||||
|
id = "INBOUND_MAIL:ExistingRuleSetWorkorderEmailRuleEA29F845-PKtaDBvIg61a"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- SHOC KMS + secret ---
|
||||||
|
import {
|
||||||
|
to = aws_kms_key.shoc_webhook
|
||||||
|
id = "d10fd1f0-a61a-4405-8568-85e9fd11ba18"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_kms_alias.shoc_webhook
|
||||||
|
id = "alias/workorder-ingest-shoc-webhook-kms"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_secretsmanager_secret.shoc_webhook_hmac
|
||||||
|
id = "arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_secretsmanager_secret_rotation.shoc_webhook_hmac
|
||||||
|
id = "arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Metric filters ---
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_metric_filter.po_sender_auth_rejected
|
||||||
|
id = "/aws/lambda/po-email-processor:EmailProcessorSenderAuthRejectedFilterC0DAEDFE-3K6wBb003iDv"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_log_metric_filter.wo_sender_auth_rejected
|
||||||
|
id = "/aws/lambda/workorder-email-processor:EmailProcessorSenderAuthRejectedFilterC0DAEDFE-Wkbow1VNDeDM"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- API Gateway ---
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_rest_api.procurement
|
||||||
|
id = "mvul1efda2"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_domain_name.procurement
|
||||||
|
id = "procurement-api.seahaven.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_stage.prod
|
||||||
|
id = "mvul1efda2/prod"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_base_path_mapping.procurement
|
||||||
|
id = "procurement-api.seahaven.com/"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_deployment.procurement
|
||||||
|
id = "mvul1efda2/uax9sq"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_resource.work_orders
|
||||||
|
id = "mvul1efda2/h5iu4f"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_resource.work_order_id
|
||||||
|
id = "mvul1efda2/4uk6uh"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_resource.work_order_comments
|
||||||
|
id = "mvul1efda2/gfx0te"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_resource.purchase_orders
|
||||||
|
id = "mvul1efda2/pfn6qm"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_resource.po_number
|
||||||
|
id = "mvul1efda2/nclnn3"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_resource.verified_sites
|
||||||
|
id = "mvul1efda2/vyst7e"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_resource.site_code
|
||||||
|
id = "mvul1efda2/rmaawy"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_resource.docs
|
||||||
|
id = "mvul1efda2/k4vl85"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_resource.openapi_json
|
||||||
|
id = "mvul1efda2/xos715"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.iam["work_orders_get"]
|
||||||
|
id = "mvul1efda2/h5iu4f/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.iam["work_order_id_get"]
|
||||||
|
id = "mvul1efda2/4uk6uh/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.iam["work_order_id_patch"]
|
||||||
|
id = "mvul1efda2/4uk6uh/PATCH"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.iam["work_order_comments_get"]
|
||||||
|
id = "mvul1efda2/gfx0te/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.iam["work_order_comments_post"]
|
||||||
|
id = "mvul1efda2/gfx0te/POST"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.iam["purchase_orders_get"]
|
||||||
|
id = "mvul1efda2/pfn6qm/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.iam["po_number_get"]
|
||||||
|
id = "mvul1efda2/nclnn3/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.iam["verified_sites_get"]
|
||||||
|
id = "mvul1efda2/vyst7e/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.iam["site_code_get"]
|
||||||
|
id = "mvul1efda2/rmaawy/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.none["docs_get"]
|
||||||
|
id = "mvul1efda2/k4vl85/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_method.none["openapi_json_get"]
|
||||||
|
id = "mvul1efda2/xos715/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.iam["work_orders_get"]
|
||||||
|
id = "mvul1efda2/h5iu4f/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.iam["work_order_id_get"]
|
||||||
|
id = "mvul1efda2/4uk6uh/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.iam["work_order_id_patch"]
|
||||||
|
id = "mvul1efda2/4uk6uh/PATCH"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.iam["work_order_comments_get"]
|
||||||
|
id = "mvul1efda2/gfx0te/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.iam["work_order_comments_post"]
|
||||||
|
id = "mvul1efda2/gfx0te/POST"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.iam["purchase_orders_get"]
|
||||||
|
id = "mvul1efda2/pfn6qm/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.iam["po_number_get"]
|
||||||
|
id = "mvul1efda2/nclnn3/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.iam["verified_sites_get"]
|
||||||
|
id = "mvul1efda2/vyst7e/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.iam["site_code_get"]
|
||||||
|
id = "mvul1efda2/rmaawy/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.none["docs_get"]
|
||||||
|
id = "mvul1efda2/k4vl85/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_api_gateway_integration.none["openapi_json_get"]
|
||||||
|
id = "mvul1efda2/xos715/GET"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- CloudWatch alarms (PO) ---
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_email_processor_errors
|
||||||
|
id = "po-email-processor-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_email_processor_throttles
|
||||||
|
id = "po-email-processor-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_email_processor_dlq
|
||||||
|
id = "po-email-processor-dlq-messages"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_email_processor_duration
|
||||||
|
id = "po-email-processor-duration"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_email_processor_sender_auth_rejected
|
||||||
|
id = "po-email-processor-sender-auth-rejected"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_email_processor_fallback_rate
|
||||||
|
id = "po-email-processor-template-fallback-rate"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_email_processor_ai_fallback_rejected
|
||||||
|
id = "po-email-processor-ai-fallback-rejected"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_web_ui_throttles
|
||||||
|
id = "po-web-ui-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_web_ui_duration
|
||||||
|
id = "po-web-ui-duration"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_site_extractor_errors
|
||||||
|
id = "po-ingest-site-extractor-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_site_extractor_throttles
|
||||||
|
id = "po-ingest-site-extractor-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_site_extractor_duration
|
||||||
|
id = "po-ingest-site-extractor-duration"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_ddb_throttles["purchase-orders"]
|
||||||
|
id = "purchase-orders-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_ddb_system_errors["purchase-orders"]
|
||||||
|
id = "purchase-orders-system-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_ddb_throttles["verified-sites"]
|
||||||
|
id = "verified-sites-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_ddb_system_errors["verified-sites"]
|
||||||
|
id = "verified-sites-system-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_ddb_throttles["pending-site-review"]
|
||||||
|
id = "pending-site-review-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.po_ddb_system_errors["pending-site-review"]
|
||||||
|
id = "pending-site-review-system-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- CloudWatch alarms (WO) ---
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_email_processor_errors
|
||||||
|
id = "workorder-email-processor-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_email_processor_throttles
|
||||||
|
id = "workorder-email-processor-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_email_processor_dlq
|
||||||
|
id = "workorder-email-processor-dlq-messages"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_email_processor_duration
|
||||||
|
id = "workorder-email-processor-duration"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_email_processor_sender_auth_rejected
|
||||||
|
id = "workorder-email-processor-sender-auth-rejected"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_email_processor_fallback_rate
|
||||||
|
id = "workorder-email-processor-template-fallback-rate"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_email_processor_ai_fallback_rejected
|
||||||
|
id = "workorder-email-processor-ai-fallback-rejected"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_shoc_hmac_rotator_errors
|
||||||
|
id = "workorder-shoc-hmac-rotator-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_shoc_hmac_rotator_throttles
|
||||||
|
id = "workorder-shoc-hmac-rotator-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_shoc_hmac_rotator_duration
|
||||||
|
id = "workorder-shoc-hmac-rotator-duration"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_shoc_emitter_errors
|
||||||
|
id = "workorder-shoc-emitter-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_shoc_emitter_throttles
|
||||||
|
id = "workorder-shoc-emitter-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_shoc_emitter_duration
|
||||||
|
id = "workorder-shoc-emitter-duration"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_shoc_emitter_iterator_age
|
||||||
|
id = "workorder-shoc-emitter-iterator-age"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_shoc_emitter_failures_messages
|
||||||
|
id = "workorder-shoc-emitter-failures-messages"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_shoc_emitter_rejected_messages
|
||||||
|
id = "workorder-shoc-emitter-rejected-messages"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_ddb_throttles["WorkOrders"]
|
||||||
|
id = "WorkOrders-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_ddb_system_errors["WorkOrders"]
|
||||||
|
id = "WorkOrders-system-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_ddb_throttles["WorkOrderComments"]
|
||||||
|
id = "WorkOrderComments-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.wo_ddb_system_errors["WorkOrderComments"]
|
||||||
|
id = "WorkOrderComments-system-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- CloudWatch alarms (API) ---
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.procurement_api_errors
|
||||||
|
id = "procurement-api-errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.procurement_api_throttles
|
||||||
|
id = "procurement-api-throttles"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.procurement_api_duration
|
||||||
|
id = "procurement-api-duration"
|
||||||
|
}
|
||||||
|
|
||||||
|
import {
|
||||||
|
to = aws_cloudwatch_metric_alarm.procurement_api_5xx
|
||||||
|
id = "procurement-api-5xx"
|
||||||
|
}
|
||||||
56
terraform/locals.tf
Normal file
56
terraform/locals.tf
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
locals {
|
||||||
|
account_id = "011934824531"
|
||||||
|
boundary_arn = "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary"
|
||||||
|
|
||||||
|
bedrock_model_id = "us.anthropic.claude-haiku-4-5-20251001-v1:0"
|
||||||
|
|
||||||
|
shoc_consumer_role_arn = var.shoc_consumer_role_arn
|
||||||
|
|
||||||
|
po_email_bucket_name = "po-ingest-emails-${local.account_id}"
|
||||||
|
wo_email_bucket_name = "workorder-ingest-emails-${local.account_id}"
|
||||||
|
|
||||||
|
po_email_processor_dlq_name = "po-ingest-EmailProcessorDlqA753DED5-Mn33HvhsDPEu"
|
||||||
|
wo_email_processor_dlq_name = "WorkorderIngestStack-EmailProcessorDlqA753DED5-qCTHrsoEucas"
|
||||||
|
|
||||||
|
po_ses_rule_name = "ExistingRuleSetPoEmailRuleAC8E9C87-qwGDj9lBoL1G"
|
||||||
|
wo_ses_rule_name = "ExistingRuleSetWorkorderEmailRuleEA29F845-PKtaDBvIg61a"
|
||||||
|
|
||||||
|
po_sender_auth_filter_name = "EmailProcessorSenderAuthRejectedFilterC0DAEDFE-3K6wBb003iDv"
|
||||||
|
wo_sender_auth_filter_name = "EmailProcessorSenderAuthRejectedFilterC0DAEDFE-Wkbow1VNDeDM"
|
||||||
|
|
||||||
|
api_rest_api_id = "mvul1efda2"
|
||||||
|
api_root_id = "babtawlzki"
|
||||||
|
api_domain_name = "procurement-api.seahaven.com"
|
||||||
|
api_stage_name = "prod"
|
||||||
|
api_deployment_id = "uax9sq"
|
||||||
|
|
||||||
|
ddb_alarm_operations = [
|
||||||
|
"GetItem",
|
||||||
|
"BatchGetItem",
|
||||||
|
"Query",
|
||||||
|
"Scan",
|
||||||
|
"PutItem",
|
||||||
|
"UpdateItem",
|
||||||
|
"DeleteItem",
|
||||||
|
"BatchWriteItem",
|
||||||
|
]
|
||||||
|
|
||||||
|
ddb_throttle_expr = "getitem + batchgetitem + query + scan + putitem + updateitem + deleteitem + batchwriteitem"
|
||||||
|
|
||||||
|
# Active SES receipt rule set in prod is INBOUND_MAIL (import-map OOB).
|
||||||
|
ses_rule_set_name = data.aws_ses_active_receipt_rule_set.inbound.rule_set_name
|
||||||
|
}
|
||||||
|
|
||||||
|
data "aws_sns_topic" "site_alerts" {
|
||||||
|
name = "site-alerts"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "aws_ssm_parameter" "dynamodb_cmk_arn" {
|
||||||
|
name = "/seahaven/dynamodb/cmk-arn"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "aws_ssm_parameter" "procurement_api_cert_arn" {
|
||||||
|
name = "/procurement-api/custom-domain/certificate-arn"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "aws_ses_active_receipt_rule_set" "inbound" {}
|
||||||
34
terraform/outputs.tf
Normal file
34
terraform/outputs.tf
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
output "po_email_processor_arn" {
|
||||||
|
description = "ARN of po-email-processor"
|
||||||
|
value = aws_lambda_function.po_email_processor.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
output "wo_email_processor_arn" {
|
||||||
|
description = "ARN of workorder-email-processor"
|
||||||
|
value = aws_lambda_function.wo_email_processor.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
output "procurement_api_arn" {
|
||||||
|
description = "ARN of procurement-api Lambda"
|
||||||
|
value = aws_lambda_function.procurement_api.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
output "procurement_api_invoke_url" {
|
||||||
|
description = "API Gateway stage invoke URL"
|
||||||
|
value = aws_api_gateway_stage.prod.invoke_url
|
||||||
|
}
|
||||||
|
|
||||||
|
output "procurement_api_custom_domain" {
|
||||||
|
description = "Custom domain (Route53 alias stays OOB in mgmt)"
|
||||||
|
value = "https://${aws_api_gateway_domain_name.procurement.domain_name}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "shoc_hmac_secret_arn" {
|
||||||
|
description = "SHOC webhook HMAC secret ARN"
|
||||||
|
value = aws_secretsmanager_secret.shoc_webhook_hmac.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
output "artifacts_bucket" {
|
||||||
|
description = "Lambda artifact bucket"
|
||||||
|
value = aws_s3_bucket.artifacts.bucket
|
||||||
|
}
|
||||||
270
terraform/po_alarms.tf
Normal file
270
terraform/po_alarms.tf
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
resource "aws_cloudwatch_log_metric_filter" "po_sender_auth_rejected" {
|
||||||
|
name = local.po_sender_auth_filter_name
|
||||||
|
log_group_name = aws_cloudwatch_log_group.po_email_processor.name
|
||||||
|
pattern = "\"sender_auth_rejected\""
|
||||||
|
|
||||||
|
metric_transformation {
|
||||||
|
name = "po-email-processor-sender-auth-rejected"
|
||||||
|
namespace = "Seahaven/ProcurementIngest"
|
||||||
|
value = "1"
|
||||||
|
default_value = "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_email_processor_errors" {
|
||||||
|
alarm_name = "po-email-processor-errors"
|
||||||
|
alarm_description = "po-email-processor async invocation errors"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Errors"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.po_email_processor.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_email_processor_throttles" {
|
||||||
|
alarm_name = "po-email-processor-throttles"
|
||||||
|
alarm_description = "po-email-processor invocation throttles"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Throttles"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.po_email_processor.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_email_processor_dlq" {
|
||||||
|
alarm_name = "po-email-processor-dlq-messages"
|
||||||
|
alarm_description = "po-email-processor DLQ has messages (dropped PO emails)"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "ApproximateNumberOfMessagesVisible"
|
||||||
|
namespace = "AWS/SQS"
|
||||||
|
period = 300
|
||||||
|
statistic = "Maximum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
QueueName = aws_sqs_queue.po_email_processor_dlq.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_email_processor_duration" {
|
||||||
|
alarm_name = "po-email-processor-duration"
|
||||||
|
alarm_description = "po-email-processor p99 duration approaching the 60s timeout"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 3
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "Duration"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
extended_statistic = "p99"
|
||||||
|
threshold = 45000
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.po_email_processor.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_email_processor_sender_auth_rejected" {
|
||||||
|
alarm_name = "po-email-processor-sender-auth-rejected"
|
||||||
|
alarm_description = "po-email-processor rejected inbound mail on sender authentication (possible allowlist/DKIM-domain drift silently dropping real mail)"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 6
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "po-email-processor-sender-auth-rejected"
|
||||||
|
namespace = "Seahaven/ProcurementIngest"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 1
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_email_processor_fallback_rate" {
|
||||||
|
alarm_name = "po-email-processor-template-fallback-rate"
|
||||||
|
alarm_description = "po-email-processor deterministic-template coverage collapse: >20% of parses fell back to the Bedrock AI extractor"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 4
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
threshold = 20
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "expr_1"
|
||||||
|
expression = "IF((FILL(fb,0)+FILL(tmpl,0))>=8, 100*FILL(fb,0)/(FILL(fb,0)+FILL(tmpl,0)), 0)"
|
||||||
|
label = "TemplateFallbackRatePct"
|
||||||
|
return_data = true
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "fb"
|
||||||
|
metric {
|
||||||
|
metric_name = "ParseOutcome"
|
||||||
|
namespace = "Seahaven/PoIngest"
|
||||||
|
period = 21600
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
ParseMethod = "ai_fallback"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "tmpl"
|
||||||
|
metric {
|
||||||
|
metric_name = "ParseOutcome"
|
||||||
|
namespace = "Seahaven/PoIngest"
|
||||||
|
period = 21600
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
ParseMethod = "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_email_processor_ai_fallback_rejected" {
|
||||||
|
alarm_name = "po-email-processor-ai-fallback-rejected"
|
||||||
|
alarm_description = "po-email-processor is rejecting Bedrock AI-fallback output at the validation gate (possible prompt-injection probing or template drift silently dropping real mail)"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 4
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
threshold = 1
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "expr_1"
|
||||||
|
expression = "IF(FILL(rej,0)>=1, FILL(rej,0), 0)"
|
||||||
|
label = "AiFallbackRejectedCount"
|
||||||
|
return_data = true
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "rej"
|
||||||
|
metric {
|
||||||
|
metric_name = "ParseOutcome"
|
||||||
|
namespace = "Seahaven/PoIngest"
|
||||||
|
period = 21600
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
ParseMethod = "ai_fallback_rejected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_web_ui_throttles" {
|
||||||
|
alarm_name = "po-web-ui-throttles"
|
||||||
|
alarm_description = "po-web-ui invocation throttles"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Throttles"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.po_web_ui.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_web_ui_duration" {
|
||||||
|
alarm_name = "po-web-ui-duration"
|
||||||
|
alarm_description = "po-web-ui p99 duration approaching the 60s timeout"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 3
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "Duration"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
extended_statistic = "p99"
|
||||||
|
threshold = 45000
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.po_web_ui.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_site_extractor_errors" {
|
||||||
|
alarm_name = "po-ingest-site-extractor-errors"
|
||||||
|
alarm_description = "po-ingest-site-extractor invocation errors"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Errors"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.po_site_extractor.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_site_extractor_throttles" {
|
||||||
|
alarm_name = "po-ingest-site-extractor-throttles"
|
||||||
|
alarm_description = "po-ingest-site-extractor invocation throttles"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Throttles"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.po_site_extractor.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_site_extractor_duration" {
|
||||||
|
alarm_name = "po-ingest-site-extractor-duration"
|
||||||
|
alarm_description = "po-ingest-site-extractor p99 duration approaching the 60s timeout"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 3
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "Duration"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
extended_statistic = "p99"
|
||||||
|
threshold = 45000
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.po_site_extractor.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
134
terraform/po_ddb.tf
Normal file
134
terraform/po_ddb.tf
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
resource "aws_dynamodb_table" "purchase_orders" {
|
||||||
|
name = "purchase-orders"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "po_number"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "po_number"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_enabled = true
|
||||||
|
stream_view_type = "NEW_IMAGE"
|
||||||
|
|
||||||
|
server_side_encryption {
|
||||||
|
enabled = true
|
||||||
|
kms_key_arn = data.aws_ssm_parameter.dynamodb_cmk_arn.value
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_dynamodb_table" "verified_sites" {
|
||||||
|
name = "verified-sites"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "siteCode"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "siteCode"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_dynamodb_table" "pending_site_review" {
|
||||||
|
name = "pending-site-review"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "po_number"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "po_number"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
po_ddb_alarm_tables = {
|
||||||
|
"purchase-orders" = aws_dynamodb_table.purchase_orders.name
|
||||||
|
"verified-sites" = aws_dynamodb_table.verified_sites.name
|
||||||
|
"pending-site-review" = aws_dynamodb_table.pending_site_review.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_ddb_throttles" {
|
||||||
|
for_each = local.po_ddb_alarm_tables
|
||||||
|
|
||||||
|
alarm_name = "${each.key}-throttles"
|
||||||
|
alarm_description = "${each.key} DynamoDB throttled requests"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "expr_1"
|
||||||
|
expression = local.ddb_throttle_expr
|
||||||
|
label = "Sum of throttled requests across all operations"
|
||||||
|
return_data = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic "metric_query" {
|
||||||
|
for_each = local.ddb_alarm_operations
|
||||||
|
content {
|
||||||
|
id = lower(metric_query.value)
|
||||||
|
metric {
|
||||||
|
metric_name = "ThrottledRequests"
|
||||||
|
namespace = "AWS/DynamoDB"
|
||||||
|
period = 300
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
TableName = each.value
|
||||||
|
Operation = metric_query.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "po_ddb_system_errors" {
|
||||||
|
for_each = local.po_ddb_alarm_tables
|
||||||
|
|
||||||
|
alarm_name = "${each.key}-system-errors"
|
||||||
|
alarm_description = "${each.key} DynamoDB server-side (5xx) errors"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "expr_1"
|
||||||
|
expression = local.ddb_throttle_expr
|
||||||
|
label = "Sum of system errors across all operations"
|
||||||
|
return_data = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic "metric_query" {
|
||||||
|
for_each = local.ddb_alarm_operations
|
||||||
|
content {
|
||||||
|
id = lower(metric_query.value)
|
||||||
|
metric {
|
||||||
|
metric_name = "SystemErrors"
|
||||||
|
namespace = "AWS/DynamoDB"
|
||||||
|
period = 300
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
TableName = each.value
|
||||||
|
Operation = metric_query.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
134
terraform/po_lambda.tf
Normal file
134
terraform/po_lambda.tf
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
resource "aws_cloudwatch_log_group" "po_email_processor" {
|
||||||
|
name = "/aws/lambda/po-email-processor"
|
||||||
|
retention_in_days = 60
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_log_group" "po_web_ui" {
|
||||||
|
name = "/aws/lambda/po-web-ui"
|
||||||
|
retention_in_days = 60
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_log_group" "po_site_extractor" {
|
||||||
|
name = "/aws/lambda/po-ingest-site-extractor"
|
||||||
|
retention_in_days = 60
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_function" "po_email_processor" {
|
||||||
|
function_name = "po-email-processor"
|
||||||
|
role = aws_iam_role.po_email_processor.arn
|
||||||
|
handler = "handler.handler"
|
||||||
|
runtime = "python3.12"
|
||||||
|
architectures = ["arm64"]
|
||||||
|
memory_size = 256
|
||||||
|
timeout = 60
|
||||||
|
|
||||||
|
s3_bucket = aws_s3_bucket.artifacts.id
|
||||||
|
s3_key = aws_s3_object.lambda["po_email_processor"].key
|
||||||
|
source_code_hash = data.archive_file.lambda["po_email_processor"].output_base64sha256
|
||||||
|
|
||||||
|
dead_letter_config {
|
||||||
|
target_arn = aws_sqs_queue.po_email_processor_dlq.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
environment {
|
||||||
|
variables = {
|
||||||
|
PO_TABLE = aws_dynamodb_table.purchase_orders.name
|
||||||
|
ALLOWED_DKIM_DOMAINS = "amazon.coupahost.com"
|
||||||
|
BEDROCK_MODEL_ID = local.bedrock_model_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
aws_s3_object.lambda,
|
||||||
|
aws_cloudwatch_log_group.po_email_processor,
|
||||||
|
aws_iam_role_policy_attachment.po_email_processor_basic,
|
||||||
|
aws_iam_role_policy.po_email_processor_s3,
|
||||||
|
aws_iam_role_policy.po_email_processor_ddb,
|
||||||
|
aws_iam_role_policy.po_email_processor_kms,
|
||||||
|
aws_iam_role_policy.po_email_processor_bedrock,
|
||||||
|
aws_iam_role_policy.po_email_processor_dlq,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_function" "po_web_ui" {
|
||||||
|
function_name = "po-web-ui"
|
||||||
|
role = aws_iam_role.po_web_ui.arn
|
||||||
|
handler = "handler.handler"
|
||||||
|
runtime = "python3.12"
|
||||||
|
architectures = ["arm64"]
|
||||||
|
memory_size = 256
|
||||||
|
timeout = 60
|
||||||
|
|
||||||
|
s3_bucket = aws_s3_bucket.artifacts.id
|
||||||
|
s3_key = aws_s3_object.lambda["po_web_ui"].key
|
||||||
|
source_code_hash = data.archive_file.lambda["po_web_ui"].output_base64sha256
|
||||||
|
|
||||||
|
environment {
|
||||||
|
variables = {
|
||||||
|
PO_TABLE = aws_dynamodb_table.purchase_orders.name
|
||||||
|
WEB_UI_AUTH_TOKEN_SECRET_ARN = var.web_ui_auth_token_secret_arn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
aws_s3_object.lambda,
|
||||||
|
aws_cloudwatch_log_group.po_web_ui,
|
||||||
|
aws_iam_role_policy_attachment.po_web_ui_basic,
|
||||||
|
aws_iam_role_policy.po_web_ui_ddb,
|
||||||
|
aws_iam_role_policy.po_web_ui_kms,
|
||||||
|
aws_iam_role_policy.po_web_ui_secrets,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_function" "po_site_extractor" {
|
||||||
|
function_name = "po-ingest-site-extractor"
|
||||||
|
role = aws_iam_role.po_site_extractor.arn
|
||||||
|
handler = "handler.handler"
|
||||||
|
runtime = "python3.12"
|
||||||
|
architectures = ["arm64"]
|
||||||
|
memory_size = 256
|
||||||
|
timeout = 60
|
||||||
|
|
||||||
|
s3_bucket = aws_s3_bucket.artifacts.id
|
||||||
|
s3_key = aws_s3_object.lambda["po_site_extractor"].key
|
||||||
|
source_code_hash = data.archive_file.lambda["po_site_extractor"].output_base64sha256
|
||||||
|
|
||||||
|
environment {
|
||||||
|
variables = {
|
||||||
|
VERIFIED_SITES_TABLE = aws_dynamodb_table.verified_sites.name
|
||||||
|
PENDING_REVIEW_TABLE = aws_dynamodb_table.pending_site_review.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
aws_s3_object.lambda,
|
||||||
|
aws_cloudwatch_log_group.po_site_extractor,
|
||||||
|
aws_iam_role_policy_attachment.po_site_extractor_basic,
|
||||||
|
aws_iam_role_policy.po_site_extractor_ddb,
|
||||||
|
aws_iam_role_policy.po_site_extractor_kms,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_event_source_mapping" "po_site_extractor" {
|
||||||
|
event_source_arn = aws_dynamodb_table.purchase_orders.stream_arn
|
||||||
|
function_name = aws_lambda_function.po_site_extractor.arn
|
||||||
|
starting_position = "TRIM_HORIZON"
|
||||||
|
batch_size = 10
|
||||||
|
maximum_batching_window_in_seconds = 30
|
||||||
|
bisect_batch_on_function_error = true
|
||||||
|
maximum_retry_attempts = 3
|
||||||
|
parallelization_factor = 1
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
96
terraform/po_s3.tf
Normal file
96
terraform/po_s3.tf
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
resource "aws_s3_bucket" "po_emails" {
|
||||||
|
bucket = local.po_email_bucket_name
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_public_access_block" "po_emails" {
|
||||||
|
bucket = aws_s3_bucket.po_emails.id
|
||||||
|
|
||||||
|
block_public_acls = true
|
||||||
|
block_public_policy = true
|
||||||
|
ignore_public_acls = true
|
||||||
|
restrict_public_buckets = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_lifecycle_configuration" "po_emails" {
|
||||||
|
bucket = aws_s3_bucket.po_emails.id
|
||||||
|
|
||||||
|
rule {
|
||||||
|
id = "expire-90-days"
|
||||||
|
status = "Enabled"
|
||||||
|
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
expiration {
|
||||||
|
days = 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_policy" "po_emails" {
|
||||||
|
bucket = aws_s3_bucket.po_emails.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Principal = { Service = "ses.amazonaws.com" }
|
||||||
|
Action = "s3:PutObject"
|
||||||
|
Resource = "${aws_s3_bucket.po_emails.arn}/inbound/*"
|
||||||
|
Condition = {
|
||||||
|
StringEquals = {
|
||||||
|
"aws:SourceAccount" = local.account_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_notification" "po_emails" {
|
||||||
|
bucket = aws_s3_bucket.po_emails.id
|
||||||
|
|
||||||
|
lambda_function {
|
||||||
|
id = "po-email-processor-inbound"
|
||||||
|
lambda_function_arn = aws_lambda_function.po_email_processor.arn
|
||||||
|
events = ["s3:ObjectCreated:*"]
|
||||||
|
filter_prefix = "inbound/"
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [aws_lambda_permission.po_emails_invoke]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_permission" "po_emails_invoke" {
|
||||||
|
statement_id = "AllowS3InvokePoEmailProcessor"
|
||||||
|
action = "lambda:InvokeFunction"
|
||||||
|
function_name = aws_lambda_function.po_email_processor.function_name
|
||||||
|
principal = "s3.amazonaws.com"
|
||||||
|
source_arn = aws_s3_bucket.po_emails.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_sqs_queue" "po_email_processor_dlq" {
|
||||||
|
name = local.po_email_processor_dlq_name
|
||||||
|
message_retention_seconds = 1209600
|
||||||
|
sqs_managed_sse_enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_sqs_queue_policy" "po_email_processor_dlq" {
|
||||||
|
queue_url = aws_sqs_queue.po_email_processor_dlq.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Deny"
|
||||||
|
Principal = { AWS = "*" }
|
||||||
|
Action = "sqs:*"
|
||||||
|
Resource = aws_sqs_queue.po_email_processor_dlq.arn
|
||||||
|
Condition = {
|
||||||
|
Bool = { "aws:SecureTransport" = "false" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
14
terraform/po_ses.tf
Normal file
14
terraform/po_ses.tf
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
resource "aws_ses_receipt_rule" "po_email" {
|
||||||
|
name = local.po_ses_rule_name
|
||||||
|
rule_set_name = local.ses_rule_set_name
|
||||||
|
recipients = ["amazon_po@int.seahaven.com"]
|
||||||
|
enabled = true
|
||||||
|
scan_enabled = false
|
||||||
|
tls_policy = "Optional"
|
||||||
|
|
||||||
|
s3_action {
|
||||||
|
bucket_name = aws_s3_bucket.po_emails.id
|
||||||
|
object_key_prefix = "inbound/"
|
||||||
|
position = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
11
terraform/providers.tf
Normal file
11
terraform/providers.tf
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
provider "aws" {
|
||||||
|
region = var.aws_region
|
||||||
|
|
||||||
|
default_tags {
|
||||||
|
tags = {
|
||||||
|
Project = "procurement-ingest"
|
||||||
|
ManagedBy = "terraform"
|
||||||
|
Workspace = "procurement-ingest-prod"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
terraform/terraform.tfvars.example
Normal file
8
terraform/terraform.tfvars.example
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Wire these as HCP workspace Terraform variables (never commit real .tfvars).
|
||||||
|
# Exact ARNs only; secret VALUES must never appear in this repo.
|
||||||
|
web_ui_auth_token_secret_arn = "arn:aws:secretsmanager:us-east-1:011934824531:secret:procurement-ingest/web-ui-auth-token-ApAMmr"
|
||||||
|
shoc_hmac_secret_arn = "arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB"
|
||||||
|
# Required: no Terraform default. Live emitter target today (contract Rev 2026-07-23).
|
||||||
|
shoc_webhook_url = "https://api.dev.seahaven.com/api/webhooks/work-orders"
|
||||||
|
# Required exact-ARN pin for cross-account HMAC read (docs/shoc-webhook-contract.md).
|
||||||
|
shoc_consumer_role_arn = "arn:aws:iam::396287094661:role/shoc-backend-dev"
|
||||||
25
terraform/variables.tf
Normal file
25
terraform/variables.tf
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
variable "aws_region" {
|
||||||
|
type = string
|
||||||
|
description = "AWS region for all resources"
|
||||||
|
default = "us-east-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "web_ui_auth_token_secret_arn" {
|
||||||
|
type = string
|
||||||
|
description = "Secrets Manager ARN for the shared web UI / docs auth token (exact ARN, including suffix)"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "shoc_hmac_secret_arn" {
|
||||||
|
type = string
|
||||||
|
description = "Secrets Manager ARN for the SHOC webhook HMAC secret (exact ARN, including suffix)"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "shoc_webhook_url" {
|
||||||
|
type = string
|
||||||
|
description = "SHOC webhook HTTPS endpoint URL (required; no default — set explicitly in HCP workspace vars)"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "shoc_consumer_role_arn" {
|
||||||
|
type = string
|
||||||
|
description = "Exact IAM role ARN allowed to GetSecretValue / kms:Decrypt the SHOC HMAC secret (cross-account consumer). Live pin today is arn:aws:iam::396287094661:role/shoc-backend-dev per docs/shoc-webhook-contract.md."
|
||||||
|
}
|
||||||
26
terraform/versions.tf
Normal file
26
terraform/versions.tf
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
terraform {
|
||||||
|
required_version = ">= 1.7.0"
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
aws = {
|
||||||
|
source = "hashicorp/aws"
|
||||||
|
version = "~> 6.57"
|
||||||
|
}
|
||||||
|
archive = {
|
||||||
|
source = "hashicorp/archive"
|
||||||
|
version = "~> 2.0"
|
||||||
|
}
|
||||||
|
external = {
|
||||||
|
source = "hashicorp/external"
|
||||||
|
version = "~> 2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cloud {
|
||||||
|
organization = "seahaven"
|
||||||
|
|
||||||
|
workspaces {
|
||||||
|
name = "procurement-ingest-prod"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
344
terraform/wo_alarms.tf
Normal file
344
terraform/wo_alarms.tf
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
resource "aws_cloudwatch_log_metric_filter" "wo_sender_auth_rejected" {
|
||||||
|
name = local.wo_sender_auth_filter_name
|
||||||
|
log_group_name = aws_cloudwatch_log_group.wo_email_processor.name
|
||||||
|
pattern = "\"sender_auth_rejected\""
|
||||||
|
|
||||||
|
metric_transformation {
|
||||||
|
name = "workorder-email-processor-sender-auth-rejected"
|
||||||
|
namespace = "Seahaven/ProcurementIngest"
|
||||||
|
value = "1"
|
||||||
|
default_value = "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_email_processor_errors" {
|
||||||
|
alarm_name = "workorder-email-processor-errors"
|
||||||
|
alarm_description = "workorder-email-processor async invocation errors"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Errors"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_email_processor.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_email_processor_throttles" {
|
||||||
|
alarm_name = "workorder-email-processor-throttles"
|
||||||
|
alarm_description = "workorder-email-processor invocation throttles"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Throttles"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_email_processor.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_email_processor_dlq" {
|
||||||
|
alarm_name = "workorder-email-processor-dlq-messages"
|
||||||
|
alarm_description = "workorder-email-processor DLQ has visible messages (dropped emails)"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "ApproximateNumberOfMessagesVisible"
|
||||||
|
namespace = "AWS/SQS"
|
||||||
|
period = 300
|
||||||
|
statistic = "Maximum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
QueueName = aws_sqs_queue.wo_email_processor_dlq.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_email_processor_duration" {
|
||||||
|
alarm_name = "workorder-email-processor-duration"
|
||||||
|
alarm_description = "workorder-email-processor p95 duration approaching the 60s timeout"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 3
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "Duration"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
extended_statistic = "p95"
|
||||||
|
threshold = 45000
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_email_processor.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_email_processor_sender_auth_rejected" {
|
||||||
|
alarm_name = "workorder-email-processor-sender-auth-rejected"
|
||||||
|
alarm_description = "workorder-email-processor rejected inbound mail on sender authentication (possible allowlist/DKIM-domain drift silently dropping real mail)"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 6
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "workorder-email-processor-sender-auth-rejected"
|
||||||
|
namespace = "Seahaven/ProcurementIngest"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 1
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_email_processor_fallback_rate" {
|
||||||
|
alarm_name = "workorder-email-processor-template-fallback-rate"
|
||||||
|
alarm_description = "workorder-email-processor deterministic-template coverage collapse: >15% of parses fell back to the Bedrock AI extractor"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 3
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
threshold = 15
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "expr_1"
|
||||||
|
expression = "IF((FILL(fb,0)+FILL(rej,0)+FILL(tmpl,0))>=10, 100*(FILL(fb,0)+FILL(rej,0))/(FILL(fb,0)+FILL(rej,0)+FILL(tmpl,0)), 0)"
|
||||||
|
label = "TemplateFallbackRatePct"
|
||||||
|
return_data = true
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "fb"
|
||||||
|
metric {
|
||||||
|
metric_name = "ParseOutcome"
|
||||||
|
namespace = "Seahaven/WorkorderIngest"
|
||||||
|
period = 900
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
ParseMethod = "ai_fallback"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "rej"
|
||||||
|
metric {
|
||||||
|
metric_name = "ParseOutcome"
|
||||||
|
namespace = "Seahaven/WorkorderIngest"
|
||||||
|
period = 900
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
ParseMethod = "ai_fallback_rejected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "tmpl"
|
||||||
|
metric {
|
||||||
|
metric_name = "ParseOutcome"
|
||||||
|
namespace = "Seahaven/WorkorderIngest"
|
||||||
|
period = 900
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
ParseMethod = "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_email_processor_ai_fallback_rejected" {
|
||||||
|
alarm_name = "workorder-email-processor-ai-fallback-rejected"
|
||||||
|
alarm_description = "workorder-email-processor is rejecting Bedrock AI-fallback output at the validation gate (possible prompt-injection probing or template drift silently dropping real mail)"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 6
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "ParseOutcome"
|
||||||
|
namespace = "Seahaven/WorkorderIngest"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 1
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
ParseMethod = "ai_fallback_rejected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_shoc_hmac_rotator_errors" {
|
||||||
|
alarm_name = "workorder-shoc-hmac-rotator-errors"
|
||||||
|
alarm_description = "workorder-shoc-hmac-rotator invocation errors"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Errors"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_shoc_hmac_rotator.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_shoc_hmac_rotator_throttles" {
|
||||||
|
alarm_name = "workorder-shoc-hmac-rotator-throttles"
|
||||||
|
alarm_description = "workorder-shoc-hmac-rotator invocation throttles"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Throttles"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_shoc_hmac_rotator.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_shoc_hmac_rotator_duration" {
|
||||||
|
alarm_name = "workorder-shoc-hmac-rotator-duration"
|
||||||
|
alarm_description = "workorder-shoc-hmac-rotator p99 duration approaching the 60s timeout"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 3
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "Duration"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
extended_statistic = "p99"
|
||||||
|
threshold = 45000
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_shoc_hmac_rotator.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_shoc_emitter_errors" {
|
||||||
|
alarm_name = "workorder-shoc-emitter-errors"
|
||||||
|
alarm_description = "workorder-shoc-emitter invocation errors"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Errors"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_shoc_emitter.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_shoc_emitter_throttles" {
|
||||||
|
alarm_name = "workorder-shoc-emitter-throttles"
|
||||||
|
alarm_description = "workorder-shoc-emitter invocation throttles"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "Throttles"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Sum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_shoc_emitter.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_shoc_emitter_duration" {
|
||||||
|
alarm_name = "workorder-shoc-emitter-duration"
|
||||||
|
alarm_description = "workorder-shoc-emitter p99 duration approaching the 60s timeout"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 3
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "Duration"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
extended_statistic = "p99"
|
||||||
|
threshold = 45000
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_shoc_emitter.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_shoc_emitter_iterator_age" {
|
||||||
|
alarm_name = "workorder-shoc-emitter-iterator-age"
|
||||||
|
alarm_description = "workorder-shoc-emitter stream lag >= 10 min (SHOC receiver likely down; shard blocking on retries)"
|
||||||
|
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||||
|
evaluation_periods = 3
|
||||||
|
datapoints_to_alarm = 2
|
||||||
|
metric_name = "IteratorAge"
|
||||||
|
namespace = "AWS/Lambda"
|
||||||
|
period = 300
|
||||||
|
statistic = "Maximum"
|
||||||
|
threshold = 600000
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
FunctionName = aws_lambda_function.wo_shoc_emitter.function_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_shoc_emitter_failures_messages" {
|
||||||
|
alarm_name = "workorder-shoc-emitter-failures-messages"
|
||||||
|
alarm_description = "workorder-shoc-emitter retry-exhausted stream records parked (ESM failure metadata; replay rebuilds from DynamoDB)"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "ApproximateNumberOfMessagesVisible"
|
||||||
|
namespace = "AWS/SQS"
|
||||||
|
period = 300
|
||||||
|
statistic = "Maximum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
QueueName = aws_sqs_queue.shoc_emitter_failures.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_shoc_emitter_rejected_messages" {
|
||||||
|
alarm_name = "workorder-shoc-emitter-rejected-messages"
|
||||||
|
alarm_description = "workorder-shoc-emitter parked non-retryable 4xx deliveries (contract bug; inspect payloads and replay)"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
metric_name = "ApproximateNumberOfMessagesVisible"
|
||||||
|
namespace = "AWS/SQS"
|
||||||
|
period = 300
|
||||||
|
statistic = "Maximum"
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
dimensions = {
|
||||||
|
QueueName = aws_sqs_queue.shoc_emitter_rejected.name
|
||||||
|
}
|
||||||
|
}
|
||||||
122
terraform/wo_ddb.tf
Normal file
122
terraform/wo_ddb.tf
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
resource "aws_dynamodb_table" "work_orders" {
|
||||||
|
name = "WorkOrders"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "work_order_id"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "work_order_id"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_enabled = true
|
||||||
|
stream_view_type = "NEW_AND_OLD_IMAGES"
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_dynamodb_table" "work_order_comments" {
|
||||||
|
name = "WorkOrderComments"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "work_order_id"
|
||||||
|
range_key = "comment_id"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "work_order_id"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "comment_id"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_enabled = true
|
||||||
|
stream_view_type = "NEW_AND_OLD_IMAGES"
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
wo_ddb_alarm_tables = {
|
||||||
|
"WorkOrders" = aws_dynamodb_table.work_orders.name
|
||||||
|
"WorkOrderComments" = aws_dynamodb_table.work_order_comments.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_ddb_throttles" {
|
||||||
|
for_each = local.wo_ddb_alarm_tables
|
||||||
|
|
||||||
|
alarm_name = "${each.key}-throttles"
|
||||||
|
alarm_description = "${each.key} DynamoDB throttled requests"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "expr_1"
|
||||||
|
expression = local.ddb_throttle_expr
|
||||||
|
label = "Sum of throttled requests across all operations"
|
||||||
|
return_data = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic "metric_query" {
|
||||||
|
for_each = local.ddb_alarm_operations
|
||||||
|
content {
|
||||||
|
id = lower(metric_query.value)
|
||||||
|
metric {
|
||||||
|
metric_name = "ThrottledRequests"
|
||||||
|
namespace = "AWS/DynamoDB"
|
||||||
|
period = 300
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
TableName = each.value
|
||||||
|
Operation = metric_query.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_metric_alarm" "wo_ddb_system_errors" {
|
||||||
|
for_each = local.wo_ddb_alarm_tables
|
||||||
|
|
||||||
|
alarm_name = "${each.key}-system-errors"
|
||||||
|
alarm_description = "${each.key} DynamoDB server-side (5xx) errors"
|
||||||
|
comparison_operator = "GreaterThanThreshold"
|
||||||
|
evaluation_periods = 1
|
||||||
|
threshold = 0
|
||||||
|
treat_missing_data = "notBreaching"
|
||||||
|
alarm_actions = [data.aws_sns_topic.site_alerts.arn]
|
||||||
|
|
||||||
|
metric_query {
|
||||||
|
id = "expr_1"
|
||||||
|
expression = local.ddb_throttle_expr
|
||||||
|
label = "Sum of system errors across all operations"
|
||||||
|
return_data = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic "metric_query" {
|
||||||
|
for_each = local.ddb_alarm_operations
|
||||||
|
content {
|
||||||
|
id = lower(metric_query.value)
|
||||||
|
metric {
|
||||||
|
metric_name = "SystemErrors"
|
||||||
|
namespace = "AWS/DynamoDB"
|
||||||
|
period = 300
|
||||||
|
stat = "Sum"
|
||||||
|
dimensions = {
|
||||||
|
TableName = each.value
|
||||||
|
Operation = metric_query.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return_data = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
terraform/wo_lambda.tf
Normal file
84
terraform/wo_lambda.tf
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
resource "aws_cloudwatch_log_group" "wo_email_processor" {
|
||||||
|
name = "/aws/lambda/workorder-email-processor"
|
||||||
|
retention_in_days = 60
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_log_group" "wo_web_ui" {
|
||||||
|
name = "/aws/lambda/workorder-web-ui"
|
||||||
|
retention_in_days = 60
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_function" "wo_email_processor" {
|
||||||
|
function_name = "workorder-email-processor"
|
||||||
|
role = aws_iam_role.wo_email_processor.arn
|
||||||
|
handler = "handler.handler"
|
||||||
|
runtime = "python3.12"
|
||||||
|
architectures = ["arm64"]
|
||||||
|
memory_size = 256
|
||||||
|
timeout = 60
|
||||||
|
|
||||||
|
s3_bucket = aws_s3_bucket.artifacts.id
|
||||||
|
s3_key = aws_s3_object.lambda["wo_email_processor"].key
|
||||||
|
source_code_hash = data.archive_file.lambda["wo_email_processor"].output_base64sha256
|
||||||
|
|
||||||
|
dead_letter_config {
|
||||||
|
target_arn = aws_sqs_queue.wo_email_processor_dlq.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
environment {
|
||||||
|
variables = {
|
||||||
|
WORK_ORDERS_TABLE = aws_dynamodb_table.work_orders.name
|
||||||
|
COMMENTS_TABLE = aws_dynamodb_table.work_order_comments.name
|
||||||
|
ALLOWED_DKIM_DOMAINS = "seahaven.com"
|
||||||
|
BEDROCK_MODEL_ID = local.bedrock_model_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
aws_s3_object.lambda,
|
||||||
|
aws_cloudwatch_log_group.wo_email_processor,
|
||||||
|
aws_iam_role_policy_attachment.wo_email_processor_basic,
|
||||||
|
aws_iam_role_policy.wo_email_processor_s3,
|
||||||
|
aws_iam_role_policy.wo_email_processor_ddb,
|
||||||
|
aws_iam_role_policy.wo_email_processor_bedrock,
|
||||||
|
aws_iam_role_policy.wo_email_processor_dlq,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_function" "wo_web_ui" {
|
||||||
|
function_name = "workorder-web-ui"
|
||||||
|
role = aws_iam_role.wo_web_ui.arn
|
||||||
|
handler = "handler.handler"
|
||||||
|
runtime = "python3.12"
|
||||||
|
architectures = ["arm64"]
|
||||||
|
memory_size = 128
|
||||||
|
timeout = 15
|
||||||
|
|
||||||
|
s3_bucket = aws_s3_bucket.artifacts.id
|
||||||
|
s3_key = aws_s3_object.lambda["wo_web_ui"].key
|
||||||
|
source_code_hash = data.archive_file.lambda["wo_web_ui"].output_base64sha256
|
||||||
|
|
||||||
|
environment {
|
||||||
|
variables = {
|
||||||
|
WORK_ORDERS_TABLE = aws_dynamodb_table.work_orders.name
|
||||||
|
COMMENTS_TABLE = aws_dynamodb_table.work_order_comments.name
|
||||||
|
WEB_UI_AUTH_TOKEN_SECRET_ARN = var.web_ui_auth_token_secret_arn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
aws_s3_object.lambda,
|
||||||
|
aws_cloudwatch_log_group.wo_web_ui,
|
||||||
|
aws_iam_role_policy_attachment.wo_web_ui_basic,
|
||||||
|
aws_iam_role_policy.wo_web_ui_ddb,
|
||||||
|
aws_iam_role_policy.wo_web_ui_secrets,
|
||||||
|
]
|
||||||
|
}
|
||||||
96
terraform/wo_s3.tf
Normal file
96
terraform/wo_s3.tf
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
resource "aws_s3_bucket" "wo_emails" {
|
||||||
|
bucket = local.wo_email_bucket_name
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_public_access_block" "wo_emails" {
|
||||||
|
bucket = aws_s3_bucket.wo_emails.id
|
||||||
|
|
||||||
|
block_public_acls = true
|
||||||
|
block_public_policy = true
|
||||||
|
ignore_public_acls = true
|
||||||
|
restrict_public_buckets = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_lifecycle_configuration" "wo_emails" {
|
||||||
|
bucket = aws_s3_bucket.wo_emails.id
|
||||||
|
|
||||||
|
rule {
|
||||||
|
id = "expire-90-days"
|
||||||
|
status = "Enabled"
|
||||||
|
|
||||||
|
filter {}
|
||||||
|
|
||||||
|
expiration {
|
||||||
|
days = 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_policy" "wo_emails" {
|
||||||
|
bucket = aws_s3_bucket.wo_emails.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Principal = { Service = "ses.amazonaws.com" }
|
||||||
|
Action = "s3:PutObject"
|
||||||
|
Resource = "${aws_s3_bucket.wo_emails.arn}/inbound/*"
|
||||||
|
Condition = {
|
||||||
|
StringEquals = {
|
||||||
|
"aws:SourceAccount" = local.account_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_notification" "wo_emails" {
|
||||||
|
bucket = aws_s3_bucket.wo_emails.id
|
||||||
|
|
||||||
|
lambda_function {
|
||||||
|
id = "wo-email-processor-inbound"
|
||||||
|
lambda_function_arn = aws_lambda_function.wo_email_processor.arn
|
||||||
|
events = ["s3:ObjectCreated:*"]
|
||||||
|
filter_prefix = "inbound/"
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [aws_lambda_permission.wo_emails_invoke]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_permission" "wo_emails_invoke" {
|
||||||
|
statement_id = "AllowS3InvokeWoEmailProcessor"
|
||||||
|
action = "lambda:InvokeFunction"
|
||||||
|
function_name = aws_lambda_function.wo_email_processor.function_name
|
||||||
|
principal = "s3.amazonaws.com"
|
||||||
|
source_arn = aws_s3_bucket.wo_emails.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_sqs_queue" "wo_email_processor_dlq" {
|
||||||
|
name = local.wo_email_processor_dlq_name
|
||||||
|
message_retention_seconds = 1209600
|
||||||
|
sqs_managed_sse_enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_sqs_queue_policy" "wo_email_processor_dlq" {
|
||||||
|
queue_url = aws_sqs_queue.wo_email_processor_dlq.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Deny"
|
||||||
|
Principal = { AWS = "*" }
|
||||||
|
Action = "sqs:*"
|
||||||
|
Resource = aws_sqs_queue.wo_email_processor_dlq.arn
|
||||||
|
Condition = {
|
||||||
|
Bool = { "aws:SecureTransport" = "false" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
14
terraform/wo_ses.tf
Normal file
14
terraform/wo_ses.tf
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
resource "aws_ses_receipt_rule" "wo_email" {
|
||||||
|
name = local.wo_ses_rule_name
|
||||||
|
rule_set_name = local.ses_rule_set_name
|
||||||
|
recipients = ["apm@int.seahaven.com"]
|
||||||
|
enabled = true
|
||||||
|
scan_enabled = false
|
||||||
|
tls_policy = "Optional"
|
||||||
|
|
||||||
|
s3_action {
|
||||||
|
bucket_name = aws_s3_bucket.wo_emails.id
|
||||||
|
object_key_prefix = "inbound/"
|
||||||
|
position = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
345
terraform/wo_shoc.tf
Normal file
345
terraform/wo_shoc.tf
Normal file
|
|
@ -0,0 +1,345 @@
|
||||||
|
data "aws_iam_policy_document" "shoc_webhook_kms" {
|
||||||
|
statement {
|
||||||
|
sid = "EnableRootAccountPermissions"
|
||||||
|
effect = "Allow"
|
||||||
|
actions = ["kms:*"]
|
||||||
|
resources = ["*"]
|
||||||
|
|
||||||
|
principals {
|
||||||
|
type = "AWS"
|
||||||
|
identifiers = ["arn:aws:iam::${local.account_id}:root"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statement {
|
||||||
|
sid = "ShocBackendDevDecrypt"
|
||||||
|
effect = "Allow"
|
||||||
|
actions = ["kms:Decrypt"]
|
||||||
|
resources = ["*"]
|
||||||
|
|
||||||
|
principals {
|
||||||
|
type = "AWS"
|
||||||
|
identifiers = [local.shoc_consumer_role_arn]
|
||||||
|
}
|
||||||
|
|
||||||
|
condition {
|
||||||
|
test = "StringEquals"
|
||||||
|
variable = "kms:ViaService"
|
||||||
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statement {
|
||||||
|
sid = "AllowSecretsManagerViaServiceAccount"
|
||||||
|
effect = "Allow"
|
||||||
|
actions = [
|
||||||
|
"kms:Decrypt",
|
||||||
|
"kms:Encrypt",
|
||||||
|
"kms:ReEncrypt*",
|
||||||
|
"kms:GenerateDataKey*",
|
||||||
|
]
|
||||||
|
resources = ["*"]
|
||||||
|
|
||||||
|
principals {
|
||||||
|
type = "AWS"
|
||||||
|
identifiers = ["arn:aws:iam::${local.account_id}:root"]
|
||||||
|
}
|
||||||
|
|
||||||
|
condition {
|
||||||
|
test = "StringEquals"
|
||||||
|
variable = "kms:ViaService"
|
||||||
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statement {
|
||||||
|
sid = "AllowSecretsManagerGrants"
|
||||||
|
effect = "Allow"
|
||||||
|
actions = [
|
||||||
|
"kms:CreateGrant",
|
||||||
|
"kms:DescribeKey",
|
||||||
|
]
|
||||||
|
resources = ["*"]
|
||||||
|
|
||||||
|
principals {
|
||||||
|
type = "AWS"
|
||||||
|
identifiers = ["arn:aws:iam::${local.account_id}:root"]
|
||||||
|
}
|
||||||
|
|
||||||
|
condition {
|
||||||
|
test = "StringEquals"
|
||||||
|
variable = "kms:ViaService"
|
||||||
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statement {
|
||||||
|
sid = "ShocHmacRotator"
|
||||||
|
effect = "Allow"
|
||||||
|
actions = [
|
||||||
|
"kms:Decrypt",
|
||||||
|
"kms:Encrypt",
|
||||||
|
"kms:ReEncrypt*",
|
||||||
|
"kms:GenerateDataKey*",
|
||||||
|
]
|
||||||
|
resources = ["*"]
|
||||||
|
|
||||||
|
principals {
|
||||||
|
type = "AWS"
|
||||||
|
identifiers = [aws_iam_role.wo_shoc_hmac_rotator.arn]
|
||||||
|
}
|
||||||
|
|
||||||
|
condition {
|
||||||
|
test = "StringEquals"
|
||||||
|
variable = "kms:ViaService"
|
||||||
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statement {
|
||||||
|
sid = "ShocEmitterDecrypt"
|
||||||
|
effect = "Allow"
|
||||||
|
actions = ["kms:Decrypt"]
|
||||||
|
resources = ["*"]
|
||||||
|
|
||||||
|
principals {
|
||||||
|
type = "AWS"
|
||||||
|
identifiers = [aws_iam_role.wo_shoc_emitter.arn]
|
||||||
|
}
|
||||||
|
|
||||||
|
condition {
|
||||||
|
test = "StringEquals"
|
||||||
|
variable = "kms:ViaService"
|
||||||
|
values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_kms_key" "shoc_webhook" {
|
||||||
|
description = "Dedicated CMK for the workorder-ingest/shoc-webhook-hmac secret (cross-account readable by the SHOC backend)"
|
||||||
|
enable_key_rotation = true
|
||||||
|
deletion_window_in_days = 7
|
||||||
|
policy = data.aws_iam_policy_document.shoc_webhook_kms.json
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_kms_alias" "shoc_webhook" {
|
||||||
|
name = "alias/workorder-ingest-shoc-webhook-kms"
|
||||||
|
target_key_id = aws_kms_key.shoc_webhook.key_id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_secretsmanager_secret" "shoc_webhook_hmac" {
|
||||||
|
name = "workorder-ingest/shoc-webhook-hmac"
|
||||||
|
description = "HMAC signing keys for the SHOC work-order webhook (docs/shoc-webhook-contract.md section 6)"
|
||||||
|
kms_key_id = aws_kms_key.shoc_webhook.arn
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Purpose = "shoc-webhook-hmac"
|
||||||
|
ManagedBy = "terraform"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_secretsmanager_secret_policy" "shoc_webhook_hmac" {
|
||||||
|
secret_arn = aws_secretsmanager_secret.shoc_webhook_hmac.arn
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Allow"
|
||||||
|
Principal = {
|
||||||
|
AWS = local.shoc_consumer_role_arn
|
||||||
|
}
|
||||||
|
Action = [
|
||||||
|
"secretsmanager:GetSecretValue",
|
||||||
|
"secretsmanager:DescribeSecret",
|
||||||
|
]
|
||||||
|
Resource = "*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Effect = "Deny"
|
||||||
|
Principal = {
|
||||||
|
AWS = "arn:aws:iam::${local.account_id}:root"
|
||||||
|
}
|
||||||
|
Action = "secretsmanager:DeleteSecret"
|
||||||
|
Resource = "*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_log_group" "wo_shoc_hmac_rotator" {
|
||||||
|
name = "/aws/lambda/workorder-shoc-hmac-rotator"
|
||||||
|
retention_in_days = 60
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_function" "wo_shoc_hmac_rotator" {
|
||||||
|
function_name = "workorder-shoc-hmac-rotator"
|
||||||
|
role = aws_iam_role.wo_shoc_hmac_rotator.arn
|
||||||
|
handler = "handler.handler"
|
||||||
|
runtime = "python3.12"
|
||||||
|
architectures = ["arm64"]
|
||||||
|
memory_size = 128
|
||||||
|
timeout = 60
|
||||||
|
|
||||||
|
s3_bucket = aws_s3_bucket.artifacts.id
|
||||||
|
s3_key = aws_s3_object.lambda["wo_shoc_hmac_rotator"].key
|
||||||
|
source_code_hash = data.archive_file.lambda["wo_shoc_hmac_rotator"].output_base64sha256
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
aws_s3_object.lambda,
|
||||||
|
aws_cloudwatch_log_group.wo_shoc_hmac_rotator,
|
||||||
|
aws_iam_role_policy_attachment.wo_shoc_hmac_rotator_basic,
|
||||||
|
aws_iam_role_policy.wo_shoc_hmac_rotator_secrets,
|
||||||
|
aws_iam_role_policy.wo_shoc_hmac_rotator_kms,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_permission" "wo_shoc_hmac_rotator" {
|
||||||
|
statement_id = "AllowSecretsManagerRotate"
|
||||||
|
action = "lambda:InvokeFunction"
|
||||||
|
function_name = aws_lambda_function.wo_shoc_hmac_rotator.function_name
|
||||||
|
principal = "secretsmanager.amazonaws.com"
|
||||||
|
source_account = local.account_id
|
||||||
|
source_arn = aws_secretsmanager_secret.shoc_webhook_hmac.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_secretsmanager_secret_rotation" "shoc_webhook_hmac" {
|
||||||
|
secret_id = aws_secretsmanager_secret.shoc_webhook_hmac.id
|
||||||
|
rotation_lambda_arn = aws_lambda_function.wo_shoc_hmac_rotator.arn
|
||||||
|
|
||||||
|
rotation_rules {
|
||||||
|
automatically_after_days = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [aws_lambda_permission.wo_shoc_hmac_rotator]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_sqs_queue" "shoc_emitter_failures" {
|
||||||
|
name = "workorder-shoc-emitter-failures"
|
||||||
|
message_retention_seconds = 1209600
|
||||||
|
sqs_managed_sse_enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_sqs_queue_policy" "shoc_emitter_failures" {
|
||||||
|
queue_url = aws_sqs_queue.shoc_emitter_failures.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Deny"
|
||||||
|
Principal = { AWS = "*" }
|
||||||
|
Action = "sqs:*"
|
||||||
|
Resource = aws_sqs_queue.shoc_emitter_failures.arn
|
||||||
|
Condition = {
|
||||||
|
Bool = { "aws:SecureTransport" = "false" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_sqs_queue" "shoc_emitter_rejected" {
|
||||||
|
name = "workorder-shoc-emitter-rejected"
|
||||||
|
message_retention_seconds = 1209600
|
||||||
|
sqs_managed_sse_enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_sqs_queue_policy" "shoc_emitter_rejected" {
|
||||||
|
queue_url = aws_sqs_queue.shoc_emitter_rejected.id
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Effect = "Deny"
|
||||||
|
Principal = { AWS = "*" }
|
||||||
|
Action = "sqs:*"
|
||||||
|
Resource = aws_sqs_queue.shoc_emitter_rejected.arn
|
||||||
|
Condition = {
|
||||||
|
Bool = { "aws:SecureTransport" = "false" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cloudwatch_log_group" "wo_shoc_emitter" {
|
||||||
|
name = "/aws/lambda/workorder-shoc-emitter"
|
||||||
|
retention_in_days = 60
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_function" "wo_shoc_emitter" {
|
||||||
|
function_name = "workorder-shoc-emitter"
|
||||||
|
role = aws_iam_role.wo_shoc_emitter.arn
|
||||||
|
handler = "handler.handler"
|
||||||
|
runtime = "python3.12"
|
||||||
|
architectures = ["arm64"]
|
||||||
|
memory_size = 256
|
||||||
|
timeout = 60
|
||||||
|
|
||||||
|
s3_bucket = aws_s3_bucket.artifacts.id
|
||||||
|
s3_key = aws_s3_object.lambda["wo_shoc_emitter"].key
|
||||||
|
source_code_hash = data.archive_file.lambda["wo_shoc_emitter"].output_base64sha256
|
||||||
|
|
||||||
|
environment {
|
||||||
|
variables = {
|
||||||
|
HMAC_SECRET_ARN = var.shoc_hmac_secret_arn
|
||||||
|
REJECTED_QUEUE_URL = aws_sqs_queue.shoc_emitter_rejected.url
|
||||||
|
SHOC_WEBHOOK_URL = var.shoc_webhook_url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
aws_s3_object.lambda,
|
||||||
|
aws_cloudwatch_log_group.wo_shoc_emitter,
|
||||||
|
aws_iam_role_policy_attachment.wo_shoc_emitter_basic,
|
||||||
|
aws_iam_role_policy.wo_shoc_emitter_streams,
|
||||||
|
aws_iam_role_policy.wo_shoc_emitter_secrets,
|
||||||
|
aws_iam_role_policy.wo_shoc_emitter_kms,
|
||||||
|
aws_iam_role_policy.wo_shoc_emitter_sqs,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_event_source_mapping" "wo_shoc_emitter_work_orders" {
|
||||||
|
event_source_arn = aws_dynamodb_table.work_orders.stream_arn
|
||||||
|
function_name = aws_lambda_function.wo_shoc_emitter.arn
|
||||||
|
starting_position = "LATEST"
|
||||||
|
batch_size = 10
|
||||||
|
parallelization_factor = 1
|
||||||
|
maximum_record_age_in_seconds = 86400
|
||||||
|
maximum_retry_attempts = -1
|
||||||
|
bisect_batch_on_function_error = false
|
||||||
|
function_response_types = ["ReportBatchItemFailures"]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
destination_config {
|
||||||
|
on_failure {
|
||||||
|
destination_arn = aws_sqs_queue.shoc_emitter_failures.arn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_lambda_event_source_mapping" "wo_shoc_emitter_comments" {
|
||||||
|
event_source_arn = aws_dynamodb_table.work_order_comments.stream_arn
|
||||||
|
function_name = aws_lambda_function.wo_shoc_emitter.arn
|
||||||
|
starting_position = "LATEST"
|
||||||
|
batch_size = 10
|
||||||
|
parallelization_factor = 1
|
||||||
|
maximum_record_age_in_seconds = 86400
|
||||||
|
maximum_retry_attempts = -1
|
||||||
|
bisect_batch_on_function_error = false
|
||||||
|
function_response_types = ["ReportBatchItemFailures"]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
destination_config {
|
||||||
|
on_failure {
|
||||||
|
destination_arn = aws_sqs_queue.shoc_emitter_failures.arn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue