@aws/nx-plugin 1.0.0-rc.83 → 1.0.0-rc.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/generators.json +7 -0
  2. package/migrations.json +31 -1
  3. package/package.json +1 -1
  4. package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +51 -2
  5. package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +4 -5
  6. package/src/agentcore-harness/files/project/README.md.template +4 -5
  7. package/src/internal/test-matrix/generator.js +16 -0
  8. package/src/internal/test-matrix/generator.js.map +1 -1
  9. package/src/migrations/latest/agentcore-harness-no-tools-by-default/__snapshots__/migration.spec.ts.snap +79 -0
  10. package/src/migrations/latest/agentcore-harness-no-tools-by-default/metadata.json +3 -0
  11. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.d.ts +6 -0
  12. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js +87 -0
  13. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +1 -0
  14. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/__snapshots__/migration.spec.ts.snap +54 -0
  15. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/metadata.json +3 -0
  16. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.d.ts +6 -0
  17. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js +86 -0
  18. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
  19. package/src/migrations/latest/terraform-declare-used-providers/metadata.json +3 -0
  20. package/src/migrations/latest/terraform-declare-used-providers/migration.d.ts +6 -0
  21. package/src/migrations/latest/terraform-declare-used-providers/migration.js +147 -0
  22. package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +1 -0
  23. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/__snapshots__/migration.spec.ts.snap +521 -0
  24. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/metadata.json +3 -0
  25. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.d.ts +6 -0
  26. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js +504 -0
  27. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
  28. package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/metadata.json +3 -0
  29. package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.d.ts +6 -0
  30. package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js +146 -0
  31. package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js.map +1 -0
  32. package/src/migrations/latest/waf-log-group-cmk-encryption/__snapshots__/migration.spec.ts.snap +286 -0
  33. package/src/migrations/latest/waf-log-group-cmk-encryption/metadata.json +3 -0
  34. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.d.ts +6 -0
  35. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js +238 -0
  36. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +1 -0
  37. package/src/open-api/json-metadata/generator.d.ts +24 -0
  38. package/src/open-api/json-metadata/generator.js +37 -0
  39. package/src/open-api/json-metadata/generator.js.map +1 -0
  40. package/src/open-api/json-metadata/schema.d.js +6 -0
  41. package/src/open-api/json-metadata/schema.d.js.map +1 -0
  42. package/src/open-api/json-metadata/schema.d.ts +9 -0
  43. package/src/open-api/json-metadata/schema.json +20 -0
  44. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +12 -0
  45. package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +1875 -623
  46. package/src/py/fast-api/generator.js +2 -1
  47. package/src/py/fast-api/generator.js.map +1 -1
  48. package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  49. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  50. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +31 -2
  51. package/src/sdk/open-api.d.ts +2 -0
  52. package/src/sdk/open-api.js +2 -1
  53. package/src/sdk/open-api.js.map +1 -1
  54. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +1756 -457
  55. package/src/smithy/ts/api/generator.js +2 -1
  56. package/src/smithy/ts/api/generator.js.map +1 -1
  57. package/src/terraform/project/files/application/bootstrap/providers.tf.template +11 -0
  58. package/src/terraform/project/files/application/scripts/bootstrap-destroy.ts.template +111 -0
  59. package/src/terraform/project/files/application/src/providers.tf.template +6 -0
  60. package/src/terraform/project/files/checkov/checkov.yml.template +8 -0
  61. package/src/terraform/project/files/library/src/main.tf.template +9 -0
  62. package/src/terraform/project/generator.js +47 -4
  63. package/src/terraform/project/generator.js.map +1 -1
  64. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +1848 -614
  65. package/src/trpc/backend/files-operations/scripts/generate-operations.ts.template +40 -0
  66. package/src/trpc/backend/generator.js +10 -0
  67. package/src/trpc/backend/generator.js.map +1 -1
  68. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +8 -0
  69. package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +4 -0
  70. package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  71. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  72. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +62 -4
  73. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +4 -0
  74. package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +35 -8
  75. package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +4 -0
  76. package/src/utils/agent-core-constructs/files/cdk/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.ts.template +39 -1
  77. package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +8 -1
  78. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +57 -2
  79. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +258 -24
  80. package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +4 -0
  81. package/src/utils/api-constructs/api-constructs.js +11 -0
  82. package/src/utils/api-constructs/api-constructs.js.map +1 -1
  83. package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +251 -3
  84. package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +437 -2
  85. package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +60 -7
  86. package/src/utils/api-constructs/open-api-metadata.d.ts +20 -3
  87. package/src/utils/api-constructs/open-api-metadata.js +64 -8
  88. package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
  89. package/src/utils/api-constructs/trpc-operations-metadata.d.ts +21 -0
  90. package/src/utils/api-constructs/trpc-operations-metadata.js +55 -0
  91. package/src/utils/api-constructs/trpc-operations-metadata.js.map +1 -0
  92. package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  93. package/src/utils/files/terraform/src/metrics/providers.tf.template +12 -0
  94. package/src/utils/function-constructs/files/terraform/app/lambda-functions/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  95. package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +33 -8
  96. package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +4 -0
  97. package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +51 -2
  98. package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +18 -0
  99. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +15 -4
  100. package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +2 -2
  101. package/src/utils/rdb-constructs/rdb-constructs.d.ts +12 -0
  102. package/src/utils/rdb-constructs/rdb-constructs.js +24 -3
  103. package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
  104. package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +4 -0
@@ -2,10 +2,18 @@ terraform {
2
2
  required_version = ">= 1.0"
3
3
 
4
4
  required_providers {
5
+ archive = {
6
+ source = "hashicorp/archive"
7
+ version = "<%- archiveProviderVersion %>"
8
+ }
5
9
  aws = {
6
10
  source = "hashicorp/aws"
7
11
  version = "<%- awsProviderVersion %>"
8
12
  }
13
+ random = {
14
+ source = "hashicorp/random"
15
+ version = "<%- randomProviderVersion %>"
16
+ }
9
17
  }
10
18
  }
11
19
 
@@ -127,6 +135,51 @@ resource "random_string" "suffix" {
127
135
  upper = false
128
136
  }
129
137
 
138
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
139
+ locals {
140
+ # Generated at build time from the API definition
141
+ operations_file = "${path.module}/../../../generated/<%- apiNameKebabCase %>/operations.json"
142
+ operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
143
+
144
+ operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
145
+ operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
146
+
147
+ function_name = { for op in keys(local.operations) : op =>
148
+ "${substr("<%- apiNameClassName %>-${local.operation_slug[op]}", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-${local.operation_hash[op]}-${random_string.suffix.result}"
149
+ }
150
+ role_name = { for op in keys(local.operations) : op =>
151
+ "${substr("<%- apiNameClassName %>-${local.operation_slug[op]}-role", 0, 64 - length(local.operation_hash[op]) - length(random_string.suffix.result) - 2)}-${local.operation_hash[op]}-${random_string.suffix.result}"
152
+ }
153
+
154
+ operation_path = { for op, details in local.operations : op =>
155
+ startswith(details.path, "/") ? details.path : "/${details.path}"
156
+ }
157
+ route_key = { for op, details in local.operations : op =>
158
+ "${upper(details.method)} ${local.operation_path[op]}"
159
+ }
160
+
161
+ permission_source_suffix = { for op, details in local.operations : op =>
162
+ "${upper(details.method)}${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
163
+ }
164
+ }
165
+
166
+ resource "terraform_data" "operations_metadata" {
167
+ # Fails the plan when the operations metadata has not been generated
168
+ input = local.operations_file
169
+
170
+ lifecycle {
171
+ precondition {
172
+ condition = fileexists(local.operations_file)
173
+ error_message = "Operations metadata not found at ${local.operations_file}. Build the <%- apiNameClassName %> project to generate it."
174
+ }
175
+ precondition {
176
+ condition = length(local.operations) > 0
177
+ error_message = "No operations found in ${local.operations_file}. The <%- apiNameClassName %> API must define at least one operation."
178
+ }
179
+ }
180
+ }
181
+ <%_ } _%>
182
+
130
183
  # Resources
131
184
 
132
185
  # Create Lambda ZIP file from the bundle directory
@@ -187,9 +240,14 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
187
240
  description = "Allow outbound HTTPS to AWS service endpoints"
188
241
  }
189
242
 
190
- # Lambda function
191
- # This configures a single "router" lambda to serve all requests
192
243
  resource "aws_lambda_function" "api_lambda" {
244
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
245
+ # One lambda function per operation, each serving just that operation
246
+ for_each = local.operations
247
+
248
+ <%_ } else { _%>
249
+ # A single "router" lambda serving all requests
250
+ <%_ } _%>
193
251
  #checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
194
252
  #checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
195
253
  #checkov:skip=CKV_AWS_272:Code signing not required for this use case
@@ -198,8 +256,13 @@ resource "aws_lambda_function" "api_lambda" {
198
256
  s3_bucket = aws_s3_object.lambda_zip.bucket
199
257
  s3_key = aws_s3_object.lambda_zip.key
200
258
  s3_object_version = aws_s3_object.lambda_zip.version_id
259
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
260
+ function_name = local.function_name[each.key]
261
+ role = aws_iam_role.lambda_execution_role[each.key].arn
262
+ <%_ } else { _%>
201
263
  function_name = "<%- apiNameClassName %>Handler-${random_string.suffix.result}"
202
264
  role = aws_iam_role.lambda_execution_role.arn
265
+ <%_ } _%>
203
266
  <%_ if (['trpc', 'smithy'].includes(backend.type)) { _%>
204
267
  handler = "index.handler"
205
268
  runtime = "nodejs22.x"
@@ -255,7 +318,13 @@ resource "aws_lambda_function" "api_lambda" {
255
318
 
256
319
  # IAM role for Lambda execution
257
320
  resource "aws_iam_role" "lambda_execution_role" {
321
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
322
+ for_each = local.operations
323
+
324
+ name = local.role_name[each.key]
325
+ <%_ } else { _%>
258
326
  name = "<%- apiNameClassName %>Handler-execution-role-${random_string.suffix.result}"
327
+ <%_ } _%>
259
328
 
260
329
  assume_role_policy = jsonencode({
261
330
  Version = "2012-10-17"
@@ -275,25 +344,57 @@ resource "aws_iam_role" "lambda_execution_role" {
275
344
 
276
345
  # Attach basic execution policy to Lambda role
277
346
  resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
347
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
348
+ for_each = local.operations
349
+
350
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
351
+ role = aws_iam_role.lambda_execution_role[each.key].name
352
+ <%_ } else { _%>
278
353
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
279
354
  role = aws_iam_role.lambda_execution_role.name
355
+ <%_ } _%>
280
356
  }
281
357
 
282
358
  # Attach X-Ray tracing policy to Lambda role
283
359
  resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
360
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
361
+ for_each = local.operations
362
+
363
+ policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
364
+ role = aws_iam_role.lambda_execution_role[each.key].name
365
+ <%_ } else { _%>
284
366
  policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
285
367
  role = aws_iam_role.lambda_execution_role.name
368
+ <%_ } _%>
286
369
  }
287
370
 
288
371
  # Attach VPC access policy to Lambda role when deployed into a VPC
289
372
  resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
373
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
374
+ for_each = var.enable_vpc ? local.operations : {}
375
+
376
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
377
+ role = aws_iam_role.lambda_execution_role[each.key].name
378
+ <%_ } else { _%>
290
379
  count = var.enable_vpc ? 1 : 0
291
380
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
292
381
  role = aws_iam_role.lambda_execution_role.name
382
+ <%_ } _%>
293
383
  }
294
384
 
295
385
  # Additional IAM policies for Lambda (if provided)
296
386
  resource "aws_iam_role_policy" "lambda_additional_policies" {
387
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
388
+ for_each = local.operations
389
+
390
+ name = "${substr(local.function_name[each.key], 0, 55)}-policies"
391
+ role = aws_iam_role.lambda_execution_role[each.key].id
392
+
393
+ policy = jsonencode({
394
+ Version = "2012-10-17"
395
+ Statement = local.lambda_policy_document_statements
396
+ })
397
+ <%_ } else { _%>
297
398
  count = length(local.lambda_policy_statements) > 0 ? 1 : 0
298
399
  name = "<%- apiNameClassName %>Handler-additional-policies-${random_string.suffix.result}"
299
400
  role = aws_iam_role.lambda_execution_role.id
@@ -302,6 +403,7 @@ resource "aws_iam_role_policy" "lambda_additional_policies" {
302
403
  Version = "2012-10-17"
303
404
  Statement = local.lambda_policy_statements
304
405
  })
406
+ <%_ } _%>
305
407
  }
306
408
 
307
409
  locals {
@@ -316,6 +418,17 @@ locals {
316
418
  Resource = ["${var.appconfig_application_arn}/*"]
317
419
  }
318
420
  ] : [], var.additional_iam_policy_statements)
421
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
422
+
423
+ # IAM rejects an empty statement list, so fall back to a no-op deny
424
+ lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
425
+ {
426
+ Effect = "Deny"
427
+ Action = ["appconfig:GetLatestConfiguration"]
428
+ Resource = ["arn:aws:appconfig:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:application/none"]
429
+ }
430
+ ]
431
+ <%_ } _%>
319
432
  }
320
433
 
321
434
  # CloudWatch Log Group for Lambda
@@ -323,16 +436,31 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
323
436
  #checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
324
437
  #checkov:skip=CKV_AWS_338:Log retention set to forever
325
438
  #checkov:skip=CKV_AWS_66:Log retention set to forever
439
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
440
+ for_each = local.operations
441
+
442
+ name = "/aws/lambda/${local.function_name[each.key]}"
443
+ tags = var.tags
444
+ <%_ } else { _%>
326
445
  name = "/aws/lambda/<%- apiNameClassName %>Handler-${random_string.suffix.result}"
327
446
  tags = var.tags
447
+ <%_ } _%>
328
448
  }
329
449
 
330
450
  <%_ if (backend.type === 'fastapi') { _%>
331
451
  # Lambda alias pointing to the latest published version for SnapStart
332
452
  resource "aws_lambda_alias" "live" {
453
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
454
+ for_each = local.operations
455
+
456
+ name = "live"
457
+ function_name = aws_lambda_function.api_lambda[each.key].function_name
458
+ function_version = aws_lambda_function.api_lambda[each.key].version
459
+ <%_ } else { _%>
333
460
  name = "live"
334
461
  function_name = aws_lambda_function.api_lambda.function_name
335
462
  function_version = aws_lambda_function.api_lambda.version
463
+ <%_ } _%>
336
464
 
337
465
  depends_on = [aws_lambda_function.api_lambda]
338
466
  }
@@ -447,7 +575,8 @@ resource "aws_apigatewayv2_authorizer" "custom_authorizer" {
447
575
  authorizer_type = "REQUEST"
448
576
  authorizer_uri = aws_lambda_function.authorizer_lambda.invoke_arn
449
577
  authorizer_payload_format_version = "2.0"
450
- authorizer_result_ttl_in_seconds = 0
578
+ authorizer_result_ttl_in_seconds = 300
579
+ identity_sources = ["$request.header.Authorization"]
451
580
  enable_simple_responses = true
452
581
  name = "<%- apiNameClassName %>Authorizer-${random_string.suffix.result}"
453
582
  }
@@ -463,6 +592,17 @@ resource "aws_lambda_permission" "authorizer_invoke" {
463
592
 
464
593
  # Lambda integration for HTTP API
465
594
  resource "aws_apigatewayv2_integration" "lambda_integration" {
595
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
596
+ for_each = local.operations
597
+
598
+ api_id = module.http_api.api_id
599
+ integration_type = "AWS_PROXY"
600
+ <%_ if (backend.type === 'fastapi') { _%>
601
+ integration_uri = aws_lambda_alias.live[each.key].invoke_arn
602
+ <%_ } else { _%>
603
+ integration_uri = aws_lambda_function.api_lambda[each.key].invoke_arn
604
+ <%_ } _%>
605
+ <%_ } else { _%>
466
606
  api_id = module.http_api.api_id
467
607
  integration_type = "AWS_PROXY"
468
608
  <%_ if (backend.type === 'fastapi') { _%>
@@ -470,6 +610,7 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
470
610
  <%_ } else { _%>
471
611
  integration_uri = aws_lambda_function.api_lambda.invoke_arn
472
612
  <%_ } _%>
613
+ <%_ } _%>
473
614
 
474
615
  payload_format_version = "2.0"
475
616
  timeout_milliseconds = 30000
@@ -481,6 +622,28 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
481
622
  <%_ } _%>
482
623
  }
483
624
 
625
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
626
+ # One route per operation, each targeting that operation's integration
627
+ resource "aws_apigatewayv2_route" "operation_routes" {
628
+ for_each = local.operations
629
+
630
+ api_id = module.http_api.api_id
631
+ route_key = local.route_key[each.key]
632
+ target = "integrations/${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
633
+
634
+ <%_ if (auth === 'iam') { _%>
635
+ authorization_type = "AWS_IAM"
636
+ <%_ } else if (auth === 'cognito') { _%>
637
+ authorization_type = "JWT"
638
+ authorizer_id = aws_apigatewayv2_authorizer.cognito_authorizer.id
639
+ <%_ } else if (auth === 'custom') { _%>
640
+ authorization_type = "CUSTOM"
641
+ authorizer_id = aws_apigatewayv2_authorizer.custom_authorizer.id
642
+ <%_ } _%>
643
+
644
+ depends_on = [aws_apigatewayv2_integration.lambda_integration<% if (auth === 'cognito') { %>, aws_apigatewayv2_authorizer.cognito_authorizer<% } else if (auth === 'custom') { %>, aws_apigatewayv2_authorizer.custom_authorizer<% } %>]
645
+ }
646
+ <%_ } else { _%>
484
647
  # Route for proxy integration (catches all requests)
485
648
  resource "aws_apigatewayv2_route" "proxy_routes" {
486
649
  # NB: OPTIONS is omitted here since API Gateway manages responding to preflight requests
@@ -503,6 +666,7 @@ resource "aws_apigatewayv2_route" "proxy_routes" {
503
666
 
504
667
  depends_on = [aws_apigatewayv2_integration.lambda_integration<% if (auth === 'cognito') { %>, aws_apigatewayv2_authorizer.cognito_authorizer<% } else if (auth === 'custom') { %>, aws_apigatewayv2_authorizer.custom_authorizer<% } %>]
505
668
  }
669
+ <%_ } _%>
506
670
 
507
671
  # Add API url to runtime config
508
672
  module "add_url_to_runtime_config" {
@@ -517,6 +681,18 @@ module "add_url_to_runtime_config" {
517
681
 
518
682
  # Lambda permission for API Gateway to invoke the function<% if (backend.type === 'fastapi') { %> via alias<% } %>
519
683
  resource "aws_lambda_permission" "api_gateway_invoke" {
684
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
685
+ for_each = local.operations
686
+
687
+ statement_id = "AllowExecutionFromAPIGateway"
688
+ action = "lambda:InvokeFunction"
689
+ function_name = aws_lambda_function.api_lambda[each.key].function_name
690
+ <%_ if (backend.type === 'fastapi') { _%>
691
+ qualifier = aws_lambda_alias.live[each.key].name
692
+ <%_ } _%>
693
+ principal = "apigateway.amazonaws.com"
694
+ source_arn = "${module.http_api.api_execution_arn}/*/${local.permission_source_suffix[each.key]}"
695
+ <%_ } else { _%>
520
696
  statement_id = "AllowExecutionFromAPIGateway"
521
697
  action = "lambda:InvokeFunction"
522
698
  function_name = aws_lambda_function.api_lambda.function_name
@@ -525,6 +701,7 @@ resource "aws_lambda_permission" "api_gateway_invoke" {
525
701
  <%_ } _%>
526
702
  principal = "apigateway.amazonaws.com"
527
703
  source_arn = "${module.http_api.api_execution_arn}/*/*"
704
+ <%_ } _%>
528
705
 
529
706
  <%_ if (backend.type === 'fastapi') { _%>
530
707
  depends_on = [module.http_api, aws_lambda_alias.live]
@@ -571,6 +748,76 @@ output "stage_execution_arn" {
571
748
  value = module.http_api.stage_execution_arn
572
749
  }
573
750
 
751
+ <%_ if (backend.integrationPattern === 'isolated') { _%>
752
+ # Lambda Function Outputs, keyed by operation name
753
+ output "operations" {
754
+ description = "Names of the API operations, each with its own Lambda function"
755
+ value = keys(local.operations)
756
+ }
757
+
758
+ output "lambda_function_names" {
759
+ description = "Name of each operation's Lambda function, keyed by operation name"
760
+ value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
761
+ }
762
+
763
+ output "lambda_function_arns" {
764
+ description = "ARN of each operation's Lambda function, keyed by operation name"
765
+ value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
766
+ }
767
+
768
+ output "lambda_invoke_arns" {
769
+ description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
770
+ value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
771
+ }
772
+
773
+ output "lambda_qualified_arns" {
774
+ description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
775
+ value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
776
+ }
777
+
778
+ output "lambda_versions" {
779
+ description = "Version of each operation's Lambda function, keyed by operation name"
780
+ value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
781
+ }
782
+
783
+ output "lambda_source_code_hash" {
784
+ description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
785
+ value = data.archive_file.lambda_zip.output_base64sha256
786
+ }
787
+
788
+ # IAM Role Outputs, keyed by operation name
789
+ output "lambda_execution_role_arns" {
790
+ description = "ARN of each operation's Lambda execution role, keyed by operation name"
791
+ value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
792
+ }
793
+
794
+ output "lambda_execution_role_names" {
795
+ description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
796
+ value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
797
+ }
798
+
799
+ output "security_group_id" {
800
+ description = "Security group ID shared by the API Lambda functions, for use in ingress rules on resources they must reach (e.g. a database). Null unless enable_vpc is true."
801
+ value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
802
+ }
803
+
804
+ # Integration Outputs, keyed by operation name
805
+ output "integration_ids" {
806
+ description = "ID of each operation's Lambda integration, keyed by operation name"
807
+ value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
808
+ }
809
+
810
+ # CloudWatch Log Groups, keyed by operation name
811
+ output "lambda_log_group_names" {
812
+ description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
813
+ value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
814
+ }
815
+
816
+ output "lambda_log_group_arns" {
817
+ description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
818
+ value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
819
+ }
820
+ <%_ } else { _%>
574
821
  # Lambda Function Outputs
575
822
  output "lambda_function_name" {
576
823
  description = "Name of the Lambda function"
@@ -639,6 +886,7 @@ output "lambda_log_group_arn" {
639
886
  description = "ARN of the Lambda CloudWatch log group"
640
887
  value = aws_cloudwatch_log_group.lambda_logs.arn
641
888
  }
889
+ <%_ } _%>
642
890
 
643
891
  output "api_log_group_name" {
644
892
  description = "Name of the API Gateway CloudWatch log group"