@aws/nx-plugin 1.0.0-rc.84 → 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.
- package/generators.json +7 -0
- package/migrations.json +6 -1
- package/package.json +1 -1
- package/src/internal/test-matrix/generator.js +16 -0
- package/src/internal/test-matrix/generator.js.map +1 -1
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/metadata.json +3 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js +146 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js.map +1 -0
- package/src/open-api/json-metadata/generator.d.ts +24 -0
- package/src/open-api/json-metadata/generator.js +37 -0
- package/src/open-api/json-metadata/generator.js.map +1 -0
- package/src/open-api/json-metadata/schema.d.js +6 -0
- package/src/open-api/json-metadata/schema.d.js.map +1 -0
- package/src/open-api/json-metadata/schema.d.ts +9 -0
- package/src/open-api/json-metadata/schema.json +20 -0
- package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +1604 -559
- package/src/py/fast-api/generator.js +2 -1
- package/src/py/fast-api/generator.js.map +1 -1
- package/src/sdk/open-api.d.ts +2 -0
- package/src/sdk/open-api.js +2 -1
- package/src/sdk/open-api.js.map +1 -1
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +1548 -440
- package/src/smithy/ts/api/generator.js +2 -1
- package/src/smithy/ts/api/generator.js.map +1 -1
- package/src/terraform/project/files/application/bootstrap/providers.tf.template +2 -0
- package/src/terraform/project/files/application/scripts/bootstrap-destroy.ts.template +111 -0
- package/src/terraform/project/files/application/src/providers.tf.template +2 -0
- package/src/terraform/project/files/checkov/checkov.yml.template +8 -0
- package/src/terraform/project/generator.js +47 -4
- package/src/terraform/project/generator.js.map +1 -1
- package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +1552 -525
- package/src/trpc/backend/files-operations/scripts/generate-operations.ts.template +40 -0
- package/src/trpc/backend/generator.js +10 -0
- package/src/trpc/backend/generator.js.map +1 -1
- package/src/utils/api-constructs/api-constructs.js +11 -0
- package/src/utils/api-constructs/api-constructs.js.map +1 -1
- package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +243 -3
- package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +428 -1
- package/src/utils/api-constructs/open-api-metadata.d.ts +20 -3
- package/src/utils/api-constructs/open-api-metadata.js +64 -8
- package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
- package/src/utils/api-constructs/trpc-operations-metadata.d.ts +21 -0
- package/src/utils/api-constructs/trpc-operations-metadata.js +55 -0
- package/src/utils/api-constructs/trpc-operations-metadata.js.map +1 -0
|
@@ -387,6 +387,49 @@ resource "random_string" "suffix" {
|
|
|
387
387
|
upper = false
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
+
locals {
|
|
391
|
+
# Generated at build time from the API definition
|
|
392
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
393
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
394
|
+
|
|
395
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
396
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
397
|
+
|
|
398
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
399
|
+
"\${substr("TestApi-\${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}"
|
|
400
|
+
}
|
|
401
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
402
|
+
"\${substr("TestApi-\${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}"
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
operation_path = { for op, details in local.operations : op =>
|
|
406
|
+
startswith(details.path, "/") ? details.path : "/\${details.path}"
|
|
407
|
+
}
|
|
408
|
+
route_key = { for op, details in local.operations : op =>
|
|
409
|
+
"\${upper(details.method)} \${local.operation_path[op]}"
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
413
|
+
"\${upper(details.method)}\${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
resource "terraform_data" "operations_metadata" {
|
|
418
|
+
# Fails the plan when the operations metadata has not been generated
|
|
419
|
+
input = local.operations_file
|
|
420
|
+
|
|
421
|
+
lifecycle {
|
|
422
|
+
precondition {
|
|
423
|
+
condition = fileexists(local.operations_file)
|
|
424
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
425
|
+
}
|
|
426
|
+
precondition {
|
|
427
|
+
condition = length(local.operations) > 0
|
|
428
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
390
433
|
# Resources
|
|
391
434
|
|
|
392
435
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -447,9 +490,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
447
490
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
448
491
|
}
|
|
449
492
|
|
|
450
|
-
# Lambda function
|
|
451
|
-
# This configures a single "router" lambda to serve all requests
|
|
452
493
|
resource "aws_lambda_function" "api_lambda" {
|
|
494
|
+
# One lambda function per operation, each serving just that operation
|
|
495
|
+
for_each = local.operations
|
|
496
|
+
|
|
453
497
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
454
498
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
455
499
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -458,8 +502,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
458
502
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
459
503
|
s3_key = aws_s3_object.lambda_zip.key
|
|
460
504
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
461
|
-
function_name =
|
|
462
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
505
|
+
function_name = local.function_name[each.key]
|
|
506
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
463
507
|
handler = "run.sh"
|
|
464
508
|
runtime = "python3.14"
|
|
465
509
|
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
@@ -504,7 +548,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
504
548
|
|
|
505
549
|
# IAM role for Lambda execution
|
|
506
550
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
507
|
-
|
|
551
|
+
for_each = local.operations
|
|
552
|
+
|
|
553
|
+
name = local.role_name[each.key]
|
|
508
554
|
|
|
509
555
|
assume_role_policy = jsonencode({
|
|
510
556
|
Version = "2012-10-17"
|
|
@@ -524,32 +570,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
524
570
|
|
|
525
571
|
# Attach basic execution policy to Lambda role
|
|
526
572
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
573
|
+
for_each = local.operations
|
|
574
|
+
|
|
527
575
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
528
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
576
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
529
577
|
}
|
|
530
578
|
|
|
531
579
|
# Attach X-Ray tracing policy to Lambda role
|
|
532
580
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
581
|
+
for_each = local.operations
|
|
582
|
+
|
|
533
583
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
534
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
584
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
535
585
|
}
|
|
536
586
|
|
|
537
587
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
538
588
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
539
|
-
|
|
589
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
590
|
+
|
|
540
591
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
541
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
592
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
542
593
|
}
|
|
543
594
|
|
|
544
595
|
# Additional IAM policies for Lambda (if provided)
|
|
545
596
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
597
|
+
for_each = local.operations
|
|
598
|
+
|
|
599
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
600
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
549
601
|
|
|
550
602
|
policy = jsonencode({
|
|
551
603
|
Version = "2012-10-17"
|
|
552
|
-
Statement = local.
|
|
604
|
+
Statement = local.lambda_policy_document_statements
|
|
553
605
|
})
|
|
554
606
|
}
|
|
555
607
|
|
|
@@ -565,6 +617,15 @@ locals {
|
|
|
565
617
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
566
618
|
}
|
|
567
619
|
] : [], var.additional_iam_policy_statements)
|
|
620
|
+
|
|
621
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
622
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
623
|
+
{
|
|
624
|
+
Effect = "Deny"
|
|
625
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
626
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
627
|
+
}
|
|
628
|
+
]
|
|
568
629
|
}
|
|
569
630
|
|
|
570
631
|
# CloudWatch Log Group for Lambda
|
|
@@ -572,15 +633,19 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
572
633
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
573
634
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
574
635
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
575
|
-
|
|
576
|
-
|
|
636
|
+
for_each = local.operations
|
|
637
|
+
|
|
638
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
639
|
+
tags = var.tags
|
|
577
640
|
}
|
|
578
641
|
|
|
579
642
|
# Lambda alias pointing to the latest published version for SnapStart
|
|
580
643
|
resource "aws_lambda_alias" "live" {
|
|
644
|
+
for_each = local.operations
|
|
645
|
+
|
|
581
646
|
name = "live"
|
|
582
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
583
|
-
function_version = aws_lambda_function.api_lambda.version
|
|
647
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
648
|
+
function_version = aws_lambda_function.api_lambda[each.key].version
|
|
584
649
|
|
|
585
650
|
depends_on = [aws_lambda_function.api_lambda]
|
|
586
651
|
}
|
|
@@ -600,9 +665,11 @@ resource "aws_apigatewayv2_authorizer" "cognito_authorizer" {
|
|
|
600
665
|
|
|
601
666
|
# Lambda integration for HTTP API
|
|
602
667
|
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
668
|
+
for_each = local.operations
|
|
669
|
+
|
|
603
670
|
api_id = module.http_api.api_id
|
|
604
671
|
integration_type = "AWS_PROXY"
|
|
605
|
-
integration_uri = aws_lambda_alias.live.invoke_arn
|
|
672
|
+
integration_uri = aws_lambda_alias.live[each.key].invoke_arn
|
|
606
673
|
|
|
607
674
|
payload_format_version = "2.0"
|
|
608
675
|
timeout_milliseconds = 30000
|
|
@@ -610,15 +677,13 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
|
610
677
|
depends_on = [aws_lambda_alias.live]
|
|
611
678
|
}
|
|
612
679
|
|
|
613
|
-
#
|
|
614
|
-
resource "aws_apigatewayv2_route" "
|
|
615
|
-
|
|
616
|
-
# when cors settings are configured
|
|
617
|
-
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
680
|
+
# One route per operation, each targeting that operation's integration
|
|
681
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
682
|
+
for_each = local.operations
|
|
618
683
|
|
|
619
684
|
api_id = module.http_api.api_id
|
|
620
|
-
route_key =
|
|
621
|
-
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
685
|
+
route_key = local.route_key[each.key]
|
|
686
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
|
|
622
687
|
|
|
623
688
|
authorization_type = "JWT"
|
|
624
689
|
authorizer_id = aws_apigatewayv2_authorizer.cognito_authorizer.id
|
|
@@ -639,12 +704,14 @@ module "add_url_to_runtime_config" {
|
|
|
639
704
|
|
|
640
705
|
# Lambda permission for API Gateway to invoke the function via alias
|
|
641
706
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
707
|
+
for_each = local.operations
|
|
708
|
+
|
|
642
709
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
643
710
|
action = "lambda:InvokeFunction"
|
|
644
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
645
|
-
qualifier = aws_lambda_alias.live.name
|
|
711
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
712
|
+
qualifier = aws_lambda_alias.live[each.key].name
|
|
646
713
|
principal = "apigateway.amazonaws.com"
|
|
647
|
-
source_arn = "\${module.http_api.api_execution_arn}
|
|
714
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
648
715
|
|
|
649
716
|
depends_on = [module.http_api, aws_lambda_alias.live]
|
|
650
717
|
}
|
|
@@ -687,73 +754,73 @@ output "stage_execution_arn" {
|
|
|
687
754
|
value = module.http_api.stage_execution_arn
|
|
688
755
|
}
|
|
689
756
|
|
|
690
|
-
# Lambda Function Outputs
|
|
691
|
-
output "
|
|
692
|
-
description = "
|
|
693
|
-
value =
|
|
757
|
+
# Lambda Function Outputs, keyed by operation name
|
|
758
|
+
output "operations" {
|
|
759
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
760
|
+
value = keys(local.operations)
|
|
694
761
|
}
|
|
695
762
|
|
|
696
|
-
output "
|
|
697
|
-
description = "
|
|
698
|
-
value = aws_lambda_function.api_lambda.
|
|
763
|
+
output "lambda_function_names" {
|
|
764
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
765
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
699
766
|
}
|
|
700
767
|
|
|
701
|
-
output "
|
|
702
|
-
description = "
|
|
703
|
-
value = aws_lambda_function.api_lambda.
|
|
768
|
+
output "lambda_function_arns" {
|
|
769
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
770
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
704
771
|
}
|
|
705
772
|
|
|
706
|
-
output "
|
|
707
|
-
description = "
|
|
708
|
-
value = aws_lambda_function.api_lambda.
|
|
773
|
+
output "lambda_invoke_arns" {
|
|
774
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
775
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
709
776
|
}
|
|
710
777
|
|
|
711
|
-
output "
|
|
712
|
-
description = "
|
|
713
|
-
value = aws_lambda_function.api_lambda.
|
|
778
|
+
output "lambda_qualified_arns" {
|
|
779
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
780
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
714
781
|
}
|
|
715
782
|
|
|
716
|
-
output "
|
|
717
|
-
description = "
|
|
718
|
-
value = aws_lambda_function.api_lambda.
|
|
783
|
+
output "lambda_versions" {
|
|
784
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
785
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
719
786
|
}
|
|
720
787
|
|
|
721
|
-
output "
|
|
722
|
-
description = "
|
|
723
|
-
value =
|
|
788
|
+
output "lambda_source_code_hash" {
|
|
789
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
790
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
724
791
|
}
|
|
725
792
|
|
|
726
|
-
# IAM Role Outputs
|
|
727
|
-
output "
|
|
728
|
-
description = "ARN of
|
|
729
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
793
|
+
# IAM Role Outputs, keyed by operation name
|
|
794
|
+
output "lambda_execution_role_arns" {
|
|
795
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
796
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
730
797
|
}
|
|
731
798
|
|
|
732
|
-
output "
|
|
733
|
-
description = "Name of
|
|
734
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
799
|
+
output "lambda_execution_role_names" {
|
|
800
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
801
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
735
802
|
}
|
|
736
803
|
|
|
737
804
|
output "security_group_id" {
|
|
738
|
-
description = "Security group ID
|
|
805
|
+
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."
|
|
739
806
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
740
807
|
}
|
|
741
808
|
|
|
742
|
-
# Integration Outputs
|
|
743
|
-
output "
|
|
744
|
-
description = "ID of
|
|
745
|
-
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
809
|
+
# Integration Outputs, keyed by operation name
|
|
810
|
+
output "integration_ids" {
|
|
811
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
812
|
+
value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
|
|
746
813
|
}
|
|
747
814
|
|
|
748
|
-
# CloudWatch Log Groups
|
|
749
|
-
output "
|
|
750
|
-
description = "Name of
|
|
751
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
815
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
816
|
+
output "lambda_log_group_names" {
|
|
817
|
+
description = "Name 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.name }
|
|
752
819
|
}
|
|
753
820
|
|
|
754
|
-
output "
|
|
755
|
-
description = "ARN of
|
|
756
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
821
|
+
output "lambda_log_group_arns" {
|
|
822
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
823
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
757
824
|
}
|
|
758
825
|
|
|
759
826
|
output "api_log_group_name" {
|
|
@@ -1145,6 +1212,49 @@ resource "random_string" "suffix" {
|
|
|
1145
1212
|
upper = false
|
|
1146
1213
|
}
|
|
1147
1214
|
|
|
1215
|
+
locals {
|
|
1216
|
+
# Generated at build time from the API definition
|
|
1217
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
1218
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
1219
|
+
|
|
1220
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
1221
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
1222
|
+
|
|
1223
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
1224
|
+
"\${substr("TestApi-\${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}"
|
|
1225
|
+
}
|
|
1226
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
1227
|
+
"\${substr("TestApi-\${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}"
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
operation_path = { for op, details in local.operations : op =>
|
|
1231
|
+
startswith(details.path, "/") ? details.path : "/\${details.path}"
|
|
1232
|
+
}
|
|
1233
|
+
route_key = { for op, details in local.operations : op =>
|
|
1234
|
+
"\${upper(details.method)} \${local.operation_path[op]}"
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
1238
|
+
"\${upper(details.method)}\${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
resource "terraform_data" "operations_metadata" {
|
|
1243
|
+
# Fails the plan when the operations metadata has not been generated
|
|
1244
|
+
input = local.operations_file
|
|
1245
|
+
|
|
1246
|
+
lifecycle {
|
|
1247
|
+
precondition {
|
|
1248
|
+
condition = fileexists(local.operations_file)
|
|
1249
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
1250
|
+
}
|
|
1251
|
+
precondition {
|
|
1252
|
+
condition = length(local.operations) > 0
|
|
1253
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1148
1258
|
# Resources
|
|
1149
1259
|
|
|
1150
1260
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -1205,9 +1315,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
1205
1315
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
1206
1316
|
}
|
|
1207
1317
|
|
|
1208
|
-
# Lambda function
|
|
1209
|
-
# This configures a single "router" lambda to serve all requests
|
|
1210
1318
|
resource "aws_lambda_function" "api_lambda" {
|
|
1319
|
+
# One lambda function per operation, each serving just that operation
|
|
1320
|
+
for_each = local.operations
|
|
1321
|
+
|
|
1211
1322
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
1212
1323
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
1213
1324
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -1216,8 +1327,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
1216
1327
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
1217
1328
|
s3_key = aws_s3_object.lambda_zip.key
|
|
1218
1329
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
1219
|
-
function_name =
|
|
1220
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
1330
|
+
function_name = local.function_name[each.key]
|
|
1331
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
1221
1332
|
handler = "run.sh"
|
|
1222
1333
|
runtime = "python3.14"
|
|
1223
1334
|
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
@@ -1262,7 +1373,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
1262
1373
|
|
|
1263
1374
|
# IAM role for Lambda execution
|
|
1264
1375
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
1265
|
-
|
|
1376
|
+
for_each = local.operations
|
|
1377
|
+
|
|
1378
|
+
name = local.role_name[each.key]
|
|
1266
1379
|
|
|
1267
1380
|
assume_role_policy = jsonencode({
|
|
1268
1381
|
Version = "2012-10-17"
|
|
@@ -1282,32 +1395,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
1282
1395
|
|
|
1283
1396
|
# Attach basic execution policy to Lambda role
|
|
1284
1397
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
1398
|
+
for_each = local.operations
|
|
1399
|
+
|
|
1285
1400
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
1286
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
1401
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
1287
1402
|
}
|
|
1288
1403
|
|
|
1289
1404
|
# Attach X-Ray tracing policy to Lambda role
|
|
1290
1405
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
1406
|
+
for_each = local.operations
|
|
1407
|
+
|
|
1291
1408
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
1292
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
1409
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
1293
1410
|
}
|
|
1294
1411
|
|
|
1295
1412
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
1296
1413
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
1297
|
-
|
|
1414
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
1415
|
+
|
|
1298
1416
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
1299
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
1417
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
1300
1418
|
}
|
|
1301
1419
|
|
|
1302
1420
|
# Additional IAM policies for Lambda (if provided)
|
|
1303
1421
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1422
|
+
for_each = local.operations
|
|
1423
|
+
|
|
1424
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
1425
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
1307
1426
|
|
|
1308
1427
|
policy = jsonencode({
|
|
1309
1428
|
Version = "2012-10-17"
|
|
1310
|
-
Statement = local.
|
|
1429
|
+
Statement = local.lambda_policy_document_statements
|
|
1311
1430
|
})
|
|
1312
1431
|
}
|
|
1313
1432
|
|
|
@@ -1323,6 +1442,15 @@ locals {
|
|
|
1323
1442
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
1324
1443
|
}
|
|
1325
1444
|
] : [], var.additional_iam_policy_statements)
|
|
1445
|
+
|
|
1446
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
1447
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
1448
|
+
{
|
|
1449
|
+
Effect = "Deny"
|
|
1450
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
1451
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
1452
|
+
}
|
|
1453
|
+
]
|
|
1326
1454
|
}
|
|
1327
1455
|
|
|
1328
1456
|
# CloudWatch Log Group for Lambda
|
|
@@ -1330,15 +1458,19 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
1330
1458
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
1331
1459
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
1332
1460
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
1333
|
-
|
|
1334
|
-
|
|
1461
|
+
for_each = local.operations
|
|
1462
|
+
|
|
1463
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
1464
|
+
tags = var.tags
|
|
1335
1465
|
}
|
|
1336
1466
|
|
|
1337
1467
|
# Lambda alias pointing to the latest published version for SnapStart
|
|
1338
1468
|
resource "aws_lambda_alias" "live" {
|
|
1469
|
+
for_each = local.operations
|
|
1470
|
+
|
|
1339
1471
|
name = "live"
|
|
1340
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
1341
|
-
function_version = aws_lambda_function.api_lambda.version
|
|
1472
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
1473
|
+
function_version = aws_lambda_function.api_lambda[each.key].version
|
|
1342
1474
|
|
|
1343
1475
|
depends_on = [aws_lambda_function.api_lambda]
|
|
1344
1476
|
}
|
|
@@ -1425,7 +1557,8 @@ resource "aws_apigatewayv2_authorizer" "custom_authorizer" {
|
|
|
1425
1557
|
authorizer_type = "REQUEST"
|
|
1426
1558
|
authorizer_uri = aws_lambda_function.authorizer_lambda.invoke_arn
|
|
1427
1559
|
authorizer_payload_format_version = "2.0"
|
|
1428
|
-
authorizer_result_ttl_in_seconds =
|
|
1560
|
+
authorizer_result_ttl_in_seconds = 300
|
|
1561
|
+
identity_sources = ["$request.header.Authorization"]
|
|
1429
1562
|
enable_simple_responses = true
|
|
1430
1563
|
name = "TestApiAuthorizer-\${random_string.suffix.result}"
|
|
1431
1564
|
}
|
|
@@ -1440,9 +1573,11 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
1440
1573
|
|
|
1441
1574
|
# Lambda integration for HTTP API
|
|
1442
1575
|
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
1576
|
+
for_each = local.operations
|
|
1577
|
+
|
|
1443
1578
|
api_id = module.http_api.api_id
|
|
1444
1579
|
integration_type = "AWS_PROXY"
|
|
1445
|
-
integration_uri = aws_lambda_alias.live.invoke_arn
|
|
1580
|
+
integration_uri = aws_lambda_alias.live[each.key].invoke_arn
|
|
1446
1581
|
|
|
1447
1582
|
payload_format_version = "2.0"
|
|
1448
1583
|
timeout_milliseconds = 30000
|
|
@@ -1450,15 +1585,13 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
|
1450
1585
|
depends_on = [aws_lambda_alias.live]
|
|
1451
1586
|
}
|
|
1452
1587
|
|
|
1453
|
-
#
|
|
1454
|
-
resource "aws_apigatewayv2_route" "
|
|
1455
|
-
|
|
1456
|
-
# when cors settings are configured
|
|
1457
|
-
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
1588
|
+
# One route per operation, each targeting that operation's integration
|
|
1589
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
1590
|
+
for_each = local.operations
|
|
1458
1591
|
|
|
1459
1592
|
api_id = module.http_api.api_id
|
|
1460
|
-
route_key =
|
|
1461
|
-
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
1593
|
+
route_key = local.route_key[each.key]
|
|
1594
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
|
|
1462
1595
|
|
|
1463
1596
|
authorization_type = "CUSTOM"
|
|
1464
1597
|
authorizer_id = aws_apigatewayv2_authorizer.custom_authorizer.id
|
|
@@ -1479,12 +1612,14 @@ module "add_url_to_runtime_config" {
|
|
|
1479
1612
|
|
|
1480
1613
|
# Lambda permission for API Gateway to invoke the function via alias
|
|
1481
1614
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
1615
|
+
for_each = local.operations
|
|
1616
|
+
|
|
1482
1617
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
1483
1618
|
action = "lambda:InvokeFunction"
|
|
1484
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
1485
|
-
qualifier = aws_lambda_alias.live.name
|
|
1619
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
1620
|
+
qualifier = aws_lambda_alias.live[each.key].name
|
|
1486
1621
|
principal = "apigateway.amazonaws.com"
|
|
1487
|
-
source_arn = "\${module.http_api.api_execution_arn}
|
|
1622
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
1488
1623
|
|
|
1489
1624
|
depends_on = [module.http_api, aws_lambda_alias.live]
|
|
1490
1625
|
}
|
|
@@ -1527,73 +1662,73 @@ output "stage_execution_arn" {
|
|
|
1527
1662
|
value = module.http_api.stage_execution_arn
|
|
1528
1663
|
}
|
|
1529
1664
|
|
|
1530
|
-
# Lambda Function Outputs
|
|
1531
|
-
output "
|
|
1532
|
-
description = "
|
|
1533
|
-
value =
|
|
1665
|
+
# Lambda Function Outputs, keyed by operation name
|
|
1666
|
+
output "operations" {
|
|
1667
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
1668
|
+
value = keys(local.operations)
|
|
1534
1669
|
}
|
|
1535
1670
|
|
|
1536
|
-
output "
|
|
1537
|
-
description = "
|
|
1538
|
-
value = aws_lambda_function.api_lambda.
|
|
1671
|
+
output "lambda_function_names" {
|
|
1672
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
1673
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
1539
1674
|
}
|
|
1540
1675
|
|
|
1541
|
-
output "
|
|
1542
|
-
description = "
|
|
1543
|
-
value = aws_lambda_function.api_lambda.
|
|
1676
|
+
output "lambda_function_arns" {
|
|
1677
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
1678
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
1544
1679
|
}
|
|
1545
1680
|
|
|
1546
|
-
output "
|
|
1547
|
-
description = "
|
|
1548
|
-
value = aws_lambda_function.api_lambda.
|
|
1681
|
+
output "lambda_invoke_arns" {
|
|
1682
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
1683
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
1549
1684
|
}
|
|
1550
1685
|
|
|
1551
|
-
output "
|
|
1552
|
-
description = "
|
|
1553
|
-
value = aws_lambda_function.api_lambda.
|
|
1686
|
+
output "lambda_qualified_arns" {
|
|
1687
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
1688
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
1554
1689
|
}
|
|
1555
1690
|
|
|
1556
|
-
output "
|
|
1557
|
-
description = "
|
|
1558
|
-
value = aws_lambda_function.api_lambda.
|
|
1691
|
+
output "lambda_versions" {
|
|
1692
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
1693
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
1559
1694
|
}
|
|
1560
1695
|
|
|
1561
|
-
output "
|
|
1562
|
-
description = "
|
|
1563
|
-
value =
|
|
1696
|
+
output "lambda_source_code_hash" {
|
|
1697
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
1698
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
1564
1699
|
}
|
|
1565
1700
|
|
|
1566
|
-
# IAM Role Outputs
|
|
1567
|
-
output "
|
|
1568
|
-
description = "ARN of
|
|
1569
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
1701
|
+
# IAM Role Outputs, keyed by operation name
|
|
1702
|
+
output "lambda_execution_role_arns" {
|
|
1703
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
1704
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
1570
1705
|
}
|
|
1571
1706
|
|
|
1572
|
-
output "
|
|
1573
|
-
description = "Name of
|
|
1574
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
1707
|
+
output "lambda_execution_role_names" {
|
|
1708
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
1709
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
1575
1710
|
}
|
|
1576
1711
|
|
|
1577
1712
|
output "security_group_id" {
|
|
1578
|
-
description = "Security group ID
|
|
1713
|
+
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."
|
|
1579
1714
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
1580
1715
|
}
|
|
1581
1716
|
|
|
1582
|
-
# Integration Outputs
|
|
1583
|
-
output "
|
|
1584
|
-
description = "ID of
|
|
1585
|
-
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
1717
|
+
# Integration Outputs, keyed by operation name
|
|
1718
|
+
output "integration_ids" {
|
|
1719
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
1720
|
+
value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
|
|
1586
1721
|
}
|
|
1587
1722
|
|
|
1588
|
-
# CloudWatch Log Groups
|
|
1589
|
-
output "
|
|
1590
|
-
description = "Name of
|
|
1591
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
1723
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
1724
|
+
output "lambda_log_group_names" {
|
|
1725
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
1726
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
1592
1727
|
}
|
|
1593
1728
|
|
|
1594
|
-
output "
|
|
1595
|
-
description = "ARN of
|
|
1596
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
1729
|
+
output "lambda_log_group_arns" {
|
|
1730
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
1731
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
1597
1732
|
}
|
|
1598
1733
|
|
|
1599
1734
|
output "api_log_group_name" {
|
|
@@ -1985,6 +2120,49 @@ resource "random_string" "suffix" {
|
|
|
1985
2120
|
upper = false
|
|
1986
2121
|
}
|
|
1987
2122
|
|
|
2123
|
+
locals {
|
|
2124
|
+
# Generated at build time from the API definition
|
|
2125
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
2126
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
2127
|
+
|
|
2128
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
2129
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
2130
|
+
|
|
2131
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
2132
|
+
"\${substr("TestApi-\${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}"
|
|
2133
|
+
}
|
|
2134
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
2135
|
+
"\${substr("TestApi-\${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}"
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
operation_path = { for op, details in local.operations : op =>
|
|
2139
|
+
startswith(details.path, "/") ? details.path : "/\${details.path}"
|
|
2140
|
+
}
|
|
2141
|
+
route_key = { for op, details in local.operations : op =>
|
|
2142
|
+
"\${upper(details.method)} \${local.operation_path[op]}"
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
2146
|
+
"\${upper(details.method)}\${replace(local.operation_path[op], "/{[^}]*}/", "*")}"
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
resource "terraform_data" "operations_metadata" {
|
|
2151
|
+
# Fails the plan when the operations metadata has not been generated
|
|
2152
|
+
input = local.operations_file
|
|
2153
|
+
|
|
2154
|
+
lifecycle {
|
|
2155
|
+
precondition {
|
|
2156
|
+
condition = fileexists(local.operations_file)
|
|
2157
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
2158
|
+
}
|
|
2159
|
+
precondition {
|
|
2160
|
+
condition = length(local.operations) > 0
|
|
2161
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
1988
2166
|
# Resources
|
|
1989
2167
|
|
|
1990
2168
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -2045,9 +2223,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
2045
2223
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
2046
2224
|
}
|
|
2047
2225
|
|
|
2048
|
-
# Lambda function
|
|
2049
|
-
# This configures a single "router" lambda to serve all requests
|
|
2050
2226
|
resource "aws_lambda_function" "api_lambda" {
|
|
2227
|
+
# One lambda function per operation, each serving just that operation
|
|
2228
|
+
for_each = local.operations
|
|
2229
|
+
|
|
2051
2230
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
2052
2231
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
2053
2232
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -2056,8 +2235,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
2056
2235
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
2057
2236
|
s3_key = aws_s3_object.lambda_zip.key
|
|
2058
2237
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
2059
|
-
function_name =
|
|
2060
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
2238
|
+
function_name = local.function_name[each.key]
|
|
2239
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
2061
2240
|
handler = "run.sh"
|
|
2062
2241
|
runtime = "python3.14"
|
|
2063
2242
|
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
@@ -2102,7 +2281,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
2102
2281
|
|
|
2103
2282
|
# IAM role for Lambda execution
|
|
2104
2283
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
2105
|
-
|
|
2284
|
+
for_each = local.operations
|
|
2285
|
+
|
|
2286
|
+
name = local.role_name[each.key]
|
|
2106
2287
|
|
|
2107
2288
|
assume_role_policy = jsonencode({
|
|
2108
2289
|
Version = "2012-10-17"
|
|
@@ -2122,32 +2303,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
2122
2303
|
|
|
2123
2304
|
# Attach basic execution policy to Lambda role
|
|
2124
2305
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
2306
|
+
for_each = local.operations
|
|
2307
|
+
|
|
2125
2308
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
2126
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
2309
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2127
2310
|
}
|
|
2128
2311
|
|
|
2129
2312
|
# Attach X-Ray tracing policy to Lambda role
|
|
2130
2313
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
2314
|
+
for_each = local.operations
|
|
2315
|
+
|
|
2131
2316
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
2132
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
2317
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2133
2318
|
}
|
|
2134
2319
|
|
|
2135
2320
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
2136
2321
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
2137
|
-
|
|
2322
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
2323
|
+
|
|
2138
2324
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
2139
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
2325
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2140
2326
|
}
|
|
2141
2327
|
|
|
2142
2328
|
# Additional IAM policies for Lambda (if provided)
|
|
2143
2329
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2330
|
+
for_each = local.operations
|
|
2331
|
+
|
|
2332
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
2333
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
2147
2334
|
|
|
2148
2335
|
policy = jsonencode({
|
|
2149
2336
|
Version = "2012-10-17"
|
|
2150
|
-
Statement = local.
|
|
2337
|
+
Statement = local.lambda_policy_document_statements
|
|
2151
2338
|
})
|
|
2152
2339
|
}
|
|
2153
2340
|
|
|
@@ -2163,6 +2350,15 @@ locals {
|
|
|
2163
2350
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
2164
2351
|
}
|
|
2165
2352
|
] : [], var.additional_iam_policy_statements)
|
|
2353
|
+
|
|
2354
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
2355
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
2356
|
+
{
|
|
2357
|
+
Effect = "Deny"
|
|
2358
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
2359
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
2360
|
+
}
|
|
2361
|
+
]
|
|
2166
2362
|
}
|
|
2167
2363
|
|
|
2168
2364
|
# CloudWatch Log Group for Lambda
|
|
@@ -2170,15 +2366,19 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
2170
2366
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
2171
2367
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
2172
2368
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
2173
|
-
|
|
2174
|
-
|
|
2369
|
+
for_each = local.operations
|
|
2370
|
+
|
|
2371
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
2372
|
+
tags = var.tags
|
|
2175
2373
|
}
|
|
2176
2374
|
|
|
2177
2375
|
# Lambda alias pointing to the latest published version for SnapStart
|
|
2178
2376
|
resource "aws_lambda_alias" "live" {
|
|
2377
|
+
for_each = local.operations
|
|
2378
|
+
|
|
2179
2379
|
name = "live"
|
|
2180
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
2181
|
-
function_version = aws_lambda_function.api_lambda.version
|
|
2380
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
2381
|
+
function_version = aws_lambda_function.api_lambda[each.key].version
|
|
2182
2382
|
|
|
2183
2383
|
depends_on = [aws_lambda_function.api_lambda]
|
|
2184
2384
|
}
|
|
@@ -2186,9 +2386,11 @@ resource "aws_lambda_alias" "live" {
|
|
|
2186
2386
|
|
|
2187
2387
|
# Lambda integration for HTTP API
|
|
2188
2388
|
resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
2389
|
+
for_each = local.operations
|
|
2390
|
+
|
|
2189
2391
|
api_id = module.http_api.api_id
|
|
2190
2392
|
integration_type = "AWS_PROXY"
|
|
2191
|
-
integration_uri = aws_lambda_alias.live.invoke_arn
|
|
2393
|
+
integration_uri = aws_lambda_alias.live[each.key].invoke_arn
|
|
2192
2394
|
|
|
2193
2395
|
payload_format_version = "2.0"
|
|
2194
2396
|
timeout_milliseconds = 30000
|
|
@@ -2196,15 +2398,13 @@ resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
|
2196
2398
|
depends_on = [aws_lambda_alias.live]
|
|
2197
2399
|
}
|
|
2198
2400
|
|
|
2199
|
-
#
|
|
2200
|
-
resource "aws_apigatewayv2_route" "
|
|
2201
|
-
|
|
2202
|
-
# when cors settings are configured
|
|
2203
|
-
for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
2401
|
+
# One route per operation, each targeting that operation's integration
|
|
2402
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
2403
|
+
for_each = local.operations
|
|
2204
2404
|
|
|
2205
2405
|
api_id = module.http_api.api_id
|
|
2206
|
-
route_key =
|
|
2207
|
-
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
2406
|
+
route_key = local.route_key[each.key]
|
|
2407
|
+
target = "integrations/\${aws_apigatewayv2_integration.lambda_integration[each.key].id}"
|
|
2208
2408
|
|
|
2209
2409
|
authorization_type = "AWS_IAM"
|
|
2210
2410
|
|
|
@@ -2224,12 +2424,14 @@ module "add_url_to_runtime_config" {
|
|
|
2224
2424
|
|
|
2225
2425
|
# Lambda permission for API Gateway to invoke the function via alias
|
|
2226
2426
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
2427
|
+
for_each = local.operations
|
|
2428
|
+
|
|
2227
2429
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
2228
2430
|
action = "lambda:InvokeFunction"
|
|
2229
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
2230
|
-
qualifier = aws_lambda_alias.live.name
|
|
2431
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
2432
|
+
qualifier = aws_lambda_alias.live[each.key].name
|
|
2231
2433
|
principal = "apigateway.amazonaws.com"
|
|
2232
|
-
source_arn = "\${module.http_api.api_execution_arn}
|
|
2434
|
+
source_arn = "\${module.http_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
2233
2435
|
|
|
2234
2436
|
depends_on = [module.http_api, aws_lambda_alias.live]
|
|
2235
2437
|
}
|
|
@@ -2272,73 +2474,73 @@ output "stage_execution_arn" {
|
|
|
2272
2474
|
value = module.http_api.stage_execution_arn
|
|
2273
2475
|
}
|
|
2274
2476
|
|
|
2275
|
-
# Lambda Function Outputs
|
|
2276
|
-
output "
|
|
2277
|
-
description = "
|
|
2278
|
-
value =
|
|
2477
|
+
# Lambda Function Outputs, keyed by operation name
|
|
2478
|
+
output "operations" {
|
|
2479
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
2480
|
+
value = keys(local.operations)
|
|
2279
2481
|
}
|
|
2280
2482
|
|
|
2281
|
-
output "
|
|
2282
|
-
description = "
|
|
2283
|
-
value = aws_lambda_function.api_lambda.
|
|
2483
|
+
output "lambda_function_names" {
|
|
2484
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
2485
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
2284
2486
|
}
|
|
2285
2487
|
|
|
2286
|
-
output "
|
|
2287
|
-
description = "
|
|
2288
|
-
value = aws_lambda_function.api_lambda.
|
|
2488
|
+
output "lambda_function_arns" {
|
|
2489
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
2490
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
2289
2491
|
}
|
|
2290
2492
|
|
|
2291
|
-
output "
|
|
2292
|
-
description = "
|
|
2293
|
-
value = aws_lambda_function.api_lambda.
|
|
2493
|
+
output "lambda_invoke_arns" {
|
|
2494
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
2495
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
2294
2496
|
}
|
|
2295
2497
|
|
|
2296
|
-
output "
|
|
2297
|
-
description = "
|
|
2298
|
-
value = aws_lambda_function.api_lambda.
|
|
2498
|
+
output "lambda_qualified_arns" {
|
|
2499
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
2500
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
2299
2501
|
}
|
|
2300
2502
|
|
|
2301
|
-
output "
|
|
2302
|
-
description = "
|
|
2303
|
-
value = aws_lambda_function.api_lambda.
|
|
2503
|
+
output "lambda_versions" {
|
|
2504
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
2505
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
2304
2506
|
}
|
|
2305
2507
|
|
|
2306
|
-
output "
|
|
2307
|
-
description = "
|
|
2308
|
-
value =
|
|
2508
|
+
output "lambda_source_code_hash" {
|
|
2509
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
2510
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
2309
2511
|
}
|
|
2310
2512
|
|
|
2311
|
-
# IAM Role Outputs
|
|
2312
|
-
output "
|
|
2313
|
-
description = "ARN of
|
|
2314
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
2513
|
+
# IAM Role Outputs, keyed by operation name
|
|
2514
|
+
output "lambda_execution_role_arns" {
|
|
2515
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
2516
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
2315
2517
|
}
|
|
2316
2518
|
|
|
2317
|
-
output "
|
|
2318
|
-
description = "Name of
|
|
2319
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
2519
|
+
output "lambda_execution_role_names" {
|
|
2520
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
2521
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
2320
2522
|
}
|
|
2321
2523
|
|
|
2322
2524
|
output "security_group_id" {
|
|
2323
|
-
description = "Security group ID
|
|
2525
|
+
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."
|
|
2324
2526
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
2325
2527
|
}
|
|
2326
2528
|
|
|
2327
|
-
# Integration Outputs
|
|
2328
|
-
output "
|
|
2329
|
-
description = "ID of
|
|
2330
|
-
value = aws_apigatewayv2_integration.lambda_integration.id
|
|
2529
|
+
# Integration Outputs, keyed by operation name
|
|
2530
|
+
output "integration_ids" {
|
|
2531
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
2532
|
+
value = { for op, i in aws_apigatewayv2_integration.lambda_integration : op => i.id }
|
|
2331
2533
|
}
|
|
2332
2534
|
|
|
2333
|
-
# CloudWatch Log Groups
|
|
2334
|
-
output "
|
|
2335
|
-
description = "Name of
|
|
2336
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
2535
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
2536
|
+
output "lambda_log_group_names" {
|
|
2537
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
2538
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
2337
2539
|
}
|
|
2338
2540
|
|
|
2339
|
-
output "
|
|
2340
|
-
description = "ARN of
|
|
2341
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
2541
|
+
output "lambda_log_group_arns" {
|
|
2542
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
2543
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
2342
2544
|
}
|
|
2343
2545
|
|
|
2344
2546
|
output "api_log_group_name" {
|
|
@@ -2798,6 +3000,235 @@ resource "random_string" "suffix" {
|
|
|
2798
3000
|
upper = false
|
|
2799
3001
|
}
|
|
2800
3002
|
|
|
3003
|
+
locals {
|
|
3004
|
+
# Generated at build time from the API definition
|
|
3005
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
3006
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
3007
|
+
|
|
3008
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
3009
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
3010
|
+
|
|
3011
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
3012
|
+
"\${substr("TestApi-\${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}"
|
|
3013
|
+
}
|
|
3014
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
3015
|
+
"\${substr("TestApi-\${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}"
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
3019
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
3020
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
3021
|
+
|
|
3022
|
+
path_nodes = merge([
|
|
3023
|
+
for op, segments in local.operation_segments : {
|
|
3024
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
3025
|
+
depth = i
|
|
3026
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
3027
|
+
part = segment
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
]...)
|
|
3031
|
+
|
|
3032
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
3033
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
3034
|
+
max_path_depth = 16
|
|
3035
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
3036
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
3037
|
+
} }
|
|
3038
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
3039
|
+
|
|
3040
|
+
resource_ids = merge(
|
|
3041
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
3042
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
3043
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
3044
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
3045
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
3046
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
3047
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
3048
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
3049
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
3050
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
3051
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
3052
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
3053
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
3054
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
3055
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
3056
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
3057
|
+
)
|
|
3058
|
+
|
|
3059
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
3060
|
+
local.resource_ids[join("/", segments)]
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
3064
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
resource "terraform_data" "operations_metadata" {
|
|
3069
|
+
# Fails the plan when the operations metadata has not been generated
|
|
3070
|
+
input = local.operations_file
|
|
3071
|
+
|
|
3072
|
+
lifecycle {
|
|
3073
|
+
precondition {
|
|
3074
|
+
condition = fileexists(local.operations_file)
|
|
3075
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
3076
|
+
}
|
|
3077
|
+
precondition {
|
|
3078
|
+
condition = length(local.operations) > 0
|
|
3079
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
3080
|
+
}
|
|
3081
|
+
precondition {
|
|
3082
|
+
condition = length(local.paths_too_deep) == 0
|
|
3083
|
+
error_message = "Operation paths exceed the supported depth of \${local.max_path_depth} segments: \${join(", ", local.paths_too_deep)}. Add further aws_api_gateway_resource levels to this module to support them."
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
# API Gateway resources at path depth 0
|
|
3089
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
3090
|
+
for_each = local.path_nodes_by_depth[0]
|
|
3091
|
+
|
|
3092
|
+
rest_api_id = module.rest_api.api_id
|
|
3093
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
3094
|
+
path_part = each.value.part
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
# API Gateway resources at path depth 1
|
|
3098
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
3099
|
+
for_each = local.path_nodes_by_depth[1]
|
|
3100
|
+
|
|
3101
|
+
rest_api_id = module.rest_api.api_id
|
|
3102
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
3103
|
+
path_part = each.value.part
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
# API Gateway resources at path depth 2
|
|
3107
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
3108
|
+
for_each = local.path_nodes_by_depth[2]
|
|
3109
|
+
|
|
3110
|
+
rest_api_id = module.rest_api.api_id
|
|
3111
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
3112
|
+
path_part = each.value.part
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
# API Gateway resources at path depth 3
|
|
3116
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
3117
|
+
for_each = local.path_nodes_by_depth[3]
|
|
3118
|
+
|
|
3119
|
+
rest_api_id = module.rest_api.api_id
|
|
3120
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
3121
|
+
path_part = each.value.part
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
# API Gateway resources at path depth 4
|
|
3125
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
3126
|
+
for_each = local.path_nodes_by_depth[4]
|
|
3127
|
+
|
|
3128
|
+
rest_api_id = module.rest_api.api_id
|
|
3129
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
3130
|
+
path_part = each.value.part
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
# API Gateway resources at path depth 5
|
|
3134
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
3135
|
+
for_each = local.path_nodes_by_depth[5]
|
|
3136
|
+
|
|
3137
|
+
rest_api_id = module.rest_api.api_id
|
|
3138
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
3139
|
+
path_part = each.value.part
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
# API Gateway resources at path depth 6
|
|
3143
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
3144
|
+
for_each = local.path_nodes_by_depth[6]
|
|
3145
|
+
|
|
3146
|
+
rest_api_id = module.rest_api.api_id
|
|
3147
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
3148
|
+
path_part = each.value.part
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
# API Gateway resources at path depth 7
|
|
3152
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
3153
|
+
for_each = local.path_nodes_by_depth[7]
|
|
3154
|
+
|
|
3155
|
+
rest_api_id = module.rest_api.api_id
|
|
3156
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
3157
|
+
path_part = each.value.part
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
# API Gateway resources at path depth 8
|
|
3161
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
3162
|
+
for_each = local.path_nodes_by_depth[8]
|
|
3163
|
+
|
|
3164
|
+
rest_api_id = module.rest_api.api_id
|
|
3165
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
3166
|
+
path_part = each.value.part
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
# API Gateway resources at path depth 9
|
|
3170
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
3171
|
+
for_each = local.path_nodes_by_depth[9]
|
|
3172
|
+
|
|
3173
|
+
rest_api_id = module.rest_api.api_id
|
|
3174
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
3175
|
+
path_part = each.value.part
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
# API Gateway resources at path depth 10
|
|
3179
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
3180
|
+
for_each = local.path_nodes_by_depth[10]
|
|
3181
|
+
|
|
3182
|
+
rest_api_id = module.rest_api.api_id
|
|
3183
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
3184
|
+
path_part = each.value.part
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
# API Gateway resources at path depth 11
|
|
3188
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
3189
|
+
for_each = local.path_nodes_by_depth[11]
|
|
3190
|
+
|
|
3191
|
+
rest_api_id = module.rest_api.api_id
|
|
3192
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
3193
|
+
path_part = each.value.part
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
# API Gateway resources at path depth 12
|
|
3197
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
3198
|
+
for_each = local.path_nodes_by_depth[12]
|
|
3199
|
+
|
|
3200
|
+
rest_api_id = module.rest_api.api_id
|
|
3201
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
3202
|
+
path_part = each.value.part
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
# API Gateway resources at path depth 13
|
|
3206
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
3207
|
+
for_each = local.path_nodes_by_depth[13]
|
|
3208
|
+
|
|
3209
|
+
rest_api_id = module.rest_api.api_id
|
|
3210
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
3211
|
+
path_part = each.value.part
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
# API Gateway resources at path depth 14
|
|
3215
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
3216
|
+
for_each = local.path_nodes_by_depth[14]
|
|
3217
|
+
|
|
3218
|
+
rest_api_id = module.rest_api.api_id
|
|
3219
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
3220
|
+
path_part = each.value.part
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
# API Gateway resources at path depth 15
|
|
3224
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
3225
|
+
for_each = local.path_nodes_by_depth[15]
|
|
3226
|
+
|
|
3227
|
+
rest_api_id = module.rest_api.api_id
|
|
3228
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
3229
|
+
path_part = each.value.part
|
|
3230
|
+
}
|
|
3231
|
+
|
|
2801
3232
|
# Resources
|
|
2802
3233
|
|
|
2803
3234
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -2872,8 +3303,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
2872
3303
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
2873
3304
|
}
|
|
2874
3305
|
|
|
2875
|
-
# Lambda function
|
|
2876
3306
|
resource "aws_lambda_function" "api_lambda" {
|
|
3307
|
+
# One lambda function per operation, each serving just that operation
|
|
3308
|
+
for_each = local.operations
|
|
3309
|
+
|
|
2877
3310
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
2878
3311
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
2879
3312
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -2882,8 +3315,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
2882
3315
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
2883
3316
|
s3_key = aws_s3_object.lambda_zip.key
|
|
2884
3317
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
2885
|
-
function_name =
|
|
2886
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
3318
|
+
function_name = local.function_name[each.key]
|
|
3319
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
2887
3320
|
handler = "run.sh"
|
|
2888
3321
|
runtime = "python3.14"
|
|
2889
3322
|
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
@@ -2928,7 +3361,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
2928
3361
|
|
|
2929
3362
|
# IAM role for Lambda execution
|
|
2930
3363
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
2931
|
-
|
|
3364
|
+
for_each = local.operations
|
|
3365
|
+
|
|
3366
|
+
name = local.role_name[each.key]
|
|
2932
3367
|
|
|
2933
3368
|
assume_role_policy = jsonencode({
|
|
2934
3369
|
Version = "2012-10-17"
|
|
@@ -2948,32 +3383,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
2948
3383
|
|
|
2949
3384
|
# Attach basic execution policy to Lambda role
|
|
2950
3385
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
3386
|
+
for_each = local.operations
|
|
3387
|
+
|
|
2951
3388
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
2952
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3389
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2953
3390
|
}
|
|
2954
3391
|
|
|
2955
3392
|
# Attach X-Ray tracing policy to Lambda role
|
|
2956
3393
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
3394
|
+
for_each = local.operations
|
|
3395
|
+
|
|
2957
3396
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
2958
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3397
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2959
3398
|
}
|
|
2960
3399
|
|
|
2961
3400
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
2962
3401
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
2963
|
-
|
|
3402
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
3403
|
+
|
|
2964
3404
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
2965
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
3405
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
2966
3406
|
}
|
|
2967
3407
|
|
|
2968
3408
|
# Additional IAM policies for Lambda (if provided)
|
|
2969
3409
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
3410
|
+
for_each = local.operations
|
|
3411
|
+
|
|
3412
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
3413
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
2973
3414
|
|
|
2974
3415
|
policy = jsonencode({
|
|
2975
3416
|
Version = "2012-10-17"
|
|
2976
|
-
Statement = local.
|
|
3417
|
+
Statement = local.lambda_policy_document_statements
|
|
2977
3418
|
})
|
|
2978
3419
|
}
|
|
2979
3420
|
|
|
@@ -2989,6 +3430,15 @@ locals {
|
|
|
2989
3430
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
2990
3431
|
}
|
|
2991
3432
|
] : [], var.additional_iam_policy_statements)
|
|
3433
|
+
|
|
3434
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
3435
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
3436
|
+
{
|
|
3437
|
+
Effect = "Deny"
|
|
3438
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
3439
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
3440
|
+
}
|
|
3441
|
+
]
|
|
2992
3442
|
}
|
|
2993
3443
|
|
|
2994
3444
|
# CloudWatch Log Group for Lambda
|
|
@@ -2996,15 +3446,19 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
2996
3446
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
2997
3447
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
2998
3448
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
2999
|
-
|
|
3000
|
-
|
|
3449
|
+
for_each = local.operations
|
|
3450
|
+
|
|
3451
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
3452
|
+
tags = var.tags
|
|
3001
3453
|
}
|
|
3002
3454
|
|
|
3003
3455
|
# Lambda alias pointing to the latest published version for SnapStart
|
|
3004
3456
|
resource "aws_lambda_alias" "live" {
|
|
3457
|
+
for_each = local.operations
|
|
3458
|
+
|
|
3005
3459
|
name = "live"
|
|
3006
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
3007
|
-
function_version = aws_lambda_function.api_lambda.version
|
|
3460
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
3461
|
+
function_version = aws_lambda_function.api_lambda[each.key].version
|
|
3008
3462
|
|
|
3009
3463
|
depends_on = [aws_lambda_function.api_lambda]
|
|
3010
3464
|
}
|
|
@@ -3018,34 +3472,14 @@ resource "aws_api_gateway_authorizer" "cognito_authorizer" {
|
|
|
3018
3472
|
identity_source = "method.request.header.Authorization"
|
|
3019
3473
|
}
|
|
3020
3474
|
|
|
3021
|
-
#
|
|
3022
|
-
resource "
|
|
3023
|
-
rest_api_id = module.rest_api.api_id
|
|
3024
|
-
parent_id = module.rest_api.api_root_resource_id
|
|
3025
|
-
path_part = "{proxy+}"
|
|
3026
|
-
}
|
|
3027
|
-
|
|
3028
|
-
# Lambda integration for REST API
|
|
3029
|
-
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
3030
|
-
rest_api_id = module.rest_api.api_id
|
|
3031
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
3032
|
-
http_method = aws_api_gateway_method.proxy_method.http_method
|
|
3033
|
-
|
|
3034
|
-
integration_http_method = "POST"
|
|
3035
|
-
type = "AWS_PROXY"
|
|
3036
|
-
# Use the response streaming invocation path with the alias ARN for SnapStart
|
|
3037
|
-
uri = "arn:aws:apigateway:\${data.aws_region.current.region}:lambda:path/2021-11-15/functions/\${aws_lambda_alias.live.arn}/response-streaming-invocations"
|
|
3038
|
-
response_transfer_mode = "STREAM"
|
|
3039
|
-
|
|
3040
|
-
depends_on = [aws_lambda_alias.live]
|
|
3041
|
-
}
|
|
3042
|
-
|
|
3043
|
-
# Method for proxy integration
|
|
3044
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
3475
|
+
# Method per operation
|
|
3476
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
3045
3477
|
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
3478
|
+
for_each = local.operations
|
|
3479
|
+
|
|
3046
3480
|
rest_api_id = module.rest_api.api_id
|
|
3047
|
-
resource_id =
|
|
3048
|
-
http_method =
|
|
3481
|
+
resource_id = local.operation_resource_id[each.key]
|
|
3482
|
+
http_method = upper(each.value.method)
|
|
3049
3483
|
|
|
3050
3484
|
authorization = "COGNITO_USER_POOLS"
|
|
3051
3485
|
authorizer_id = aws_api_gateway_authorizer.cognito_authorizer.id
|
|
@@ -3053,28 +3487,52 @@ resource "aws_api_gateway_method" "proxy_method" {
|
|
|
3053
3487
|
# and 'aws.cognito.signin.user.admin' (Cognito admin/SRP auth APIs).
|
|
3054
3488
|
authorization_scopes = ["openid", "aws.cognito.signin.user.admin"]
|
|
3055
3489
|
|
|
3490
|
+
# Path parameters must be declared on the method
|
|
3056
3491
|
request_parameters = {
|
|
3057
|
-
|
|
3492
|
+
for segment in local.operation_segments[each.key] :
|
|
3493
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
3494
|
+
if startswith(segment, "{")
|
|
3058
3495
|
}
|
|
3059
3496
|
|
|
3060
3497
|
depends_on = [aws_api_gateway_authorizer.cognito_authorizer]
|
|
3061
3498
|
}
|
|
3062
3499
|
|
|
3063
|
-
#
|
|
3500
|
+
# Lambda integration per operation
|
|
3501
|
+
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
3502
|
+
for_each = local.operations
|
|
3503
|
+
|
|
3504
|
+
rest_api_id = module.rest_api.api_id
|
|
3505
|
+
resource_id = local.operation_resource_id[each.key]
|
|
3506
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
3507
|
+
|
|
3508
|
+
integration_http_method = "POST"
|
|
3509
|
+
type = "AWS_PROXY"
|
|
3510
|
+
# Use the response streaming invocation path with the alias ARN for SnapStart
|
|
3511
|
+
uri = "arn:aws:apigateway:\${data.aws_region.current.region}:lambda:path/2021-11-15/functions/\${aws_lambda_alias.live[each.key].arn}/response-streaming-invocations"
|
|
3512
|
+
response_transfer_mode = "STREAM"
|
|
3513
|
+
|
|
3514
|
+
depends_on = [aws_lambda_alias.live]
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
3064
3518
|
resource "aws_api_gateway_method" "options_method" {
|
|
3065
3519
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
3066
3520
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
3521
|
+
for_each = local.resource_ids
|
|
3522
|
+
|
|
3067
3523
|
rest_api_id = module.rest_api.api_id
|
|
3068
|
-
resource_id =
|
|
3524
|
+
resource_id = each.value
|
|
3069
3525
|
http_method = "OPTIONS"
|
|
3070
3526
|
authorization = "NONE"
|
|
3071
3527
|
}
|
|
3072
3528
|
|
|
3073
|
-
# CORS integration for OPTIONS
|
|
3529
|
+
# CORS integration for OPTIONS methods
|
|
3074
3530
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
3531
|
+
for_each = local.resource_ids
|
|
3532
|
+
|
|
3075
3533
|
rest_api_id = module.rest_api.api_id
|
|
3076
|
-
resource_id =
|
|
3077
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
3534
|
+
resource_id = each.value
|
|
3535
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
3078
3536
|
|
|
3079
3537
|
type = "MOCK"
|
|
3080
3538
|
request_templates = {
|
|
@@ -3082,11 +3540,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
3082
3540
|
}
|
|
3083
3541
|
}
|
|
3084
3542
|
|
|
3085
|
-
# OPTIONS method
|
|
3543
|
+
# OPTIONS method responses
|
|
3086
3544
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
3545
|
+
for_each = local.resource_ids
|
|
3546
|
+
|
|
3087
3547
|
rest_api_id = module.rest_api.api_id
|
|
3088
|
-
resource_id =
|
|
3089
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
3548
|
+
resource_id = each.value
|
|
3549
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
3090
3550
|
status_code = "204"
|
|
3091
3551
|
|
|
3092
3552
|
response_parameters = {
|
|
@@ -3096,18 +3556,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
3096
3556
|
}
|
|
3097
3557
|
}
|
|
3098
3558
|
|
|
3099
|
-
# OPTIONS integration
|
|
3559
|
+
# OPTIONS integration responses
|
|
3100
3560
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
3561
|
+
for_each = local.resource_ids
|
|
3562
|
+
|
|
3101
3563
|
rest_api_id = module.rest_api.api_id
|
|
3102
|
-
resource_id =
|
|
3103
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
3104
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
3564
|
+
resource_id = each.value
|
|
3565
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
3566
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
3105
3567
|
|
|
3106
3568
|
response_parameters = {
|
|
3107
3569
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
3108
3570
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
3109
3571
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
3110
3572
|
}
|
|
3573
|
+
|
|
3574
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
3111
3575
|
}
|
|
3112
3576
|
|
|
3113
3577
|
# API Gateway deployment
|
|
@@ -3115,13 +3579,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
3115
3579
|
rest_api_id = module.rest_api.api_id
|
|
3116
3580
|
|
|
3117
3581
|
triggers = {
|
|
3118
|
-
redeployment = sha1(jsonencode(
|
|
3119
|
-
aws_api_gateway_resource.
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3582
|
+
redeployment = sha1(jsonencode(concat(
|
|
3583
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
3584
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
3585
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
3586
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
3587
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
3588
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
3589
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
3590
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
3591
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
3592
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
3593
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
3594
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
3595
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
3596
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
3597
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
3598
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
3599
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
3600
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
3601
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
3602
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
3603
|
+
)))
|
|
3125
3604
|
}
|
|
3126
3605
|
|
|
3127
3606
|
lifecycle {
|
|
@@ -3129,7 +3608,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
3129
3608
|
}
|
|
3130
3609
|
|
|
3131
3610
|
depends_on = [
|
|
3132
|
-
aws_api_gateway_method.
|
|
3611
|
+
aws_api_gateway_method.operation_methods,
|
|
3133
3612
|
aws_api_gateway_integration.lambda_integration,
|
|
3134
3613
|
aws_api_gateway_method.options_method,
|
|
3135
3614
|
aws_api_gateway_integration.options_integration,
|
|
@@ -3257,24 +3736,28 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
3257
3736
|
|
|
3258
3737
|
# Lambda permission for API Gateway to invoke the function via alias
|
|
3259
3738
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
3739
|
+
for_each = local.operations
|
|
3740
|
+
|
|
3260
3741
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
3261
3742
|
action = "lambda:InvokeFunction"
|
|
3262
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
3263
|
-
qualifier = aws_lambda_alias.live.name
|
|
3743
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
3744
|
+
qualifier = aws_lambda_alias.live[each.key].name
|
|
3264
3745
|
principal = "apigateway.amazonaws.com"
|
|
3265
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
3746
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
3266
3747
|
|
|
3267
3748
|
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
3268
3749
|
}
|
|
3269
3750
|
|
|
3270
3751
|
# Lambda permission for API Gateway to invoke with response streaming via alias
|
|
3271
3752
|
resource "aws_lambda_permission" "api_gateway_invoke_streaming" {
|
|
3753
|
+
for_each = local.operations
|
|
3754
|
+
|
|
3272
3755
|
statement_id = "AllowStreamingFromAPIGateway"
|
|
3273
3756
|
action = "lambda:InvokeFunctionWithResponseStream"
|
|
3274
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
3275
|
-
qualifier = aws_lambda_alias.live.name
|
|
3757
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
3758
|
+
qualifier = aws_lambda_alias.live[each.key].name
|
|
3276
3759
|
principal = "apigateway.amazonaws.com"
|
|
3277
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
3760
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
3278
3761
|
|
|
3279
3762
|
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
3280
3763
|
}
|
|
@@ -3338,83 +3821,83 @@ output "stage_id" {
|
|
|
3338
3821
|
value = aws_api_gateway_stage.api_stage.id
|
|
3339
3822
|
}
|
|
3340
3823
|
|
|
3341
|
-
# Lambda Function Outputs
|
|
3342
|
-
output "
|
|
3343
|
-
description = "
|
|
3344
|
-
value =
|
|
3824
|
+
# Lambda Function Outputs, keyed by operation name
|
|
3825
|
+
output "operations" {
|
|
3826
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
3827
|
+
value = keys(local.operations)
|
|
3345
3828
|
}
|
|
3346
3829
|
|
|
3347
|
-
output "
|
|
3348
|
-
description = "
|
|
3349
|
-
value = aws_lambda_function.api_lambda.
|
|
3830
|
+
output "lambda_function_names" {
|
|
3831
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
3832
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
3350
3833
|
}
|
|
3351
3834
|
|
|
3352
|
-
output "
|
|
3353
|
-
description = "
|
|
3354
|
-
value = aws_lambda_function.api_lambda.
|
|
3835
|
+
output "lambda_function_arns" {
|
|
3836
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
3837
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
3355
3838
|
}
|
|
3356
3839
|
|
|
3357
|
-
output "
|
|
3358
|
-
description = "
|
|
3359
|
-
value = aws_lambda_function.api_lambda.
|
|
3840
|
+
output "lambda_invoke_arns" {
|
|
3841
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
3842
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
3360
3843
|
}
|
|
3361
3844
|
|
|
3362
|
-
output "
|
|
3363
|
-
description = "
|
|
3364
|
-
value = aws_lambda_function.api_lambda.
|
|
3845
|
+
output "lambda_qualified_arns" {
|
|
3846
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
3847
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
3365
3848
|
}
|
|
3366
3849
|
|
|
3367
|
-
output "
|
|
3368
|
-
description = "
|
|
3369
|
-
value = aws_lambda_function.api_lambda.
|
|
3850
|
+
output "lambda_versions" {
|
|
3851
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
3852
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
3370
3853
|
}
|
|
3371
3854
|
|
|
3372
|
-
output "
|
|
3373
|
-
description = "
|
|
3374
|
-
value =
|
|
3855
|
+
output "lambda_source_code_hash" {
|
|
3856
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
3857
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
3375
3858
|
}
|
|
3376
3859
|
|
|
3377
|
-
# IAM Role Outputs
|
|
3378
|
-
output "
|
|
3379
|
-
description = "ARN of
|
|
3380
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
3860
|
+
# IAM Role Outputs, keyed by operation name
|
|
3861
|
+
output "lambda_execution_role_arns" {
|
|
3862
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
3863
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
3381
3864
|
}
|
|
3382
3865
|
|
|
3383
|
-
output "
|
|
3384
|
-
description = "Name of
|
|
3385
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
3866
|
+
output "lambda_execution_role_names" {
|
|
3867
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
3868
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
3386
3869
|
}
|
|
3387
3870
|
|
|
3388
3871
|
output "security_group_id" {
|
|
3389
|
-
description = "Security group ID
|
|
3872
|
+
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."
|
|
3390
3873
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
3391
3874
|
}
|
|
3392
3875
|
|
|
3393
|
-
# Integration Outputs
|
|
3394
|
-
output "
|
|
3395
|
-
description = "ID of
|
|
3396
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
3876
|
+
# Integration Outputs, keyed by operation name
|
|
3877
|
+
output "integration_ids" {
|
|
3878
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
3879
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
3397
3880
|
}
|
|
3398
3881
|
|
|
3399
|
-
output "
|
|
3400
|
-
description = "ID of
|
|
3401
|
-
value =
|
|
3882
|
+
output "method_ids" {
|
|
3883
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
3884
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
3402
3885
|
}
|
|
3403
3886
|
|
|
3404
|
-
output "
|
|
3405
|
-
description = "ID of
|
|
3406
|
-
value =
|
|
3887
|
+
output "resource_ids" {
|
|
3888
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
3889
|
+
value = local.resource_ids
|
|
3407
3890
|
}
|
|
3408
3891
|
|
|
3409
|
-
# CloudWatch Log Groups
|
|
3410
|
-
output "
|
|
3411
|
-
description = "Name of
|
|
3412
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
3892
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
3893
|
+
output "lambda_log_group_names" {
|
|
3894
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
3895
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
3413
3896
|
}
|
|
3414
3897
|
|
|
3415
|
-
output "
|
|
3416
|
-
description = "ARN of
|
|
3417
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
3898
|
+
output "lambda_log_group_arns" {
|
|
3899
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
3900
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
3418
3901
|
}
|
|
3419
3902
|
",
|
|
3420
3903
|
}
|
|
@@ -3855,13 +4338,242 @@ resource "random_string" "suffix" {
|
|
|
3855
4338
|
upper = false
|
|
3856
4339
|
}
|
|
3857
4340
|
|
|
3858
|
-
|
|
4341
|
+
locals {
|
|
4342
|
+
# Generated at build time from the API definition
|
|
4343
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
4344
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
3859
4345
|
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
4346
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
4347
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
4348
|
+
|
|
4349
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
4350
|
+
"\${substr("TestApi-\${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}"
|
|
4351
|
+
}
|
|
4352
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
4353
|
+
"\${substr("TestApi-\${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}"
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
4357
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
4358
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
4359
|
+
|
|
4360
|
+
path_nodes = merge([
|
|
4361
|
+
for op, segments in local.operation_segments : {
|
|
4362
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
4363
|
+
depth = i
|
|
4364
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
4365
|
+
part = segment
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
]...)
|
|
4369
|
+
|
|
4370
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
4371
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
4372
|
+
max_path_depth = 16
|
|
4373
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
4374
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
4375
|
+
} }
|
|
4376
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
4377
|
+
|
|
4378
|
+
resource_ids = merge(
|
|
4379
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
4380
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
4381
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
4382
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
4383
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
4384
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
4385
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
4386
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
4387
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
4388
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
4389
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
4390
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
4391
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
4392
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
4393
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
4394
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
4395
|
+
)
|
|
4396
|
+
|
|
4397
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
4398
|
+
local.resource_ids[join("/", segments)]
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4401
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
4402
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
4403
|
+
}
|
|
4404
|
+
}
|
|
4405
|
+
|
|
4406
|
+
resource "terraform_data" "operations_metadata" {
|
|
4407
|
+
# Fails the plan when the operations metadata has not been generated
|
|
4408
|
+
input = local.operations_file
|
|
4409
|
+
|
|
4410
|
+
lifecycle {
|
|
4411
|
+
precondition {
|
|
4412
|
+
condition = fileexists(local.operations_file)
|
|
4413
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
4414
|
+
}
|
|
4415
|
+
precondition {
|
|
4416
|
+
condition = length(local.operations) > 0
|
|
4417
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
4418
|
+
}
|
|
4419
|
+
precondition {
|
|
4420
|
+
condition = length(local.paths_too_deep) == 0
|
|
4421
|
+
error_message = "Operation paths exceed the supported depth of \${local.max_path_depth} segments: \${join(", ", local.paths_too_deep)}. Add further aws_api_gateway_resource levels to this module to support them."
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
|
|
4426
|
+
# API Gateway resources at path depth 0
|
|
4427
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
4428
|
+
for_each = local.path_nodes_by_depth[0]
|
|
4429
|
+
|
|
4430
|
+
rest_api_id = module.rest_api.api_id
|
|
4431
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
4432
|
+
path_part = each.value.part
|
|
4433
|
+
}
|
|
4434
|
+
|
|
4435
|
+
# API Gateway resources at path depth 1
|
|
4436
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
4437
|
+
for_each = local.path_nodes_by_depth[1]
|
|
4438
|
+
|
|
4439
|
+
rest_api_id = module.rest_api.api_id
|
|
4440
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
4441
|
+
path_part = each.value.part
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
# API Gateway resources at path depth 2
|
|
4445
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
4446
|
+
for_each = local.path_nodes_by_depth[2]
|
|
4447
|
+
|
|
4448
|
+
rest_api_id = module.rest_api.api_id
|
|
4449
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
4450
|
+
path_part = each.value.part
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4453
|
+
# API Gateway resources at path depth 3
|
|
4454
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
4455
|
+
for_each = local.path_nodes_by_depth[3]
|
|
4456
|
+
|
|
4457
|
+
rest_api_id = module.rest_api.api_id
|
|
4458
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
4459
|
+
path_part = each.value.part
|
|
4460
|
+
}
|
|
4461
|
+
|
|
4462
|
+
# API Gateway resources at path depth 4
|
|
4463
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
4464
|
+
for_each = local.path_nodes_by_depth[4]
|
|
4465
|
+
|
|
4466
|
+
rest_api_id = module.rest_api.api_id
|
|
4467
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
4468
|
+
path_part = each.value.part
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
# API Gateway resources at path depth 5
|
|
4472
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
4473
|
+
for_each = local.path_nodes_by_depth[5]
|
|
4474
|
+
|
|
4475
|
+
rest_api_id = module.rest_api.api_id
|
|
4476
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
4477
|
+
path_part = each.value.part
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
# API Gateway resources at path depth 6
|
|
4481
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
4482
|
+
for_each = local.path_nodes_by_depth[6]
|
|
4483
|
+
|
|
4484
|
+
rest_api_id = module.rest_api.api_id
|
|
4485
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
4486
|
+
path_part = each.value.part
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
# API Gateway resources at path depth 7
|
|
4490
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
4491
|
+
for_each = local.path_nodes_by_depth[7]
|
|
4492
|
+
|
|
4493
|
+
rest_api_id = module.rest_api.api_id
|
|
4494
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
4495
|
+
path_part = each.value.part
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
# API Gateway resources at path depth 8
|
|
4499
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
4500
|
+
for_each = local.path_nodes_by_depth[8]
|
|
4501
|
+
|
|
4502
|
+
rest_api_id = module.rest_api.api_id
|
|
4503
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
4504
|
+
path_part = each.value.part
|
|
4505
|
+
}
|
|
4506
|
+
|
|
4507
|
+
# API Gateway resources at path depth 9
|
|
4508
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
4509
|
+
for_each = local.path_nodes_by_depth[9]
|
|
4510
|
+
|
|
4511
|
+
rest_api_id = module.rest_api.api_id
|
|
4512
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
4513
|
+
path_part = each.value.part
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
# API Gateway resources at path depth 10
|
|
4517
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
4518
|
+
for_each = local.path_nodes_by_depth[10]
|
|
4519
|
+
|
|
4520
|
+
rest_api_id = module.rest_api.api_id
|
|
4521
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
4522
|
+
path_part = each.value.part
|
|
4523
|
+
}
|
|
4524
|
+
|
|
4525
|
+
# API Gateway resources at path depth 11
|
|
4526
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
4527
|
+
for_each = local.path_nodes_by_depth[11]
|
|
4528
|
+
|
|
4529
|
+
rest_api_id = module.rest_api.api_id
|
|
4530
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
4531
|
+
path_part = each.value.part
|
|
4532
|
+
}
|
|
4533
|
+
|
|
4534
|
+
# API Gateway resources at path depth 12
|
|
4535
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
4536
|
+
for_each = local.path_nodes_by_depth[12]
|
|
4537
|
+
|
|
4538
|
+
rest_api_id = module.rest_api.api_id
|
|
4539
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
4540
|
+
path_part = each.value.part
|
|
4541
|
+
}
|
|
4542
|
+
|
|
4543
|
+
# API Gateway resources at path depth 13
|
|
4544
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
4545
|
+
for_each = local.path_nodes_by_depth[13]
|
|
4546
|
+
|
|
4547
|
+
rest_api_id = module.rest_api.api_id
|
|
4548
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
4549
|
+
path_part = each.value.part
|
|
4550
|
+
}
|
|
4551
|
+
|
|
4552
|
+
# API Gateway resources at path depth 14
|
|
4553
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
4554
|
+
for_each = local.path_nodes_by_depth[14]
|
|
4555
|
+
|
|
4556
|
+
rest_api_id = module.rest_api.api_id
|
|
4557
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
4558
|
+
path_part = each.value.part
|
|
4559
|
+
}
|
|
4560
|
+
|
|
4561
|
+
# API Gateway resources at path depth 15
|
|
4562
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
4563
|
+
for_each = local.path_nodes_by_depth[15]
|
|
4564
|
+
|
|
4565
|
+
rest_api_id = module.rest_api.api_id
|
|
4566
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
4567
|
+
path_part = each.value.part
|
|
4568
|
+
}
|
|
4569
|
+
|
|
4570
|
+
# Resources
|
|
4571
|
+
|
|
4572
|
+
# Create Lambda ZIP file from the bundle directory
|
|
4573
|
+
data "archive_file" "lambda_zip" {
|
|
4574
|
+
type = "zip"
|
|
4575
|
+
source_dir = "\${path.module}/../../../../../../../dist/apps/test_api/bundle-x86"
|
|
4576
|
+
output_path = "\${path.module}/../../../../../../../dist/packages/common/terraform/apis/test-api/lambda.zip"
|
|
3865
4577
|
}
|
|
3866
4578
|
|
|
3867
4579
|
resource "aws_s3_object" "lambda_zip" {
|
|
@@ -3929,8 +4641,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
3929
4641
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
3930
4642
|
}
|
|
3931
4643
|
|
|
3932
|
-
# Lambda function
|
|
3933
4644
|
resource "aws_lambda_function" "api_lambda" {
|
|
4645
|
+
# One lambda function per operation, each serving just that operation
|
|
4646
|
+
for_each = local.operations
|
|
4647
|
+
|
|
3934
4648
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
3935
4649
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
3936
4650
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -3939,8 +4653,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3939
4653
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
3940
4654
|
s3_key = aws_s3_object.lambda_zip.key
|
|
3941
4655
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
3942
|
-
function_name =
|
|
3943
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
4656
|
+
function_name = local.function_name[each.key]
|
|
4657
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
3944
4658
|
handler = "run.sh"
|
|
3945
4659
|
runtime = "python3.14"
|
|
3946
4660
|
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
@@ -3985,7 +4699,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
3985
4699
|
|
|
3986
4700
|
# IAM role for Lambda execution
|
|
3987
4701
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
3988
|
-
|
|
4702
|
+
for_each = local.operations
|
|
4703
|
+
|
|
4704
|
+
name = local.role_name[each.key]
|
|
3989
4705
|
|
|
3990
4706
|
assume_role_policy = jsonencode({
|
|
3991
4707
|
Version = "2012-10-17"
|
|
@@ -4005,32 +4721,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
4005
4721
|
|
|
4006
4722
|
# Attach basic execution policy to Lambda role
|
|
4007
4723
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
4724
|
+
for_each = local.operations
|
|
4725
|
+
|
|
4008
4726
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
4009
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4727
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4010
4728
|
}
|
|
4011
4729
|
|
|
4012
4730
|
# Attach X-Ray tracing policy to Lambda role
|
|
4013
4731
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
4732
|
+
for_each = local.operations
|
|
4733
|
+
|
|
4014
4734
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
4015
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4735
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4016
4736
|
}
|
|
4017
4737
|
|
|
4018
4738
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
4019
4739
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
4020
|
-
|
|
4740
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
4741
|
+
|
|
4021
4742
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
4022
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
4743
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
4023
4744
|
}
|
|
4024
4745
|
|
|
4025
4746
|
# Additional IAM policies for Lambda (if provided)
|
|
4026
4747
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4748
|
+
for_each = local.operations
|
|
4749
|
+
|
|
4750
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
4751
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
4030
4752
|
|
|
4031
4753
|
policy = jsonencode({
|
|
4032
4754
|
Version = "2012-10-17"
|
|
4033
|
-
Statement = local.
|
|
4755
|
+
Statement = local.lambda_policy_document_statements
|
|
4034
4756
|
})
|
|
4035
4757
|
}
|
|
4036
4758
|
|
|
@@ -4046,6 +4768,15 @@ locals {
|
|
|
4046
4768
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
4047
4769
|
}
|
|
4048
4770
|
] : [], var.additional_iam_policy_statements)
|
|
4771
|
+
|
|
4772
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
4773
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
4774
|
+
{
|
|
4775
|
+
Effect = "Deny"
|
|
4776
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
4777
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
4778
|
+
}
|
|
4779
|
+
]
|
|
4049
4780
|
}
|
|
4050
4781
|
|
|
4051
4782
|
# CloudWatch Log Group for Lambda
|
|
@@ -4053,73 +4784,81 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
4053
4784
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
4054
4785
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
4055
4786
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
4056
|
-
|
|
4057
|
-
|
|
4787
|
+
for_each = local.operations
|
|
4788
|
+
|
|
4789
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
4790
|
+
tags = var.tags
|
|
4058
4791
|
}
|
|
4059
4792
|
|
|
4060
4793
|
# Lambda alias pointing to the latest published version for SnapStart
|
|
4061
4794
|
resource "aws_lambda_alias" "live" {
|
|
4795
|
+
for_each = local.operations
|
|
4796
|
+
|
|
4062
4797
|
name = "live"
|
|
4063
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
4064
|
-
function_version = aws_lambda_function.api_lambda.version
|
|
4798
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
4799
|
+
function_version = aws_lambda_function.api_lambda[each.key].version
|
|
4065
4800
|
|
|
4066
4801
|
depends_on = [aws_lambda_function.api_lambda]
|
|
4067
4802
|
}
|
|
4068
4803
|
|
|
4069
4804
|
|
|
4070
|
-
#
|
|
4071
|
-
resource "
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4805
|
+
# Method per operation
|
|
4806
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
4807
|
+
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
4808
|
+
for_each = local.operations
|
|
4809
|
+
|
|
4810
|
+
rest_api_id = module.rest_api.api_id
|
|
4811
|
+
resource_id = local.operation_resource_id[each.key]
|
|
4812
|
+
http_method = upper(each.value.method)
|
|
4813
|
+
|
|
4814
|
+
authorization = "AWS_IAM"
|
|
4815
|
+
|
|
4816
|
+
# Path parameters must be declared on the method
|
|
4817
|
+
request_parameters = {
|
|
4818
|
+
for segment in local.operation_segments[each.key] :
|
|
4819
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
4820
|
+
if startswith(segment, "{")
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4823
|
+
depends_on = []
|
|
4075
4824
|
}
|
|
4076
4825
|
|
|
4077
|
-
# Lambda integration
|
|
4826
|
+
# Lambda integration per operation
|
|
4078
4827
|
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
4828
|
+
for_each = local.operations
|
|
4829
|
+
|
|
4079
4830
|
rest_api_id = module.rest_api.api_id
|
|
4080
|
-
resource_id =
|
|
4081
|
-
http_method = aws_api_gateway_method.
|
|
4831
|
+
resource_id = local.operation_resource_id[each.key]
|
|
4832
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
4082
4833
|
|
|
4083
4834
|
integration_http_method = "POST"
|
|
4084
4835
|
type = "AWS_PROXY"
|
|
4085
4836
|
# Use the response streaming invocation path with the alias ARN for SnapStart
|
|
4086
|
-
uri = "arn:aws:apigateway:\${data.aws_region.current.region}:lambda:path/2021-11-15/functions/\${aws_lambda_alias.live.arn}/response-streaming-invocations"
|
|
4837
|
+
uri = "arn:aws:apigateway:\${data.aws_region.current.region}:lambda:path/2021-11-15/functions/\${aws_lambda_alias.live[each.key].arn}/response-streaming-invocations"
|
|
4087
4838
|
response_transfer_mode = "STREAM"
|
|
4088
4839
|
|
|
4089
4840
|
depends_on = [aws_lambda_alias.live]
|
|
4090
4841
|
}
|
|
4091
4842
|
|
|
4092
|
-
#
|
|
4093
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
4094
|
-
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
4095
|
-
rest_api_id = module.rest_api.api_id
|
|
4096
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
4097
|
-
http_method = "ANY"
|
|
4098
|
-
|
|
4099
|
-
authorization = "AWS_IAM"
|
|
4100
|
-
|
|
4101
|
-
request_parameters = {
|
|
4102
|
-
"method.request.path.proxy" = true
|
|
4103
|
-
}
|
|
4104
|
-
|
|
4105
|
-
depends_on = []
|
|
4106
|
-
}
|
|
4107
|
-
|
|
4108
|
-
# OPTIONS method for CORS preflight
|
|
4843
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
4109
4844
|
resource "aws_api_gateway_method" "options_method" {
|
|
4110
4845
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
4111
4846
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
4847
|
+
for_each = local.resource_ids
|
|
4848
|
+
|
|
4112
4849
|
rest_api_id = module.rest_api.api_id
|
|
4113
|
-
resource_id =
|
|
4850
|
+
resource_id = each.value
|
|
4114
4851
|
http_method = "OPTIONS"
|
|
4115
4852
|
authorization = "NONE"
|
|
4116
4853
|
}
|
|
4117
4854
|
|
|
4118
|
-
# CORS integration for OPTIONS
|
|
4855
|
+
# CORS integration for OPTIONS methods
|
|
4119
4856
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
4857
|
+
for_each = local.resource_ids
|
|
4858
|
+
|
|
4120
4859
|
rest_api_id = module.rest_api.api_id
|
|
4121
|
-
resource_id =
|
|
4122
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
4860
|
+
resource_id = each.value
|
|
4861
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
4123
4862
|
|
|
4124
4863
|
type = "MOCK"
|
|
4125
4864
|
request_templates = {
|
|
@@ -4127,11 +4866,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
4127
4866
|
}
|
|
4128
4867
|
}
|
|
4129
4868
|
|
|
4130
|
-
# OPTIONS method
|
|
4869
|
+
# OPTIONS method responses
|
|
4131
4870
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
4871
|
+
for_each = local.resource_ids
|
|
4872
|
+
|
|
4132
4873
|
rest_api_id = module.rest_api.api_id
|
|
4133
|
-
resource_id =
|
|
4134
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
4874
|
+
resource_id = each.value
|
|
4875
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
4135
4876
|
status_code = "204"
|
|
4136
4877
|
|
|
4137
4878
|
response_parameters = {
|
|
@@ -4141,18 +4882,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
4141
4882
|
}
|
|
4142
4883
|
}
|
|
4143
4884
|
|
|
4144
|
-
# OPTIONS integration
|
|
4885
|
+
# OPTIONS integration responses
|
|
4145
4886
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
4887
|
+
for_each = local.resource_ids
|
|
4888
|
+
|
|
4146
4889
|
rest_api_id = module.rest_api.api_id
|
|
4147
|
-
resource_id =
|
|
4148
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
4149
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
4890
|
+
resource_id = each.value
|
|
4891
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
4892
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
4150
4893
|
|
|
4151
4894
|
response_parameters = {
|
|
4152
4895
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
4153
4896
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
4154
4897
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
4155
4898
|
}
|
|
4899
|
+
|
|
4900
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
4156
4901
|
}
|
|
4157
4902
|
|
|
4158
4903
|
# API Gateway deployment
|
|
@@ -4160,13 +4905,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
4160
4905
|
rest_api_id = module.rest_api.api_id
|
|
4161
4906
|
|
|
4162
4907
|
triggers = {
|
|
4163
|
-
redeployment = sha1(jsonencode(
|
|
4164
|
-
aws_api_gateway_resource.
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4908
|
+
redeployment = sha1(jsonencode(concat(
|
|
4909
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
4910
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
4911
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
4912
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
4913
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
4914
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
4915
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
4916
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
4917
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
4918
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
4919
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
4920
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
4921
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
4922
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
4923
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
4924
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
4925
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
4926
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
4927
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
4928
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
4929
|
+
)))
|
|
4170
4930
|
}
|
|
4171
4931
|
|
|
4172
4932
|
lifecycle {
|
|
@@ -4174,7 +4934,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
4174
4934
|
}
|
|
4175
4935
|
|
|
4176
4936
|
depends_on = [
|
|
4177
|
-
aws_api_gateway_method.
|
|
4937
|
+
aws_api_gateway_method.operation_methods,
|
|
4178
4938
|
aws_api_gateway_integration.lambda_integration,
|
|
4179
4939
|
aws_api_gateway_method.options_method,
|
|
4180
4940
|
aws_api_gateway_integration.options_integration,
|
|
@@ -4313,24 +5073,28 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
4313
5073
|
|
|
4314
5074
|
# Lambda permission for API Gateway to invoke the function via alias
|
|
4315
5075
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
5076
|
+
for_each = local.operations
|
|
5077
|
+
|
|
4316
5078
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
4317
5079
|
action = "lambda:InvokeFunction"
|
|
4318
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
4319
|
-
qualifier = aws_lambda_alias.live.name
|
|
5080
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
5081
|
+
qualifier = aws_lambda_alias.live[each.key].name
|
|
4320
5082
|
principal = "apigateway.amazonaws.com"
|
|
4321
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
5083
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
4322
5084
|
|
|
4323
5085
|
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
4324
5086
|
}
|
|
4325
5087
|
|
|
4326
5088
|
# Lambda permission for API Gateway to invoke with response streaming via alias
|
|
4327
5089
|
resource "aws_lambda_permission" "api_gateway_invoke_streaming" {
|
|
5090
|
+
for_each = local.operations
|
|
5091
|
+
|
|
4328
5092
|
statement_id = "AllowStreamingFromAPIGateway"
|
|
4329
5093
|
action = "lambda:InvokeFunctionWithResponseStream"
|
|
4330
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
4331
|
-
qualifier = aws_lambda_alias.live.name
|
|
5094
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
5095
|
+
qualifier = aws_lambda_alias.live[each.key].name
|
|
4332
5096
|
principal = "apigateway.amazonaws.com"
|
|
4333
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
5097
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
4334
5098
|
|
|
4335
5099
|
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
4336
5100
|
}
|
|
@@ -4394,83 +5158,83 @@ output "stage_id" {
|
|
|
4394
5158
|
value = aws_api_gateway_stage.api_stage.id
|
|
4395
5159
|
}
|
|
4396
5160
|
|
|
4397
|
-
# Lambda Function Outputs
|
|
4398
|
-
output "
|
|
4399
|
-
description = "
|
|
4400
|
-
value =
|
|
5161
|
+
# Lambda Function Outputs, keyed by operation name
|
|
5162
|
+
output "operations" {
|
|
5163
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
5164
|
+
value = keys(local.operations)
|
|
4401
5165
|
}
|
|
4402
5166
|
|
|
4403
|
-
output "
|
|
4404
|
-
description = "
|
|
4405
|
-
value = aws_lambda_function.api_lambda.
|
|
5167
|
+
output "lambda_function_names" {
|
|
5168
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
5169
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
4406
5170
|
}
|
|
4407
5171
|
|
|
4408
|
-
output "
|
|
4409
|
-
description = "
|
|
4410
|
-
value = aws_lambda_function.api_lambda.
|
|
5172
|
+
output "lambda_function_arns" {
|
|
5173
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
5174
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
4411
5175
|
}
|
|
4412
5176
|
|
|
4413
|
-
output "
|
|
4414
|
-
description = "
|
|
4415
|
-
value = aws_lambda_function.api_lambda.
|
|
5177
|
+
output "lambda_invoke_arns" {
|
|
5178
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
5179
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
4416
5180
|
}
|
|
4417
5181
|
|
|
4418
|
-
output "
|
|
4419
|
-
description = "
|
|
4420
|
-
value = aws_lambda_function.api_lambda.
|
|
5182
|
+
output "lambda_qualified_arns" {
|
|
5183
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
5184
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
4421
5185
|
}
|
|
4422
5186
|
|
|
4423
|
-
output "
|
|
4424
|
-
description = "
|
|
4425
|
-
value = aws_lambda_function.api_lambda.
|
|
5187
|
+
output "lambda_versions" {
|
|
5188
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
5189
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
4426
5190
|
}
|
|
4427
5191
|
|
|
4428
|
-
output "
|
|
4429
|
-
description = "
|
|
4430
|
-
value =
|
|
5192
|
+
output "lambda_source_code_hash" {
|
|
5193
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
5194
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
4431
5195
|
}
|
|
4432
5196
|
|
|
4433
|
-
# IAM Role Outputs
|
|
4434
|
-
output "
|
|
4435
|
-
description = "ARN of
|
|
4436
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
5197
|
+
# IAM Role Outputs, keyed by operation name
|
|
5198
|
+
output "lambda_execution_role_arns" {
|
|
5199
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
5200
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
4437
5201
|
}
|
|
4438
5202
|
|
|
4439
|
-
output "
|
|
4440
|
-
description = "Name of
|
|
4441
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
5203
|
+
output "lambda_execution_role_names" {
|
|
5204
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
5205
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
4442
5206
|
}
|
|
4443
5207
|
|
|
4444
5208
|
output "security_group_id" {
|
|
4445
|
-
description = "Security group ID
|
|
5209
|
+
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."
|
|
4446
5210
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
4447
5211
|
}
|
|
4448
5212
|
|
|
4449
|
-
# Integration Outputs
|
|
4450
|
-
output "
|
|
4451
|
-
description = "ID of
|
|
4452
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
5213
|
+
# Integration Outputs, keyed by operation name
|
|
5214
|
+
output "integration_ids" {
|
|
5215
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
5216
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
4453
5217
|
}
|
|
4454
5218
|
|
|
4455
|
-
output "
|
|
4456
|
-
description = "ID of
|
|
4457
|
-
value =
|
|
5219
|
+
output "method_ids" {
|
|
5220
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
5221
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
4458
5222
|
}
|
|
4459
5223
|
|
|
4460
|
-
output "
|
|
4461
|
-
description = "ID of
|
|
4462
|
-
value =
|
|
5224
|
+
output "resource_ids" {
|
|
5225
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
5226
|
+
value = local.resource_ids
|
|
4463
5227
|
}
|
|
4464
5228
|
|
|
4465
|
-
# CloudWatch Log Groups
|
|
4466
|
-
output "
|
|
4467
|
-
description = "Name of
|
|
4468
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
5229
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
5230
|
+
output "lambda_log_group_names" {
|
|
5231
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
5232
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
4469
5233
|
}
|
|
4470
5234
|
|
|
4471
|
-
output "
|
|
4472
|
-
description = "ARN of
|
|
4473
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
5235
|
+
output "lambda_log_group_arns" {
|
|
5236
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
5237
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
4474
5238
|
}
|
|
4475
5239
|
",
|
|
4476
5240
|
}
|
|
@@ -4911,6 +5675,235 @@ resource "random_string" "suffix" {
|
|
|
4911
5675
|
upper = false
|
|
4912
5676
|
}
|
|
4913
5677
|
|
|
5678
|
+
locals {
|
|
5679
|
+
# Generated at build time from the API definition
|
|
5680
|
+
operations_file = "\${path.module}/../../../generated/test-api/operations.json"
|
|
5681
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
5682
|
+
|
|
5683
|
+
operation_slug = { for op in keys(local.operations) : op => replace(op, "/[^a-zA-Z0-9-_]/", "-") }
|
|
5684
|
+
operation_hash = { for op in keys(local.operations) : op => substr(sha256(op), 0, 8) }
|
|
5685
|
+
|
|
5686
|
+
function_name = { for op in keys(local.operations) : op =>
|
|
5687
|
+
"\${substr("TestApi-\${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}"
|
|
5688
|
+
}
|
|
5689
|
+
role_name = { for op in keys(local.operations) : op =>
|
|
5690
|
+
"\${substr("TestApi-\${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}"
|
|
5691
|
+
}
|
|
5692
|
+
|
|
5693
|
+
# A REST API needs a resource per path segment, so each operation's path is
|
|
5694
|
+
# split into the distinct path prefixes the resource tree is built from below
|
|
5695
|
+
operation_segments = { for op, details in local.operations : op => compact(split("/", details.path)) }
|
|
5696
|
+
|
|
5697
|
+
path_nodes = merge([
|
|
5698
|
+
for op, segments in local.operation_segments : {
|
|
5699
|
+
for i, segment in segments : join("/", slice(segments, 0, i + 1)) => {
|
|
5700
|
+
depth = i
|
|
5701
|
+
parent = i == 0 ? "" : join("/", slice(segments, 0, i))
|
|
5702
|
+
part = segment
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
]...)
|
|
5706
|
+
|
|
5707
|
+
# Instances of a resource cannot reference one another, so each depth is a
|
|
5708
|
+
# separate resource. Deeper paths are reported by the precondition below.
|
|
5709
|
+
max_path_depth = 16
|
|
5710
|
+
path_nodes_by_depth = { for depth in range(local.max_path_depth) : depth => {
|
|
5711
|
+
for path, node in local.path_nodes : path => node if node.depth == depth
|
|
5712
|
+
} }
|
|
5713
|
+
paths_too_deep = [for path, node in local.path_nodes : path if node.depth >= local.max_path_depth]
|
|
5714
|
+
|
|
5715
|
+
resource_ids = merge(
|
|
5716
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_0 : path => resource.id },
|
|
5717
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_1 : path => resource.id },
|
|
5718
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_2 : path => resource.id },
|
|
5719
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_3 : path => resource.id },
|
|
5720
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_4 : path => resource.id },
|
|
5721
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_5 : path => resource.id },
|
|
5722
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_6 : path => resource.id },
|
|
5723
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_7 : path => resource.id },
|
|
5724
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_8 : path => resource.id },
|
|
5725
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_9 : path => resource.id },
|
|
5726
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_10 : path => resource.id },
|
|
5727
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_11 : path => resource.id },
|
|
5728
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_12 : path => resource.id },
|
|
5729
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_13 : path => resource.id },
|
|
5730
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_14 : path => resource.id },
|
|
5731
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_15 : path => resource.id },
|
|
5732
|
+
)
|
|
5733
|
+
|
|
5734
|
+
operation_resource_id = { for op, segments in local.operation_segments : op =>
|
|
5735
|
+
local.resource_ids[join("/", segments)]
|
|
5736
|
+
}
|
|
5737
|
+
|
|
5738
|
+
permission_source_suffix = { for op, details in local.operations : op =>
|
|
5739
|
+
"\${upper(details.method)}/\${replace(join("/", local.operation_segments[op]), "/{[^}]*}/", "*")}"
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
|
|
5743
|
+
resource "terraform_data" "operations_metadata" {
|
|
5744
|
+
# Fails the plan when the operations metadata has not been generated
|
|
5745
|
+
input = local.operations_file
|
|
5746
|
+
|
|
5747
|
+
lifecycle {
|
|
5748
|
+
precondition {
|
|
5749
|
+
condition = fileexists(local.operations_file)
|
|
5750
|
+
error_message = "Operations metadata not found at \${local.operations_file}. Build the TestApi project to generate it."
|
|
5751
|
+
}
|
|
5752
|
+
precondition {
|
|
5753
|
+
condition = length(local.operations) > 0
|
|
5754
|
+
error_message = "No operations found in \${local.operations_file}. The TestApi API must define at least one operation."
|
|
5755
|
+
}
|
|
5756
|
+
precondition {
|
|
5757
|
+
condition = length(local.paths_too_deep) == 0
|
|
5758
|
+
error_message = "Operation paths exceed the supported depth of \${local.max_path_depth} segments: \${join(", ", local.paths_too_deep)}. Add further aws_api_gateway_resource levels to this module to support them."
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
|
|
5763
|
+
# API Gateway resources at path depth 0
|
|
5764
|
+
resource "aws_api_gateway_resource" "path_depth_0" {
|
|
5765
|
+
for_each = local.path_nodes_by_depth[0]
|
|
5766
|
+
|
|
5767
|
+
rest_api_id = module.rest_api.api_id
|
|
5768
|
+
parent_id = module.rest_api.api_root_resource_id
|
|
5769
|
+
path_part = each.value.part
|
|
5770
|
+
}
|
|
5771
|
+
|
|
5772
|
+
# API Gateway resources at path depth 1
|
|
5773
|
+
resource "aws_api_gateway_resource" "path_depth_1" {
|
|
5774
|
+
for_each = local.path_nodes_by_depth[1]
|
|
5775
|
+
|
|
5776
|
+
rest_api_id = module.rest_api.api_id
|
|
5777
|
+
parent_id = aws_api_gateway_resource.path_depth_0[each.value.parent].id
|
|
5778
|
+
path_part = each.value.part
|
|
5779
|
+
}
|
|
5780
|
+
|
|
5781
|
+
# API Gateway resources at path depth 2
|
|
5782
|
+
resource "aws_api_gateway_resource" "path_depth_2" {
|
|
5783
|
+
for_each = local.path_nodes_by_depth[2]
|
|
5784
|
+
|
|
5785
|
+
rest_api_id = module.rest_api.api_id
|
|
5786
|
+
parent_id = aws_api_gateway_resource.path_depth_1[each.value.parent].id
|
|
5787
|
+
path_part = each.value.part
|
|
5788
|
+
}
|
|
5789
|
+
|
|
5790
|
+
# API Gateway resources at path depth 3
|
|
5791
|
+
resource "aws_api_gateway_resource" "path_depth_3" {
|
|
5792
|
+
for_each = local.path_nodes_by_depth[3]
|
|
5793
|
+
|
|
5794
|
+
rest_api_id = module.rest_api.api_id
|
|
5795
|
+
parent_id = aws_api_gateway_resource.path_depth_2[each.value.parent].id
|
|
5796
|
+
path_part = each.value.part
|
|
5797
|
+
}
|
|
5798
|
+
|
|
5799
|
+
# API Gateway resources at path depth 4
|
|
5800
|
+
resource "aws_api_gateway_resource" "path_depth_4" {
|
|
5801
|
+
for_each = local.path_nodes_by_depth[4]
|
|
5802
|
+
|
|
5803
|
+
rest_api_id = module.rest_api.api_id
|
|
5804
|
+
parent_id = aws_api_gateway_resource.path_depth_3[each.value.parent].id
|
|
5805
|
+
path_part = each.value.part
|
|
5806
|
+
}
|
|
5807
|
+
|
|
5808
|
+
# API Gateway resources at path depth 5
|
|
5809
|
+
resource "aws_api_gateway_resource" "path_depth_5" {
|
|
5810
|
+
for_each = local.path_nodes_by_depth[5]
|
|
5811
|
+
|
|
5812
|
+
rest_api_id = module.rest_api.api_id
|
|
5813
|
+
parent_id = aws_api_gateway_resource.path_depth_4[each.value.parent].id
|
|
5814
|
+
path_part = each.value.part
|
|
5815
|
+
}
|
|
5816
|
+
|
|
5817
|
+
# API Gateway resources at path depth 6
|
|
5818
|
+
resource "aws_api_gateway_resource" "path_depth_6" {
|
|
5819
|
+
for_each = local.path_nodes_by_depth[6]
|
|
5820
|
+
|
|
5821
|
+
rest_api_id = module.rest_api.api_id
|
|
5822
|
+
parent_id = aws_api_gateway_resource.path_depth_5[each.value.parent].id
|
|
5823
|
+
path_part = each.value.part
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5826
|
+
# API Gateway resources at path depth 7
|
|
5827
|
+
resource "aws_api_gateway_resource" "path_depth_7" {
|
|
5828
|
+
for_each = local.path_nodes_by_depth[7]
|
|
5829
|
+
|
|
5830
|
+
rest_api_id = module.rest_api.api_id
|
|
5831
|
+
parent_id = aws_api_gateway_resource.path_depth_6[each.value.parent].id
|
|
5832
|
+
path_part = each.value.part
|
|
5833
|
+
}
|
|
5834
|
+
|
|
5835
|
+
# API Gateway resources at path depth 8
|
|
5836
|
+
resource "aws_api_gateway_resource" "path_depth_8" {
|
|
5837
|
+
for_each = local.path_nodes_by_depth[8]
|
|
5838
|
+
|
|
5839
|
+
rest_api_id = module.rest_api.api_id
|
|
5840
|
+
parent_id = aws_api_gateway_resource.path_depth_7[each.value.parent].id
|
|
5841
|
+
path_part = each.value.part
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5844
|
+
# API Gateway resources at path depth 9
|
|
5845
|
+
resource "aws_api_gateway_resource" "path_depth_9" {
|
|
5846
|
+
for_each = local.path_nodes_by_depth[9]
|
|
5847
|
+
|
|
5848
|
+
rest_api_id = module.rest_api.api_id
|
|
5849
|
+
parent_id = aws_api_gateway_resource.path_depth_8[each.value.parent].id
|
|
5850
|
+
path_part = each.value.part
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5853
|
+
# API Gateway resources at path depth 10
|
|
5854
|
+
resource "aws_api_gateway_resource" "path_depth_10" {
|
|
5855
|
+
for_each = local.path_nodes_by_depth[10]
|
|
5856
|
+
|
|
5857
|
+
rest_api_id = module.rest_api.api_id
|
|
5858
|
+
parent_id = aws_api_gateway_resource.path_depth_9[each.value.parent].id
|
|
5859
|
+
path_part = each.value.part
|
|
5860
|
+
}
|
|
5861
|
+
|
|
5862
|
+
# API Gateway resources at path depth 11
|
|
5863
|
+
resource "aws_api_gateway_resource" "path_depth_11" {
|
|
5864
|
+
for_each = local.path_nodes_by_depth[11]
|
|
5865
|
+
|
|
5866
|
+
rest_api_id = module.rest_api.api_id
|
|
5867
|
+
parent_id = aws_api_gateway_resource.path_depth_10[each.value.parent].id
|
|
5868
|
+
path_part = each.value.part
|
|
5869
|
+
}
|
|
5870
|
+
|
|
5871
|
+
# API Gateway resources at path depth 12
|
|
5872
|
+
resource "aws_api_gateway_resource" "path_depth_12" {
|
|
5873
|
+
for_each = local.path_nodes_by_depth[12]
|
|
5874
|
+
|
|
5875
|
+
rest_api_id = module.rest_api.api_id
|
|
5876
|
+
parent_id = aws_api_gateway_resource.path_depth_11[each.value.parent].id
|
|
5877
|
+
path_part = each.value.part
|
|
5878
|
+
}
|
|
5879
|
+
|
|
5880
|
+
# API Gateway resources at path depth 13
|
|
5881
|
+
resource "aws_api_gateway_resource" "path_depth_13" {
|
|
5882
|
+
for_each = local.path_nodes_by_depth[13]
|
|
5883
|
+
|
|
5884
|
+
rest_api_id = module.rest_api.api_id
|
|
5885
|
+
parent_id = aws_api_gateway_resource.path_depth_12[each.value.parent].id
|
|
5886
|
+
path_part = each.value.part
|
|
5887
|
+
}
|
|
5888
|
+
|
|
5889
|
+
# API Gateway resources at path depth 14
|
|
5890
|
+
resource "aws_api_gateway_resource" "path_depth_14" {
|
|
5891
|
+
for_each = local.path_nodes_by_depth[14]
|
|
5892
|
+
|
|
5893
|
+
rest_api_id = module.rest_api.api_id
|
|
5894
|
+
parent_id = aws_api_gateway_resource.path_depth_13[each.value.parent].id
|
|
5895
|
+
path_part = each.value.part
|
|
5896
|
+
}
|
|
5897
|
+
|
|
5898
|
+
# API Gateway resources at path depth 15
|
|
5899
|
+
resource "aws_api_gateway_resource" "path_depth_15" {
|
|
5900
|
+
for_each = local.path_nodes_by_depth[15]
|
|
5901
|
+
|
|
5902
|
+
rest_api_id = module.rest_api.api_id
|
|
5903
|
+
parent_id = aws_api_gateway_resource.path_depth_14[each.value.parent].id
|
|
5904
|
+
path_part = each.value.part
|
|
5905
|
+
}
|
|
5906
|
+
|
|
4914
5907
|
# Resources
|
|
4915
5908
|
|
|
4916
5909
|
# Create Lambda ZIP file from the bundle directory
|
|
@@ -4985,8 +5978,10 @@ resource "aws_vpc_security_group_egress_rule" "api_lambda_https" {
|
|
|
4985
5978
|
description = "Allow outbound HTTPS to AWS service endpoints"
|
|
4986
5979
|
}
|
|
4987
5980
|
|
|
4988
|
-
# Lambda function
|
|
4989
5981
|
resource "aws_lambda_function" "api_lambda" {
|
|
5982
|
+
# One lambda function per operation, each serving just that operation
|
|
5983
|
+
for_each = local.operations
|
|
5984
|
+
|
|
4990
5985
|
#checkov:skip=CKV_AWS_117:Lambda function is optionally deployed into a VPC via vpc_id/subnet_ids; not required for this use case
|
|
4991
5986
|
#checkov:skip=CKV_AWS_116:Dead Letter Queue not required for this simple API use case
|
|
4992
5987
|
#checkov:skip=CKV_AWS_272:Code signing not required for this use case
|
|
@@ -4995,8 +5990,8 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
4995
5990
|
s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
4996
5991
|
s3_key = aws_s3_object.lambda_zip.key
|
|
4997
5992
|
s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
4998
|
-
function_name =
|
|
4999
|
-
role = aws_iam_role.lambda_execution_role.arn
|
|
5993
|
+
function_name = local.function_name[each.key]
|
|
5994
|
+
role = aws_iam_role.lambda_execution_role[each.key].arn
|
|
5000
5995
|
handler = "run.sh"
|
|
5001
5996
|
runtime = "python3.14"
|
|
5002
5997
|
layers = ["arn:aws:lambda:\${data.aws_region.current.region}:753240598075:layer:LambdaAdapterLayerX86:24"]
|
|
@@ -5041,7 +6036,9 @@ resource "aws_lambda_function" "api_lambda" {
|
|
|
5041
6036
|
|
|
5042
6037
|
# IAM role for Lambda execution
|
|
5043
6038
|
resource "aws_iam_role" "lambda_execution_role" {
|
|
5044
|
-
|
|
6039
|
+
for_each = local.operations
|
|
6040
|
+
|
|
6041
|
+
name = local.role_name[each.key]
|
|
5045
6042
|
|
|
5046
6043
|
assume_role_policy = jsonencode({
|
|
5047
6044
|
Version = "2012-10-17"
|
|
@@ -5061,32 +6058,38 @@ resource "aws_iam_role" "lambda_execution_role" {
|
|
|
5061
6058
|
|
|
5062
6059
|
# Attach basic execution policy to Lambda role
|
|
5063
6060
|
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
|
|
6061
|
+
for_each = local.operations
|
|
6062
|
+
|
|
5064
6063
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
5065
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
6064
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5066
6065
|
}
|
|
5067
6066
|
|
|
5068
6067
|
# Attach X-Ray tracing policy to Lambda role
|
|
5069
6068
|
resource "aws_iam_role_policy_attachment" "lambda_xray_execution" {
|
|
6069
|
+
for_each = local.operations
|
|
6070
|
+
|
|
5070
6071
|
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
|
|
5071
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
6072
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5072
6073
|
}
|
|
5073
6074
|
|
|
5074
6075
|
# Attach VPC access policy to Lambda role when deployed into a VPC
|
|
5075
6076
|
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
|
|
5076
|
-
|
|
6077
|
+
for_each = var.enable_vpc ? local.operations : {}
|
|
6078
|
+
|
|
5077
6079
|
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
|
|
5078
|
-
role = aws_iam_role.lambda_execution_role.name
|
|
6080
|
+
role = aws_iam_role.lambda_execution_role[each.key].name
|
|
5079
6081
|
}
|
|
5080
6082
|
|
|
5081
6083
|
# Additional IAM policies for Lambda (if provided)
|
|
5082
6084
|
resource "aws_iam_role_policy" "lambda_additional_policies" {
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
6085
|
+
for_each = local.operations
|
|
6086
|
+
|
|
6087
|
+
name = "\${substr(local.function_name[each.key], 0, 55)}-policies"
|
|
6088
|
+
role = aws_iam_role.lambda_execution_role[each.key].id
|
|
5086
6089
|
|
|
5087
6090
|
policy = jsonencode({
|
|
5088
6091
|
Version = "2012-10-17"
|
|
5089
|
-
Statement = local.
|
|
6092
|
+
Statement = local.lambda_policy_document_statements
|
|
5090
6093
|
})
|
|
5091
6094
|
}
|
|
5092
6095
|
|
|
@@ -5102,6 +6105,15 @@ locals {
|
|
|
5102
6105
|
Resource = ["\${var.appconfig_application_arn}/*"]
|
|
5103
6106
|
}
|
|
5104
6107
|
] : [], var.additional_iam_policy_statements)
|
|
6108
|
+
|
|
6109
|
+
# IAM rejects an empty statement list, so fall back to a no-op deny
|
|
6110
|
+
lambda_policy_document_statements = length(local.lambda_policy_statements) > 0 ? local.lambda_policy_statements : [
|
|
6111
|
+
{
|
|
6112
|
+
Effect = "Deny"
|
|
6113
|
+
Action = ["appconfig:GetLatestConfiguration"]
|
|
6114
|
+
Resource = ["arn:aws:appconfig:\${data.aws_region.current.region}:\${data.aws_caller_identity.current.account_id}:application/none"]
|
|
6115
|
+
}
|
|
6116
|
+
]
|
|
5105
6117
|
}
|
|
5106
6118
|
|
|
5107
6119
|
# CloudWatch Log Group for Lambda
|
|
@@ -5109,15 +6121,19 @@ resource "aws_cloudwatch_log_group" "lambda_logs" {
|
|
|
5109
6121
|
#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption
|
|
5110
6122
|
#checkov:skip=CKV_AWS_338:Log retention set to forever
|
|
5111
6123
|
#checkov:skip=CKV_AWS_66:Log retention set to forever
|
|
5112
|
-
|
|
5113
|
-
|
|
6124
|
+
for_each = local.operations
|
|
6125
|
+
|
|
6126
|
+
name = "/aws/lambda/\${local.function_name[each.key]}"
|
|
6127
|
+
tags = var.tags
|
|
5114
6128
|
}
|
|
5115
6129
|
|
|
5116
6130
|
# Lambda alias pointing to the latest published version for SnapStart
|
|
5117
6131
|
resource "aws_lambda_alias" "live" {
|
|
6132
|
+
for_each = local.operations
|
|
6133
|
+
|
|
5118
6134
|
name = "live"
|
|
5119
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
5120
|
-
function_version = aws_lambda_function.api_lambda.version
|
|
6135
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
6136
|
+
function_version = aws_lambda_function.api_lambda[each.key].version
|
|
5121
6137
|
|
|
5122
6138
|
depends_on = [aws_lambda_function.api_lambda]
|
|
5123
6139
|
}
|
|
@@ -5215,60 +6231,64 @@ resource "aws_lambda_permission" "authorizer_invoke" {
|
|
|
5215
6231
|
source_arn = "\${module.rest_api.api_execution_arn}/authorizers/\${aws_api_gateway_authorizer.custom_authorizer.id}"
|
|
5216
6232
|
}
|
|
5217
6233
|
|
|
5218
|
-
#
|
|
5219
|
-
resource "
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
6234
|
+
# Method per operation
|
|
6235
|
+
resource "aws_api_gateway_method" "operation_methods" {
|
|
6236
|
+
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
6237
|
+
for_each = local.operations
|
|
6238
|
+
|
|
6239
|
+
rest_api_id = module.rest_api.api_id
|
|
6240
|
+
resource_id = local.operation_resource_id[each.key]
|
|
6241
|
+
http_method = upper(each.value.method)
|
|
6242
|
+
|
|
6243
|
+
authorization = "CUSTOM"
|
|
6244
|
+
authorizer_id = aws_api_gateway_authorizer.custom_authorizer.id
|
|
6245
|
+
|
|
6246
|
+
# Path parameters must be declared on the method
|
|
6247
|
+
request_parameters = {
|
|
6248
|
+
for segment in local.operation_segments[each.key] :
|
|
6249
|
+
"method.request.path.\${trimsuffix(trimprefix(segment, "{"), "}")}" => true
|
|
6250
|
+
if startswith(segment, "{")
|
|
6251
|
+
}
|
|
6252
|
+
|
|
6253
|
+
depends_on = [aws_api_gateway_authorizer.custom_authorizer]
|
|
5223
6254
|
}
|
|
5224
6255
|
|
|
5225
|
-
# Lambda integration
|
|
6256
|
+
# Lambda integration per operation
|
|
5226
6257
|
resource "aws_api_gateway_integration" "lambda_integration" {
|
|
6258
|
+
for_each = local.operations
|
|
6259
|
+
|
|
5227
6260
|
rest_api_id = module.rest_api.api_id
|
|
5228
|
-
resource_id =
|
|
5229
|
-
http_method = aws_api_gateway_method.
|
|
6261
|
+
resource_id = local.operation_resource_id[each.key]
|
|
6262
|
+
http_method = aws_api_gateway_method.operation_methods[each.key].http_method
|
|
5230
6263
|
|
|
5231
6264
|
integration_http_method = "POST"
|
|
5232
6265
|
type = "AWS_PROXY"
|
|
5233
6266
|
# Use the response streaming invocation path with the alias ARN for SnapStart
|
|
5234
|
-
uri = "arn:aws:apigateway:\${data.aws_region.current.region}:lambda:path/2021-11-15/functions/\${aws_lambda_alias.live.arn}/response-streaming-invocations"
|
|
6267
|
+
uri = "arn:aws:apigateway:\${data.aws_region.current.region}:lambda:path/2021-11-15/functions/\${aws_lambda_alias.live[each.key].arn}/response-streaming-invocations"
|
|
5235
6268
|
response_transfer_mode = "STREAM"
|
|
5236
6269
|
|
|
5237
6270
|
depends_on = [aws_lambda_alias.live]
|
|
5238
6271
|
}
|
|
5239
6272
|
|
|
5240
|
-
#
|
|
5241
|
-
resource "aws_api_gateway_method" "proxy_method" {
|
|
5242
|
-
#checkov:skip=CKV2_AWS_53:Request validation not required for proxy integration as Lambda handles validation
|
|
5243
|
-
rest_api_id = module.rest_api.api_id
|
|
5244
|
-
resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
5245
|
-
http_method = "ANY"
|
|
5246
|
-
|
|
5247
|
-
authorization = "CUSTOM"
|
|
5248
|
-
authorizer_id = aws_api_gateway_authorizer.custom_authorizer.id
|
|
5249
|
-
|
|
5250
|
-
request_parameters = {
|
|
5251
|
-
"method.request.path.proxy" = true
|
|
5252
|
-
}
|
|
5253
|
-
|
|
5254
|
-
depends_on = [aws_api_gateway_authorizer.custom_authorizer]
|
|
5255
|
-
}
|
|
5256
|
-
|
|
5257
|
-
# OPTIONS method for CORS preflight
|
|
6273
|
+
# OPTIONS method for CORS preflight, on every resource an operation is served from
|
|
5258
6274
|
resource "aws_api_gateway_method" "options_method" {
|
|
5259
6275
|
#checkov:skip=CKV2_AWS_70:OPTIONS method must be unauthenticated for CORS preflight requests
|
|
5260
6276
|
#checkov:skip=CKV2_AWS_53:Request validation not required for OPTIONS CORS preflight method
|
|
6277
|
+
for_each = local.resource_ids
|
|
6278
|
+
|
|
5261
6279
|
rest_api_id = module.rest_api.api_id
|
|
5262
|
-
resource_id =
|
|
6280
|
+
resource_id = each.value
|
|
5263
6281
|
http_method = "OPTIONS"
|
|
5264
6282
|
authorization = "NONE"
|
|
5265
6283
|
}
|
|
5266
6284
|
|
|
5267
|
-
# CORS integration for OPTIONS
|
|
6285
|
+
# CORS integration for OPTIONS methods
|
|
5268
6286
|
resource "aws_api_gateway_integration" "options_integration" {
|
|
6287
|
+
for_each = local.resource_ids
|
|
6288
|
+
|
|
5269
6289
|
rest_api_id = module.rest_api.api_id
|
|
5270
|
-
resource_id =
|
|
5271
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
6290
|
+
resource_id = each.value
|
|
6291
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
5272
6292
|
|
|
5273
6293
|
type = "MOCK"
|
|
5274
6294
|
request_templates = {
|
|
@@ -5276,11 +6296,13 @@ resource "aws_api_gateway_integration" "options_integration" {
|
|
|
5276
6296
|
}
|
|
5277
6297
|
}
|
|
5278
6298
|
|
|
5279
|
-
# OPTIONS method
|
|
6299
|
+
# OPTIONS method responses
|
|
5280
6300
|
resource "aws_api_gateway_method_response" "options_response" {
|
|
6301
|
+
for_each = local.resource_ids
|
|
6302
|
+
|
|
5281
6303
|
rest_api_id = module.rest_api.api_id
|
|
5282
|
-
resource_id =
|
|
5283
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
6304
|
+
resource_id = each.value
|
|
6305
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
5284
6306
|
status_code = "204"
|
|
5285
6307
|
|
|
5286
6308
|
response_parameters = {
|
|
@@ -5290,18 +6312,22 @@ resource "aws_api_gateway_method_response" "options_response" {
|
|
|
5290
6312
|
}
|
|
5291
6313
|
}
|
|
5292
6314
|
|
|
5293
|
-
# OPTIONS integration
|
|
6315
|
+
# OPTIONS integration responses
|
|
5294
6316
|
resource "aws_api_gateway_integration_response" "options_integration_response" {
|
|
6317
|
+
for_each = local.resource_ids
|
|
6318
|
+
|
|
5295
6319
|
rest_api_id = module.rest_api.api_id
|
|
5296
|
-
resource_id =
|
|
5297
|
-
http_method = aws_api_gateway_method.options_method.http_method
|
|
5298
|
-
status_code = aws_api_gateway_method_response.options_response.status_code
|
|
6320
|
+
resource_id = each.value
|
|
6321
|
+
http_method = aws_api_gateway_method.options_method[each.key].http_method
|
|
6322
|
+
status_code = aws_api_gateway_method_response.options_response[each.key].status_code
|
|
5299
6323
|
|
|
5300
6324
|
response_parameters = {
|
|
5301
6325
|
"method.response.header.Access-Control-Allow-Headers" = "'\${join(",", var.cors_allow_headers)}'"
|
|
5302
6326
|
"method.response.header.Access-Control-Allow-Methods" = "'\${join(",", var.cors_allow_methods)}'"
|
|
5303
6327
|
"method.response.header.Access-Control-Allow-Origin" = "'\${join(",", var.cors_allow_origins)}'"
|
|
5304
6328
|
}
|
|
6329
|
+
|
|
6330
|
+
depends_on = [aws_api_gateway_integration.options_integration]
|
|
5305
6331
|
}
|
|
5306
6332
|
|
|
5307
6333
|
# API Gateway deployment
|
|
@@ -5309,13 +6335,28 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
5309
6335
|
rest_api_id = module.rest_api.api_id
|
|
5310
6336
|
|
|
5311
6337
|
triggers = {
|
|
5312
|
-
redeployment = sha1(jsonencode(
|
|
5313
|
-
aws_api_gateway_resource.
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
6338
|
+
redeployment = sha1(jsonencode(concat(
|
|
6339
|
+
[for r in aws_api_gateway_resource.path_depth_0 : r.id],
|
|
6340
|
+
[for r in aws_api_gateway_resource.path_depth_1 : r.id],
|
|
6341
|
+
[for r in aws_api_gateway_resource.path_depth_2 : r.id],
|
|
6342
|
+
[for r in aws_api_gateway_resource.path_depth_3 : r.id],
|
|
6343
|
+
[for r in aws_api_gateway_resource.path_depth_4 : r.id],
|
|
6344
|
+
[for r in aws_api_gateway_resource.path_depth_5 : r.id],
|
|
6345
|
+
[for r in aws_api_gateway_resource.path_depth_6 : r.id],
|
|
6346
|
+
[for r in aws_api_gateway_resource.path_depth_7 : r.id],
|
|
6347
|
+
[for r in aws_api_gateway_resource.path_depth_8 : r.id],
|
|
6348
|
+
[for r in aws_api_gateway_resource.path_depth_9 : r.id],
|
|
6349
|
+
[for r in aws_api_gateway_resource.path_depth_10 : r.id],
|
|
6350
|
+
[for r in aws_api_gateway_resource.path_depth_11 : r.id],
|
|
6351
|
+
[for r in aws_api_gateway_resource.path_depth_12 : r.id],
|
|
6352
|
+
[for r in aws_api_gateway_resource.path_depth_13 : r.id],
|
|
6353
|
+
[for r in aws_api_gateway_resource.path_depth_14 : r.id],
|
|
6354
|
+
[for r in aws_api_gateway_resource.path_depth_15 : r.id],
|
|
6355
|
+
[for m in aws_api_gateway_method.operation_methods : m.id],
|
|
6356
|
+
[for i in aws_api_gateway_integration.lambda_integration : i.id],
|
|
6357
|
+
[for m in aws_api_gateway_method.options_method : m.id],
|
|
6358
|
+
[for i in aws_api_gateway_integration.options_integration : i.id],
|
|
6359
|
+
)))
|
|
5319
6360
|
}
|
|
5320
6361
|
|
|
5321
6362
|
lifecycle {
|
|
@@ -5323,7 +6364,7 @@ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
|
5323
6364
|
}
|
|
5324
6365
|
|
|
5325
6366
|
depends_on = [
|
|
5326
|
-
aws_api_gateway_method.
|
|
6367
|
+
aws_api_gateway_method.operation_methods,
|
|
5327
6368
|
aws_api_gateway_integration.lambda_integration,
|
|
5328
6369
|
aws_api_gateway_method.options_method,
|
|
5329
6370
|
aws_api_gateway_integration.options_integration,
|
|
@@ -5452,24 +6493,28 @@ resource "aws_api_gateway_rest_api_policy" "api_policy" {
|
|
|
5452
6493
|
|
|
5453
6494
|
# Lambda permission for API Gateway to invoke the function via alias
|
|
5454
6495
|
resource "aws_lambda_permission" "api_gateway_invoke" {
|
|
6496
|
+
for_each = local.operations
|
|
6497
|
+
|
|
5455
6498
|
statement_id = "AllowExecutionFromAPIGateway"
|
|
5456
6499
|
action = "lambda:InvokeFunction"
|
|
5457
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
5458
|
-
qualifier = aws_lambda_alias.live.name
|
|
6500
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
6501
|
+
qualifier = aws_lambda_alias.live[each.key].name
|
|
5459
6502
|
principal = "apigateway.amazonaws.com"
|
|
5460
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
6503
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
5461
6504
|
|
|
5462
6505
|
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
5463
6506
|
}
|
|
5464
6507
|
|
|
5465
6508
|
# Lambda permission for API Gateway to invoke with response streaming via alias
|
|
5466
6509
|
resource "aws_lambda_permission" "api_gateway_invoke_streaming" {
|
|
6510
|
+
for_each = local.operations
|
|
6511
|
+
|
|
5467
6512
|
statement_id = "AllowStreamingFromAPIGateway"
|
|
5468
6513
|
action = "lambda:InvokeFunctionWithResponseStream"
|
|
5469
|
-
function_name = aws_lambda_function.api_lambda.function_name
|
|
5470
|
-
qualifier = aws_lambda_alias.live.name
|
|
6514
|
+
function_name = aws_lambda_function.api_lambda[each.key].function_name
|
|
6515
|
+
qualifier = aws_lambda_alias.live[each.key].name
|
|
5471
6516
|
principal = "apigateway.amazonaws.com"
|
|
5472
|
-
source_arn = "\${module.rest_api.api_execution_arn}
|
|
6517
|
+
source_arn = "\${module.rest_api.api_execution_arn}/*/\${local.permission_source_suffix[each.key]}"
|
|
5473
6518
|
|
|
5474
6519
|
depends_on = [module.rest_api, aws_lambda_alias.live]
|
|
5475
6520
|
}
|
|
@@ -5533,83 +6578,83 @@ output "stage_id" {
|
|
|
5533
6578
|
value = aws_api_gateway_stage.api_stage.id
|
|
5534
6579
|
}
|
|
5535
6580
|
|
|
5536
|
-
# Lambda Function Outputs
|
|
5537
|
-
output "
|
|
5538
|
-
description = "
|
|
5539
|
-
value =
|
|
6581
|
+
# Lambda Function Outputs, keyed by operation name
|
|
6582
|
+
output "operations" {
|
|
6583
|
+
description = "Names of the API operations, each with its own Lambda function"
|
|
6584
|
+
value = keys(local.operations)
|
|
5540
6585
|
}
|
|
5541
6586
|
|
|
5542
|
-
output "
|
|
5543
|
-
description = "
|
|
5544
|
-
value = aws_lambda_function.api_lambda.
|
|
6587
|
+
output "lambda_function_names" {
|
|
6588
|
+
description = "Name of each operation's Lambda function, keyed by operation name"
|
|
6589
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.function_name }
|
|
5545
6590
|
}
|
|
5546
6591
|
|
|
5547
|
-
output "
|
|
5548
|
-
description = "
|
|
5549
|
-
value = aws_lambda_function.api_lambda.
|
|
6592
|
+
output "lambda_function_arns" {
|
|
6593
|
+
description = "ARN of each operation's Lambda function, keyed by operation name"
|
|
6594
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.arn }
|
|
5550
6595
|
}
|
|
5551
6596
|
|
|
5552
|
-
output "
|
|
5553
|
-
description = "
|
|
5554
|
-
value = aws_lambda_function.api_lambda.
|
|
6597
|
+
output "lambda_invoke_arns" {
|
|
6598
|
+
description = "Invoke ARN of each operation's Lambda function, keyed by operation name"
|
|
6599
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.invoke_arn }
|
|
5555
6600
|
}
|
|
5556
6601
|
|
|
5557
|
-
output "
|
|
5558
|
-
description = "
|
|
5559
|
-
value = aws_lambda_function.api_lambda.
|
|
6602
|
+
output "lambda_qualified_arns" {
|
|
6603
|
+
description = "Qualified ARN of each operation's Lambda function, keyed by operation name"
|
|
6604
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.qualified_arn }
|
|
5560
6605
|
}
|
|
5561
6606
|
|
|
5562
|
-
output "
|
|
5563
|
-
description = "
|
|
5564
|
-
value = aws_lambda_function.api_lambda.
|
|
6607
|
+
output "lambda_versions" {
|
|
6608
|
+
description = "Version of each operation's Lambda function, keyed by operation name"
|
|
6609
|
+
value = { for op, fn in aws_lambda_function.api_lambda : op => fn.version }
|
|
5565
6610
|
}
|
|
5566
6611
|
|
|
5567
|
-
output "
|
|
5568
|
-
description = "
|
|
5569
|
-
value =
|
|
6612
|
+
output "lambda_source_code_hash" {
|
|
6613
|
+
description = "Base64-encoded SHA256 hash of the Lambda deployment package, shared by every operation"
|
|
6614
|
+
value = data.archive_file.lambda_zip.output_base64sha256
|
|
5570
6615
|
}
|
|
5571
6616
|
|
|
5572
|
-
# IAM Role Outputs
|
|
5573
|
-
output "
|
|
5574
|
-
description = "ARN of
|
|
5575
|
-
value = aws_iam_role.lambda_execution_role.arn
|
|
6617
|
+
# IAM Role Outputs, keyed by operation name
|
|
6618
|
+
output "lambda_execution_role_arns" {
|
|
6619
|
+
description = "ARN of each operation's Lambda execution role, keyed by operation name"
|
|
6620
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.arn }
|
|
5576
6621
|
}
|
|
5577
6622
|
|
|
5578
|
-
output "
|
|
5579
|
-
description = "Name of
|
|
5580
|
-
value = aws_iam_role.lambda_execution_role.name
|
|
6623
|
+
output "lambda_execution_role_names" {
|
|
6624
|
+
description = "Name of each operation's Lambda execution role, keyed by operation name. Attach additional policies to a specific operation's role by name."
|
|
6625
|
+
value = { for op, role in aws_iam_role.lambda_execution_role : op => role.name }
|
|
5581
6626
|
}
|
|
5582
6627
|
|
|
5583
6628
|
output "security_group_id" {
|
|
5584
|
-
description = "Security group ID
|
|
6629
|
+
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."
|
|
5585
6630
|
value = var.enable_vpc ? aws_security_group.api_lambda[0].id : null
|
|
5586
6631
|
}
|
|
5587
6632
|
|
|
5588
|
-
# Integration Outputs
|
|
5589
|
-
output "
|
|
5590
|
-
description = "ID of
|
|
5591
|
-
value = aws_api_gateway_integration.lambda_integration.id
|
|
6633
|
+
# Integration Outputs, keyed by operation name
|
|
6634
|
+
output "integration_ids" {
|
|
6635
|
+
description = "ID of each operation's Lambda integration, keyed by operation name"
|
|
6636
|
+
value = { for op, i in aws_api_gateway_integration.lambda_integration : op => i.id }
|
|
5592
6637
|
}
|
|
5593
6638
|
|
|
5594
|
-
output "
|
|
5595
|
-
description = "ID of
|
|
5596
|
-
value =
|
|
6639
|
+
output "method_ids" {
|
|
6640
|
+
description = "ID of each operation's API Gateway method, keyed by operation name"
|
|
6641
|
+
value = { for op, m in aws_api_gateway_method.operation_methods : op => m.id }
|
|
5597
6642
|
}
|
|
5598
6643
|
|
|
5599
|
-
output "
|
|
5600
|
-
description = "ID of
|
|
5601
|
-
value =
|
|
6644
|
+
output "resource_ids" {
|
|
6645
|
+
description = "ID of each API Gateway resource, keyed by its path"
|
|
6646
|
+
value = local.resource_ids
|
|
5602
6647
|
}
|
|
5603
6648
|
|
|
5604
|
-
# CloudWatch Log Groups
|
|
5605
|
-
output "
|
|
5606
|
-
description = "Name of
|
|
5607
|
-
value = aws_cloudwatch_log_group.lambda_logs.name
|
|
6649
|
+
# CloudWatch Log Groups, keyed by operation name
|
|
6650
|
+
output "lambda_log_group_names" {
|
|
6651
|
+
description = "Name of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
6652
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.name }
|
|
5608
6653
|
}
|
|
5609
6654
|
|
|
5610
|
-
output "
|
|
5611
|
-
description = "ARN of
|
|
5612
|
-
value = aws_cloudwatch_log_group.lambda_logs.arn
|
|
6655
|
+
output "lambda_log_group_arns" {
|
|
6656
|
+
description = "ARN of each operation's Lambda CloudWatch log group, keyed by operation name"
|
|
6657
|
+
value = { for op, lg in aws_cloudwatch_log_group.lambda_logs : op => lg.arn }
|
|
5613
6658
|
}
|
|
5614
6659
|
",
|
|
5615
6660
|
}
|