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

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 (68) hide show
  1. package/migrations.json +26 -1
  2. package/package.json +1 -1
  3. package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +51 -2
  4. package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +4 -5
  5. package/src/agentcore-harness/files/project/README.md.template +4 -5
  6. package/src/migrations/latest/agentcore-harness-no-tools-by-default/__snapshots__/migration.spec.ts.snap +79 -0
  7. package/src/migrations/latest/agentcore-harness-no-tools-by-default/metadata.json +3 -0
  8. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.d.ts +6 -0
  9. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js +87 -0
  10. package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +1 -0
  11. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/__snapshots__/migration.spec.ts.snap +54 -0
  12. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/metadata.json +3 -0
  13. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.d.ts +6 -0
  14. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js +86 -0
  15. package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
  16. package/src/migrations/latest/terraform-declare-used-providers/metadata.json +3 -0
  17. package/src/migrations/latest/terraform-declare-used-providers/migration.d.ts +6 -0
  18. package/src/migrations/latest/terraform-declare-used-providers/migration.js +147 -0
  19. package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +1 -0
  20. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/__snapshots__/migration.spec.ts.snap +521 -0
  21. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/metadata.json +3 -0
  22. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.d.ts +6 -0
  23. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js +504 -0
  24. package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
  25. package/src/migrations/latest/waf-log-group-cmk-encryption/__snapshots__/migration.spec.ts.snap +286 -0
  26. package/src/migrations/latest/waf-log-group-cmk-encryption/metadata.json +3 -0
  27. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.d.ts +6 -0
  28. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js +238 -0
  29. package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +1 -0
  30. package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +12 -0
  31. package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +231 -24
  32. package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  33. package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  34. package/src/py/rdb/__snapshots__/generator.spec.ts.snap +31 -2
  35. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +216 -25
  36. package/src/terraform/project/files/application/bootstrap/providers.tf.template +9 -0
  37. package/src/terraform/project/files/application/src/providers.tf.template +4 -0
  38. package/src/terraform/project/files/library/src/main.tf.template +9 -0
  39. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +231 -24
  40. package/src/ts/agent/__snapshots__/generator.spec.ts.snap +8 -0
  41. package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +4 -0
  42. package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
  43. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
  44. package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +62 -4
  45. package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +4 -0
  46. package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +35 -8
  47. package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +4 -0
  48. package/src/utils/agent-core-constructs/files/cdk/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.ts.template +39 -1
  49. package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +8 -1
  50. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +57 -2
  51. package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +258 -24
  52. package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +4 -0
  53. package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +8 -0
  54. package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +9 -1
  55. package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +60 -7
  56. package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  57. package/src/utils/files/terraform/src/metrics/providers.tf.template +12 -0
  58. package/src/utils/function-constructs/files/terraform/app/lambda-functions/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
  59. package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +33 -8
  60. package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +4 -0
  61. package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +51 -2
  62. package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +18 -0
  63. package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +15 -4
  64. package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +2 -2
  65. package/src/utils/rdb-constructs/rdb-constructs.d.ts +12 -0
  66. package/src/utils/rdb-constructs/rdb-constructs.js +24 -3
  67. package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
  68. package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +4 -0
@@ -0,0 +1,504 @@
1
+ /**
2
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */ import { joinPathFragments } from "@nx/devkit";
5
+ import { applyGritQL, captureAllGritQL, GRIT_INSERT_PLACEHOLDER, insertViaGritQL, matchGritQL } from "../../../utils/ast.js";
6
+ import { formatFilesInSubtree } from "../../../utils/format.js";
7
+ import { PACKAGES_DIR, SHARED_TERRAFORM_DIR } from "../../../utils/shared-constructs-constants.js";
8
+ /**
9
+ * Bring each vended Terraform AgentCore Harness app module
10
+ * (`app/harnesses/<name>/<name>.tf`) up to the shape the generator now writes:
11
+ *
12
+ * - `enable_vpc` / `vpc_id` / `subnet_ids` with a security group and a 443-only
13
+ * egress rule, so the Harness can reach private resources.
14
+ * - `create_execution_role` / `execution_role_arn`, so an existing role can be
15
+ * supplied. The generated role and its baseline policy are then not created.
16
+ * - `allowed_tools`, `memory`, `environment_variables`, `max_iterations` and
17
+ * `timeout_seconds` as variables rather than HCL edits.
18
+ * - The managed-memory grant moved to its own policy, made conditional and
19
+ * re-scoped from a name-prefix guess to the ARN the service assigns.
20
+ *
21
+ * These files are generated with `KeepExisting`, so an upgraded workspace keeps
22
+ * the older module until this runs. Modules that have diverged from the
23
+ * generated shape are left untouched and reported via `nextSteps`.
24
+ */ const TERRAFORM_HARNESS_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/harnesses`;
25
+ const hcl = (pattern)=>`language hcl\n${pattern}`;
26
+ const divergedMessage = (filePath)=>`${filePath}: has diverged from the generated shape - left untouched. To pick up VPC support, a supplied execution role, the allowed_tools/memory/environment_variables/max_iterations/timeout_seconds variables and the corrected managed-memory grant, compare it against the agentcore-harness generator's Terraform template and apply the parts you want.`;
27
+ /**
28
+ * Variables added ahead of the existing `model_resource_arns` variable.
29
+ * `allowed_tools` is not among them: the earlier `agentcore-harness-no-tools-by-default`
30
+ * migration already declares it, and runs first.
31
+ */ const NEW_VARIABLES_TEXT = `variable "memory" {
32
+ description = "Memory configuration for the Harness. Null leaves the service to provision managed memory with default strategies. Set exactly one field: managed_memory_configuration to tune that managed memory, agentcore_memory_configuration to use a memory resource you own, or disabled to turn memory off."
33
+ type = object({
34
+ managed_memory_configuration = optional(object({
35
+ strategies = optional(set(string))
36
+ event_expiry_duration = optional(number)
37
+ encryption_key_arn = optional(string)
38
+ }))
39
+ agentcore_memory_configuration = optional(object({
40
+ arn = string
41
+ actor_id = optional(string)
42
+ messages_count = optional(number)
43
+ }))
44
+ disabled = optional(bool, false)
45
+ })
46
+ default = null
47
+
48
+ validation {
49
+ condition = var.memory == null || length([
50
+ for configured in [
51
+ var.memory.managed_memory_configuration != null,
52
+ var.memory.agentcore_memory_configuration != null,
53
+ var.memory.disabled,
54
+ ] : configured if configured
55
+ ]) == 1
56
+ error_message = "Set exactly one of memory.managed_memory_configuration, memory.agentcore_memory_configuration or memory.disabled."
57
+ }
58
+ }
59
+
60
+ variable "environment_variables" {
61
+ description = "Environment variables available to the Harness."
62
+ type = map(string)
63
+ default = null
64
+ sensitive = true
65
+ }
66
+
67
+ variable "max_iterations" {
68
+ description = "Maximum agent loop iterations per invocation. Uses the service default when null."
69
+ type = number
70
+ default = null
71
+ }
72
+
73
+ variable "timeout_seconds" {
74
+ description = "Maximum seconds an invocation may run. Uses the service default when null."
75
+ type = number
76
+ default = null
77
+ }
78
+
79
+ variable "create_execution_role" {
80
+ description = "Create the execution role with the baseline permissions below. Set false alongside execution_role_arn to use a role you already have, which is then used as-is."
81
+ type = bool
82
+ default = true
83
+
84
+ validation {
85
+ condition = var.create_execution_role || var.execution_role_arn != null
86
+ error_message = "execution_role_arn must be set when create_execution_role is false."
87
+ }
88
+
89
+ validation {
90
+ condition = var.create_execution_role || (
91
+ var.model_resource_arns == null && length(var.additional_execution_role_policy_statements) == 0
92
+ )
93
+ error_message = "model_resource_arns and additional_execution_role_policy_statements configure the generated execution role, which is not created when create_execution_role is false. Grant those permissions on the supplied role instead."
94
+ }
95
+ }
96
+
97
+ variable "execution_role_arn" {
98
+ description = "ARN of an existing IAM role for the Harness to assume. Requires create_execution_role = false."
99
+ type = string
100
+ default = null
101
+ }`;
102
+ /** VPC and tag variables added after `additional_execution_role_policy_statements`. */ const VPC_VARIABLES_TEXT = `# VPC Configuration (optional)
103
+ variable "enable_vpc" {
104
+ description = "Run the Harness inside a VPC so it can reach private resources. Set alongside vpc_id/subnet_ids."
105
+ type = bool
106
+ default = false
107
+ }
108
+
109
+ variable "vpc_id" {
110
+ description = "VPC ID to run the Harness in. Required when enable_vpc is true."
111
+ type = string
112
+ default = null
113
+
114
+ validation {
115
+ condition = !var.enable_vpc || (var.vpc_id != null && var.subnet_ids != null)
116
+ error_message = "vpc_id and subnet_ids must be set when enable_vpc is true."
117
+ }
118
+ }
119
+
120
+ variable "subnet_ids" {
121
+ description = "Subnet IDs to run the Harness in. Required when enable_vpc is true."
122
+ type = list(string)
123
+ default = null
124
+ }
125
+
126
+ variable "tags" {
127
+ description = "Tags to apply to resources"
128
+ type = map(string)
129
+ default = {}
130
+ }`;
131
+ /** Locals added to the module's existing `locals` block. */ const NEW_LOCALS_TEXT = ` execution_role_arn = var.create_execution_role ? aws_iam_role.execution_role[0].arn : var.execution_role_arn
132
+
133
+ # The service provisions managed memory unless var.memory redirects it
134
+ # elsewhere or turns it off.
135
+ has_managed_memory = var.memory == null || var.memory.managed_memory_configuration != null`;
136
+ /** Security group and egress rule, added before the Harness resource. */ const SECURITY_GROUP_TEXT = `# Security group for the Harness, used when running inside a VPC
137
+ resource "aws_security_group" "harness" {
138
+ count = var.enable_vpc ? 1 : 0
139
+
140
+ #checkov:skip=CKV2_AWS_5:Attached to the Harness via its network configuration; Checkov cannot resolve this reference
141
+ name_prefix = "\${local.harness_name_prefix}-harness-"
142
+ description = "Security group for the \${local.harness_name_prefix} Harness"
143
+ vpc_id = var.vpc_id
144
+ tags = var.tags
145
+ }
146
+
147
+ resource "aws_vpc_security_group_egress_rule" "harness_https" {
148
+ count = var.enable_vpc ? 1 : 0
149
+
150
+ security_group_id = aws_security_group.harness[0].id
151
+ cidr_ipv4 = "0.0.0.0/0"
152
+ from_port = 443
153
+ to_port = 443
154
+ ip_protocol = "tcp"
155
+ description = "Allow outbound HTTPS to AWS service endpoints"
156
+ }`;
157
+ /**
158
+ * Native fields added to the Harness resource. `allowed_tools` is re-emitted
159
+ * here, aligned with the rest, replacing the line the earlier
160
+ * no-tools-by-default migration added.
161
+ */ const HARNESS_FIELDS_TEXT = ` allowed_tools = var.allowed_tools
162
+ environment_variables = var.environment_variables
163
+ max_iterations = var.max_iterations
164
+ timeout_seconds = var.timeout_seconds`;
165
+ const HARNESS_BLOCKS_TEXT = ` dynamic "memory" {
166
+ for_each = var.memory != null ? [var.memory] : []
167
+ content {
168
+ dynamic "managed_memory_configuration" {
169
+ for_each = memory.value.managed_memory_configuration != null ? [memory.value.managed_memory_configuration] : []
170
+ content {
171
+ strategies = managed_memory_configuration.value.strategies
172
+ event_expiry_duration = managed_memory_configuration.value.event_expiry_duration
173
+ encryption_key_arn = managed_memory_configuration.value.encryption_key_arn
174
+ }
175
+ }
176
+
177
+ dynamic "agentcore_memory_configuration" {
178
+ for_each = memory.value.agentcore_memory_configuration != null ? [memory.value.agentcore_memory_configuration] : []
179
+ content {
180
+ arn = agentcore_memory_configuration.value.arn
181
+ actor_id = agentcore_memory_configuration.value.actor_id
182
+ messages_count = agentcore_memory_configuration.value.messages_count
183
+ }
184
+ }
185
+
186
+ dynamic "disabled" {
187
+ for_each = memory.value.disabled ? [1] : []
188
+ content {}
189
+ }
190
+ }
191
+ }
192
+
193
+ dynamic "environment" {
194
+ for_each = var.enable_vpc ? [1] : []
195
+ content {
196
+ agentcore_runtime_environment {
197
+ network_configuration {
198
+ network_mode = "VPC"
199
+ network_mode_config {
200
+ security_groups = [aws_security_group.harness[0].id]
201
+ subnets = var.subnet_ids
202
+ }
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ tags = var.tags`;
209
+ /** The managed-memory grant, as its own policy scoped to the assigned ARN. */ const MANAGED_MEMORY_POLICY_TEXT = `# Scoped to the memory ARN the service assigns, which is only readable after the
210
+ # Harness exists, so this is a policy of its own rather than a baseline
211
+ # statement. Skipped when the Harness uses a role or memory the module did not
212
+ # create.
213
+ resource "aws_iam_role_policy" "managed_memory" {
214
+ count = var.create_execution_role && local.has_managed_memory ? 1 : 0
215
+
216
+ name = "\${local.harness_name_prefix}-HarnessManagedMemoryPolicy"
217
+ role = aws_iam_role.execution_role[0].id
218
+
219
+ policy = jsonencode({
220
+ Version = "2012-10-17"
221
+ Statement = [{
222
+ Sid = "AgentCoreManagedMemory"
223
+ Effect = "Allow"
224
+ Action = [
225
+ "bedrock-agentcore:CreateEvent",
226
+ "bedrock-agentcore:DeleteEvent",
227
+ "bedrock-agentcore:GetEvent",
228
+ "bedrock-agentcore:ListEvents",
229
+ "bedrock-agentcore:RetrieveMemoryRecords",
230
+ ]
231
+ Resource = [
232
+ aws_bedrockagentcore_harness.this.memory_actual[0].managed_memory_configuration[0].arn,
233
+ ]
234
+ }]
235
+ })
236
+ }`;
237
+ const SECURITY_GROUP_OUTPUT_TEXT = `output "security_group_id" {
238
+ description = "Security group ID of the Harness, for use in ingress rules on resources it must reach (e.g. a database). Null unless enable_vpc is true."
239
+ value = var.enable_vpc ? aws_security_group.harness[0].id : null
240
+ }`;
241
+ /**
242
+ * The baseline policy, replaced whole: the managed-memory statement leaves it
243
+ * for a policy of its own, and the resource gains a `count`. Rewriting the
244
+ * statement list in place would strand the comment and comma around the
245
+ * statement being removed.
246
+ */ const BASELINE_POLICY_TEXT = `resource "aws_iam_role_policy" "execution_role" {
247
+ count = var.create_execution_role ? 1 : 0
248
+
249
+ name = "\${local.harness_name_prefix}-HarnessPolicy"
250
+ role = aws_iam_role.execution_role[0].id
251
+
252
+ policy = jsonencode({
253
+ Version = "2012-10-17"
254
+ Statement = concat([
255
+ {
256
+ Sid = "BedrockModelInvocation"
257
+ Effect = "Allow"
258
+ Action = [
259
+ "bedrock:InvokeModel",
260
+ "bedrock:InvokeModelWithResponseStream",
261
+ ]
262
+ Resource = local.model_resource_arns
263
+ },
264
+ {
265
+ #checkov:skip=CKV_AWS_355:EcrPublicTokenAccess requires a wildcard resource; ecr-public:GetAuthorizationToken has no resource-level permission
266
+ Sid = "EcrPublicTokenAccess"
267
+ Effect = "Allow"
268
+ Action = ["ecr-public:GetAuthorizationToken"]
269
+ Resource = ["*"]
270
+ },
271
+ {
272
+ #checkov:skip=CKV_AWS_355:StsForEcrPublicPull requires a wildcard resource; sts:GetServiceBearerToken has no resource-level permission
273
+ Sid = "StsForEcrPublicPull"
274
+ Effect = "Allow"
275
+ Action = ["sts:GetServiceBearerToken"]
276
+ Resource = ["*"]
277
+ },
278
+ {
279
+ #checkov:skip=CKV_AWS_355:XRayTracingAccess requires a wildcard resource; the X-Ray segment and sampling APIs have no resource-level permission
280
+ #checkov:skip=CKV_AWS_290:XRayTracingAccess requires a wildcard resource; the X-Ray segment and sampling APIs have no resource-level permission
281
+ Sid = "XRayTracingAccess"
282
+ Effect = "Allow"
283
+ Action = [
284
+ "xray:PutTraceSegments",
285
+ "xray:PutTelemetryRecords",
286
+ "xray:GetSamplingRules",
287
+ "xray:GetSamplingTargets",
288
+ ]
289
+ Resource = ["*"]
290
+ },
291
+ {
292
+ Sid = "CloudWatchLogsGroup"
293
+ Effect = "Allow"
294
+ Action = [
295
+ "logs:CreateLogGroup",
296
+ "logs:DescribeLogStreams",
297
+ ]
298
+ Resource = [
299
+ "arn:\${local.partition}:logs:\${local.region}:\${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*",
300
+ ]
301
+ },
302
+ {
303
+ Sid = "CloudWatchLogsDescribeGroups"
304
+ Effect = "Allow"
305
+ Action = ["logs:DescribeLogGroups"]
306
+ Resource = ["arn:\${local.partition}:logs:\${local.region}:\${local.account_id}:log-group:*"]
307
+ },
308
+ {
309
+ Sid = "CloudWatchLogsStream"
310
+ Effect = "Allow"
311
+ Action = [
312
+ "logs:CreateLogStream",
313
+ "logs:PutLogEvents",
314
+ ]
315
+ Resource = [
316
+ "arn:\${local.partition}:logs:\${local.region}:\${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*",
317
+ ]
318
+ },
319
+ {
320
+ #checkov:skip=CKV_AWS_355:CloudWatchMetricsPublish requires a wildcard resource; cloudwatch:PutMetricData is scoped by the namespace condition instead
321
+ #checkov:skip=CKV_AWS_290:CloudWatchMetricsPublish requires a wildcard resource; cloudwatch:PutMetricData is scoped by the namespace condition instead
322
+ Sid = "CloudWatchMetricsPublish"
323
+ Effect = "Allow"
324
+ Action = ["cloudwatch:PutMetricData"]
325
+ Resource = ["*"]
326
+ Condition = {
327
+ StringEquals = {
328
+ "cloudwatch:namespace" = "bedrock-agentcore"
329
+ }
330
+ }
331
+ },
332
+ {
333
+ Sid = "AgentCoreWorkloadIdentity"
334
+ Effect = "Allow"
335
+ Action = [
336
+ "bedrock-agentcore:GetWorkloadAccessToken",
337
+ "bedrock-agentcore:GetWorkloadAccessTokenForJWT",
338
+ ]
339
+ Resource = [
340
+ "arn:\${local.partition}:bedrock-agentcore:\${local.region}:\${local.account_id}:workload-identity-directory/default",
341
+ "arn:\${local.partition}:bedrock-agentcore:\${local.region}:\${local.account_id}:workload-identity-directory/default/workload-identity/harness_\${local.harness_name}-*",
342
+ ]
343
+ },
344
+ {
345
+ Sid = "AgentCoreBrowserDefault"
346
+ Effect = "Allow"
347
+ Action = [
348
+ "bedrock-agentcore:StartBrowserSession",
349
+ "bedrock-agentcore:StopBrowserSession",
350
+ "bedrock-agentcore:GetBrowserSession",
351
+ "bedrock-agentcore:ListBrowserSessions",
352
+ "bedrock-agentcore:UpdateBrowserStream",
353
+ "bedrock-agentcore:ConnectBrowserAutomationStream",
354
+ "bedrock-agentcore:ConnectBrowserLiveViewStream",
355
+ ]
356
+ Resource = [
357
+ "arn:\${local.partition}:bedrock-agentcore:\${local.region}:aws:browser/*",
358
+ ]
359
+ },
360
+ {
361
+ Sid = "AgentCoreCodeInterpreterDefault"
362
+ Effect = "Allow"
363
+ Action = [
364
+ "bedrock-agentcore:StartCodeInterpreterSession",
365
+ "bedrock-agentcore:StopCodeInterpreterSession",
366
+ "bedrock-agentcore:GetCodeInterpreterSession",
367
+ "bedrock-agentcore:ListCodeInterpreterSessions",
368
+ "bedrock-agentcore:InvokeCodeInterpreter",
369
+ ]
370
+ Resource = [
371
+ "arn:\${local.partition}:bedrock-agentcore:\${local.region}:aws:code-interpreter/*",
372
+ ]
373
+ },
374
+ ],
375
+ # In VPC mode the Harness pulls its managed container from a private ECR
376
+ # repository in the region rather than ECR Public, so it needs pull
377
+ # permissions on it. The repository is service-owned, hence the wildcard
378
+ # account.
379
+ var.enable_vpc ? [
380
+ {
381
+ Sid = "EcrManagedImagePull"
382
+ Effect = "Allow"
383
+ Action = [
384
+ "ecr:BatchGetImage",
385
+ "ecr:GetDownloadUrlForLayer",
386
+ "ecr:BatchCheckLayerAvailability",
387
+ ]
388
+ Resource = ["arn:\${local.partition}:ecr:\${local.region}:*:repository/harness-*"]
389
+ },
390
+ {
391
+ #checkov:skip=CKV_AWS_355:EcrManagedImageToken requires a wildcard resource; ecr:GetAuthorizationToken has no resource-level permission
392
+ Sid = "EcrManagedImageToken"
393
+ Effect = "Allow"
394
+ Action = ["ecr:GetAuthorizationToken"]
395
+ Resource = ["*"]
396
+ },
397
+ ] : [],
398
+ [
399
+ # Null Sid/Condition fields are dropped from the rendered policy.
400
+ for statement in var.additional_execution_role_policy_statements :
401
+ { for key, value in statement : key => value if value != null }
402
+ ])
403
+ })
404
+ }`;
405
+ /**
406
+ * Every shape the migration rewrites, so a module missing any of them is
407
+ * reported rather than half-migrated.
408
+ */ const REQUIRED_SHAPES = [
409
+ '`variable "model_resource_arns" { $_ }`',
410
+ '`variable "additional_execution_role_policy_statements" { $_ }`',
411
+ '`locals { $_ }`',
412
+ '`resource "aws_iam_role" "execution_role" { $_ }`',
413
+ '`resource "aws_iam_role_policy" "execution_role" { $_ }`',
414
+ '`resource "aws_bedrockagentcore_harness" "this" { $_ }`',
415
+ '`output "execution_role_arn" { $_ }`',
416
+ // The no-tools-by-default migration runs first, so its assignment is present
417
+ // and is what the native fields below are anchored on.
418
+ '`allowed_tools = var.allowed_tools`'
419
+ ];
420
+ /**
421
+ * The baseline policy is replaced whole, so it is only safe to touch when it
422
+ * still carries exactly the statements the generator wrote. Any addition,
423
+ * removal or reordering means the user has edited it, and the replacement would
424
+ * discard that.
425
+ */ const BASELINE_SIDS = [
426
+ 'BedrockModelInvocation',
427
+ 'EcrPublicTokenAccess',
428
+ 'StsForEcrPublicPull',
429
+ 'XRayTracingAccess',
430
+ 'CloudWatchLogsGroup',
431
+ 'CloudWatchLogsDescribeGroups',
432
+ 'CloudWatchLogsStream',
433
+ 'CloudWatchMetricsPublish',
434
+ 'AgentCoreWorkloadIdentity',
435
+ 'AgentCoreManagedMemory',
436
+ 'AgentCoreBrowserDefault',
437
+ 'AgentCoreCodeInterpreterDefault'
438
+ ];
439
+ /**
440
+ * Whether the module's baseline policy still carries exactly the generated
441
+ * statements, in order. Sids are read from the whole file: the only other `Sid`
442
+ * is the `optional(string)` field of the additional-statements variable, which
443
+ * carries no string literal and so is filtered out.
444
+ */ const hasUneditedBaselinePolicy = async (tree, filePath)=>{
445
+ const sids = (await captureAllGritQL(tree, filePath, hcl('`Sid = $sid`'))).map((text)=>/=\s*"([^"]+)"/.exec(text)?.[1]).filter((sid)=>sid !== undefined);
446
+ return sids.length === BASELINE_SIDS.length && sids.every((sid, index)=>sid === BASELINE_SIDS[index]);
447
+ };
448
+ export default async function migration(tree) {
449
+ const nextSteps = [];
450
+ if (!tree.exists(TERRAFORM_HARNESS_APP_DIR)) {
451
+ return {
452
+ nextSteps
453
+ }; // This workspace has no Terraform harness modules.
454
+ }
455
+ for (const dirName of tree.children(TERRAFORM_HARNESS_APP_DIR)){
456
+ const filePath = joinPathFragments(TERRAFORM_HARNESS_APP_DIR, dirName, `${dirName}.tf`);
457
+ if (!tree.exists(filePath)) {
458
+ continue; // Not a harness app module directory.
459
+ }
460
+ if (await matchGritQL(tree, filePath, hcl('`variable "enable_vpc" { $_ }`'))) {
461
+ continue; // Already migrated.
462
+ }
463
+ const shapeChecks = await Promise.all(REQUIRED_SHAPES.map((shape)=>matchGritQL(tree, filePath, hcl(shape))));
464
+ if (shapeChecks.includes(false) || !await hasUneditedBaselinePolicy(tree, filePath)) {
465
+ nextSteps.push(divergedMessage(filePath));
466
+ continue;
467
+ }
468
+ // Variables, ahead of the model/role variables they sit beside in the
469
+ // template.
470
+ await insertViaGritQL(tree, filePath, hcl(`\`variable "model_resource_arns" { $body }\` => \`${GRIT_INSERT_PLACEHOLDER}\n\nvariable "model_resource_arns" {\n $body\n}\``), NEW_VARIABLES_TEXT);
471
+ await insertViaGritQL(tree, filePath, hcl(`\`variable "additional_execution_role_policy_statements" { $body }\` => \`variable "additional_execution_role_policy_statements" {\n $body\n}\n\n${GRIT_INSERT_PLACEHOLDER}\``), VPC_VARIABLES_TEXT);
472
+ // Locals resolving the role ARN and whether managed memory applies.
473
+ await insertViaGritQL(tree, filePath, hcl(`\`locals { $body }\` => \`locals {\n $body\n\n${GRIT_INSERT_PLACEHOLDER}\n}\``), NEW_LOCALS_TEXT);
474
+ // The role becomes conditional on none being supplied, so it is indexed
475
+ // wherever it is referenced, and gains the shared tags.
476
+ await applyGritQL(tree, filePath, hcl('`resource "aws_iam_role" "execution_role" { $body }` => `resource "aws_iam_role" "execution_role" {\n count = var.create_execution_role ? 1 : 0\n\n $body\n\n tags = var.tags\n}`'));
477
+ // The baseline policy is replaced whole, dropping the managed-memory
478
+ // statement, which becomes a policy of its own scoped to the assigned ARN.
479
+ await insertViaGritQL(tree, filePath, hcl(`\`resource "aws_iam_role_policy" "execution_role" { $_ }\` => \`${GRIT_INSERT_PLACEHOLDER}\``), BASELINE_POLICY_TEXT);
480
+ // The role ARN follows whichever role is in use, and the `allowed_tools`
481
+ // line the no-tools-by-default migration added is re-aligned with the
482
+ // native fields that now sit beside it.
483
+ await applyGritQL(tree, filePath, hcl('`allowed_tools = var.allowed_tools` => ``'));
484
+ await insertViaGritQL(tree, filePath, hcl(`\`execution_role_arn = aws_iam_role.execution_role.arn\` => \`execution_role_arn = local.execution_role_arn\n\n${GRIT_INSERT_PLACEHOLDER}\``), HARNESS_FIELDS_TEXT);
485
+ // Ahead of `depends_on`, which the resource ends with.
486
+ await insertViaGritQL(tree, filePath, hcl(`\`depends_on = [aws_iam_role_policy.execution_role]\` => \`${GRIT_INSERT_PLACEHOLDER}\n\n depends_on = [aws_iam_role_policy.execution_role]\``), // The placeholder already sits at the block's indentation.
487
+ HARNESS_BLOCKS_TEXT.replace(/^ {2}/, ''));
488
+ // Security group ahead of the Harness resource, and the managed-memory
489
+ // policy after it, since it reads an attribute of the deployed Harness.
490
+ // Anchored on the comment introducing the resource so it stays attached.
491
+ await insertViaGritQL(tree, filePath, hcl(`\`# Omitting authorizer_configuration uses IAM inbound authorization; add a\` => \`${GRIT_INSERT_PLACEHOLDER}\n\n# Omitting authorizer_configuration uses IAM inbound authorization; add a\``), SECURITY_GROUP_TEXT);
492
+ await insertViaGritQL(tree, filePath, hcl(`\`resource "aws_bedrockagentcore_harness" "this" { $body }\` => \`resource "aws_bedrockagentcore_harness" "this" {\n $body\n}\n\n${GRIT_INSERT_PLACEHOLDER}\``), MANAGED_MEMORY_POLICY_TEXT);
493
+ // The role ARN output follows whichever role is in use, and the security
494
+ // group is exposed for ingress rules on resources the Harness reaches.
495
+ await applyGritQL(tree, filePath, hcl('`output "execution_role_arn" { $_ }` => `output "execution_role_arn" {\n description = "ARN of the IAM role assumed by the Harness, whether generated or supplied via var.execution_role_arn"\n value = local.execution_role_arn\n}`'));
496
+ await insertViaGritQL(tree, filePath, hcl(`\`output "execution_role_arn" { $body }\` => \`output "execution_role_arn" {\n $body\n}\n\n${GRIT_INSERT_PLACEHOLDER}\``), SECURITY_GROUP_OUTPUT_TEXT);
497
+ }
498
+ await formatFilesInSubtree(tree);
499
+ return {
500
+ nextSteps
501
+ };
502
+ }
503
+
504
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport {\n applyGritQL,\n captureAllGritQL,\n GRIT_INSERT_PLACEHOLDER,\n insertViaGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Bring each vended Terraform AgentCore Harness app module\n * (`app/harnesses/<name>/<name>.tf`) up to the shape the generator now writes:\n *\n * - `enable_vpc` / `vpc_id` / `subnet_ids` with a security group and a 443-only\n * egress rule, so the Harness can reach private resources.\n * - `create_execution_role` / `execution_role_arn`, so an existing role can be\n * supplied. The generated role and its baseline policy are then not created.\n * - `allowed_tools`, `memory`, `environment_variables`, `max_iterations` and\n * `timeout_seconds` as variables rather than HCL edits.\n * - The managed-memory grant moved to its own policy, made conditional and\n * re-scoped from a name-prefix guess to the ARN the service assigns.\n *\n * These files are generated with `KeepExisting`, so an upgraded workspace keeps\n * the older module until this runs. Modules that have diverged from the\n * generated shape are left untouched and reported via `nextSteps`.\n */\n\nconst TERRAFORM_HARNESS_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/harnesses`;\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\nconst divergedMessage = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To pick up VPC support, a supplied execution role, the allowed_tools/memory/environment_variables/max_iterations/timeout_seconds variables and the corrected managed-memory grant, compare it against the agentcore-harness generator's Terraform template and apply the parts you want.`;\n\n/**\n * Variables added ahead of the existing `model_resource_arns` variable.\n * `allowed_tools` is not among them: the earlier `agentcore-harness-no-tools-by-default`\n * migration already declares it, and runs first.\n */\nconst NEW_VARIABLES_TEXT = `variable \"memory\" {\n description = \"Memory configuration for the Harness. Null leaves the service to provision managed memory with default strategies. Set exactly one field: managed_memory_configuration to tune that managed memory, agentcore_memory_configuration to use a memory resource you own, or disabled to turn memory off.\"\n type = object({\n managed_memory_configuration = optional(object({\n strategies = optional(set(string))\n event_expiry_duration = optional(number)\n encryption_key_arn = optional(string)\n }))\n agentcore_memory_configuration = optional(object({\n arn = string\n actor_id = optional(string)\n messages_count = optional(number)\n }))\n disabled = optional(bool, false)\n })\n default = null\n\n validation {\n condition = var.memory == null || length([\n for configured in [\n var.memory.managed_memory_configuration != null,\n var.memory.agentcore_memory_configuration != null,\n var.memory.disabled,\n ] : configured if configured\n ]) == 1\n error_message = \"Set exactly one of memory.managed_memory_configuration, memory.agentcore_memory_configuration or memory.disabled.\"\n }\n}\n\nvariable \"environment_variables\" {\n description = \"Environment variables available to the Harness.\"\n type = map(string)\n default = null\n sensitive = true\n}\n\nvariable \"max_iterations\" {\n description = \"Maximum agent loop iterations per invocation. Uses the service default when null.\"\n type = number\n default = null\n}\n\nvariable \"timeout_seconds\" {\n description = \"Maximum seconds an invocation may run. Uses the service default when null.\"\n type = number\n default = null\n}\n\nvariable \"create_execution_role\" {\n description = \"Create the execution role with the baseline permissions below. Set false alongside execution_role_arn to use a role you already have, which is then used as-is.\"\n type = bool\n default = true\n\n validation {\n condition = var.create_execution_role || var.execution_role_arn != null\n error_message = \"execution_role_arn must be set when create_execution_role is false.\"\n }\n\n validation {\n condition = var.create_execution_role || (\n var.model_resource_arns == null && length(var.additional_execution_role_policy_statements) == 0\n )\n error_message = \"model_resource_arns and additional_execution_role_policy_statements configure the generated execution role, which is not created when create_execution_role is false. Grant those permissions on the supplied role instead.\"\n }\n}\n\nvariable \"execution_role_arn\" {\n description = \"ARN of an existing IAM role for the Harness to assume. Requires create_execution_role = false.\"\n type = string\n default = null\n}`;\n\n/** VPC and tag variables added after `additional_execution_role_policy_statements`. */\nconst VPC_VARIABLES_TEXT = `# VPC Configuration (optional)\nvariable \"enable_vpc\" {\n description = \"Run the Harness inside a VPC so it can reach private resources. Set alongside vpc_id/subnet_ids.\"\n type = bool\n default = false\n}\n\nvariable \"vpc_id\" {\n description = \"VPC ID to run the Harness in. Required when enable_vpc is true.\"\n type = string\n default = null\n\n validation {\n condition = !var.enable_vpc || (var.vpc_id != null && var.subnet_ids != null)\n error_message = \"vpc_id and subnet_ids must be set when enable_vpc is true.\"\n }\n}\n\nvariable \"subnet_ids\" {\n description = \"Subnet IDs to run the Harness in. Required when enable_vpc is true.\"\n type = list(string)\n default = null\n}\n\nvariable \"tags\" {\n description = \"Tags to apply to resources\"\n type = map(string)\n default = {}\n}`;\n\n/** Locals added to the module's existing `locals` block. */\nconst NEW_LOCALS_TEXT = ` execution_role_arn = var.create_execution_role ? aws_iam_role.execution_role[0].arn : var.execution_role_arn\n\n # The service provisions managed memory unless var.memory redirects it\n # elsewhere or turns it off.\n has_managed_memory = var.memory == null || var.memory.managed_memory_configuration != null`;\n\n/** Security group and egress rule, added before the Harness resource. */\nconst SECURITY_GROUP_TEXT = `# Security group for the Harness, used when running inside a VPC\nresource \"aws_security_group\" \"harness\" {\n count = var.enable_vpc ? 1 : 0\n\n #checkov:skip=CKV2_AWS_5:Attached to the Harness via its network configuration; Checkov cannot resolve this reference\n name_prefix = \"\\${local.harness_name_prefix}-harness-\"\n description = \"Security group for the \\${local.harness_name_prefix} Harness\"\n vpc_id = var.vpc_id\n tags = var.tags\n}\n\nresource \"aws_vpc_security_group_egress_rule\" \"harness_https\" {\n count = var.enable_vpc ? 1 : 0\n\n security_group_id = aws_security_group.harness[0].id\n cidr_ipv4 = \"0.0.0.0/0\"\n from_port = 443\n to_port = 443\n ip_protocol = \"tcp\"\n description = \"Allow outbound HTTPS to AWS service endpoints\"\n}`;\n\n/**\n * Native fields added to the Harness resource. `allowed_tools` is re-emitted\n * here, aligned with the rest, replacing the line the earlier\n * no-tools-by-default migration added.\n */\nconst HARNESS_FIELDS_TEXT = ` allowed_tools = var.allowed_tools\n environment_variables = var.environment_variables\n max_iterations = var.max_iterations\n timeout_seconds = var.timeout_seconds`;\n\nconst HARNESS_BLOCKS_TEXT = ` dynamic \"memory\" {\n for_each = var.memory != null ? [var.memory] : []\n content {\n dynamic \"managed_memory_configuration\" {\n for_each = memory.value.managed_memory_configuration != null ? [memory.value.managed_memory_configuration] : []\n content {\n strategies = managed_memory_configuration.value.strategies\n event_expiry_duration = managed_memory_configuration.value.event_expiry_duration\n encryption_key_arn = managed_memory_configuration.value.encryption_key_arn\n }\n }\n\n dynamic \"agentcore_memory_configuration\" {\n for_each = memory.value.agentcore_memory_configuration != null ? [memory.value.agentcore_memory_configuration] : []\n content {\n arn = agentcore_memory_configuration.value.arn\n actor_id = agentcore_memory_configuration.value.actor_id\n messages_count = agentcore_memory_configuration.value.messages_count\n }\n }\n\n dynamic \"disabled\" {\n for_each = memory.value.disabled ? [1] : []\n content {}\n }\n }\n }\n\n dynamic \"environment\" {\n for_each = var.enable_vpc ? [1] : []\n content {\n agentcore_runtime_environment {\n network_configuration {\n network_mode = \"VPC\"\n network_mode_config {\n security_groups = [aws_security_group.harness[0].id]\n subnets = var.subnet_ids\n }\n }\n }\n }\n }\n\n tags = var.tags`;\n\n/** The managed-memory grant, as its own policy scoped to the assigned ARN. */\nconst MANAGED_MEMORY_POLICY_TEXT = `# Scoped to the memory ARN the service assigns, which is only readable after the\n# Harness exists, so this is a policy of its own rather than a baseline\n# statement. Skipped when the Harness uses a role or memory the module did not\n# create.\nresource \"aws_iam_role_policy\" \"managed_memory\" {\n count = var.create_execution_role && local.has_managed_memory ? 1 : 0\n\n name = \"\\${local.harness_name_prefix}-HarnessManagedMemoryPolicy\"\n role = aws_iam_role.execution_role[0].id\n\n policy = jsonencode({\n Version = \"2012-10-17\"\n Statement = [{\n Sid = \"AgentCoreManagedMemory\"\n Effect = \"Allow\"\n Action = [\n \"bedrock-agentcore:CreateEvent\",\n \"bedrock-agentcore:DeleteEvent\",\n \"bedrock-agentcore:GetEvent\",\n \"bedrock-agentcore:ListEvents\",\n \"bedrock-agentcore:RetrieveMemoryRecords\",\n ]\n Resource = [\n aws_bedrockagentcore_harness.this.memory_actual[0].managed_memory_configuration[0].arn,\n ]\n }]\n })\n}`;\n\nconst SECURITY_GROUP_OUTPUT_TEXT = `output \"security_group_id\" {\n description = \"Security group ID of the Harness, for use in ingress rules on resources it must reach (e.g. a database). Null unless enable_vpc is true.\"\n value = var.enable_vpc ? aws_security_group.harness[0].id : null\n}`;\n\n/**\n * The baseline policy, replaced whole: the managed-memory statement leaves it\n * for a policy of its own, and the resource gains a `count`. Rewriting the\n * statement list in place would strand the comment and comma around the\n * statement being removed.\n */\nconst BASELINE_POLICY_TEXT = `resource \"aws_iam_role_policy\" \"execution_role\" {\n count = var.create_execution_role ? 1 : 0\n\n name = \"\\${local.harness_name_prefix}-HarnessPolicy\"\n role = aws_iam_role.execution_role[0].id\n\n policy = jsonencode({\n Version = \"2012-10-17\"\n Statement = concat([\n {\n Sid = \"BedrockModelInvocation\"\n Effect = \"Allow\"\n Action = [\n \"bedrock:InvokeModel\",\n \"bedrock:InvokeModelWithResponseStream\",\n ]\n Resource = local.model_resource_arns\n },\n {\n #checkov:skip=CKV_AWS_355:EcrPublicTokenAccess requires a wildcard resource; ecr-public:GetAuthorizationToken has no resource-level permission\n Sid = \"EcrPublicTokenAccess\"\n Effect = \"Allow\"\n Action = [\"ecr-public:GetAuthorizationToken\"]\n Resource = [\"*\"]\n },\n {\n #checkov:skip=CKV_AWS_355:StsForEcrPublicPull requires a wildcard resource; sts:GetServiceBearerToken has no resource-level permission\n Sid = \"StsForEcrPublicPull\"\n Effect = \"Allow\"\n Action = [\"sts:GetServiceBearerToken\"]\n Resource = [\"*\"]\n },\n {\n #checkov:skip=CKV_AWS_355:XRayTracingAccess requires a wildcard resource; the X-Ray segment and sampling APIs have no resource-level permission\n #checkov:skip=CKV_AWS_290:XRayTracingAccess requires a wildcard resource; the X-Ray segment and sampling APIs have no resource-level permission\n Sid = \"XRayTracingAccess\"\n Effect = \"Allow\"\n Action = [\n \"xray:PutTraceSegments\",\n \"xray:PutTelemetryRecords\",\n \"xray:GetSamplingRules\",\n \"xray:GetSamplingTargets\",\n ]\n Resource = [\"*\"]\n },\n {\n Sid = \"CloudWatchLogsGroup\"\n Effect = \"Allow\"\n Action = [\n \"logs:CreateLogGroup\",\n \"logs:DescribeLogStreams\",\n ]\n Resource = [\n \"arn:\\${local.partition}:logs:\\${local.region}:\\${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*\",\n ]\n },\n {\n Sid = \"CloudWatchLogsDescribeGroups\"\n Effect = \"Allow\"\n Action = [\"logs:DescribeLogGroups\"]\n Resource = [\"arn:\\${local.partition}:logs:\\${local.region}:\\${local.account_id}:log-group:*\"]\n },\n {\n Sid = \"CloudWatchLogsStream\"\n Effect = \"Allow\"\n Action = [\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\",\n ]\n Resource = [\n \"arn:\\${local.partition}:logs:\\${local.region}:\\${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*\",\n ]\n },\n {\n #checkov:skip=CKV_AWS_355:CloudWatchMetricsPublish requires a wildcard resource; cloudwatch:PutMetricData is scoped by the namespace condition instead\n #checkov:skip=CKV_AWS_290:CloudWatchMetricsPublish requires a wildcard resource; cloudwatch:PutMetricData is scoped by the namespace condition instead\n Sid = \"CloudWatchMetricsPublish\"\n Effect = \"Allow\"\n Action = [\"cloudwatch:PutMetricData\"]\n Resource = [\"*\"]\n Condition = {\n StringEquals = {\n \"cloudwatch:namespace\" = \"bedrock-agentcore\"\n }\n }\n },\n {\n Sid = \"AgentCoreWorkloadIdentity\"\n Effect = \"Allow\"\n Action = [\n \"bedrock-agentcore:GetWorkloadAccessToken\",\n \"bedrock-agentcore:GetWorkloadAccessTokenForJWT\",\n ]\n Resource = [\n \"arn:\\${local.partition}:bedrock-agentcore:\\${local.region}:\\${local.account_id}:workload-identity-directory/default\",\n \"arn:\\${local.partition}:bedrock-agentcore:\\${local.region}:\\${local.account_id}:workload-identity-directory/default/workload-identity/harness_\\${local.harness_name}-*\",\n ]\n },\n {\n Sid = \"AgentCoreBrowserDefault\"\n Effect = \"Allow\"\n Action = [\n \"bedrock-agentcore:StartBrowserSession\",\n \"bedrock-agentcore:StopBrowserSession\",\n \"bedrock-agentcore:GetBrowserSession\",\n \"bedrock-agentcore:ListBrowserSessions\",\n \"bedrock-agentcore:UpdateBrowserStream\",\n \"bedrock-agentcore:ConnectBrowserAutomationStream\",\n \"bedrock-agentcore:ConnectBrowserLiveViewStream\",\n ]\n Resource = [\n \"arn:\\${local.partition}:bedrock-agentcore:\\${local.region}:aws:browser/*\",\n ]\n },\n {\n Sid = \"AgentCoreCodeInterpreterDefault\"\n Effect = \"Allow\"\n Action = [\n \"bedrock-agentcore:StartCodeInterpreterSession\",\n \"bedrock-agentcore:StopCodeInterpreterSession\",\n \"bedrock-agentcore:GetCodeInterpreterSession\",\n \"bedrock-agentcore:ListCodeInterpreterSessions\",\n \"bedrock-agentcore:InvokeCodeInterpreter\",\n ]\n Resource = [\n \"arn:\\${local.partition}:bedrock-agentcore:\\${local.region}:aws:code-interpreter/*\",\n ]\n },\n ],\n # In VPC mode the Harness pulls its managed container from a private ECR\n # repository in the region rather than ECR Public, so it needs pull\n # permissions on it. The repository is service-owned, hence the wildcard\n # account.\n var.enable_vpc ? [\n {\n Sid = \"EcrManagedImagePull\"\n Effect = \"Allow\"\n Action = [\n \"ecr:BatchGetImage\",\n \"ecr:GetDownloadUrlForLayer\",\n \"ecr:BatchCheckLayerAvailability\",\n ]\n Resource = [\"arn:\\${local.partition}:ecr:\\${local.region}:*:repository/harness-*\"]\n },\n {\n #checkov:skip=CKV_AWS_355:EcrManagedImageToken requires a wildcard resource; ecr:GetAuthorizationToken has no resource-level permission\n Sid = \"EcrManagedImageToken\"\n Effect = \"Allow\"\n Action = [\"ecr:GetAuthorizationToken\"]\n Resource = [\"*\"]\n },\n ] : [],\n [\n # Null Sid/Condition fields are dropped from the rendered policy.\n for statement in var.additional_execution_role_policy_statements :\n { for key, value in statement : key => value if value != null }\n ])\n })\n}`;\n\n/**\n * Every shape the migration rewrites, so a module missing any of them is\n * reported rather than half-migrated.\n */\nconst REQUIRED_SHAPES = [\n '`variable \"model_resource_arns\" { $_ }`',\n '`variable \"additional_execution_role_policy_statements\" { $_ }`',\n '`locals { $_ }`',\n '`resource \"aws_iam_role\" \"execution_role\" { $_ }`',\n '`resource \"aws_iam_role_policy\" \"execution_role\" { $_ }`',\n '`resource \"aws_bedrockagentcore_harness\" \"this\" { $_ }`',\n '`output \"execution_role_arn\" { $_ }`',\n // The no-tools-by-default migration runs first, so its assignment is present\n // and is what the native fields below are anchored on.\n '`allowed_tools = var.allowed_tools`',\n];\n\n/**\n * The baseline policy is replaced whole, so it is only safe to touch when it\n * still carries exactly the statements the generator wrote. Any addition,\n * removal or reordering means the user has edited it, and the replacement would\n * discard that.\n */\nconst BASELINE_SIDS = [\n 'BedrockModelInvocation',\n 'EcrPublicTokenAccess',\n 'StsForEcrPublicPull',\n 'XRayTracingAccess',\n 'CloudWatchLogsGroup',\n 'CloudWatchLogsDescribeGroups',\n 'CloudWatchLogsStream',\n 'CloudWatchMetricsPublish',\n 'AgentCoreWorkloadIdentity',\n 'AgentCoreManagedMemory',\n 'AgentCoreBrowserDefault',\n 'AgentCoreCodeInterpreterDefault',\n];\n\n/**\n * Whether the module's baseline policy still carries exactly the generated\n * statements, in order. Sids are read from the whole file: the only other `Sid`\n * is the `optional(string)` field of the additional-statements variable, which\n * carries no string literal and so is filtered out.\n */\nconst hasUneditedBaselinePolicy = async (\n tree: Tree,\n filePath: string,\n): Promise<boolean> => {\n const sids = (await captureAllGritQL(tree, filePath, hcl('`Sid = $sid`')))\n .map((text) => /=\\s*\"([^\"]+)\"/.exec(text)?.[1])\n .filter((sid): sid is string => sid !== undefined);\n return (\n sids.length === BASELINE_SIDS.length &&\n sids.every((sid, index) => sid === BASELINE_SIDS[index])\n );\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (!tree.exists(TERRAFORM_HARNESS_APP_DIR)) {\n return { nextSteps }; // This workspace has no Terraform harness modules.\n }\n\n for (const dirName of tree.children(TERRAFORM_HARNESS_APP_DIR)) {\n const filePath = joinPathFragments(\n TERRAFORM_HARNESS_APP_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a harness app module directory.\n }\n\n if (\n await matchGritQL(tree, filePath, hcl('`variable \"enable_vpc\" { $_ }`'))\n ) {\n continue; // Already migrated.\n }\n\n const shapeChecks = await Promise.all(\n REQUIRED_SHAPES.map((shape) => matchGritQL(tree, filePath, hcl(shape))),\n );\n if (\n shapeChecks.includes(false) ||\n !(await hasUneditedBaselinePolicy(tree, filePath))\n ) {\n nextSteps.push(divergedMessage(filePath));\n continue;\n }\n\n // Variables, ahead of the model/role variables they sit beside in the\n // template.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`variable \"model_resource_arns\" { $body }\\` => \\`${GRIT_INSERT_PLACEHOLDER}\\n\\nvariable \"model_resource_arns\" {\\n $body\\n}\\``,\n ),\n NEW_VARIABLES_TEXT,\n );\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`variable \"additional_execution_role_policy_statements\" { $body }\\` => \\`variable \"additional_execution_role_policy_statements\" {\\n $body\\n}\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n VPC_VARIABLES_TEXT,\n );\n\n // Locals resolving the role ARN and whether managed memory applies.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`locals { $body }\\` => \\`locals {\\n $body\\n\\n${GRIT_INSERT_PLACEHOLDER}\\n}\\``,\n ),\n NEW_LOCALS_TEXT,\n );\n\n // The role becomes conditional on none being supplied, so it is indexed\n // wherever it is referenced, and gains the shared tags.\n await applyGritQL(\n tree,\n filePath,\n hcl(\n '`resource \"aws_iam_role\" \"execution_role\" { $body }` => `resource \"aws_iam_role\" \"execution_role\" {\\n count = var.create_execution_role ? 1 : 0\\n\\n $body\\n\\n tags = var.tags\\n}`',\n ),\n );\n\n // The baseline policy is replaced whole, dropping the managed-memory\n // statement, which becomes a policy of its own scoped to the assigned ARN.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"aws_iam_role_policy\" \"execution_role\" { $_ }\\` => \\`${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n BASELINE_POLICY_TEXT,\n );\n\n // The role ARN follows whichever role is in use, and the `allowed_tools`\n // line the no-tools-by-default migration added is re-aligned with the\n // native fields that now sit beside it.\n await applyGritQL(\n tree,\n filePath,\n hcl('`allowed_tools = var.allowed_tools` => ``'),\n );\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`execution_role_arn = aws_iam_role.execution_role.arn\\` => \\`execution_role_arn = local.execution_role_arn\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n HARNESS_FIELDS_TEXT,\n );\n // Ahead of `depends_on`, which the resource ends with.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`depends_on = [aws_iam_role_policy.execution_role]\\` => \\`${GRIT_INSERT_PLACEHOLDER}\\n\\n depends_on = [aws_iam_role_policy.execution_role]\\``,\n ),\n // The placeholder already sits at the block's indentation.\n HARNESS_BLOCKS_TEXT.replace(/^ {2}/, ''),\n );\n\n // Security group ahead of the Harness resource, and the managed-memory\n // policy after it, since it reads an attribute of the deployed Harness.\n // Anchored on the comment introducing the resource so it stays attached.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`# Omitting authorizer_configuration uses IAM inbound authorization; add a\\` => \\`${GRIT_INSERT_PLACEHOLDER}\\n\\n# Omitting authorizer_configuration uses IAM inbound authorization; add a\\``,\n ),\n SECURITY_GROUP_TEXT,\n );\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"aws_bedrockagentcore_harness\" \"this\" { $body }\\` => \\`resource \"aws_bedrockagentcore_harness\" \"this\" {\\n $body\\n}\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n MANAGED_MEMORY_POLICY_TEXT,\n );\n\n // The role ARN output follows whichever role is in use, and the security\n // group is exposed for ingress rules on resources the Harness reaches.\n await applyGritQL(\n tree,\n filePath,\n hcl(\n '`output \"execution_role_arn\" { $_ }` => `output \"execution_role_arn\" {\\n description = \"ARN of the IAM role assumed by the Harness, whether generated or supplied via var.execution_role_arn\"\\n value = local.execution_role_arn\\n}`',\n ),\n );\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`output \"execution_role_arn\" { $body }\\` => \\`output \"execution_role_arn\" {\\n $body\\n}\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n SECURITY_GROUP_OUTPUT_TEXT,\n );\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","applyGritQL","captureAllGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TERRAFORM_HARNESS_APP_DIR","hcl","pattern","divergedMessage","filePath","NEW_VARIABLES_TEXT","VPC_VARIABLES_TEXT","NEW_LOCALS_TEXT","SECURITY_GROUP_TEXT","HARNESS_FIELDS_TEXT","HARNESS_BLOCKS_TEXT","MANAGED_MEMORY_POLICY_TEXT","SECURITY_GROUP_OUTPUT_TEXT","BASELINE_POLICY_TEXT","REQUIRED_SHAPES","BASELINE_SIDS","hasUneditedBaselinePolicy","tree","sids","map","text","exec","filter","sid","undefined","length","every","index","migration","nextSteps","exists","dirName","children","shapeChecks","Promise","all","shape","includes","push","replace"],"mappings":"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"}