@aws/nx-plugin-mcp 1.0.0-rc.84 → 1.0.0-rc.86
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/bin/aws-nx-mcp.js +7 -0
- package/docs/guides/fastapi.mdx +4 -0
- package/docs/guides/py-rdb.mdx +4 -0
- package/docs/guides/terraform-project.mdx +66 -3
- package/docs/guides/trpc.mdx +4 -0
- package/docs/guides/ts-rdb.mdx +4 -0
- package/docs/guides/ts-smithy-api.mdx +4 -0
- package/docs/snippets/api/type-safe-api-integrations.mdx +184 -358
- package/docs/snippets/rdb/admin-credentials.mdx +42 -0
- package/docs/snippets/rdb/architecture.mdx +1 -0
- package/generators.json +7 -0
- package/package.json +1 -1
- package/src/open-api/json-metadata/schema.json +20 -0
package/bin/aws-nx-mcp.js
CHANGED
|
@@ -54266,6 +54266,13 @@ var generators$1 = {
|
|
|
54266
54266
|
"metric": "g19",
|
|
54267
54267
|
"hidden": true
|
|
54268
54268
|
},
|
|
54269
|
+
"open-api#json-metadata": {
|
|
54270
|
+
"factory": "./src/open-api/json-metadata/generator",
|
|
54271
|
+
"schema": "./src/open-api/json-metadata/schema.json",
|
|
54272
|
+
"description": "Generate a JSON metadata file describing an OpenAPI specification's operations",
|
|
54273
|
+
"metric": "g72",
|
|
54274
|
+
"hidden": true
|
|
54275
|
+
},
|
|
54269
54276
|
"preset": {
|
|
54270
54277
|
"factory": "./src/preset/generator",
|
|
54271
54278
|
"schema": "./src/preset/schema.json",
|
package/docs/guides/fastapi.mdx
CHANGED
|
@@ -491,6 +491,8 @@ The `UserIdentity` construct can be generated using the <Link path="/guides/reac
|
|
|
491
491
|
<OptionFilter when={{ auth: 'custom' }} description="Custom Lambda Authorizer CDK usage">
|
|
492
492
|
:::caution[Custom Lambda Authorizer]
|
|
493
493
|
When using `Custom` auth, the construct creates a Lambda Authorizer internally from the generated `authorizer.py` file, which **denies all requests by default**. You must implement your authorization logic in that file before your API will accept any traffic.
|
|
494
|
+
|
|
495
|
+
Authorizer results are cached for 5 minutes, keyed on the `Authorization` header, so a revoked token may be accepted until its cached result expires. Reduce the cache duration in the generated infrastructure if you need faster revocation.
|
|
494
496
|
:::
|
|
495
497
|
</OptionFilter>
|
|
496
498
|
</Fragment>
|
|
@@ -604,6 +606,8 @@ module "my_api" {
|
|
|
604
606
|
<OptionFilter when={{ auth: 'custom' }} description="Custom Lambda Authorizer usage with Terraform">
|
|
605
607
|
:::caution[Custom Lambda Authorizer]
|
|
606
608
|
When using `Custom` auth, your API is protected by a Lambda Authorizer that **denies all requests by default**. You must implement your authorization logic in the generated `authorizer.py` file before your API will accept any traffic.
|
|
609
|
+
|
|
610
|
+
Authorizer results are cached for 5 minutes, keyed on the `Authorization` header, so a revoked token may be accepted until its cached result expires. Reduce the cache duration in the generated infrastructure if you need faster revocation.
|
|
607
611
|
:::
|
|
608
612
|
</OptionFilter>
|
|
609
613
|
</Fragment>
|
package/docs/guides/py-rdb.mdx
CHANGED
|
@@ -231,6 +231,10 @@ When using RDS Proxy, you do not need to configure the RDS CA bundle in the runt
|
|
|
231
231
|
|
|
232
232
|
<Snippet name="rdb/encryption-key-rotation" />
|
|
233
233
|
|
|
234
|
+
### Admin Credentials
|
|
235
|
+
|
|
236
|
+
<Snippet name="rdb/admin-credentials" />
|
|
237
|
+
|
|
234
238
|
## Connections
|
|
235
239
|
|
|
236
240
|
Use the <Link path="guides/connection">`connection`</Link> generator to integrate this project with others in your workspace. The following connections involve this project:
|
|
@@ -48,10 +48,12 @@ For application projects (`--type=application`), the generator creates a complet
|
|
|
48
48
|
- main.tf S3 bucket and policies for state storage
|
|
49
49
|
- providers.tf AWS provider configuration
|
|
50
50
|
- variables.tf Bootstrap variable definitions
|
|
51
|
-
- scripts Node helpers run by the nx `bootstrap` and `init` targets
|
|
51
|
+
- scripts Node helpers run by the nx `bootstrap`, `bootstrap-destroy` and `init` targets
|
|
52
52
|
- aws-config.ts Resolves account + region via the AWS SDK credential chain
|
|
53
53
|
- bootstrap.ts Pulls/pushes the bootstrap tfstate and runs `terraform apply`
|
|
54
|
+
- bootstrap-destroy.ts Empties the state bucket and runs `terraform destroy`
|
|
54
55
|
- init.ts Runs `terraform init` with the S3 backend config
|
|
56
|
+
- checkov.yml Checkov configuration, including the checks to skip
|
|
55
57
|
- project.json Project configuration and build targets
|
|
56
58
|
|
|
57
59
|
</FileTree>
|
|
@@ -71,6 +73,7 @@ For library projects (`--type=library`), the generator creates a simpler structu
|
|
|
71
73
|
|
|
72
74
|
- src
|
|
73
75
|
- main.tf Main Terraform module file
|
|
76
|
+
- checkov.yml Checkov configuration, including the checks to skip
|
|
74
77
|
- project.json Project configuration and build targets
|
|
75
78
|
|
|
76
79
|
</FileTree>
|
|
@@ -250,12 +253,66 @@ Format your Terraform code using the `fmt` target:
|
|
|
250
253
|
|
|
251
254
|
#### Security Testing
|
|
252
255
|
|
|
253
|
-
Run security checks on your infrastructure using Checkov with the `
|
|
256
|
+
Run security checks on your infrastructure using Checkov with the `checkov` target:
|
|
254
257
|
|
|
255
|
-
<NxCommands commands={['
|
|
258
|
+
<NxCommands commands={['checkov tf-infra']} />
|
|
256
259
|
|
|
257
260
|
You will find your security test results in the root `dist` folder, under `dist/packages/<my-terraform-project>/checkov`.
|
|
258
261
|
|
|
262
|
+
Checks are configured in the project's `checkov.yml`. Add a check id to `skip-check` to suppress it across the whole project:
|
|
263
|
+
|
|
264
|
+
```yaml title="checkov.yml"
|
|
265
|
+
skip-check:
|
|
266
|
+
- CKV_AWS_115 # Concurrent execution limit
|
|
267
|
+
- CKV_AWS_116 # Dead Letter Queue
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
To suppress a check for a single resource instead, add a `#checkov:skip=<id>:<reason>` comment inside the resource block:
|
|
271
|
+
|
|
272
|
+
```hcl
|
|
273
|
+
resource "aws_s3_bucket" "example" {
|
|
274
|
+
#checkov:skip=CKV_AWS_18:Access logging not required for this bucket
|
|
275
|
+
bucket = "example"
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Running Terraform Tests
|
|
280
|
+
|
|
281
|
+
The `test` target runs [Terraform's native test framework](https://developer.hashicorp.com/terraform/language/tests) over any `.tftest.hcl` files in your project:
|
|
282
|
+
|
|
283
|
+
<NxCommands commands={['test tf-infra']} />
|
|
284
|
+
|
|
285
|
+
A project with no test files is a no-op success, so you can add tests when you need them. `build` runs this target, so your tests run as part of a normal build.
|
|
286
|
+
|
|
287
|
+
Each `run` block evaluates your configuration. Use `command = plan` to check what Terraform _would_ do (this expands the whole module graph, so it catches plan-time errors that `validate` cannot), or `command = apply` to create real resources and assert on their outputs. Declaring `mock_provider` means no API calls are made and no AWS credentials are needed, which keeps `plan` tests fast and safe to run in CI:
|
|
288
|
+
|
|
289
|
+
```hcl title="src/main.tftest.hcl"
|
|
290
|
+
mock_provider "aws" {
|
|
291
|
+
mock_data "aws_caller_identity" {
|
|
292
|
+
defaults = { account_id = "123456789012" }
|
|
293
|
+
}
|
|
294
|
+
mock_data "aws_region" {
|
|
295
|
+
defaults = { region = "us-east-1" }
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
variables {
|
|
300
|
+
aws_region = "us-east-1"
|
|
301
|
+
environment = "dev"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
run "plan_is_valid" {
|
|
305
|
+
command = plan
|
|
306
|
+
|
|
307
|
+
assert {
|
|
308
|
+
condition = data.aws_caller_identity.current.account_id == "123456789012"
|
|
309
|
+
error_message = "Unexpected account id"
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Set every variable your configuration requires in the `variables` block, otherwise the run fails with "has a required variable ... with no set value".
|
|
315
|
+
|
|
259
316
|
<OptionFilter when={{ type: 'application' }} description="Application-only targets: plan / apply / destroy / etc.">
|
|
260
317
|
### Application-Only Targets
|
|
261
318
|
|
|
@@ -310,6 +367,12 @@ This will permanently delete all resources managed by this Terraform configurati
|
|
|
310
367
|
To clean up the bootstrap resources (S3 bucket for state storage):
|
|
311
368
|
|
|
312
369
|
<NxCommands commands={['bootstrap-destroy tf-infra']} />
|
|
370
|
+
|
|
371
|
+
This empties the state bucket before destroying it, and resolves the region from the AWS SDK credential chain, so it runs unattended in CI.
|
|
372
|
+
|
|
373
|
+
:::caution[Destroy your infrastructure first]
|
|
374
|
+
The state bucket holds the state for every environment, so run `destroy` for each environment before this. Once the bucket is gone, Terraform no longer knows about the resources it created.
|
|
375
|
+
:::
|
|
313
376
|
</OptionFilter>
|
|
314
377
|
|
|
315
378
|
## More Information
|
package/docs/guides/trpc.mdx
CHANGED
|
@@ -573,6 +573,8 @@ export class ExampleStack extends Stack {
|
|
|
573
573
|
|
|
574
574
|
:::caution[Custom Lambda Authorizer]
|
|
575
575
|
When using `Custom` auth, the construct creates a Lambda Authorizer internally from the generated `src/authorizer.ts` file, which **denies all requests by default**. You must implement your authorization logic in that file before your API will accept any traffic.
|
|
576
|
+
|
|
577
|
+
Authorizer results are cached for 5 minutes, keyed on the `Authorization` header, so a revoked token may be accepted until its cached result expires. Reduce the cache duration in the generated infrastructure if you need faster revocation.
|
|
576
578
|
:::
|
|
577
579
|
</OptionFilter>
|
|
578
580
|
|
|
@@ -718,6 +720,8 @@ module "my_api" {
|
|
|
718
720
|
<OptionFilter when={{ auth: 'custom' }} description="Custom Lambda Authorizer usage with Terraform">
|
|
719
721
|
:::caution[Custom Lambda Authorizer]
|
|
720
722
|
When using `Custom` auth, your API is protected by a Lambda Authorizer that **denies all requests by default**. You must implement your authorization logic in the generated `src/authorizer.ts` file before your API will accept any traffic.
|
|
723
|
+
|
|
724
|
+
Authorizer results are cached for 5 minutes, keyed on the `Authorization` header, so a revoked token may be accepted until its cached result expires. Reduce the cache duration in the generated infrastructure if you need faster revocation.
|
|
721
725
|
:::
|
|
722
726
|
</OptionFilter>
|
|
723
727
|
</Fragment>
|
package/docs/guides/ts-rdb.mdx
CHANGED
|
@@ -311,6 +311,10 @@ For more details, see the AWS Lambda [SSL/TLS requirements for Amazon RDS connec
|
|
|
311
311
|
|
|
312
312
|
<Snippet name="rdb/encryption-key-rotation" />
|
|
313
313
|
|
|
314
|
+
### Admin Credentials
|
|
315
|
+
|
|
316
|
+
<Snippet name="rdb/admin-credentials" />
|
|
317
|
+
|
|
314
318
|
## Limitations
|
|
315
319
|
|
|
316
320
|
<OptionFilter when={{ engine: 'mysql' }}>
|
|
@@ -697,6 +697,8 @@ The `UserIdentity` construct can be generated using the <Link path="/guides/reac
|
|
|
697
697
|
<OptionFilter when={{ auth: 'custom' }} description="Custom Lambda Authorizer CDK usage">
|
|
698
698
|
:::caution[Custom Lambda Authorizer]
|
|
699
699
|
When using `Custom` auth, the construct creates a Lambda Authorizer internally from the generated `src/authorizer.ts` file, which **denies all requests by default**. You must implement your authorization logic in that file before your API will accept any traffic.
|
|
700
|
+
|
|
701
|
+
Authorizer results are cached for 5 minutes, keyed on the `Authorization` header, so a revoked token may be accepted until its cached result expires. Reduce the cache duration in the generated infrastructure if you need faster revocation.
|
|
700
702
|
:::
|
|
701
703
|
</OptionFilter>
|
|
702
704
|
</Fragment>
|
|
@@ -768,6 +770,8 @@ module "my_api" {
|
|
|
768
770
|
<OptionFilter when={{ auth: 'custom' }} description="Custom Lambda Authorizer usage with Terraform">
|
|
769
771
|
:::caution[Custom Lambda Authorizer]
|
|
770
772
|
When using `Custom` auth, your API is protected by a Lambda Authorizer that **denies all requests by default**. You must implement your authorization logic in the generated `src/authorizer.ts` file before your API will accept any traffic.
|
|
773
|
+
|
|
774
|
+
Authorizer results are cached for 5 minutes, keyed on the `Authorization` header, so a revoked token may be accepted until its cached result expires. Reduce the cache duration in the generated infrastructure if you need faster revocation.
|
|
771
775
|
:::
|
|
772
776
|
</OptionFilter>
|
|
773
777
|
|
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Type-Safe API Integrations
|
|
3
3
|
---
|
|
4
|
-
import {
|
|
4
|
+
import { Steps } from '@astrojs/starlight/components';
|
|
5
5
|
import Infrastructure from '@components/infrastructure.astro';
|
|
6
|
+
import Drawer from '@components/drawer.astro';
|
|
7
|
+
import OptionFilter from '@components/option-filter.astro';
|
|
8
|
+
import RunGenerator from '@components/run-generator.astro';
|
|
6
9
|
|
|
7
10
|
The REST/HTTP API CDK constructs are configured to provide a type-safe interface for defining integrations for each of your operations.
|
|
8
11
|
|
|
9
|
-
<Infrastructure>
|
|
10
|
-
<Fragment slot="cdk">
|
|
11
|
-
The CDK constructs provide full type-safe integration support as described below.
|
|
12
|
-
</Fragment>
|
|
13
|
-
<Fragment slot="terraform">
|
|
14
|
-
:::note[Router Pattern]
|
|
15
|
-
Terraform modules use the "router pattern" with a single Lambda function serving all operations. Type-safe integrations are not supported - the module creates one Lambda function that handles all API requests.
|
|
16
|
-
|
|
17
|
-
For explicit per-operation integrations with Terraform, you would need to manually create individual Lambda functions and API Gateway routes. See the [Explicit Integrations](#explicit-integrations) section for examples.
|
|
18
|
-
:::
|
|
19
|
-
</Fragment>
|
|
20
|
-
</Infrastructure>
|
|
21
|
-
|
|
22
12
|
#### Default Integrations
|
|
23
13
|
|
|
24
14
|
<Infrastructure>
|
|
@@ -32,7 +22,7 @@ new MyApi(this, 'MyApi', {
|
|
|
32
22
|
```
|
|
33
23
|
</Fragment>
|
|
34
24
|
<Fragment slot="terraform">
|
|
35
|
-
|
|
25
|
+
The generated module already defines the default integrations for the pattern the API was generated with, so no additional configuration is needed:
|
|
36
26
|
|
|
37
27
|
```hcl
|
|
38
28
|
module "my_api" {
|
|
@@ -40,11 +30,11 @@ module "my_api" {
|
|
|
40
30
|
|
|
41
31
|
asset_bucket_name = module.asset_bucket.bucket_name
|
|
42
32
|
|
|
43
|
-
# The module automatically creates a single Lambda function
|
|
44
|
-
# that handles all API operations
|
|
45
33
|
tags = local.common_tags
|
|
46
34
|
}
|
|
47
35
|
```
|
|
36
|
+
|
|
37
|
+
With the default `isolated` pattern, this creates one Lambda function per operation.
|
|
48
38
|
</Fragment>
|
|
49
39
|
</Infrastructure>
|
|
50
40
|
|
|
@@ -78,13 +68,13 @@ api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');
|
|
|
78
68
|
```
|
|
79
69
|
</Fragment>
|
|
80
70
|
<Fragment slot="terraform">
|
|
81
|
-
With
|
|
71
|
+
With the `isolated` pattern, the module's outputs are maps keyed by operation name, so you can reach a single operation's resources. For example, to grant one operation's Lambda function extra permissions:
|
|
82
72
|
|
|
83
73
|
```hcl
|
|
84
|
-
# Grant additional permissions to the
|
|
85
|
-
resource "aws_iam_role_policy" "
|
|
86
|
-
name = "additional-
|
|
87
|
-
role = module.my_api.
|
|
74
|
+
# Grant additional permissions to just the sayHello operation's function
|
|
75
|
+
resource "aws_iam_role_policy" "say_hello_permissions" {
|
|
76
|
+
name = "say-hello-additional-permissions"
|
|
77
|
+
role = module.my_api.lambda_execution_role_names["sayHello"]
|
|
88
78
|
|
|
89
79
|
policy = jsonencode({
|
|
90
80
|
Version = "2012-10-17"
|
|
@@ -101,6 +91,48 @@ resource "aws_iam_role_policy" "additional_permissions" {
|
|
|
101
91
|
})
|
|
102
92
|
}
|
|
103
93
|
```
|
|
94
|
+
|
|
95
|
+
To grant the same permissions to every operation, iterate the `operations` output:
|
|
96
|
+
|
|
97
|
+
```hcl
|
|
98
|
+
resource "aws_iam_role_policy" "additional_permissions" {
|
|
99
|
+
for_each = toset(module.my_api.operations)
|
|
100
|
+
|
|
101
|
+
name = "additional-api-permissions"
|
|
102
|
+
role = module.my_api.lambda_execution_role_names[each.key]
|
|
103
|
+
|
|
104
|
+
policy = jsonencode({
|
|
105
|
+
Version = "2012-10-17"
|
|
106
|
+
Statement = [
|
|
107
|
+
{
|
|
108
|
+
Effect = "Allow"
|
|
109
|
+
Action = ["s3:GetObject"]
|
|
110
|
+
Resource = "arn:aws:s3:::my-bucket/*"
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The module also exposes `lambda_function_names`, `lambda_function_arns`, `lambda_invoke_arns`, `integration_ids` and `lambda_log_group_names` as maps keyed by operation name. With the `shared` pattern the equivalent singular outputs (`lambda_execution_role_name`, `lambda_function_name`, …) are exposed instead, since there is only one function.
|
|
118
|
+
|
|
119
|
+
Permissions every operation needs are better passed to the module, which applies them to each function's role:
|
|
120
|
+
|
|
121
|
+
```hcl
|
|
122
|
+
module "my_api" {
|
|
123
|
+
source = "../../common/terraform/src/app/apis/my-api"
|
|
124
|
+
|
|
125
|
+
asset_bucket_name = module.asset_bucket.bucket_name
|
|
126
|
+
|
|
127
|
+
additional_iam_policy_statements = [
|
|
128
|
+
{
|
|
129
|
+
Effect = "Allow"
|
|
130
|
+
Action = ["s3:GetObject"]
|
|
131
|
+
Resource = ["arn:aws:s3:::my-bucket/*"]
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
```
|
|
104
136
|
</Fragment>
|
|
105
137
|
</Infrastructure>
|
|
106
138
|
|
|
@@ -125,7 +157,7 @@ new MyApi(this, 'MyApi', {
|
|
|
125
157
|
|
|
126
158
|
</Fragment>
|
|
127
159
|
<Fragment slot="terraform">
|
|
128
|
-
VPC configuration is already supported by the generated module — set `enable_vpc` along with `vpc_id` and `subnet_ids`, and the module deploys
|
|
160
|
+
VPC configuration is already supported by the generated module — set `enable_vpc` along with `vpc_id` and `subnet_ids`, and the module deploys every Lambda function into your VPC behind a shared security group it creates for you:
|
|
129
161
|
|
|
130
162
|
```hcl
|
|
131
163
|
module "my_api" {
|
|
@@ -142,7 +174,7 @@ module "my_api" {
|
|
|
142
174
|
}
|
|
143
175
|
```
|
|
144
176
|
|
|
145
|
-
For options the module does not expose, edit the `aws_lambda_function` resource in the generated Terraform module directly.
|
|
177
|
+
For options the module does not expose, edit the `aws_lambda_function` resource in the generated Terraform module directly. With the `isolated` pattern that single resource is declared `for_each = local.operations`, so an edit there applies to every operation.
|
|
146
178
|
</Fragment>
|
|
147
179
|
</Infrastructure>
|
|
148
180
|
|
|
@@ -173,7 +205,21 @@ You will encounter a type error if the same operation is targeted by both `withO
|
|
|
173
205
|
|
|
174
206
|
</Fragment>
|
|
175
207
|
<Fragment slot="terraform">
|
|
176
|
-
|
|
208
|
+
With the `isolated` pattern the Lambda function resource is already per-operation, so options can be varied by operation name. For example, to give one operation a longer timeout, edit the `aws_lambda_function` resource in the generated module:
|
|
209
|
+
|
|
210
|
+
```hcl
|
|
211
|
+
# packages/common/terraform/src/app/apis/my-api/my-api.tf
|
|
212
|
+
resource "aws_lambda_function" "api_lambda" {
|
|
213
|
+
for_each = local.operations
|
|
214
|
+
|
|
215
|
+
# Default to 30 seconds, but allow longer for specific operations
|
|
216
|
+
timeout = lookup({
|
|
217
|
+
sayHello = 60
|
|
218
|
+
}, each.key, 30)
|
|
219
|
+
|
|
220
|
+
# ... rest of configuration
|
|
221
|
+
}
|
|
222
|
+
```
|
|
177
223
|
</Fragment>
|
|
178
224
|
</Infrastructure>
|
|
179
225
|
|
|
@@ -244,11 +290,36 @@ api.integrations.getFile.bucket.grantRead(...);
|
|
|
244
290
|
```
|
|
245
291
|
</Fragment>
|
|
246
292
|
<Fragment slot="terraform">
|
|
247
|
-
|
|
248
|
-
Overriding specific integrations is not supported with Terraform modules since they use the router pattern. All operations are handled by the single Lambda function.
|
|
293
|
+
To point a specific operation at a different integration type, exclude it from the default `for_each` and declare its integration separately. For example, to serve `getDocumentation` from an external website:
|
|
249
294
|
|
|
250
|
-
|
|
251
|
-
|
|
295
|
+
```hcl
|
|
296
|
+
# packages/common/terraform/src/app/apis/my-api/my-api.tf
|
|
297
|
+
|
|
298
|
+
# Exclude the overridden operation from the default per-operation resources
|
|
299
|
+
locals {
|
|
300
|
+
overridden_operations = ["getDocumentation"]
|
|
301
|
+
default_operations = {
|
|
302
|
+
for op, details in local.operations : op => details
|
|
303
|
+
if !contains(local.overridden_operations, op)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# Then use local.default_operations in place of local.operations for the
|
|
308
|
+
# aws_lambda_function, aws_iam_role, aws_apigatewayv2_integration and
|
|
309
|
+
# aws_lambda_permission resources, and add the override:
|
|
310
|
+
resource "aws_apigatewayv2_integration" "get_documentation" {
|
|
311
|
+
api_id = module.http_api.api_id
|
|
312
|
+
integration_type = "HTTP_PROXY"
|
|
313
|
+
integration_uri = "https://example.com/documentation"
|
|
314
|
+
integration_method = "GET"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
resource "aws_apigatewayv2_route" "get_documentation" {
|
|
318
|
+
api_id = module.http_api.api_id
|
|
319
|
+
route_key = local.route_key["getDocumentation"]
|
|
320
|
+
target = "integrations/${aws_apigatewayv2_integration.get_documentation.id}"
|
|
321
|
+
}
|
|
322
|
+
```
|
|
252
323
|
</Fragment>
|
|
253
324
|
</Infrastructure>
|
|
254
325
|
|
|
@@ -274,11 +345,20 @@ new MyApi(this, 'MyApi', {
|
|
|
274
345
|
```
|
|
275
346
|
</Fragment>
|
|
276
347
|
<Fragment slot="terraform">
|
|
277
|
-
|
|
278
|
-
Per-operation authorizer overrides are not supported with Terraform modules. The entire API uses the authentication method specified when generating the API (IAM, Cognito, or Custom).
|
|
348
|
+
Authorization is set on the route (HTTP API) or method (REST API) for each operation, so it can be varied by operation name. For example, to leave one operation unauthenticated on an HTTP API:
|
|
279
349
|
|
|
280
|
-
|
|
281
|
-
|
|
350
|
+
```hcl
|
|
351
|
+
# packages/common/terraform/src/app/apis/my-api/my-api.tf
|
|
352
|
+
resource "aws_apigatewayv2_route" "operation_routes" {
|
|
353
|
+
for_each = local.operations
|
|
354
|
+
|
|
355
|
+
# ... rest of configuration
|
|
356
|
+
|
|
357
|
+
authorization_type = each.key == "getDocumentation" ? "NONE" : "AWS_IAM"
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
For an IAM-authenticated REST API, also add a resource policy statement allowing unauthenticated access to that operation's path.
|
|
282
362
|
</Fragment>
|
|
283
363
|
</Infrastructure>
|
|
284
364
|
|
|
@@ -302,331 +382,26 @@ new MyApi(this, 'MyApi', {
|
|
|
302
382
|
```
|
|
303
383
|
</Fragment>
|
|
304
384
|
<Fragment slot="terraform">
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
Edit `packages/common/terraform/src/app/apis/my-api/my-api.tf`:
|
|
308
|
-
|
|
309
|
-
1. **Remove the default proxy routes** (e.g., `resource "aws_apigatewayv2_route" "proxy_routes"`)
|
|
310
|
-
2. **Replace the single Lambda function** with individual functions for each operation
|
|
311
|
-
3. **Create specific integrations and routes** for each operation, reusing the same ZIP bundle:
|
|
312
|
-
|
|
313
|
-
<Tabs syncKey="http-rest">
|
|
314
|
-
<TabItem label="HTTP API" _filter={{ infra: 'http-lambda' }}>
|
|
315
|
-
|
|
316
|
-
```diff
|
|
317
|
-
# packages/common/terraform/src/app/apis/my-api/my-api.tf
|
|
318
|
-
|
|
319
|
-
# Remove the default single Lambda function
|
|
320
|
-
- resource "aws_lambda_function" "api_lambda" {
|
|
321
|
-
- s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
322
|
-
- s3_key = aws_s3_object.lambda_zip.key
|
|
323
|
-
- s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
324
|
-
- function_name = "MyApiHandler"
|
|
325
|
-
- role = aws_iam_role.lambda_execution_role.arn
|
|
326
|
-
- handler = "index.handler"
|
|
327
|
-
- runtime = "nodejs22.x"
|
|
328
|
-
- timeout = 30
|
|
329
|
-
- # ... rest of configuration
|
|
330
|
-
- }
|
|
331
|
-
|
|
332
|
-
# Remove the default proxy integration
|
|
333
|
-
- resource "aws_apigatewayv2_integration" "lambda_integration" {
|
|
334
|
-
- api_id = module.http_api.api_id
|
|
335
|
-
- integration_type = "AWS_PROXY"
|
|
336
|
-
- integration_uri = aws_lambda_function.api_lambda.invoke_arn
|
|
337
|
-
- # ... rest of configuration
|
|
338
|
-
- }
|
|
339
|
-
|
|
340
|
-
# Remove the default proxy routes
|
|
341
|
-
- resource "aws_apigatewayv2_route" "proxy_routes" {
|
|
342
|
-
- for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"])
|
|
343
|
-
- api_id = module.http_api.api_id
|
|
344
|
-
- route_key = "${each.key} /{proxy+}"
|
|
345
|
-
- target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}"
|
|
346
|
-
- # ... rest of configuration
|
|
347
|
-
- }
|
|
348
|
-
|
|
349
|
-
# Add individual Lambda functions for each operation using the same bundle
|
|
350
|
-
+ resource "aws_lambda_function" "say_hello_handler" {
|
|
351
|
-
+ s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
352
|
-
+ s3_key = aws_s3_object.lambda_zip.key
|
|
353
|
-
+ s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
354
|
-
+ function_name = "MyApi-SayHello"
|
|
355
|
-
+ role = aws_iam_role.lambda_execution_role.arn
|
|
356
|
-
+ handler = "sayHello.handler" # Specific handler for this operation
|
|
357
|
-
+ runtime = "nodejs22.x"
|
|
358
|
-
+ timeout = 30
|
|
359
|
-
+ source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
360
|
-
+
|
|
361
|
-
+ tracing_config {
|
|
362
|
-
+ mode = "Active"
|
|
363
|
-
+ }
|
|
364
|
-
+
|
|
365
|
-
+ environment {
|
|
366
|
-
+ variables = var.env
|
|
367
|
-
+ }
|
|
368
|
-
+
|
|
369
|
-
+ tags = var.tags
|
|
370
|
-
+ }
|
|
371
|
-
|
|
372
|
-
+ resource "aws_lambda_function" "get_documentation_handler" {
|
|
373
|
-
+ s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
374
|
-
+ s3_key = aws_s3_object.lambda_zip.key
|
|
375
|
-
+ s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
376
|
-
+ function_name = "MyApi-GetDocumentation"
|
|
377
|
-
+ role = aws_iam_role.lambda_execution_role.arn
|
|
378
|
-
+ handler = "getDocumentation.handler" # Specific handler for this operation
|
|
379
|
-
+ runtime = "nodejs22.x"
|
|
380
|
-
+ timeout = 30
|
|
381
|
-
+ source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
382
|
-
+
|
|
383
|
-
+ tracing_config {
|
|
384
|
-
+ mode = "Active"
|
|
385
|
-
+ }
|
|
386
|
-
+
|
|
387
|
-
+ environment {
|
|
388
|
-
+ variables = var.env
|
|
389
|
-
+ }
|
|
390
|
-
+
|
|
391
|
-
+ tags = var.tags
|
|
392
|
-
+ }
|
|
393
|
-
|
|
394
|
-
# Add specific integrations for each operation
|
|
395
|
-
+ resource "aws_apigatewayv2_integration" "say_hello_integration" {
|
|
396
|
-
+ api_id = module.http_api.api_id
|
|
397
|
-
+ integration_type = "AWS_PROXY"
|
|
398
|
-
+ integration_uri = aws_lambda_function.say_hello_handler.invoke_arn
|
|
399
|
-
+ payload_format_version = "2.0"
|
|
400
|
-
+ timeout_milliseconds = 30000
|
|
401
|
-
+ }
|
|
402
|
-
|
|
403
|
-
+ resource "aws_apigatewayv2_integration" "get_documentation_integration" {
|
|
404
|
-
+ api_id = module.http_api.api_id
|
|
405
|
-
+ integration_type = "HTTP_PROXY"
|
|
406
|
-
+ integration_uri = "https://example.com/documentation"
|
|
407
|
-
+ integration_method = "GET"
|
|
408
|
-
+ }
|
|
409
|
-
|
|
410
|
-
# Add specific routes for each operation
|
|
411
|
-
+ resource "aws_apigatewayv2_route" "say_hello_route" {
|
|
412
|
-
+ api_id = module.http_api.api_id
|
|
413
|
-
+ route_key = "POST /sayHello"
|
|
414
|
-
+ target = "integrations/${aws_apigatewayv2_integration.say_hello_integration.id}"
|
|
415
|
-
+ authorization_type = "AWS_IAM"
|
|
416
|
-
+ }
|
|
417
|
-
|
|
418
|
-
+ resource "aws_apigatewayv2_route" "get_documentation_route" {
|
|
419
|
-
+ api_id = module.http_api.api_id
|
|
420
|
-
+ route_key = "GET /documentation"
|
|
421
|
-
+ target = "integrations/${aws_apigatewayv2_integration.get_documentation_integration.id}"
|
|
422
|
-
+ authorization_type = "NONE"
|
|
423
|
-
+ }
|
|
424
|
-
|
|
425
|
-
# Add Lambda permissions for each function
|
|
426
|
-
+ resource "aws_lambda_permission" "say_hello_permission" {
|
|
427
|
-
+ statement_id = "AllowExecutionFromAPIGateway-SayHello"
|
|
428
|
-
+ action = "lambda:InvokeFunction"
|
|
429
|
-
+ function_name = aws_lambda_function.say_hello_handler.function_name
|
|
430
|
-
+ principal = "apigateway.amazonaws.com"
|
|
431
|
-
+ source_arn = "${module.http_api.api_execution_arn}/*/*"
|
|
432
|
-
+ }
|
|
433
|
-
|
|
434
|
-
+ resource "aws_lambda_permission" "get_documentation_permission" {
|
|
435
|
-
+ statement_id = "AllowExecutionFromAPIGateway-GetDocumentation"
|
|
436
|
-
+ action = "lambda:InvokeFunction"
|
|
437
|
-
+ function_name = aws_lambda_function.get_documentation_handler.function_name
|
|
438
|
-
+ principal = "apigateway.amazonaws.com"
|
|
439
|
-
+ source_arn = "${module.http_api.api_execution_arn}/*/*"
|
|
440
|
-
+ }
|
|
441
|
-
```
|
|
442
|
-
</TabItem>
|
|
443
|
-
<TabItem label="REST API" _filter={{ infra: 'rest-lambda' }}>
|
|
444
|
-
|
|
445
|
-
```diff
|
|
446
|
-
# packages/common/terraform/src/app/apis/my-api/my-api.tf
|
|
447
|
-
|
|
448
|
-
# Remove the default single Lambda function
|
|
449
|
-
- resource "aws_lambda_function" "api_lambda" {
|
|
450
|
-
- s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
451
|
-
- s3_key = aws_s3_object.lambda_zip.key
|
|
452
|
-
- s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
453
|
-
- function_name = "MyApiHandler-${random_string.suffix.result}"
|
|
454
|
-
- role = aws_iam_role.lambda_execution_role.arn
|
|
455
|
-
- handler = "index.handler"
|
|
456
|
-
- runtime = "nodejs22.x"
|
|
457
|
-
- timeout = 30
|
|
458
|
-
- # ... rest of configuration
|
|
459
|
-
- }
|
|
460
|
-
|
|
461
|
-
# Remove the default proxy integration
|
|
462
|
-
- resource "aws_api_gateway_integration" "lambda_integration" {
|
|
463
|
-
- rest_api_id = module.rest_api.api_id
|
|
464
|
-
- resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
465
|
-
- http_method = aws_api_gateway_method.proxy_method.http_method
|
|
466
|
-
- integration_http_method = "POST"
|
|
467
|
-
- type = "AWS_PROXY"
|
|
468
|
-
- uri = aws_lambda_function.api_lambda.invoke_arn
|
|
469
|
-
- # ... rest of configuration
|
|
470
|
-
- }
|
|
471
|
-
|
|
472
|
-
# Remove the default catch-all proxy method
|
|
473
|
-
- resource "aws_api_gateway_method" "proxy_method" {
|
|
474
|
-
- rest_api_id = module.rest_api.api_id
|
|
475
|
-
- resource_id = aws_api_gateway_resource.proxy_resource.id
|
|
476
|
-
- http_method = "ANY"
|
|
477
|
-
- # ... rest of configuration
|
|
478
|
-
- }
|
|
479
|
-
|
|
480
|
-
# Add individual Lambda functions for each operation using the same bundle
|
|
481
|
-
+ resource "aws_lambda_function" "say_hello_handler" {
|
|
482
|
-
+ s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
483
|
-
+ s3_key = aws_s3_object.lambda_zip.key
|
|
484
|
-
+ s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
485
|
-
+ function_name = "MyApi-SayHello"
|
|
486
|
-
+ role = aws_iam_role.lambda_execution_role.arn
|
|
487
|
-
+ handler = "sayHello.handler" # Specific handler for this operation
|
|
488
|
-
+ runtime = "nodejs22.x"
|
|
489
|
-
+ timeout = 30
|
|
490
|
-
+ source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
491
|
-
+
|
|
492
|
-
+ tracing_config {
|
|
493
|
-
+ mode = "Active"
|
|
494
|
-
+ }
|
|
495
|
-
+
|
|
496
|
-
+ environment {
|
|
497
|
-
+ variables = var.env
|
|
498
|
-
+ }
|
|
499
|
-
+
|
|
500
|
-
+ tags = var.tags
|
|
501
|
-
+ }
|
|
502
|
-
|
|
503
|
-
+ resource "aws_lambda_function" "get_documentation_handler" {
|
|
504
|
-
+ s3_bucket = aws_s3_object.lambda_zip.bucket
|
|
505
|
-
+ s3_key = aws_s3_object.lambda_zip.key
|
|
506
|
-
+ s3_object_version = aws_s3_object.lambda_zip.version_id
|
|
507
|
-
+ function_name = "MyApi-GetDocumentation"
|
|
508
|
-
+ role = aws_iam_role.lambda_execution_role.arn
|
|
509
|
-
+ handler = "getDocumentation.handler" # Specific handler for this operation
|
|
510
|
-
+ runtime = "nodejs22.x"
|
|
511
|
-
+ timeout = 30
|
|
512
|
-
+ source_code_hash = data.archive_file.lambda_zip.output_base64sha256
|
|
513
|
-
+
|
|
514
|
-
+ tracing_config {
|
|
515
|
-
+ mode = "Active"
|
|
516
|
-
+ }
|
|
517
|
-
+
|
|
518
|
-
+ environment {
|
|
519
|
-
+ variables = var.env
|
|
520
|
-
+ }
|
|
521
|
-
+
|
|
522
|
-
+ tags = var.tags
|
|
523
|
-
+ }
|
|
524
|
-
|
|
525
|
-
# Add specific resources and methods for each operation
|
|
526
|
-
+ resource "aws_api_gateway_resource" "say_hello_resource" {
|
|
527
|
-
+ rest_api_id = module.rest_api.api_id
|
|
528
|
-
+ parent_id = module.rest_api.api_root_resource_id
|
|
529
|
-
+ path_part = "sayHello"
|
|
530
|
-
+ }
|
|
531
|
-
|
|
532
|
-
+ resource "aws_api_gateway_method" "say_hello_method" {
|
|
533
|
-
+ rest_api_id = module.rest_api.api_id
|
|
534
|
-
+ resource_id = aws_api_gateway_resource.say_hello_resource.id
|
|
535
|
-
+ http_method = "POST"
|
|
536
|
-
+ authorization = "AWS_IAM"
|
|
537
|
-
+ }
|
|
538
|
-
|
|
539
|
-
+ resource "aws_api_gateway_integration" "say_hello_integration" {
|
|
540
|
-
+ rest_api_id = module.rest_api.api_id
|
|
541
|
-
+ resource_id = aws_api_gateway_resource.say_hello_resource.id
|
|
542
|
-
+ http_method = aws_api_gateway_method.say_hello_method.http_method
|
|
543
|
-
+
|
|
544
|
-
+ integration_http_method = "POST"
|
|
545
|
-
+ type = "AWS_PROXY"
|
|
546
|
-
+ uri = aws_lambda_function.say_hello_handler.invoke_arn
|
|
547
|
-
+ }
|
|
548
|
-
|
|
549
|
-
+ resource "aws_api_gateway_resource" "get_documentation_resource" {
|
|
550
|
-
+ rest_api_id = module.rest_api.api_id
|
|
551
|
-
+ parent_id = module.rest_api.api_root_resource_id
|
|
552
|
-
+ path_part = "documentation"
|
|
553
|
-
+ }
|
|
554
|
-
|
|
555
|
-
+ resource "aws_api_gateway_method" "get_documentation_method" {
|
|
556
|
-
+ rest_api_id = module.rest_api.api_id
|
|
557
|
-
+ resource_id = aws_api_gateway_resource.get_documentation_resource.id
|
|
558
|
-
+ http_method = "GET"
|
|
559
|
-
+ authorization = "NONE"
|
|
560
|
-
+ }
|
|
561
|
-
|
|
562
|
-
+ resource "aws_api_gateway_integration" "get_documentation_integration" {
|
|
563
|
-
+ rest_api_id = module.rest_api.api_id
|
|
564
|
-
+ resource_id = aws_api_gateway_resource.get_documentation_resource.id
|
|
565
|
-
+ http_method = aws_api_gateway_method.get_documentation_method.http_method
|
|
566
|
-
+
|
|
567
|
-
+ integration_http_method = "GET"
|
|
568
|
-
+ type = "HTTP"
|
|
569
|
-
+ uri = "https://example.com/documentation"
|
|
570
|
-
+ }
|
|
571
|
-
|
|
572
|
-
# Update deployment to depend on new integrations
|
|
573
|
-
~ resource "aws_api_gateway_deployment" "api_deployment" {
|
|
574
|
-
rest_api_id = module.rest_api.api_id
|
|
575
|
-
|
|
576
|
-
depends_on = [
|
|
577
|
-
- aws_api_gateway_integration.lambda_integration,
|
|
578
|
-
+ aws_api_gateway_integration.say_hello_integration,
|
|
579
|
-
+ aws_api_gateway_integration.get_documentation_integration,
|
|
580
|
-
]
|
|
581
|
-
|
|
582
|
-
lifecycle {
|
|
583
|
-
create_before_destroy = true
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
+ triggers = {
|
|
587
|
-
+ redeployment = sha1(jsonencode([
|
|
588
|
-
+ aws_api_gateway_integration.say_hello_integration,
|
|
589
|
-
+ aws_api_gateway_integration.get_documentation_integration,
|
|
590
|
-
+ ]))
|
|
591
|
-
+ }
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
# Add Lambda permissions for each function
|
|
595
|
-
+ resource "aws_lambda_permission" "say_hello_permission" {
|
|
596
|
-
+ statement_id = "AllowExecutionFromAPIGateway-SayHello"
|
|
597
|
-
+ action = "lambda:InvokeFunction"
|
|
598
|
-
+ function_name = aws_lambda_function.say_hello_handler.function_name
|
|
599
|
-
+ principal = "apigateway.amazonaws.com"
|
|
600
|
-
+ source_arn = "${module.rest_api.api_execution_arn}/*/*"
|
|
601
|
-
+ }
|
|
602
|
-
|
|
603
|
-
+ resource "aws_lambda_permission" "get_documentation_permission" {
|
|
604
|
-
+ statement_id = "AllowExecutionFromAPIGateway-GetDocumentation"
|
|
605
|
-
+ action = "lambda:InvokeFunction"
|
|
606
|
-
+ function_name = aws_lambda_function.get_documentation_handler.function_name
|
|
607
|
-
+ principal = "apigateway.amazonaws.com"
|
|
608
|
-
+ source_arn = "${module.rest_api.api_execution_arn}/*/*"
|
|
609
|
-
+ }
|
|
610
|
-
```
|
|
611
|
-
</TabItem>
|
|
612
|
-
</Tabs>
|
|
385
|
+
Replace the `for_each` used by the `isolated` pattern with explicit instantiations of the Lambda functions, integrations and permissions for each operation.
|
|
613
386
|
|
|
387
|
+
:::caution
|
|
388
|
+
Unlike CDK, there is no type-safety in Terraform which will give you a type error if you add a new operation in your application code but forget to declare it in your infrastructure.
|
|
389
|
+
:::
|
|
614
390
|
</Fragment>
|
|
615
391
|
</Infrastructure>
|
|
616
392
|
|
|
617
393
|
#### Integration Pattern
|
|
618
394
|
|
|
619
|
-
|
|
620
|
-
<Fragment slot="cdk">
|
|
395
|
+
Generated APIs support two integration patterns:
|
|
621
396
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
- `isolated` creates one Lambda function per operation. This is the default for generated APIs.
|
|
397
|
+
- `isolated` creates one Lambda function per operation. This is the default and recommended option for APIs.
|
|
625
398
|
- `shared` creates a single default router Lambda and reuses it for every operation unless you override specific integrations.
|
|
626
399
|
|
|
627
|
-
`isolated` gives you finer-grained permissions and configuration per operation. `shared` reduces
|
|
400
|
+
`isolated` gives you finer-grained permissions and configuration per operation, as well as better separation for logs and traces. `shared` reduces the likelihood of encountering cold-starts for low-usage APIs.
|
|
628
401
|
|
|
629
|
-
|
|
402
|
+
<Infrastructure>
|
|
403
|
+
<Fragment slot="cdk">
|
|
404
|
+
The integration pattern can be changed at any time in CDK by updating your API construct. For example, setting `pattern` to `'shared'` creates a single function instead of one per integration:
|
|
630
405
|
|
|
631
406
|
```ts {6}
|
|
632
407
|
// packages/common/constructs/src/app/apis/my-api.ts
|
|
@@ -643,20 +418,71 @@ export class MyApi<...> extends ... {
|
|
|
643
418
|
```
|
|
644
419
|
</Fragment>
|
|
645
420
|
<Fragment slot="terraform">
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
You can simply instantiate the default module to get the router pattern:
|
|
421
|
+
Unlike CDK, the integration pattern is baked into the generated module. To change the integration pattern:
|
|
649
422
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
423
|
+
<Steps>
|
|
424
|
+
1. Delete the previously generated API module in `packages/common/terraform/src/app/apis`
|
|
425
|
+
2. Re-run the API generator with the other integration pattern:
|
|
426
|
+
<RunGenerator generator="ts#api" requiredParameters={{ integrationPattern: 'shared' }} />
|
|
427
|
+
</Steps>
|
|
654
428
|
|
|
655
|
-
|
|
429
|
+
With the `isolated` pattern, the module reads the operations from a generated file:
|
|
656
430
|
|
|
657
|
-
|
|
658
|
-
|
|
431
|
+
```hcl
|
|
432
|
+
# packages/common/terraform/src/app/apis/my-api/my-api.tf
|
|
433
|
+
locals {
|
|
434
|
+
operations_file = "${path.module}/../../../generated/my-api/operations.json"
|
|
435
|
+
operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}
|
|
659
436
|
}
|
|
660
437
|
```
|
|
438
|
+
|
|
439
|
+
This file is generated from your API, so you do not need to edit this by hand. Adding an operation to your API application code adds the route and lambda function on the next deploy. It is `.gitignore`d by default; remove the entry if you prefer to check it in.
|
|
661
440
|
</Fragment>
|
|
662
441
|
</Infrastructure>
|
|
442
|
+
|
|
443
|
+
<OptionFilter when={{ iac: 'terraform', infra: 'rest-lambda' }} description="Raising the REST API path depth limit in Terraform">
|
|
444
|
+
##### Terraform REST API Path Depth Limit
|
|
445
|
+
|
|
446
|
+
:::caution
|
|
447
|
+
Due to a limitation with Terraform where instances of resources can't reference one another (see [hashicorp/terraform#26697](https://github.com/hashicorp/terraform/issues/26697)), the API Gateway resource tree is built one level at a time rather than recursively, supporting operation paths of up to 16 segments. If an operation's path exceeds this, the plan fails with a message naming the paths that are too deep.
|
|
448
|
+
|
|
449
|
+
<Drawer title="Supporting deeper paths" trigger="Click here for instructions to support deeper paths should your API require it">
|
|
450
|
+
|
|
451
|
+
Edit `packages/common/terraform/src/app/apis/<my-api>/<my-api>.tf`:
|
|
452
|
+
|
|
453
|
+
<Steps>
|
|
454
|
+
1. Increment `max_path_depth` in `locals`:
|
|
455
|
+
|
|
456
|
+
```hcl
|
|
457
|
+
locals {
|
|
458
|
+
max_path_depth = 17
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
2. Copy the last `aws_api_gateway_resource` block, incrementing both the resource name and its parent:
|
|
463
|
+
|
|
464
|
+
```hcl
|
|
465
|
+
resource "aws_api_gateway_resource" "path_depth_16" {
|
|
466
|
+
for_each = local.path_nodes_by_depth[16]
|
|
467
|
+
|
|
468
|
+
rest_api_id = module.rest_api.api_id
|
|
469
|
+
parent_id = aws_api_gateway_resource.path_depth_15[each.value.parent].id
|
|
470
|
+
path_part = each.value.part
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
3. Add the new level to the `resource_ids` local:
|
|
475
|
+
|
|
476
|
+
```hcl
|
|
477
|
+
locals {
|
|
478
|
+
resource_ids = merge(
|
|
479
|
+
...
|
|
480
|
+
{ for path, resource in aws_api_gateway_resource.path_depth_16 : path => resource.id },
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
</Steps>
|
|
485
|
+
|
|
486
|
+
</Drawer>
|
|
487
|
+
:::
|
|
488
|
+
</OptionFilter>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Admin Credentials
|
|
3
|
+
---
|
|
4
|
+
import Infrastructure from '@components/infrastructure.astro';
|
|
5
|
+
|
|
6
|
+
The Aurora admin (master) user's password is generated and rotated by Aurora itself in AWS Secrets Manager, using [RDS-managed master user passwords](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/rds-secrets-manager.html). Your infrastructure code never receives the password, so it cannot leak into a CloudFormation template or Terraform state file. Aurora rotates the secret every 7 days without any rotation function to deploy or maintain.
|
|
7
|
+
|
|
8
|
+
The secret is encrypted with the same customer-managed KMS key as the cluster.
|
|
9
|
+
|
|
10
|
+
Your application never uses these credentials. It connects as a least-privilege database user using IAM authentication — only the migration and create-db-user handlers read the admin secret, and each is granted access to just that secret and its KMS key.
|
|
11
|
+
|
|
12
|
+
<Infrastructure>
|
|
13
|
+
<Fragment slot="cdk">
|
|
14
|
+
|
|
15
|
+
The admin secret is exposed as `secret.secretArn` on the underlying cluster, and `grantSecretRead` grants a consumer read access to it:
|
|
16
|
+
|
|
17
|
+
```ts title="packages/infra/src/stacks/application-stack.ts"
|
|
18
|
+
import { MyDatabase } from '@my-scope/common-constructs';
|
|
19
|
+
|
|
20
|
+
const db = new MyDatabase(this, 'Db', { ... });
|
|
21
|
+
|
|
22
|
+
db.grantSecretRead(myFunction);
|
|
23
|
+
```
|
|
24
|
+
</Fragment>
|
|
25
|
+
<Fragment slot="terraform">
|
|
26
|
+
|
|
27
|
+
The admin secret's ARN is exposed as the `secret_arn` output, alongside the `kms_key_arn` needed to decrypt it:
|
|
28
|
+
|
|
29
|
+
```hcl title="packages/infra/src/main.tf"
|
|
30
|
+
module "my_database" {
|
|
31
|
+
source = "../../common/terraform/src/app/dbs/my-database"
|
|
32
|
+
...
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
output "database_secret_arn" {
|
|
36
|
+
value = module.my_database.secret_arn
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
An RDS-managed secret holds a JSON object with `username` and `password` only — no connection details. Take the host, port and database name from the module's `writer_endpoint`, `cluster_port` and `database_name` outputs.
|
|
41
|
+
</Fragment>
|
|
42
|
+
</Infrastructure>
|
package/generators.json
CHANGED
|
@@ -124,6 +124,13 @@
|
|
|
124
124
|
"metric": "g19",
|
|
125
125
|
"hidden": true
|
|
126
126
|
},
|
|
127
|
+
"open-api#json-metadata": {
|
|
128
|
+
"factory": "./src/open-api/json-metadata/generator",
|
|
129
|
+
"schema": "./src/open-api/json-metadata/schema.json",
|
|
130
|
+
"description": "Generate a JSON metadata file describing an OpenAPI specification's operations",
|
|
131
|
+
"metric": "g72",
|
|
132
|
+
"hidden": true
|
|
133
|
+
},
|
|
127
134
|
"preset": {
|
|
128
135
|
"factory": "./src/preset/generator",
|
|
129
136
|
"schema": "./src/preset/schema.json",
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "OpenApiJsonMetadata",
|
|
4
|
+
"title": "OpenAPI JSON Metadata",
|
|
5
|
+
"description": "Generate a JSON metadata file describing an OpenAPI specification's operations",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"openApiSpecPath": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Path to the OpenAPI specification relative to the monorepo root",
|
|
11
|
+
"x-priority": "important"
|
|
12
|
+
},
|
|
13
|
+
"outputPath": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Path to the directory in which to generate the metadata relative to the monorepo root",
|
|
16
|
+
"x-priority": "important"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"required": ["openApiSpecPath", "outputPath"]
|
|
20
|
+
}
|