@aws/nx-plugin 1.0.0-rc.18 → 1.0.0-rc.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/package.json +1 -1
  2. package/src/py/agent/__snapshots__/generator.spec.ts.snap +15 -6
  3. package/src/py/agent/a2a-connection/__snapshots__/generator.spec.ts.snap +201 -0
  4. package/src/py/agent/a2a-connection/files/agent-connection/app/{__targetAgentSnakeCase___client.py.template → __targetAgentSnakeCase___client_strands.py.template} +7 -5
  5. package/src/py/agent/a2a-connection/generator.js +6 -5
  6. package/src/py/agent/a2a-connection/generator.js.map +1 -1
  7. package/src/py/agent/files/http/main.py.template +2 -2
  8. package/src/py/agent/gateway-connection/__snapshots__/generator.spec.ts.snap +197 -0
  9. package/src/py/agent/gateway-connection/files/agent-connection/app/{__gatewaySnakeCase___client.py.template → __gatewaySnakeCase___client_strands.py.template} +6 -6
  10. package/src/py/agent/gateway-connection/generator.js +5 -5
  11. package/src/py/agent/gateway-connection/generator.js.map +1 -1
  12. package/src/py/agent/generator.js +3 -0
  13. package/src/py/agent/generator.js.map +1 -1
  14. package/src/py/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +157 -79
  15. package/src/py/agent/mcp-connection/files/agent-connection/app/{__mcpServerSnakeCase___client.py.template → __mcpServerSnakeCase___client_strands.py.template} +7 -5
  16. package/src/py/agent/mcp-connection/generator.js +5 -5
  17. package/src/py/agent/mcp-connection/generator.js.map +1 -1
  18. package/src/py/fast-api/__snapshots__/generator.spec.ts.snap +37 -3
  19. package/src/py/fast-api/files/app/__name__/main.py.template +4 -1
  20. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +13 -4
  21. package/src/smithy/project/__snapshots__/generator.spec.ts.snap +1 -0
  22. package/src/smithy/project/files/src/operations/echo.smithy.template +1 -0
  23. package/src/terraform/project/files/application/src/main.tf.template +6 -1
  24. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +35 -4
  25. package/src/trpc/backend/files/src/procedures/echo.ts.template +1 -1
  26. package/src/trpc/backend/files/src/schema/echo.ts.template +2 -2
  27. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +14 -5
  28. package/src/ts/agent/a2a-connection/__snapshots__/generator.spec.ts.snap +256 -0
  29. package/src/ts/agent/a2a-connection/files/agent-connection/app/{__targetAgentKebabCase__-client.ts.template → __targetAgentKebabCase__-client-strands.ts.template} +5 -5
  30. package/src/ts/agent/a2a-connection/generator.js +3 -3
  31. package/src/ts/agent/a2a-connection/generator.js.map +1 -1
  32. package/src/ts/agent/files/http/router.ts.template +1 -1
  33. package/src/ts/agent/gateway-connection/__snapshots__/generator.spec.ts.snap +220 -0
  34. package/src/ts/agent/gateway-connection/files/agent-connection/app/{__gatewayKebabCase__-client.ts.template → __gatewayKebabCase__-client-strands.ts.template} +5 -5
  35. package/src/ts/agent/gateway-connection/generator.js +3 -3
  36. package/src/ts/agent/gateway-connection/generator.js.map +1 -1
  37. package/src/ts/agent/generator.js +3 -0
  38. package/src/ts/agent/generator.js.map +1 -1
  39. package/src/ts/agent/mcp-connection/__snapshots__/generator.spec.ts.snap +183 -110
  40. package/src/ts/agent/mcp-connection/files/agent-connection/app/{__mcpServerKebabCase__-client.ts.template → __mcpServerKebabCase__-client-strands.ts.template} +5 -5
  41. package/src/ts/agent/mcp-connection/generator.js +3 -3
  42. package/src/ts/agent/mcp-connection/generator.js.map +1 -1
  43. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +13 -4
  44. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +64 -24
  45. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +13 -4
  46. package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +9 -3
  47. package/src/utils/agent-connection/agent-connection.d.ts +50 -24
  48. package/src/utils/agent-connection/agent-connection.js +137 -45
  49. package/src/utils/agent-connection/agent-connection.js.map +1 -1
  50. package/src/utils/agent-connection/files/core-a2a/agentcore-a2a-client-config.ts.template +93 -0
  51. package/src/utils/agent-connection/files/core-auth/agentcore-endpoints.ts.template +37 -0
  52. package/src/utils/agent-connection/files/core-auth/agentcore-fetch.ts.template +54 -0
  53. package/src/utils/agent-connection/files/core-gateway/agentcore-gateway-mcp-transport.ts.template +41 -0
  54. package/src/utils/agent-connection/files/core-mcp/agentcore-mcp-transport.ts.template +70 -0
  55. package/src/utils/agent-connection/files/core-shared/agentcore-transport.ts.template +39 -0
  56. package/src/utils/agent-connection/files/core-strands/a2a/agentcore-a2a-client-strands.ts.template +61 -0
  57. package/src/utils/agent-connection/files/core-strands/gateway/agentcore-gateway-mcp-client-strands.ts.template +26 -0
  58. package/src/utils/agent-connection/files/core-strands/mcp/agentcore-mcp-client-strands.ts.template +27 -0
  59. package/src/utils/agent-connection/files/py-core-a2a/agentcore_a2a_client_config.py.template +43 -0
  60. package/src/utils/agent-connection/files/py-core-auth/agentcore_endpoints.py.template +47 -0
  61. package/src/utils/agent-connection/files/py-core-auth/auth/session.py.template +51 -0
  62. package/src/utils/agent-connection/files/py-core-gateway/agentcore_gateway_mcp_transport.py.template +28 -0
  63. package/src/utils/agent-connection/files/py-core-mcp/agentcore_mcp_transport.py.template +34 -0
  64. package/src/utils/agent-connection/files/py-core-shared/agentcore_transport.py.template +40 -0
  65. package/src/utils/agent-connection/files/py-core-strands/a2a/agentcore_a2a_client_strands.py.template +66 -0
  66. package/src/utils/agent-connection/files/py-core-strands/gateway/agentcore_gateway_mcp_client_strands.py.template +22 -0
  67. package/src/utils/agent-connection/files/py-core-strands/mcp/agentcore_mcp_client_strands.py.template +30 -0
  68. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +7 -2
  69. package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +13 -4
  70. package/src/utils/api-constructs/files/cdk/core/api/http/http-api.ts.template +9 -0
  71. package/src/utils/api-constructs/files/cdk/core/api/rest/rest-api.ts.template +23 -1
  72. package/src/utils/files/terraform/src/core/runtime-config/appconfig-deployment/appconfig-deployment.tf.template +9 -3
  73. package/src/utils/files/terraform/src/core/runtime-config/read/read.tf.template +7 -2
  74. package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +9 -3
  75. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +22 -8
  76. package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +10 -4
  77. package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +13 -4
  78. package/src/utils/agent-connection/files/core-a2a/agentcore-a2a-client.ts.template +0 -123
  79. package/src/utils/agent-connection/files/core-gateway/agentcore-gateway-mcp-client.ts.template +0 -78
  80. package/src/utils/agent-connection/files/core-mcp/agentcore-mcp-client.ts.template +0 -128
  81. package/src/utils/agent-connection/files/py-core-a2a/agentcore_a2a_client.py.template +0 -120
  82. package/src/utils/agent-connection/files/py-core-gateway/agentcore_gateway_mcp_client.py.template +0 -78
  83. package/src/utils/agent-connection/files/py-core-mcp/agentcore_mcp_client.py.template +0 -89
  84. /package/src/utils/agent-connection/files/{core-runtime-config/model-errors.ts.template → core-strands/base/model-errors-strands.ts.template} +0 -0
  85. /package/src/utils/agent-connection/files/{core-runtime-config/with-session-id.ts.template → core-strands/base/with-session-id-strands.ts.template} +0 -0
  86. /package/src/utils/agent-connection/files/{py-core-runtime-config/model_errors.py.template → py-core-strands/base/model_errors_strands.py.template} +0 -0
  87. /package/src/utils/agent-connection/files/{py-core-runtime-config/with_session_id.py.template → py-core-strands/base/with_session_id_strands.py.template} +0 -0
@@ -0,0 +1,34 @@
1
+ from collections.abc import Callable
2
+
3
+ from .agentcore_endpoints import mcp_url_from_arn, region_from_arn
4
+ from .agentcore_transport import (
5
+ TransportFactory,
6
+ jwt_transport,
7
+ no_auth_transport,
8
+ sigv4_transport,
9
+ )
10
+
11
+
12
+ class AgentCoreMCPTransport:
13
+ """Factory for MCP transport factories that connect to an AgentCore runtime."""
14
+
15
+ @staticmethod
16
+ def with_iam_auth(agent_runtime_arn: str) -> TransportFactory:
17
+ """SigV4-authenticated transport for a Bedrock AgentCore runtime."""
18
+ return sigv4_transport(
19
+ mcp_url_from_arn(agent_runtime_arn), region_from_arn(agent_runtime_arn)
20
+ )
21
+
22
+ @staticmethod
23
+ def with_jwt_auth(
24
+ agent_runtime_arn: str, access_token_provider: Callable[[], str]
25
+ ) -> TransportFactory:
26
+ """Bearer-authenticated transport for a Bedrock AgentCore runtime."""
27
+ return jwt_transport(
28
+ mcp_url_from_arn(agent_runtime_arn), access_token_provider
29
+ )
30
+
31
+ @staticmethod
32
+ def without_auth(url: str) -> TransportFactory:
33
+ """Plain-HTTP transport — for local dev."""
34
+ return no_auth_transport(url)
@@ -0,0 +1,40 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
4
+ import boto3
5
+
6
+ from mcp.client.streamable_http import streamablehttp_client
7
+
8
+ from .auth.session import jwt_auth, plain_auth, sigv4_auth
9
+
10
+ # Agent frameworks wrap a transport *factory* — a no-arg callable returning a
11
+ # streamable-HTTP transport context manager. Typed as ``Any`` so this layer
12
+ # stays free of any framework-specific transport type.
13
+ TransportFactory = Callable[[], Any]
14
+
15
+
16
+ def _factory(url: str, auth) -> TransportFactory:
17
+ return lambda: streamablehttp_client(
18
+ url,
19
+ auth=auth,
20
+ timeout=120,
21
+ terminate_on_close=False,
22
+ )
23
+
24
+
25
+ def sigv4_transport(url: str, region: str) -> TransportFactory:
26
+ """SigV4-signed transport factory for a resolved AgentCore endpoint."""
27
+ credentials = boto3.Session(region_name=region).get_credentials()
28
+ return _factory(url, sigv4_auth(credentials, region))
29
+
30
+
31
+ def jwt_transport(
32
+ url: str, access_token_provider: Callable[[], str]
33
+ ) -> TransportFactory:
34
+ """Bearer-token transport factory for a resolved AgentCore endpoint."""
35
+ return _factory(url, jwt_auth(access_token_provider))
36
+
37
+
38
+ def no_auth_transport(url: str) -> TransportFactory:
39
+ """Plain-HTTP transport factory — for local dev."""
40
+ return _factory(url, plain_auth())
@@ -0,0 +1,66 @@
1
+ from collections.abc import Callable
2
+
3
+ from strands.agent.a2a_agent import A2AAgent
4
+
5
+ from .agentcore_a2a_client_config import AgentCoreA2aClientConfig
6
+
7
+
8
+ def _build(
9
+ config: tuple, *, name: str | None, description: str | None
10
+ ) -> A2AAgent:
11
+ url, client_config = config
12
+ kwargs: dict = {"endpoint": url, "client_config": client_config}
13
+ if name:
14
+ kwargs["name"] = name
15
+ if description:
16
+ kwargs["description"] = description
17
+ return A2AAgent(**kwargs)
18
+
19
+
20
+ class AgentCoreA2aClientStrands:
21
+ """Factory for Strands A2A clients that connect to an AgentCore runtime."""
22
+
23
+ @staticmethod
24
+ def with_iam_auth(
25
+ agent_runtime_arn: str,
26
+ *,
27
+ name: str | None = None,
28
+ description: str | None = None,
29
+ ) -> A2AAgent:
30
+ """SigV4-authenticated client for a Bedrock AgentCore runtime."""
31
+ return _build(
32
+ AgentCoreA2aClientConfig.with_iam_auth(agent_runtime_arn),
33
+ name=name,
34
+ description=description,
35
+ )
36
+
37
+ @staticmethod
38
+ def with_jwt_auth(
39
+ agent_runtime_arn: str,
40
+ access_token_provider: Callable[[], str],
41
+ *,
42
+ name: str | None = None,
43
+ description: str | None = None,
44
+ ) -> A2AAgent:
45
+ """Bearer-authenticated client for a Bedrock AgentCore runtime."""
46
+ return _build(
47
+ AgentCoreA2aClientConfig.with_jwt_auth(
48
+ agent_runtime_arn, access_token_provider
49
+ ),
50
+ name=name,
51
+ description=description,
52
+ )
53
+
54
+ @staticmethod
55
+ def without_auth(
56
+ url: str,
57
+ *,
58
+ name: str | None = None,
59
+ description: str | None = None,
60
+ ) -> A2AAgent:
61
+ """Plain-HTTP client — for local dev."""
62
+ return _build(
63
+ AgentCoreA2aClientConfig.without_auth(url),
64
+ name=name,
65
+ description=description,
66
+ )
@@ -0,0 +1,22 @@
1
+ from strands.tools.mcp.mcp_client import MCPClient
2
+
3
+ from .agentcore_gateway_mcp_transport import AgentCoreGatewayMCPTransport
4
+
5
+
6
+ class AgentCoreGatewayMCPClientStrands:
7
+ """Factory for Strands MCP clients that connect to an AgentCore Gateway."""
8
+
9
+ @staticmethod
10
+ def with_iam_auth(
11
+ gateway_url: str,
12
+ region: str | None = None,
13
+ ) -> MCPClient:
14
+ """Create a gateway MCP client authenticated with IAM SigV4."""
15
+ return MCPClient(
16
+ AgentCoreGatewayMCPTransport.with_iam_auth(gateway_url, region)
17
+ )
18
+
19
+ @staticmethod
20
+ def without_auth(gateway_url: str) -> MCPClient:
21
+ """Plain-HTTP gateway client, for the local gateway started by serve-local."""
22
+ return MCPClient(AgentCoreGatewayMCPTransport.without_auth(gateway_url))
@@ -0,0 +1,30 @@
1
+ from collections.abc import Callable
2
+
3
+ from strands.tools.mcp.mcp_client import MCPClient
4
+
5
+ from .agentcore_mcp_transport import AgentCoreMCPTransport
6
+
7
+
8
+ class AgentCoreMCPClientStrands:
9
+ """Factory for Strands MCP clients that connect to an AgentCore runtime."""
10
+
11
+ @staticmethod
12
+ def with_iam_auth(agent_runtime_arn: str) -> MCPClient:
13
+ """SigV4-authenticated client for a Bedrock AgentCore runtime."""
14
+ return MCPClient(AgentCoreMCPTransport.with_iam_auth(agent_runtime_arn))
15
+
16
+ @staticmethod
17
+ def with_jwt_auth(
18
+ agent_runtime_arn: str, access_token_provider: Callable[[], str]
19
+ ) -> MCPClient:
20
+ """Bearer-authenticated client for a Bedrock AgentCore runtime."""
21
+ return MCPClient(
22
+ AgentCoreMCPTransport.with_jwt_auth(
23
+ agent_runtime_arn, access_token_provider
24
+ )
25
+ )
26
+
27
+ @staticmethod
28
+ def without_auth(url: str) -> MCPClient:
29
+ """Plain-HTTP client — for local dev."""
30
+ return MCPClient(AgentCoreMCPTransport.without_auth(url))
@@ -235,6 +235,7 @@ resource "null_resource" "gateway_ready" {
235
235
  command = <<-EOT
236
236
  uv run --with boto3 --with httpx --with mcp python -c "
237
237
  import asyncio
238
+ import os
238
239
  import time
239
240
 
240
241
  import boto3
@@ -244,8 +245,8 @@ from botocore.awsrequest import AWSRequest
244
245
  from mcp import ClientSession
245
246
  from mcp.client.streamable_http import streamablehttp_client
246
247
 
247
- gateway_url = '${self.triggers.gateway_url}'
248
- region = '${self.triggers.region}'
248
+ gateway_url = os.environ['GATEWAY_URL']
249
+ region = os.environ['REGION']
249
250
  credentials = boto3.Session(region_name=region).get_credentials()
250
251
  signer = SigV4Auth(credentials, 'bedrock-agentcore', region)
251
252
 
@@ -293,6 +294,10 @@ while True:
293
294
  print(f'Gateway {gateway_url} is serving tools')
294
295
  "
295
296
  EOT
297
+ environment = {
298
+ GATEWAY_URL = self.triggers.gateway_url
299
+ REGION = self.triggers.region
300
+ }
296
301
  }
297
302
  }
298
303
 
@@ -281,14 +281,19 @@ resource "null_resource" "docker_publish" {
281
281
  provisioner "local-exec" {
282
282
  command = <<-EOT
283
283
  # Get ECR login token
284
- aws ecr get-login-password --region ${local.aws_region} | <%= containers %> login --username AWS --password-stdin ${self.triggers.repository_url}
284
+ aws ecr get-login-password --region "$AWS_REGION" | <%= containers %> login --username AWS --password-stdin "$REPOSITORY_URL"
285
285
 
286
286
  # Tag the image
287
- <%= containers %> tag ${self.triggers.docker_image_tag} ${self.triggers.repository_url}:latest
287
+ <%= containers %> tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
288
288
 
289
289
  # Push the image
290
- <%= containers %> push ${self.triggers.repository_url}:latest
290
+ <%= containers %> push "$REPOSITORY_URL:latest"
291
291
  EOT
292
+ environment = {
293
+ AWS_REGION = local.aws_region
294
+ REPOSITORY_URL = self.triggers.repository_url
295
+ DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
296
+ }
292
297
  }
293
298
 
294
299
  depends_on = [aws_ecr_repository_policy.agent_core_ecr_policy]
@@ -350,10 +355,11 @@ resource "null_resource" "runtime_ready" {
350
355
  uv run --with boto3 python -c "
351
356
  import boto3
352
357
  import json
358
+ import os
353
359
  import time
354
360
  import uuid
355
361
 
356
- runtime_arn = '${self.triggers.runtime_arn}'
362
+ runtime_arn = os.environ['RUNTIME_ARN']
357
363
  client = boto3.client('bedrock-agentcore')
358
364
  initialize_request = json.dumps({
359
365
  'jsonrpc': '2.0',
@@ -385,6 +391,9 @@ while True:
385
391
  print(f'Runtime {runtime_arn} is serving MCP')
386
392
  "
387
393
  EOT
394
+ environment = {
395
+ RUNTIME_ARN = self.triggers.runtime_arn
396
+ }
388
397
  }
389
398
  }
390
399
 
@@ -12,6 +12,7 @@ import {
12
12
  HttpMethod,
13
13
  HttpStage,
14
14
  LogGroupLogDestination,
15
+ ThrottleSettings,
15
16
  } from 'aws-cdk-lib/aws-apigatewayv2';
16
17
  import { LogGroup } from 'aws-cdk-lib/aws-logs';
17
18
  import { suppressRules } from '../checkov.js';
@@ -38,6 +39,12 @@ export interface HttpApiProps<
38
39
  * Map of operation names to their API Gateway integrations
39
40
  */
40
41
  readonly integrations: TIntegrations;
42
+ /**
43
+ * Default throttling limits applied to the API's default stage.
44
+ *
45
+ * @default { rateLimit: 10000, burstLimit: 5000 }
46
+ */
47
+ readonly throttle?: ThrottleSettings;
41
48
  }
42
49
 
43
50
  /**
@@ -71,6 +78,7 @@ export class HttpApi<
71
78
  apiName,
72
79
  operations,
73
80
  integrations,
81
+ throttle = { rateLimit: 10000, burstLimit: 5000 },
74
82
  ...props
75
83
  }: HttpApiProps<TIntegrations, TOperation>,
76
84
  ) {
@@ -98,6 +106,7 @@ export class HttpApi<
98
106
  this.defaultStage = new HttpStage(this, 'DefaultStage', {
99
107
  httpApi: this.api,
100
108
  autoDeploy: true,
109
+ throttle,
101
110
  accessLogSettings: {
102
111
  destination: new LogGroupLogDestination(accessLogGroup),
103
112
  },
@@ -6,6 +6,7 @@ import {
6
6
  RestApiProps as _RestApiProps,
7
7
  IResource,
8
8
  Stage,
9
+ ThrottleSettings,
9
10
  } from 'aws-cdk-lib/aws-apigateway';
10
11
  import { IAspect, RemovalPolicy } from 'aws-cdk-lib';
11
12
  import {
@@ -52,6 +53,12 @@ export interface RestApiProps<
52
53
  * @default true
53
54
  */
54
55
  readonly enableWaf?: boolean;
56
+ /**
57
+ * Default throttling limits applied to all methods on the API's default stage.
58
+ *
59
+ * @default { rateLimit: 10000, burstLimit: 5000 }
60
+ */
61
+ readonly throttle?: ThrottleSettings;
55
62
  }
56
63
 
57
64
  /**
@@ -86,6 +93,7 @@ export class RestApi<
86
93
  operations,
87
94
  integrations,
88
95
  enableWaf = true,
96
+ throttle = { rateLimit: 10000, burstLimit: 5000 },
89
97
  ...props
90
98
  }: RestApiProps<TIntegrations, TOperation>,
91
99
  ) {
@@ -93,7 +101,21 @@ export class RestApi<
93
101
  this.integrations = integrations;
94
102
 
95
103
  // Create the API Gateway REST API
96
- this.api = new _RestApi(this, 'Api', props);
104
+ this.api = new _RestApi(this, 'Api', {
105
+ ...props,
106
+ deployOptions: {
107
+ ...props.deployOptions,
108
+ methodOptions: {
109
+ ...props.deployOptions?.methodOptions,
110
+ // Default throttling applied to all resource paths and methods
111
+ '/*/*': {
112
+ throttlingRateLimit: throttle.rateLimit,
113
+ throttlingBurstLimit: throttle.burstLimit,
114
+ ...props.deployOptions?.methodOptions?.['/*/*'],
115
+ },
116
+ },
117
+ },
118
+ });
97
119
 
98
120
  if (enableWaf) {
99
121
  this.webAcl = new CfnWebACL(this, 'WebAcl', {
@@ -65,12 +65,13 @@ resource "null_resource" "aggregate_namespace" {
65
65
  command = <<-EOT
66
66
  uv run python -c "
67
67
  import json
68
+ import os
68
69
  import pathlib
69
70
  import sys
70
71
 
71
- namespace = '${each.key}'
72
- entries_dir = pathlib.Path('${local.entries_dir}') / namespace
73
- output_file = pathlib.Path('${local.config_dir}') / f'{namespace}.json'
72
+ namespace = os.environ['NAMESPACE']
73
+ entries_dir = pathlib.Path(os.environ['ENTRIES_DIR']) / namespace
74
+ output_file = pathlib.Path(os.environ['CONFIG_DIR']) / f'{namespace}.json'
74
75
 
75
76
  def deep_merge(target: dict, source: dict) -> dict:
76
77
  for k, v in source.items():
@@ -103,6 +104,11 @@ output_file.write_text(json.dumps(merged, indent=2))
103
104
  print(f'Aggregated {namespace}.json with {len(merged)} section(s)')
104
105
  "
105
106
  EOT
107
+ environment = {
108
+ NAMESPACE = each.key
109
+ ENTRIES_DIR = local.entries_dir
110
+ CONFIG_DIR = local.config_dir
111
+ }
106
112
  }
107
113
  }
108
114
 
@@ -38,11 +38,12 @@ resource "null_resource" "aggregate_namespace" {
38
38
  command = <<-EOT
39
39
  uv run python -c "
40
40
  import json
41
+ import os
41
42
  import pathlib
42
43
  import sys
43
44
 
44
- entries_dir = pathlib.Path('${local.entries_dir}')
45
- output_file = pathlib.Path('${local.namespace_path}')
45
+ entries_dir = pathlib.Path(os.environ['ENTRIES_DIR'])
46
+ output_file = pathlib.Path(os.environ['NAMESPACE_PATH'])
46
47
 
47
48
  def deep_merge(target: dict, source: dict) -> dict:
48
49
  for k, v in source.items():
@@ -71,6 +72,10 @@ output_file.parent.mkdir(parents=True, exist_ok=True)
71
72
  output_file.write_text(json.dumps(merged, indent=2))
72
73
  "
73
74
  EOT
75
+ environment = {
76
+ ENTRIES_DIR = local.entries_dir
77
+ NAMESPACE_PATH = local.namespace_path
78
+ }
74
79
  }
75
80
  }
76
81
 
@@ -61,12 +61,13 @@ resource "null_resource" "add_callback_url" {
61
61
  command = <<-EOT
62
62
  uv run --with boto3 python -c "
63
63
  import boto3
64
+ import os
64
65
  import sys
65
66
 
66
67
  # Configuration
67
- user_pool_id = '${local.user_pool_id}'
68
- client_id = '${local.user_pool_client_id}'
69
- new_callback_url = '${var.callback_url}'
68
+ user_pool_id = os.environ['USER_POOL_ID']
69
+ client_id = os.environ['CLIENT_ID']
70
+ new_callback_url = os.environ['NEW_CALLBACK_URL']
70
71
 
71
72
  # Initialize Cognito client
72
73
  cognito = boto3.client('cognito-idp')
@@ -118,6 +119,11 @@ except Exception as e:
118
119
  sys.exit(1)
119
120
  "
120
121
  EOT
122
+ environment = {
123
+ USER_POOL_ID = local.user_pool_id
124
+ CLIENT_ID = local.user_pool_client_id
125
+ NEW_CALLBACK_URL = var.callback_url
126
+ }
121
127
  }
122
128
 
123
129
  depends_on = [terraform_data.validate_cognito_props]
@@ -287,10 +287,15 @@ resource "null_resource" "docker_publish" {
287
287
 
288
288
  provisioner "local-exec" {
289
289
  command = <<-EOT
290
- aws ecr get-login-password --region ${data.aws_region.current.region} | <%= containerEngine %> login --username AWS --password-stdin ${self.triggers.repository_url}
291
- <%= containerEngine %> tag ${self.triggers.docker_image_tag} ${self.triggers.repository_url}:latest
292
- <%= containerEngine %> push ${self.triggers.repository_url}:latest
290
+ aws ecr get-login-password --region "$AWS_REGION" | <%= containerEngine %> login --username AWS --password-stdin "$REPOSITORY_URL"
291
+ <%= containerEngine %> tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
292
+ <%= containerEngine %> push "$REPOSITORY_URL:latest"
293
293
  EOT
294
+ environment = {
295
+ AWS_REGION = data.aws_region.current.region
296
+ REPOSITORY_URL = self.triggers.repository_url
297
+ DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
298
+ }
294
299
  }
295
300
 
296
301
  depends_on = [aws_ecr_repository_policy.migration_handler]
@@ -574,10 +579,10 @@ resource "null_resource" "create_db_user_trigger" {
574
579
  command = <<-EOT
575
580
  output_file=$(mktemp)
576
581
  response=$(aws lambda invoke \
577
- --region ${data.aws_region.current.region} \
578
- --function-name ${self.triggers.function_name} \
582
+ --region "$AWS_REGION" \
583
+ --function-name "$FUNCTION_NAME" \
579
584
  --cli-binary-format raw-in-base64-out \
580
- --payload '{"RequestType":"Create","PhysicalResourceId":"db-user:${self.triggers.db_user}"}' \
585
+ --payload "{\"RequestType\":\"Create\",\"PhysicalResourceId\":\"db-user:$DB_USER\"}" \
581
586
  "$output_file")
582
587
  cat "$output_file"
583
588
  echo "$response"
@@ -587,6 +592,11 @@ resource "null_resource" "create_db_user_trigger" {
587
592
  fi
588
593
  rm -f "$output_file"
589
594
  EOT
595
+ environment = {
596
+ AWS_REGION = data.aws_region.current.region
597
+ FUNCTION_NAME = self.triggers.function_name
598
+ DB_USER = self.triggers.db_user
599
+ }
590
600
  }
591
601
 
592
602
  depends_on = [aws_lambda_function.create_db_user, module.aurora]
@@ -655,8 +665,8 @@ resource "null_resource" "migration_trigger" {
655
665
  command = <<-EOT
656
666
  output_file=$(mktemp)
657
667
  response=$(aws lambda invoke \
658
- --region ${data.aws_region.current.region} \
659
- --function-name ${self.triggers.function_name} \
668
+ --region "$AWS_REGION" \
669
+ --function-name "$FUNCTION_NAME" \
660
670
  --cli-binary-format raw-in-base64-out \
661
671
  "$output_file")
662
672
  cat "$output_file"
@@ -667,6 +677,10 @@ resource "null_resource" "migration_trigger" {
667
677
  fi
668
678
  rm -f "$output_file"
669
679
  EOT
680
+ environment = {
681
+ AWS_REGION = data.aws_region.current.region
682
+ FUNCTION_NAME = self.triggers.function_name
683
+ }
670
684
  }
671
685
 
672
686
  depends_on = [
@@ -540,9 +540,9 @@ resource "null_resource" "proxy_target_ready" {
540
540
  deadline=$((start_time + 600))
541
541
  while true; do
542
542
  target_state=$(aws rds describe-db-proxy-targets \
543
- --region ${data.aws_region.current.region} \
544
- --db-proxy-name ${self.triggers.db_proxy_name} \
545
- --target-group-name ${self.triggers.target_group_name} \
543
+ --region "$AWS_REGION" \
544
+ --db-proxy-name "$DB_PROXY_NAME" \
545
+ --target-group-name "$TARGET_GROUP_NAME" \
546
546
  --query "Targets[?Type=='RDS_INSTANCE'] | [0].TargetHealth.State" \
547
547
  --output text)
548
548
 
@@ -552,13 +552,19 @@ resource "null_resource" "proxy_target_ready" {
552
552
 
553
553
  current_time=$(date +%s)
554
554
  if [ "$current_time" -ge "$deadline" ]; then
555
- echo "RDS Proxy target ${self.triggers.db_cluster_identifier} did not become AVAILABLE within 10 minutes. Last state: $target_state"
555
+ echo "RDS Proxy target $DB_CLUSTER_IDENTIFIER did not become AVAILABLE within 10 minutes. Last state: $target_state"
556
556
  exit 1
557
557
  fi
558
558
 
559
559
  sleep 10
560
560
  done
561
561
  EOT
562
+ environment = {
563
+ AWS_REGION = data.aws_region.current.region
564
+ DB_PROXY_NAME = self.triggers.db_proxy_name
565
+ TARGET_GROUP_NAME = self.triggers.target_group_name
566
+ DB_CLUSTER_IDENTIFIER = self.triggers.db_cluster_identifier
567
+ }
562
568
  }
563
569
 
564
570
  depends_on = [
@@ -553,7 +553,7 @@ resource "null_resource" "upload_website_files" {
553
553
 
554
554
  provisioner "local-exec" {
555
555
  command = <<-EOT
556
- cd "${path.root}"
556
+ cd "$ROOT_PATH"
557
557
  uv run --with boto3 python3 -c "
558
558
  import os
559
559
  import sys
@@ -642,9 +642,14 @@ def sync_to_s3(local_path, bucket_name):
642
642
  sys.exit(1)
643
643
 
644
644
  # Execute sync
645
- sync_to_s3('${var.website_file_path}', '${aws_s3_bucket.website.bucket}')
645
+ sync_to_s3(os.environ['WEBSITE_FILE_PATH'], os.environ['BUCKET_NAME'])
646
646
  "
647
647
  EOT
648
+ environment = {
649
+ ROOT_PATH = path.root
650
+ WEBSITE_FILE_PATH = var.website_file_path
651
+ BUCKET_NAME = aws_s3_bucket.website.bucket
652
+ }
648
653
  }
649
654
 
650
655
  depends_on = [aws_s3_bucket_policy.website_cloudfront_policy]
@@ -674,6 +679,7 @@ resource "null_resource" "cloudfront_invalidation" {
674
679
  command = <<-EOT
675
680
  uv run --with boto3 python3 -c "
676
681
  import boto3
682
+ import os
677
683
  import sys
678
684
  from botocore.exceptions import ClientError, NoCredentialsError
679
685
 
@@ -695,7 +701,7 @@ def create_invalidation(distribution_id):
695
701
 
696
702
  invalidation_id = response['Invalidation']['Id']
697
703
  print(f'CloudFront cache invalidation created: {invalidation_id}')
698
- print(f'Distribution: ${aws_cloudfront_distribution.website.id}')
704
+ print(f'Distribution: {distribution_id}')
699
705
  print(f'Status: {response[\"Invalidation\"][\"Status\"]}')
700
706
 
701
707
  except NoCredentialsError:
@@ -709,9 +715,12 @@ def create_invalidation(distribution_id):
709
715
  sys.exit(1)
710
716
 
711
717
  # Execute invalidation
712
- create_invalidation('${aws_cloudfront_distribution.website.id}')
718
+ create_invalidation(os.environ['DISTRIBUTION_ID'])
713
719
  "
714
720
  EOT
721
+ environment = {
722
+ DISTRIBUTION_ID = aws_cloudfront_distribution.website.id
723
+ }
715
724
  }
716
725
 
717
726
  depends_on = [