@aws/nx-plugin 1.0.0-rc.87 → 1.0.0-rc.89
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/LICENSE-THIRD-PARTY +9977 -16000
- package/migrations.json +124 -76
- package/package.json +7 -7
- package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/infra/app/__snapshots__/generator.spec.ts.snap +7 -7
- package/src/migrations/latest/nx-parallel-default/metadata.json +3 -0
- package/src/migrations/latest/nx-parallel-default/migration.d.ts +14 -0
- package/src/migrations/latest/nx-parallel-default/migration.js +29 -0
- package/src/migrations/latest/nx-parallel-default/migration.js.map +1 -0
- package/src/migrations/latest/python-version-drop-exact-patch/metadata.json +3 -0
- package/src/migrations/latest/python-version-drop-exact-patch/migration.js +43 -0
- package/src/migrations/latest/python-version-drop-exact-patch/migration.js.map +1 -0
- package/src/migrations/latest/react-website-nx-react-express-override/metadata.json +3 -0
- package/src/migrations/latest/react-website-nx-react-express-override/migration.d.ts +31 -0
- package/src/migrations/latest/react-website-nx-react-express-override/migration.js +74 -0
- package/src/migrations/latest/react-website-nx-react-express-override/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.77/0001-dynamodb-configurable-encryption/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.77/0002-user-identity-configurable-mfa/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.78/0001-static-website-configurable-waf-encryption/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.79/0001-py-agent-ag-ui-hooks/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.82/0002-runtime-config-namespace-defaults/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.84/0003-terraform-declare-used-providers/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.84/0004-waf-log-group-cmk-encryption/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
- package/src/migrations/v1.0.0-rc.85/0001-terraform-project-checkov-target-and-bootstrap-destroy/migration.d.ts +6 -0
- package/src/migrations/v1.0.0-rc.85/0001-terraform-project-checkov-target-and-bootstrap-destroy/migration.js.map +1 -0
- package/src/preset/__snapshots__/generator.spec.ts.snap +5 -4
- package/src/py/agent/__snapshots__/generator.constructs.spec.ts.snap +2 -2
- package/src/py/agent/__snapshots__/generator.frameworks.spec.ts.snap +5 -5
- package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +4 -4
- package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +12 -12
- package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +3 -3
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/smithy/project/__snapshots__/generator.spec.ts.snap +7 -7
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +8 -8
- package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +12 -12
- package/src/ts/agent/__snapshots__/generator.spec.ts.snap +3 -3
- package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +1 -1
- package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +2 -2
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +5 -5
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +59 -49
- package/src/ts/react-website/app/generator.d.ts +3 -0
- package/src/ts/react-website/app/generator.js +25 -1
- package/src/ts/react-website/app/generator.js.map +1 -1
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +2 -2
- package/src/utils/init.d.ts +1 -0
- package/src/utils/init.js +2 -0
- package/src/utils/init.js.map +1 -1
- package/src/utils/lambda-runtime-resolution.d.ts +0 -21
- package/src/utils/lambda-runtime-resolution.js +0 -14
- package/src/utils/lambda-runtime-resolution.js.map +1 -1
- package/src/utils/version-upgrade-migration/sync-python-language-version.d.ts +2 -2
- package/src/utils/version-upgrade-migration/sync-python-language-version.js +2 -2
- package/src/utils/version-upgrade-migration/sync-python-language-version.js.map +1 -1
- package/src/utils/versions.d.ts +58 -62
- package/src/utils/versions.js +57 -60
- package/src/utils/versions.js.map +1 -1
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +0 -1
- package/src/migrations/latest/dynamodb-configurable-encryption/migration.js.map +0 -1
- package/src/migrations/latest/dynamodb-terraform-app-module-outputs/migration.js.map +0 -1
- package/src/migrations/latest/py-agent-ag-ui-hooks/migration.js.map +0 -1
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +0 -1
- package/src/migrations/latest/runtime-config-namespace-defaults/migration.js.map +0 -1
- package/src/migrations/latest/static-website-configurable-waf-encryption/migration.js.map +0 -1
- package/src/migrations/latest/terraform-data-resource-destroy-protection/migration.js.map +0 -1
- package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +0 -1
- package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration.js.map +0 -1
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +0 -1
- package/src/migrations/latest/terraform-load-runtime-config-src-path/migration.js.map +0 -1
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js.map +0 -1
- package/src/migrations/latest/user-identity-configurable-mfa/migration.js.map +0 -1
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +0 -1
- /package/src/migrations/latest/{agentcore-harness-no-tools-by-default → python-version-drop-exact-patch}/migration.d.ts +0 -0
- /package/src/migrations/{latest/dynamodb-configurable-encryption → v1.0.0-rc.77/0001-dynamodb-configurable-encryption}/__snapshots__/migration.spec.ts.snap +0 -0
- /package/src/migrations/{latest/dynamodb-configurable-encryption → v1.0.0-rc.77/0001-dynamodb-configurable-encryption}/metadata.json +0 -0
- /package/src/migrations/{latest/dynamodb-configurable-encryption → v1.0.0-rc.77/0001-dynamodb-configurable-encryption}/migration.d.ts +0 -0
- /package/src/migrations/{latest/dynamodb-configurable-encryption → v1.0.0-rc.77/0001-dynamodb-configurable-encryption}/migration.js +0 -0
- /package/src/migrations/{latest/user-identity-configurable-mfa → v1.0.0-rc.77/0002-user-identity-configurable-mfa}/metadata.json +0 -0
- /package/src/migrations/{latest/terraform-data-resource-destroy-protection → v1.0.0-rc.77/0002-user-identity-configurable-mfa}/migration.d.ts +0 -0
- /package/src/migrations/{latest/user-identity-configurable-mfa → v1.0.0-rc.77/0002-user-identity-configurable-mfa}/migration.js +0 -0
- /package/src/migrations/{latest/static-website-configurable-waf-encryption → v1.0.0-rc.78/0001-static-website-configurable-waf-encryption}/metadata.json +0 -0
- /package/src/migrations/{latest/dynamodb-terraform-app-module-outputs → v1.0.0-rc.78/0001-static-website-configurable-waf-encryption}/migration.d.ts +0 -0
- /package/src/migrations/{latest/static-website-configurable-waf-encryption → v1.0.0-rc.78/0001-static-website-configurable-waf-encryption}/migration.js +0 -0
- /package/src/migrations/{latest/py-agent-ag-ui-hooks → v1.0.0-rc.79/0001-py-agent-ag-ui-hooks}/metadata.json +0 -0
- /package/src/migrations/{latest/py-agent-ag-ui-hooks → v1.0.0-rc.79/0001-py-agent-ag-ui-hooks}/migration.d.ts +0 -0
- /package/src/migrations/{latest/py-agent-ag-ui-hooks → v1.0.0-rc.79/0001-py-agent-ag-ui-hooks}/migration.js +0 -0
- /package/src/migrations/{latest/terraform-load-runtime-config-src-path → v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path}/metadata.json +0 -0
- /package/src/migrations/{latest/rdb-terraform-aurora-app-module-defaults → v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path}/migration.d.ts +0 -0
- /package/src/migrations/{latest/terraform-load-runtime-config-src-path → v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path}/migration.js +0 -0
- /package/src/migrations/{latest/runtime-config-namespace-defaults → v1.0.0-rc.82/0002-runtime-config-namespace-defaults}/metadata.json +0 -0
- /package/src/migrations/{latest/runtime-config-namespace-defaults → v1.0.0-rc.82/0002-runtime-config-namespace-defaults}/migration.d.ts +0 -0
- /package/src/migrations/{latest/runtime-config-namespace-defaults → v1.0.0-rc.82/0002-runtime-config-namespace-defaults}/migration.js +0 -0
- /package/src/migrations/{latest/dynamodb-terraform-app-module-outputs → v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs}/metadata.json +0 -0
- /package/src/migrations/{latest/static-website-configurable-waf-encryption → v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs}/migration.d.ts +0 -0
- /package/src/migrations/{latest/dynamodb-terraform-app-module-outputs → v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs}/migration.js +0 -0
- /package/src/migrations/{latest/terraform-data-resource-destroy-protection → v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection}/__snapshots__/migration.spec.ts.snap +0 -0
- /package/src/migrations/{latest/terraform-data-resource-destroy-protection → v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection}/metadata.json +0 -0
- /package/src/migrations/{latest/user-identity-configurable-mfa → v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection}/migration.d.ts +0 -0
- /package/src/migrations/{latest/terraform-data-resource-destroy-protection → v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection}/migration.js +0 -0
- /package/src/migrations/{latest/terraform-gateway-cedar-render-root-ejs → v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs}/metadata.json +0 -0
- /package/src/migrations/{latest/terraform-declare-used-providers → v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs}/migration.d.ts +0 -0
- /package/src/migrations/{latest/terraform-gateway-cedar-render-root-ejs → v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs}/migration.js +0 -0
- /package/src/migrations/{latest/rdb-terraform-aurora-app-module-defaults → v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults}/__snapshots__/migration.spec.ts.snap +0 -0
- /package/src/migrations/{latest/rdb-terraform-aurora-app-module-defaults → v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults}/metadata.json +0 -0
- /package/src/migrations/{latest/terraform-gateway-cedar-render-root-ejs → v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults}/migration.d.ts +0 -0
- /package/src/migrations/{latest/rdb-terraform-aurora-app-module-defaults → v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults}/migration.js +0 -0
- /package/src/migrations/{latest/agentcore-harness-no-tools-by-default → v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default}/__snapshots__/migration.spec.ts.snap +0 -0
- /package/src/migrations/{latest/agentcore-harness-no-tools-by-default → v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default}/metadata.json +0 -0
- /package/src/migrations/{latest/terraform-harness-vpc-role-and-memory-grant → v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default}/migration.d.ts +0 -0
- /package/src/migrations/{latest/agentcore-harness-no-tools-by-default → v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default}/migration.js +0 -0
- /package/src/migrations/{latest/terraform-declare-used-providers → v1.0.0-rc.84/0003-terraform-declare-used-providers}/metadata.json +0 -0
- /package/src/migrations/{latest/terraform-load-runtime-config-src-path → v1.0.0-rc.84/0003-terraform-declare-used-providers}/migration.d.ts +0 -0
- /package/src/migrations/{latest/terraform-declare-used-providers → v1.0.0-rc.84/0003-terraform-declare-used-providers}/migration.js +0 -0
- /package/src/migrations/{latest/waf-log-group-cmk-encryption → v1.0.0-rc.84/0004-waf-log-group-cmk-encryption}/__snapshots__/migration.spec.ts.snap +0 -0
- /package/src/migrations/{latest/waf-log-group-cmk-encryption → v1.0.0-rc.84/0004-waf-log-group-cmk-encryption}/metadata.json +0 -0
- /package/src/migrations/{latest/terraform-project-checkov-target-and-bootstrap-destroy → v1.0.0-rc.84/0004-waf-log-group-cmk-encryption}/migration.d.ts +0 -0
- /package/src/migrations/{latest/waf-log-group-cmk-encryption → v1.0.0-rc.84/0004-waf-log-group-cmk-encryption}/migration.js +0 -0
- /package/src/migrations/{latest/terraform-harness-vpc-role-and-memory-grant → v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant}/__snapshots__/migration.spec.ts.snap +0 -0
- /package/src/migrations/{latest/terraform-harness-vpc-role-and-memory-grant → v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant}/metadata.json +0 -0
- /package/src/migrations/{latest/waf-log-group-cmk-encryption → v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant}/migration.d.ts +0 -0
- /package/src/migrations/{latest/terraform-harness-vpc-role-and-memory-grant → v1.0.0-rc.84/0005-terraform-harness-vpc-role-and-memory-grant}/migration.js +0 -0
- /package/src/migrations/{latest/terraform-project-checkov-target-and-bootstrap-destroy → v1.0.0-rc.85/0001-terraform-project-checkov-target-and-bootstrap-destroy}/metadata.json +0 -0
- /package/src/migrations/{latest/terraform-project-checkov-target-and-bootstrap-destroy → v1.0.0-rc.85/0001-terraform-project-checkov-target-and-bootstrap-destroy}/migration.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.77/0001-dynamodb-configurable-encryption/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 addDestructuredImport,\n applyGritQL,\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_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Bring existing workspaces up to the generators' current DynamoDBTable\n * configurability:\n *\n * - The vended `DynamoDBTable` CDK construct gains `encryption` and\n * `encryptionKey` props. The KMS key is only created when `encryption` is\n * `TableEncryption.CUSTOMER_MANAGED` and no `encryptionKey` is supplied.\n * `enableKeyRotation` (already present) only applies to that auto-created\n * key.\n * - The generated per-table CDK app construct already forwards an optional\n * `props` parameter through to `DynamoDBTable`, so no change is needed\n * there.\n * - The vended Terraform dynamodb core module gains the equivalent\n * `encryption`, `kms_key_arn` and `create_kms_key` variables (`encryption`\n * also accepts `DEFAULT`, the AWS owned key, which CDK already supports for\n * free via `TableEncryption.DEFAULT`), with the KMS key made conditional on\n * `create_kms_key` (not on `kms_key_arn`'s nullness, which `count` can't\n * depend on when the ARN comes from another resource) and\n * `server_side_encryption.enabled` tied to `encryption`. `AWS_MANAGED`\n * resolves to the explicit `alias/aws/dynamodb` ARN rather than `null`,\n * since the provider's `kms_key_arn` is Optional+Computed and a `null`\n * there means \"leave whatever is there\", not \"clear it\".\n * - The vended Terraform per-table app module (`app/dynamodb/<name>/<name>.tf`)\n * gains pass-through `encryption`, `kms_key_arn` and `create_kms_key`\n * variables forwarded to the core module call, matching its existing\n * `enable_key_rotation` pass-through.\n *\n * These files are generated with `KeepExisting`, so without this an upgraded\n * workspace has generators that support this configuration but vended files\n * that don't. Diverged files are left untouched and reported via `nextSteps`.\n */\n\nconst CDK_DYNAMODB_FILE = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/core/dynamodb.ts`;\nconst TERRAFORM_DYNAMODB_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/dynamodb/dynamodb.tf`;\nconst TERRAFORM_DYNAMODB_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/dynamodb`;\n\nconst CDK_DIVERGED_MESSAGE = `${CDK_DYNAMODB_FILE}: has diverged from the generated shape - left untouched. To pick up the encryption and encryptionKey props, manually port them from the vended core/dynamodb.ts template (see the ts#dynamodb generator's DynamoDBTable construct).`;\n\nconst TERRAFORM_DIVERGED_MESSAGE = `${TERRAFORM_DYNAMODB_FILE}: has diverged from the generated shape - left untouched. To pick up the encryption and kms_key_arn variables, manually port them from the vended dynamodb.tf template (see the ts#dynamodb generator's dynamodb module).`;\n\nconst terraformAppDivergedMessage = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To make encryption and kms_key_arn configurable from your root Terraform configuration, add pass-through variables here and forward them to the dynamodb_table module call (see the ts#dynamodb generator's dynamodb app template).`;\n\n/**\n * Surface encryption/encryptionKey on the vended DynamoDBTable CDK\n * construct.\n */\nconst migrateCdkConstruct = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(CDK_DYNAMODB_FILE)) {\n return; // This workspace has no CDK DynamoDBTable construct.\n }\n\n if (\n await matchGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n '`readonly encryption?: TableEncryption`',\n )\n ) {\n return; // Already migrated.\n }\n\n const ENABLE_KEY_ROTATION_FIELD = '`readonly enableKeyRotation?: boolean`';\n // Matched as a literal so the new fields can be inserted *before* it (see\n // below) — GritQL doesn't include a leading comment in the span it matches\n // for the field itself, so anchoring on the field would strand this\n // comment above the newly-inserted fields instead of above the field it\n // actually documents.\n const ENABLE_KEY_ROTATION_COMMENT = `\\`/**\n * Whether to enable automatic key rotation on the KMS key used to encrypt the table.\n *\n * @default true\n */\\``;\n const DESTRUCTURE_OLD = `\\`{\n tableName,\n runtimeConfigKey,\n billingMode = BillingMode.PAY_PER_REQUEST,\n pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: true },\n deletionProtection = true,\n removalPolicy = RemovalPolicy.RETAIN,\n enableKeyRotation = true,\n ...rest\n }: DynamoDBTableProps\\``;\n const KEY_CREATION_OLD =\n \"`const key = new Key(this, 'EncryptionKey', { enableKeyRotation });`\";\n const TABLE_ENCRYPTION_OLD = '`encryption: TableEncryption.CUSTOMER_MANAGED`';\n\n const anchors = [\n ENABLE_KEY_ROTATION_FIELD,\n ENABLE_KEY_ROTATION_COMMENT,\n DESTRUCTURE_OLD,\n KEY_CREATION_OLD,\n TABLE_ENCRYPTION_OLD,\n ];\n\n const allPresent = (\n await Promise.all(\n anchors.map((pattern) => matchGritQL(tree, CDK_DYNAMODB_FILE, pattern)),\n )\n ).every(Boolean);\n\n if (!allPresent) {\n nextSteps.push(CDK_DIVERGED_MESSAGE);\n return;\n }\n\n await addDestructuredImport(\n tree,\n CDK_DYNAMODB_FILE,\n ['IKey'],\n 'aws-cdk-lib/aws-kms',\n );\n\n // Inserted before the enableKeyRotation field's own leading comment (rather\n // than before the bare field) so that comment stays directly above the\n // field it documents, instead of being stranded above the new fields.\n await insertViaGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${ENABLE_KEY_ROTATION_COMMENT} as $comment => \\`${GRIT_INSERT_PLACEHOLDER}\n $comment\\``,\n `/**\n * Server-side encryption for the table.\n *\n * @default TableEncryption.CUSTOMER_MANAGED\n */\n readonly encryption?: TableEncryption;\n\n /**\n * KMS key used to encrypt the table. Only used when \\`encryption\\` is\n * \\`TableEncryption.CUSTOMER_MANAGED\\`. When not provided, a new key is created.\n */\n readonly encryptionKey?: IKey;\n\n `,\n );\n\n // Update the enableKeyRotation field's own comment to note it now only\n // applies to the auto-created key, matching the vended template exactly.\n // Routed through the insert placeholder (rather than embedding the text\n // directly in the GritQL rewrite) since the comment's own backticks would\n // otherwise be parsed as GritQL code-block delimiters.\n await insertViaGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${ENABLE_KEY_ROTATION_COMMENT} => \\`${GRIT_INSERT_PLACEHOLDER}\\``,\n `/**\n * Whether to enable automatic key rotation on the KMS key used to encrypt the table.\n * Only applies when \\`encryption\\` is \\`TableEncryption.CUSTOMER_MANAGED\\` and no\n * \\`encryptionKey\\` is supplied.\n *\n * @default true\n */`,\n );\n\n await applyGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${DESTRUCTURE_OLD} => \\`{\n tableName,\n runtimeConfigKey,\n billingMode = BillingMode.PAY_PER_REQUEST,\n pointInTimeRecoverySpecification = { pointInTimeRecoveryEnabled: true },\n deletionProtection = true,\n removalPolicy = RemovalPolicy.RETAIN,\n encryption = TableEncryption.CUSTOMER_MANAGED,\n encryptionKey,\n enableKeyRotation = true,\n ...rest\n }: DynamoDBTableProps\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${KEY_CREATION_OLD} => \\`const key: IKey | undefined =\n encryption === TableEncryption.CUSTOMER_MANAGED\n ? (encryptionKey ?? new Key(this, 'EncryptionKey', { enableKeyRotation }))\n : undefined;\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_DYNAMODB_FILE,\n `${TABLE_ENCRYPTION_OLD} => \\`encryption\\``,\n );\n};\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\nconst NEW_TERRAFORM_VARIABLES_TEXT = [\n 'variable \"encryption\" {',\n ' description = \"Server-side encryption for the table. One of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT (the AWS owned key, at no cost and with no key to manage). Changing this on an already-deployed table away from CUSTOMER_MANAGED requires a manual step first - see the ts#dynamodb generator docs.\"',\n ' type = string',\n ' default = \"CUSTOMER_MANAGED\"',\n '',\n ' validation {',\n ' condition = contains([\"CUSTOMER_MANAGED\", \"AWS_MANAGED\", \"DEFAULT\"], var.encryption)',\n ' error_message = \"encryption must be one of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT.\"',\n ' }',\n '}',\n '',\n 'variable \"kms_key_arn\" {',\n ' description = \"ARN of an existing KMS key used to encrypt the table when encryption is CUSTOMER_MANAGED. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the DynamoDB service the necessary permissions in its own key policy.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"create_kms_key\" {',\n ' description = \"Whether to create a KMS key for the table. Only applies when encryption is CUSTOMER_MANAGED. Set to false when supplying kms_key_arn.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"enable_key_rotation\" {',\n ' description = \"Whether to enable automatic key rotation on the KMS key used to encrypt the table. Only applies when encryption is CUSTOMER_MANAGED and create_kms_key is true.\"',\n ' type = bool',\n ' default = true',\n '}',\n].join('\\n');\n\n/**\n * Surface encryption/kms_key_arn on the vended Terraform dynamodb core\n * module.\n */\nconst migrateTerraformModule = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(TERRAFORM_DYNAMODB_FILE)) {\n return; // This workspace has no Terraform dynamodb module.\n }\n\n if (\n await matchGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n hcl('`variable \"encryption\" { $_ }`'),\n )\n ) {\n return; // Already migrated.\n }\n\n const OLD_ENABLE_KEY_ROTATION_VAR = hcl(\n [\n '`variable \"enable_key_rotation\" {',\n ' description = \"Whether to enable automatic key rotation on the KMS key used to encrypt the table.\"',\n ' type = bool',\n ' default = true',\n '}`',\n ].join('\\n'),\n );\n const LOCALS_BLOCK = hcl('`locals { $_ }`');\n const DATA_PARTITION_LINE = hcl('`data \"aws_partition\" \"current\" {}`');\n const KMS_KEY_BLOCK = hcl('`resource \"aws_kms_key\" \"table\" { $_ }`');\n const SSE_ENABLED_LINE = hcl('`enabled = true`');\n const SSE_KMS_KEY_LINE = hcl('`kms_key_arn = aws_kms_key.table.arn`');\n const OUTPUT_VALUE_LINE = hcl('`value = aws_kms_key.table.arn`');\n const OUTPUT_DESCRIPTION_LINE = hcl(\n '`description = \"ARN of the KMS key used to encrypt the DynamoDB table.\"`',\n );\n\n const anchors = [\n OLD_ENABLE_KEY_ROTATION_VAR,\n LOCALS_BLOCK,\n DATA_PARTITION_LINE,\n KMS_KEY_BLOCK,\n SSE_ENABLED_LINE,\n SSE_KMS_KEY_LINE,\n OUTPUT_VALUE_LINE,\n OUTPUT_DESCRIPTION_LINE,\n ];\n\n const allPresent = (\n await Promise.all(\n anchors.map((pattern) =>\n matchGritQL(tree, TERRAFORM_DYNAMODB_FILE, pattern),\n ),\n )\n ).every(Boolean);\n\n if (!allPresent) {\n nextSteps.push(TERRAFORM_DIVERGED_MESSAGE);\n return;\n }\n\n // 1. Replace the enable_key_rotation variable with the new variables plus\n // an updated enable_key_rotation description, in the same position.\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${OLD_ENABLE_KEY_ROTATION_VAR} => \\`${NEW_TERRAFORM_VARIABLES_TEXT.replace(/`/g, '\\\\`')}\\``,\n );\n\n // 2. New locals, appended into the existing locals block.\n await insertViaGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n hcl(\n `\\`locals { $body }\\` => \\`locals {\\n $body\\n\\n ${GRIT_INSERT_PLACEHOLDER}\\n}\\``,\n ),\n [\n 'create_table_key = var.encryption == \"CUSTOMER_MANAGED\" && var.create_kms_key',\n 'table_kms_key_arn = (',\n ' var.encryption == \"CUSTOMER_MANAGED\" ? (local.create_table_key ? aws_kms_key.table[0].arn : var.kms_key_arn) :',\n ' var.encryption == \"AWS_MANAGED\" ? \"arn:${data.aws_partition.current.partition}:kms:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:alias/aws/dynamodb\" :',\n ' null',\n ')',\n ].join('\\n '),\n );\n\n // 2b. Region is needed to resolve the AWS_MANAGED key's alias ARN above.\n await insertViaGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${DATA_PARTITION_LINE} => \\`data \"aws_partition\" \"current\" {}\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n 'data \"aws_region\" \"current\" {}',\n );\n\n // 3. Make the KMS key conditional on encryption/kms_key_arn.\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n hcl(`\\`resource \"aws_kms_key\" \"table\" { $body }\\` => \\`resource \"aws_kms_key\" \"table\" {\n count = local.create_table_key ? 1 : 0\n\n $body\n}\\``),\n );\n\n // 4. Only enable KMS-backed encryption when not using the AWS owned key.\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${SSE_ENABLED_LINE} => \\`enabled = var.encryption != \"DEFAULT\"\\``,\n );\n\n // 4b. Checkov's CKV_AWS_119 only passes when server_side_encryption.enabled\n // is the literal `true` — it can't resolve the conditional above, even\n // though CUSTOMER_MANAGED (the default) always uses a real CMK. Same\n // false-positive shape as CKV_AWS_139 on the vended RDS module.\n await insertViaGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n hcl(\n `\\`resource \"aws_dynamodb_table\" \"table\" { $body }\\` => \\`resource \"aws_dynamodb_table\" \"table\" {\\n ${GRIT_INSERT_PLACEHOLDER}\\n $body\\n}\\``,\n ),\n '#checkov:skip=CKV_AWS_119:Encryption is configurable via var.encryption; checkov cannot resolve the conditional server_side_encryption.enabled expression',\n );\n\n // 5. Resolve the key ARN through the new local everywhere it's consumed.\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${SSE_KMS_KEY_LINE} => \\`kms_key_arn = local.table_kms_key_arn\\``,\n );\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${OUTPUT_VALUE_LINE} => \\`value = local.table_kms_key_arn\\``,\n );\n await applyGritQL(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n `${OUTPUT_DESCRIPTION_LINE} => \\`description = \"ARN of the KMS key used to encrypt the DynamoDB table, or null when using the AWS owned key (encryption = DEFAULT).\"\\``,\n );\n};\n\nconst NEW_APP_MODULE_VARIABLES_TEXT = [\n 'variable \"encryption\" {',\n ' description = \"Server-side encryption for the table. One of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT (the AWS owned key, at no cost and with no key to manage). Changing this on an already-deployed table away from CUSTOMER_MANAGED requires a manual step first - see the ts#dynamodb generator docs.\"',\n ' type = string',\n ' default = \"CUSTOMER_MANAGED\"',\n '',\n ' validation {',\n ' condition = contains([\"CUSTOMER_MANAGED\", \"AWS_MANAGED\", \"DEFAULT\"], var.encryption)',\n ' error_message = \"encryption must be one of CUSTOMER_MANAGED, AWS_MANAGED or DEFAULT.\"',\n ' }',\n '}',\n '',\n 'variable \"kms_key_arn\" {',\n ' description = \"ARN of an existing KMS key used to encrypt the table when encryption is CUSTOMER_MANAGED. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the DynamoDB service the necessary permissions in its own key policy.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"create_kms_key\" {',\n ' description = \"Whether to create a KMS key for the table. Only applies when encryption is CUSTOMER_MANAGED. Set to false when supplying kms_key_arn.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"enable_key_rotation\" {',\n ' description = \"Whether to enable automatic key rotation on the KMS key used to encrypt the table. Only applies when encryption is CUSTOMER_MANAGED and create_kms_key is true.\"',\n ' type = bool',\n ' default = true',\n '}',\n].join('\\n');\n\n/**\n * Surface the same configuration as pass-through variables on each vended\n * per-table Terraform app module, matching its existing pass-through\n * convention for enable_key_rotation.\n */\nconst migrateTerraformAppModules = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(TERRAFORM_DYNAMODB_APP_DIR)) {\n return;\n }\n\n for (const dirName of tree.children(TERRAFORM_DYNAMODB_APP_DIR)) {\n const filePath = joinPathFragments(\n TERRAFORM_DYNAMODB_APP_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a dynamodb app module directory.\n }\n\n if (\n await matchGritQL(tree, filePath, hcl('`variable \"encryption\" { $_ }`'))\n ) {\n continue; // Already migrated.\n }\n\n const OLD_ENABLE_KEY_ROTATION_VAR = hcl(\n [\n '`variable \"enable_key_rotation\" {',\n ' description = \"Whether to enable automatic key rotation on the KMS key used to encrypt the table.\"',\n ' type = bool',\n ' default = true',\n '}`',\n ].join('\\n'),\n );\n const MODULE_BLOCK = hcl('`module \"dynamodb_table\" { $_ }`');\n const DELETION_PROTECTION_LINE = hcl(\n '`deletion_protection_enabled = var.deletion_protection_enabled`',\n );\n\n const ready = (\n await Promise.all(\n [\n OLD_ENABLE_KEY_ROTATION_VAR,\n MODULE_BLOCK,\n DELETION_PROTECTION_LINE,\n ].map((pattern) => matchGritQL(tree, filePath, pattern)),\n )\n ).every(Boolean);\n\n if (!ready) {\n nextSteps.push(terraformAppDivergedMessage(filePath));\n continue;\n }\n\n await applyGritQL(\n tree,\n filePath,\n `${OLD_ENABLE_KEY_ROTATION_VAR} => \\`${NEW_APP_MODULE_VARIABLES_TEXT.replace(/`/g, '\\\\`')}\\``,\n );\n\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`deletion_protection_enabled = var.deletion_protection_enabled\\` => \\`deletion_protection_enabled = var.deletion_protection_enabled\\n ${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n [\n 'encryption = var.encryption',\n 'kms_key_arn = var.kms_key_arn',\n 'create_kms_key = var.create_kms_key',\n ].join('\\n '),\n );\n }\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n await migrateCdkConstruct(tree, nextSteps);\n await migrateTerraformModule(tree, nextSteps);\n await migrateTerraformAppModules(tree, nextSteps);\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","addDestructuredImport","applyGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","CDK_DYNAMODB_FILE","TERRAFORM_DYNAMODB_FILE","TERRAFORM_DYNAMODB_APP_DIR","CDK_DIVERGED_MESSAGE","TERRAFORM_DIVERGED_MESSAGE","terraformAppDivergedMessage","filePath","migrateCdkConstruct","tree","nextSteps","exists","ENABLE_KEY_ROTATION_FIELD","ENABLE_KEY_ROTATION_COMMENT","DESTRUCTURE_OLD","KEY_CREATION_OLD","TABLE_ENCRYPTION_OLD","anchors","allPresent","Promise","all","map","pattern","every","Boolean","push","hcl","NEW_TERRAFORM_VARIABLES_TEXT","join","migrateTerraformModule","OLD_ENABLE_KEY_ROTATION_VAR","LOCALS_BLOCK","DATA_PARTITION_LINE","KMS_KEY_BLOCK","SSE_ENABLED_LINE","SSE_KMS_KEY_LINE","OUTPUT_VALUE_LINE","OUTPUT_DESCRIPTION_LINE","replace","NEW_APP_MODULE_VARIABLES_TEXT","migrateTerraformAppModules","dirName","children","MODULE_BLOCK","DELETION_PROTECTION_LINE","ready","migration"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.77/0002-user-identity-configurable-mfa/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { MigrationReturnObject, Tree } from '@nx/devkit';\nimport {\n addDestructuredImport,\n applyGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Surface mfa and mfaSecondFactor on the vended UserIdentity construct/module\n *\n * CDK's `mfa` / `mfaSecondFactor` props default to what the construct already\n * hard-coded (`Mfa.REQUIRED` / `{ sms: true, otp: true }`), so this just makes\n * them overridable, matching the existing `enableWaf` prop pattern.\n *\n * Terraform has no equivalent of CloudFormation's `EnabledMfas` - SMS MFA is\n * inseparable from the `sms_configuration` block, which phone number\n * verification also relies on. So `mfa_second_factor_sms` gates\n * `sms_configuration` (and, with it, SMS phone verification) rather than only\n * MFA, unlike the CDK prop.\n *\n * CDK has the same underlying coupling for any non-OFF `mfa`: Cognito rejects\n * `SmsConfiguration` (which CDK emits whenever phone is auto-verified) unless\n * SMS is also an enabled MFA method. So `phoneVerificationEnabled` gates\n * `autoVerify`/`keepOriginal`'s `phone` flag on whether SMS is actually usable\n * as a second factor, and a `mfa: REQUIRED` with neither `sms` nor `otp`\n * enabled - a pool nobody could ever sign into - is rejected at synth time.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n *\n * Guardrails:\n * - Pattern-match before writing: skip files that have diverged from the shape\n * your generators produce and report them via `nextSteps`, rather than\n * clobbering the user's changes.\n * - Idempotent: re-running must be a no-op.\n * - Format what you write: finish with `formatFilesInSubtree` so the files your\n * migration wrote are formatted correctly.\n */\n\nconst CDK_USER_IDENTITY_FILE = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/core/user-identity.ts`;\nconst TERRAFORM_IDENTITY_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/user-identity/identity/identity.tf`;\nconst TERRAFORM_MAIN_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/user-identity/main.tf`;\n\nconst NEW_TERRAFORM_VARIABLES = `variable \"mfa\" {\n description = \"Whether users must configure MFA, may optionally configure MFA, or cannot use MFA at all\"\n type = string\n default = \"ON\"\n\n validation {\n condition = contains([\"OFF\", \"OPTIONAL\", \"ON\"], var.mfa)\n error_message = \"mfa must be one of OFF, OPTIONAL, ON\"\n }\n}\n\nvariable \"mfa_second_factor_otp\" {\n description = \"Whether users can use a time-based one time password (TOTP), generated by a hardware or software token, as an MFA method\"\n type = bool\n default = true\n}\n\nvariable \"mfa_second_factor_sms\" {\n description = \"Whether users can use SMS to their verified phone numbers as an MFA method. Due to a Terraform AWS provider limitation, disabling this also disables SMS-based phone number verification, unlike the equivalent CDK construct prop\"\n type = bool\n default = true\n}`;\n\n// Guards each file: present only once it has been migrated.\nconst CDK_MIGRATED_PATTERN = '`mfa === Mfa.OFF || mfaSecondFactor.sms`';\nconst TERRAFORM_IDENTITY_MIGRATED_PATTERN =\n 'language hcl\\n`mfa_configuration = var.mfa`';\nconst TERRAFORM_MAIN_MIGRATED_PATTERN = 'language hcl\\n`mfa = var.mfa`';\n\n// Interface members don't parse as standalone snippets, so this anchors on the\n// whole interface body (preserved via $body) rather than the enableWaf member.\nconst CDK_INTERFACE_BODY_PATTERN =\n '`export interface UserIdentityProps { $body }`';\n// A parameter's type annotation blocks matching the destructure sub-pattern in\n// isolation, so this anchors on the full constructor signature instead\n// (literal params so a customised signature is correctly seen as diverged).\nconst CDK_CONSTRUCTOR_PATTERN =\n '`constructor(scope: Construct, id: string, { enableWaf = true }: UserIdentityProps = {}) { $body }`';\nconst CDK_CREATE_CALL_PATTERN = '`this.createUserPool()`';\nconst CDK_CREATE_SIGNATURE_PATTERN = '`private createUserPool = () => $body`';\n// Trailing commas block matching a bare object property in isolation; the\n// separator is outside the matched span either way, so it survives untouched.\nconst CDK_MFA_FIELD_PATTERN = '`mfa: Mfa.REQUIRED`';\nconst CDK_MFA_SECOND_FACTOR_FIELD_PATTERN =\n '`mfaSecondFactor: { sms: true, otp: true }`';\nconst CDK_SUPER_CALL_PATTERN = '`super(scope, id);`';\n// $props preserves the whole (large) UserPool props object untouched while\n// inserting the new const declaration immediately before it.\nconst CDK_NEW_USER_POOL_PATTERN =\n \"`const userPool = new UserPool(this, 'UserPool', { $props })`\";\n// Matches both the autoVerify.phone and keepOriginal.phone occurrences in one pass.\nconst CDK_PHONE_TRUE_PATTERN = '`phone: true`';\n\nconst CDK_EDITS: Array<[string, string]> = [\n [\n CDK_INTERFACE_BODY_PATTERN,\n `${CDK_INTERFACE_BODY_PATTERN} => \\`export interface UserIdentityProps {\n $body\n\n /**\n * Whether users must configure MFA, may optionally configure MFA, or cannot use MFA at all.\n *\n * @default Mfa.REQUIRED\n */\n readonly mfa?: Mfa;\n\n /**\n * The MFA methods available to users when MFA is not off.\n *\n * @default { sms: true, otp: true }\n */\n readonly mfaSecondFactor?: MfaSecondFactor;\n}\\``,\n ],\n [\n CDK_CONSTRUCTOR_PATTERN,\n `${CDK_CONSTRUCTOR_PATTERN} => \\`constructor(\n scope: Construct,\n id: string,\n {\n enableWaf = true,\n mfa = Mfa.REQUIRED,\n mfaSecondFactor = { sms: true, otp: true },\n }: UserIdentityProps = {},\n ) { $body }\\``,\n ],\n [\n CDK_CREATE_CALL_PATTERN,\n `${CDK_CREATE_CALL_PATTERN} => \\`this.createUserPool(mfa, mfaSecondFactor)\\``,\n ],\n [\n CDK_CREATE_SIGNATURE_PATTERN,\n `${CDK_CREATE_SIGNATURE_PATTERN} => \\`private createUserPool = (mfa: Mfa, mfaSecondFactor: MfaSecondFactor) => $body\\``,\n ],\n [CDK_MFA_FIELD_PATTERN, `${CDK_MFA_FIELD_PATTERN} => \\`mfa\\``],\n [\n CDK_MFA_SECOND_FACTOR_FIELD_PATTERN,\n `${CDK_MFA_SECOND_FACTOR_FIELD_PATTERN} => \\`mfaSecondFactor\\``,\n ],\n [\n CDK_SUPER_CALL_PATTERN,\n `${CDK_SUPER_CALL_PATTERN} => \\`super(scope, id);\n\n if (mfa === Mfa.REQUIRED && !mfaSecondFactor.sms && !mfaSecondFactor.otp) {\n throw new Error(\n 'UserIdentity: mfa is REQUIRED but mfaSecondFactor has no methods enabled (sms and otp are both false)',\n );\n }\\``,\n ],\n [\n CDK_NEW_USER_POOL_PATTERN,\n `${CDK_NEW_USER_POOL_PATTERN} => \\`const phoneVerificationEnabled = mfa === Mfa.OFF || mfaSecondFactor.sms;\n\n const userPool = new UserPool(this, 'UserPool', { $props })\\``,\n ],\n [\n CDK_PHONE_TRUE_PATTERN,\n `${CDK_PHONE_TRUE_PATTERN} => \\`phone: phoneVerificationEnabled\\``,\n ],\n];\n\nconst TERRAFORM_IDENTITY_ENABLE_WAF_VARIABLE_ANCHOR = [\n 'language hcl',\n '`variable \"enable_waf\" {',\n ' description = \"Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet) and associate it with the user pool\"',\n ' type = bool',\n ' default = true',\n '}`',\n].join('\\n');\n\nconst TERRAFORM_MFA_CONFIGURATION_PATTERN = [\n 'language hcl',\n '`mfa_configuration = \"ON\"`',\n].join('\\n');\n\nconst TERRAFORM_SOFTWARE_TOKEN_MFA_PATTERN = [\n 'language hcl',\n '`software_token_mfa_configuration {',\n ' enabled = true',\n ' }`',\n].join('\\n');\n\nconst TERRAFORM_SMS_CONFIGURATION_PATTERN = [\n 'language hcl',\n '`sms_configuration {',\n ' external_id = random_uuid.sms_external_id.result',\n ' sns_caller_arn = aws_iam_role.cognito_sms_role.arn',\n ' sns_region = data.aws_region.current.region',\n ' }`',\n].join('\\n');\n\nconst TERRAFORM_RANDOM_UUID_PATTERN = [\n 'language hcl',\n '`resource \"random_uuid\" \"sms_external_id\" {}`',\n].join('\\n');\n\nconst TERRAFORM_SMS_ROLE_PATTERN = [\n 'language hcl',\n '`resource \"aws_iam_role\" \"cognito_sms_role\" { $body }`',\n].join('\\n');\n\nconst TERRAFORM_SMS_POLICY_PATTERN = [\n 'language hcl',\n '`resource \"aws_iam_role_policy\" \"cognito_sms_policy\" { $body }`',\n].join('\\n');\n\nconst TERRAFORM_SMS_ROLE_ID_REF_PATTERN = [\n 'language hcl',\n '`aws_iam_role.cognito_sms_role.id`',\n].join('\\n');\n\nconst TERRAFORM_EXTERNAL_ID_REF_PATTERN = [\n 'language hcl',\n '`random_uuid.sms_external_id.result`',\n].join('\\n');\n\nconst TERRAFORM_AUTO_VERIFIED_ATTRIBUTES_PATTERN = [\n 'language hcl',\n '`auto_verified_attributes = [\"email\", \"phone_number\"]`',\n].join('\\n');\n\nconst TERRAFORM_ATTRIBUTES_REQUIRE_VERIFICATION_PATTERN = [\n 'language hcl',\n '`attributes_require_verification_before_update = [\"email\", \"phone_number\"]`',\n].join('\\n');\n\nconst TERRAFORM_IDENTITY_EDITS: Array<[string, string]> = [\n [\n TERRAFORM_IDENTITY_ENABLE_WAF_VARIABLE_ANCHOR,\n `${TERRAFORM_IDENTITY_ENABLE_WAF_VARIABLE_ANCHOR} => \\`variable \"enable_waf\" {\n description = \"Whether to enable AWS WAFv2 with the default managed ruleset (AWSManagedRulesCommonRuleSet and AWSManagedRulesKnownBadInputsRuleSet) and associate it with the user pool\"\n type = bool\n default = true\n}\n\n${NEW_TERRAFORM_VARIABLES}\\``,\n ],\n [\n TERRAFORM_RANDOM_UUID_PATTERN,\n `${TERRAFORM_RANDOM_UUID_PATTERN} => \\`resource \"random_uuid\" \"sms_external_id\" {\n count = var.mfa_second_factor_sms ? 1 : 0\n}\\``,\n ],\n [\n TERRAFORM_SMS_ROLE_PATTERN,\n `${TERRAFORM_SMS_ROLE_PATTERN} => \\`resource \"aws_iam_role\" \"cognito_sms_role\" {\n count = var.mfa_second_factor_sms ? 1 : 0\n\n $body\n}\\``,\n ],\n [\n TERRAFORM_SMS_POLICY_PATTERN,\n `${TERRAFORM_SMS_POLICY_PATTERN} => \\`resource \"aws_iam_role_policy\" \"cognito_sms_policy\" {\n count = var.mfa_second_factor_sms ? 1 : 0\n\n $body\n}\\``,\n ],\n [\n TERRAFORM_MFA_CONFIGURATION_PATTERN,\n `${TERRAFORM_MFA_CONFIGURATION_PATTERN} => \\`mfa_configuration = var.mfa\\``,\n ],\n [\n TERRAFORM_SOFTWARE_TOKEN_MFA_PATTERN,\n `${TERRAFORM_SOFTWARE_TOKEN_MFA_PATTERN} => \\`software_token_mfa_configuration {\n enabled = var.mfa_second_factor_otp\n }\\``,\n ],\n [\n TERRAFORM_SMS_CONFIGURATION_PATTERN,\n `${TERRAFORM_SMS_CONFIGURATION_PATTERN} => \\`# SMS MFA configuration. Due to a Terraform AWS provider limitation, this also gates\n # SMS-based phone number verification below - see the mfa_second_factor_sms variable.\n dynamic \"sms_configuration\" {\n for_each = var.mfa_second_factor_sms ? [1] : []\n\n content {\n external_id = random_uuid.sms_external_id[0].result\n sns_caller_arn = aws_iam_role.cognito_sms_role[0].arn\n sns_region = data.aws_region.current.region\n }\n }\\``,\n ],\n [\n TERRAFORM_SMS_ROLE_ID_REF_PATTERN,\n `${TERRAFORM_SMS_ROLE_ID_REF_PATTERN} => \\`aws_iam_role.cognito_sms_role[0].id\\``,\n ],\n [\n TERRAFORM_EXTERNAL_ID_REF_PATTERN,\n `${TERRAFORM_EXTERNAL_ID_REF_PATTERN} => \\`random_uuid.sms_external_id[0].result\\``,\n ],\n [\n TERRAFORM_AUTO_VERIFIED_ATTRIBUTES_PATTERN,\n `${TERRAFORM_AUTO_VERIFIED_ATTRIBUTES_PATTERN} => \\`auto_verified_attributes = var.mfa_second_factor_sms ? [\"email\", \"phone_number\"] : [\"email\"]\\``,\n ],\n [\n TERRAFORM_ATTRIBUTES_REQUIRE_VERIFICATION_PATTERN,\n `${TERRAFORM_ATTRIBUTES_REQUIRE_VERIFICATION_PATTERN} => \\`attributes_require_verification_before_update = var.mfa_second_factor_sms ? [\"email\", \"phone_number\"] : [\"email\"]\\``,\n ],\n];\n\nconst TERRAFORM_MAIN_ENABLE_WAF_VARIABLE_ANCHOR = [\n 'language hcl',\n '`variable \"enable_waf\" {',\n ' description = \"Whether to enable AWS WAFv2 with the default managed ruleset and associate it with the user pool\"',\n ' type = bool',\n ' default = true',\n '}`',\n].join('\\n');\n\nconst TERRAFORM_MAIN_MODULE_PATTERN = [\n 'language hcl',\n '`module \"identity\" {',\n ' source = \"./identity\"',\n '',\n ' user_pool_domain_prefix = $prefix',\n ' allow_signup = $allowSignup',\n ' enable_waf = var.enable_waf',\n '}`',\n].join('\\n');\n\nconst TERRAFORM_MAIN_EDITS: Array<[string, string]> = [\n [\n TERRAFORM_MAIN_ENABLE_WAF_VARIABLE_ANCHOR,\n `${TERRAFORM_MAIN_ENABLE_WAF_VARIABLE_ANCHOR} => \\`variable \"enable_waf\" {\n description = \"Whether to enable AWS WAFv2 with the default managed ruleset and associate it with the user pool\"\n type = bool\n default = true\n}\n\n${NEW_TERRAFORM_VARIABLES}\\``,\n ],\n [\n TERRAFORM_MAIN_MODULE_PATTERN,\n `${TERRAFORM_MAIN_MODULE_PATTERN} => \\`module \"identity\" {\n source = \"./identity\"\n\n user_pool_domain_prefix = $prefix\n allow_signup = $allowSignup\n enable_waf = var.enable_waf\n mfa = var.mfa\n mfa_second_factor_otp = var.mfa_second_factor_otp\n mfa_second_factor_sms = var.mfa_second_factor_sms\n}\\``,\n ],\n];\n\nconst divergedNextStep = (filePath: string) =>\n `${filePath}: the UserIdentity construct/module has diverged from the generated shape - left untouched. To make MFA configurable, add mfa and mfaSecondFactor (CDK) / mfa, mfa_second_factor_otp and mfa_second_factor_sms (Terraform) yourself, following the same pattern as the existing enableWaf prop/variable.`;\n\nconst applyEdits = async (\n tree: Tree,\n filePath: string,\n migratedPattern: string,\n edits: Array<[string, string]>,\n nextSteps: string[],\n beforeApply?: () => Promise<void>,\n) => {\n if (!tree.exists(filePath)) {\n // This workspace doesn't use this IaC provider, or has no UserIdentity.\n return;\n }\n\n if (await matchGritQL(tree, filePath, migratedPattern)) {\n // Already migrated - silent skip keeps re-runs a no-op.\n return;\n }\n\n // Confirm every edit site is present before writing any of them, so a file\n // that only partly matches is left whole rather than half-edited.\n const allSitesPresent = (\n await Promise.all(\n edits.map(([match]) => matchGritQL(tree, filePath, match)),\n )\n ).every(Boolean);\n\n if (!allSitesPresent) {\n nextSteps.push(divergedNextStep(filePath));\n return;\n }\n\n await beforeApply?.();\n\n for (const [, rewrite] of edits) {\n await applyGritQL(tree, filePath, rewrite);\n }\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n await applyEdits(\n tree,\n CDK_USER_IDENTITY_FILE,\n CDK_MIGRATED_PATTERN,\n CDK_EDITS,\n nextSteps,\n () =>\n addDestructuredImport(\n tree,\n CDK_USER_IDENTITY_FILE,\n ['type MfaSecondFactor'],\n 'aws-cdk-lib/aws-cognito',\n ),\n );\n\n await applyEdits(\n tree,\n TERRAFORM_IDENTITY_FILE,\n TERRAFORM_IDENTITY_MIGRATED_PATTERN,\n TERRAFORM_IDENTITY_EDITS,\n nextSteps,\n );\n\n await applyEdits(\n tree,\n TERRAFORM_MAIN_FILE,\n TERRAFORM_MAIN_MIGRATED_PATTERN,\n TERRAFORM_MAIN_EDITS,\n nextSteps,\n );\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["addDestructuredImport","applyGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","CDK_USER_IDENTITY_FILE","TERRAFORM_IDENTITY_FILE","TERRAFORM_MAIN_FILE","NEW_TERRAFORM_VARIABLES","CDK_MIGRATED_PATTERN","TERRAFORM_IDENTITY_MIGRATED_PATTERN","TERRAFORM_MAIN_MIGRATED_PATTERN","CDK_INTERFACE_BODY_PATTERN","CDK_CONSTRUCTOR_PATTERN","CDK_CREATE_CALL_PATTERN","CDK_CREATE_SIGNATURE_PATTERN","CDK_MFA_FIELD_PATTERN","CDK_MFA_SECOND_FACTOR_FIELD_PATTERN","CDK_SUPER_CALL_PATTERN","CDK_NEW_USER_POOL_PATTERN","CDK_PHONE_TRUE_PATTERN","CDK_EDITS","TERRAFORM_IDENTITY_ENABLE_WAF_VARIABLE_ANCHOR","join","TERRAFORM_MFA_CONFIGURATION_PATTERN","TERRAFORM_SOFTWARE_TOKEN_MFA_PATTERN","TERRAFORM_SMS_CONFIGURATION_PATTERN","TERRAFORM_RANDOM_UUID_PATTERN","TERRAFORM_SMS_ROLE_PATTERN","TERRAFORM_SMS_POLICY_PATTERN","TERRAFORM_SMS_ROLE_ID_REF_PATTERN","TERRAFORM_EXTERNAL_ID_REF_PATTERN","TERRAFORM_AUTO_VERIFIED_ATTRIBUTES_PATTERN","TERRAFORM_ATTRIBUTES_REQUIRE_VERIFICATION_PATTERN","TERRAFORM_IDENTITY_EDITS","TERRAFORM_MAIN_ENABLE_WAF_VARIABLE_ANCHOR","TERRAFORM_MAIN_MODULE_PATTERN","TERRAFORM_MAIN_EDITS","divergedNextStep","filePath","applyEdits","tree","migratedPattern","edits","nextSteps","beforeApply","exists","allSitesPresent","Promise","all","map","match","every","Boolean","push","rewrite","migration"],"mappings":"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"}
|
package/src/migrations/v1.0.0-rc.78/0001-static-website-configurable-waf-encryption/migration.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.78/0001-static-website-configurable-waf-encryption/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 addDestructuredImport,\n applyGritQL,\n captureGritQLVariable,\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_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Bring existing workspaces up to the generators' current StaticWebsite\n * configurability:\n *\n * - The vended `StaticWebsite` CDK construct gains `enableWaf`, `encryption`,\n * `encryptionKey` and `enableKeyRotation` props. The WAF Web ACL is now\n * optional, the KMS key used to encrypt the website and distribution log\n * buckets is now overridable (or skippable in favour of another\n * `BucketEncryption`), and the auto-created key's rotation is configurable.\n * - Each vended per-website app construct (`app/static-websites/*.ts`) gains\n * an optional `props` constructor parameter so these can be configured per\n * app, rather than only by hand-editing the vended construct.\n * - The vended Terraform static-website core module gains the equivalent\n * `enable_waf`, `encryption`, `kms_key_arn`, `create_kms_key` and\n * `enable_key_rotation` variables. The CloudFront distribution's\n * `lifecycle.replace_triggered_by` on the WAF ACL is also dropped - it\n * can't resolve once the ACL has a count of 0 and no prior state to\n * reference, which broke `terraform plan` on any greenfield deployment\n * with `enable_waf = false`.\n * - Each vended per-website Terraform app module (`app/static-websites/<name>/<name>.tf`)\n * gains pass-through `custom_domain_names`, `acm_certificate_arn`,\n * `enable_waf`, `encryption`, `kms_key_arn`, `create_kms_key` and\n * `enable_key_rotation` variables forwarded to the core module call,\n * matching the pass-through convention every other app-wraps-core\n * Terraform module in this plugin already follows (`rdb`, `agent-core`,\n * `dcr-proxies`, the REST/HTTP API).\n *\n * These files are generated with `KeepExisting`, so without this an upgraded\n * workspace has generators that support this configuration but vended files\n * that don't. Diverged files are left untouched and reported via `nextSteps`.\n */\n\nconst CDK_STATIC_WEBSITE_FILE = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/core/static-website.ts`;\nconst CDK_STATIC_WEBSITES_APP_DIR = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/app/static-websites`;\nconst TERRAFORM_STATIC_WEBSITE_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/static-website/static-website.tf`;\nconst TERRAFORM_STATIC_WEBSITES_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/static-websites`;\n\nconst CDK_DIVERGED_MESSAGE = `${CDK_STATIC_WEBSITE_FILE}: has diverged from the generated shape - left untouched. To pick up the enableWaf, encryption, encryptionKey and enableKeyRotation props, manually port them from the vended core/static-website.ts template (see the ts#react-website generator's static-website construct).`;\n\nconst TERRAFORM_DIVERGED_MESSAGE = `${TERRAFORM_STATIC_WEBSITE_FILE}: has diverged from the generated shape - left untouched. To pick up the enable_waf, encryption, kms_key_arn, create_kms_key and enable_key_rotation variables, manually port them from the vended static-website.tf template (see the ts#react-website generator's static-website module).`;\n\nconst terraformAppDivergedMessage = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To make custom_domain_names, acm_certificate_arn, enable_waf, encryption, kms_key_arn, create_kms_key and enable_key_rotation configurable from your root Terraform configuration, add pass-through variables here and forward them to the static_website module call (see the ts#react-website generator's static-websites app template).`;\n\n// The doc comments below carry backticked markdown, which must stay out of\n// the GritQL pattern that inserts this text (see insertViaGritQL). It's routed\n// in as plain text via the placeholder instead.\nconst STATIC_WEBSITE_PROPS_TEXT = `/**\n * Whether to protect the CloudFront distribution with an AWS WAF Web ACL.\n *\n * @default true\n */\n readonly enableWaf?: boolean;\n /**\n * Server-side encryption for the website and distribution log buckets.\n *\n * @default BucketEncryption.KMS\n */\n readonly encryption?: BucketEncryption;\n /**\n * KMS key used to encrypt the website and distribution log buckets. Only used when \\`encryption\\` is\n * \\`BucketEncryption.KMS\\`. When not provided, a new key is created. Note that a key imported via\n * \\`Key.fromKeyArn\\` must already grant the CloudWatch Logs, S3 and CloudFront service principals the\n * necessary permissions in its own key policy - \\`addToResourcePolicy\\` is a no-op on an imported key,\n * so this construct cannot grant them on your behalf.\n */\n readonly encryptionKey?: IKey;\n /**\n * Whether the automatically created KMS key has rotation enabled. Only applies when \\`encryption\\` is\n * \\`BucketEncryption.KMS\\` and no \\`encryptionKey\\` is supplied.\n *\n * @default true\n */\n readonly enableKeyRotation?: boolean`;\n\n/**\n * Surface enableWaf/encryption/encryptionKey/enableKeyRotation on the vended\n * StaticWebsite CDK construct.\n */\nconst migrateCdkConstruct = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(CDK_STATIC_WEBSITE_FILE)) {\n return; // This workspace has no CDK static website construct.\n }\n\n if (\n await matchGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n '`readonly enableWaf?: boolean`',\n )\n ) {\n return; // Already migrated.\n }\n\n const CERTIFICATE_FIELD = '`readonly certificate?: ICertificate`';\n const DESTRUCTURE_OLD =\n '`{ websiteFilePath, websiteName, domainNames, certificate }: StaticWebsiteProps`';\n const KEY_CREATION_OLD =\n \"`const websiteKey = new Key(this, 'WebsiteKey', { enableKeyRotation: true })`\";\n const ADD_TO_RESOURCE_POLICY_OLD = '`websiteKey.addToResourcePolicy($args)`';\n const WEBSITE_BUCKET_ENCRYPTION_OLD =\n \"`encryption: BucketEncryption.KMS` as $prop where { $prop <: within `new Bucket($_, 'WebsiteBucket', $_)` }\";\n const DISTRIBUTION_LOGS_BUCKET_ENCRYPTION_OLD =\n \"`encryption: BucketEncryption.KMS` as $prop where { $prop <: within `new Bucket($_, 'DistributionLogBucket', $_)` }\";\n const WAF_STACK_OLD = \"`const wafStack = new CloudfrontWebAcl(this, 'waf')`\";\n const WEB_ACL_ID_OLD = '`webAclId: wafStack.wafArn`';\n\n const anchors = [\n CERTIFICATE_FIELD,\n DESTRUCTURE_OLD,\n KEY_CREATION_OLD,\n ADD_TO_RESOURCE_POLICY_OLD,\n WEBSITE_BUCKET_ENCRYPTION_OLD,\n DISTRIBUTION_LOGS_BUCKET_ENCRYPTION_OLD,\n WAF_STACK_OLD,\n WEB_ACL_ID_OLD,\n ];\n\n const allPresent = (\n await Promise.all(\n anchors.map((pattern) =>\n matchGritQL(tree, CDK_STATIC_WEBSITE_FILE, pattern),\n ),\n )\n ).every(Boolean);\n\n if (!allPresent) {\n nextSteps.push(CDK_DIVERGED_MESSAGE);\n return;\n }\n\n await addDestructuredImport(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n ['IKey'],\n 'aws-cdk-lib/aws-kms',\n );\n\n await insertViaGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${CERTIFICATE_FIELD} as $field => \\`$field;\n ${GRIT_INSERT_PLACEHOLDER}\\``,\n STATIC_WEBSITE_PROPS_TEXT,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${DESTRUCTURE_OLD} => \\`{\n websiteFilePath,\n websiteName,\n domainNames,\n certificate,\n enableWaf = true,\n encryption = BucketEncryption.KMS,\n encryptionKey,\n enableKeyRotation = true,\n }: StaticWebsiteProps\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${KEY_CREATION_OLD} => \\`const websiteKey: IKey | undefined =\n encryption === BucketEncryption.KMS\n ? (encryptionKey ?? new Key(this, 'WebsiteKey', { enableKeyRotation }))\n : undefined;\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${ADD_TO_RESOURCE_POLICY_OLD} => \\`websiteKey?.addToResourcePolicy($args)\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${WEBSITE_BUCKET_ENCRYPTION_OLD} => \\`encryption\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${DISTRIBUTION_LOGS_BUCKET_ENCRYPTION_OLD} => \\`encryption\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${WAF_STACK_OLD} => \\`const wafStack = enableWaf ? new CloudfrontWebAcl(this, 'waf') : undefined;\\``,\n );\n\n await applyGritQL(\n tree,\n CDK_STATIC_WEBSITE_FILE,\n `${WEB_ACL_ID_OLD} => \\`webAclId: wafStack?.wafArn\\``,\n );\n};\n\n/**\n * Surface the same props on each vended per-website app construct, so they\n * can be configured per app rather than only by editing the shared construct.\n */\nconst migrateCdkAppConstructs = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(CDK_STATIC_WEBSITES_APP_DIR)) {\n return;\n }\n\n for (const fileName of tree.children(CDK_STATIC_WEBSITES_APP_DIR)) {\n if (fileName === 'index.ts' || !fileName.endsWith('.ts')) {\n continue;\n }\n const filePath = joinPathFragments(CDK_STATIC_WEBSITES_APP_DIR, fileName);\n\n const contents = tree.read(filePath, 'utf-8') ?? '';\n if (contents.includes('StaticWebsiteProps')) {\n continue; // Already migrated.\n }\n\n const name = await captureGritQLVariable(\n tree,\n filePath,\n '`export class $name extends StaticWebsite { $_ }`',\n 'name',\n );\n if (!name) {\n continue; // Not a StaticWebsite subclass, not ours to touch.\n }\n\n const importPattern = '`import { StaticWebsite } from $path`';\n const ctorPattern = '`constructor(scope: Construct, id: string) { $body }`';\n // Captures the whole property list rather than anchoring on websiteName\n // being followed by exactly one more property ($rest binds a single\n // trailing node, not a variadic list). This must still match when a\n // workspace already customised the super() call with e.g. domainNames\n // and certificate (the pre-existing documented customization point).\n const superPattern = `\\`super(scope, id, { $props })\\` as $call where {\n $props <: contains \\`websiteName: '${name}'\\`\n}`;\n\n const ready = (\n await Promise.all(\n [importPattern, ctorPattern, superPattern].map((pattern) =>\n matchGritQL(tree, filePath, pattern),\n ),\n )\n ).every(Boolean);\n\n if (!ready) {\n nextSteps.push(\n `${filePath}: has diverged from the generated shape - left untouched. To make enableWaf, encryption, encryptionKey and enableKeyRotation configurable here, add an optional \\`props?: ${name}Props\\` constructor parameter (\\`Omit<StaticWebsiteProps, 'websiteName' | 'websiteFilePath'>\\`) and spread it into the super() call (see the ts#react-website generator's static-websites app template).`,\n );\n continue;\n }\n\n await applyGritQL(\n tree,\n filePath,\n `${importPattern} => \\`import { StaticWebsite, StaticWebsiteProps } from $path\\``,\n );\n\n await insertViaGritQL(\n tree,\n filePath,\n `\\`export class ${name} extends StaticWebsite { $body }\\` as $cls => \\`${GRIT_INSERT_PLACEHOLDER}\n\n$cls\\``,\n `export type ${name}Props = Omit<\\n StaticWebsiteProps,\\n 'websiteName' | 'websiteFilePath'\\n>;`,\n );\n\n await applyGritQL(\n tree,\n filePath,\n `${ctorPattern} => \\`constructor(scope: Construct, id: string, props?: ${name}Props) { $body }\\``,\n );\n\n await applyGritQL(\n tree,\n filePath,\n `${superPattern} => \\`super(scope, id, { ...props, $props })\\``,\n );\n }\n};\n\n// Terraform (HCL) patterns are built from plain strings rather than JS\n// template literals, since the HCL text itself is full of `${...}`\n// interpolations that would otherwise be parsed as JS interpolation.\nconst hcl = (pattern: string) => 'language hcl\\n' + pattern;\n\nconst withinResource = (\n linePattern: string,\n resourceType: string,\n resourceLabel: string,\n) =>\n hcl(\n '`' +\n linePattern +\n '` as $line where {\\n' +\n ' $line <: within `resource \"' +\n resourceType +\n '\" \"' +\n resourceLabel +\n '\" { $_ }`\\n' +\n '}',\n );\n\nconst NEW_VARIABLES_TEXT = [\n 'variable \"enable_waf\" {',\n ' description = \"Whether to protect the CloudFront distribution with an AWS WAF Web ACL.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"encryption\" {',\n ' description = \"Server-side encryption for the website and distribution log buckets. One of KMS or S3_MANAGED.\"',\n ' type = string',\n ' default = \"KMS\"',\n '',\n ' validation {',\n ' condition = contains([\"KMS\", \"S3_MANAGED\"], var.encryption)',\n ' error_message = \"encryption must be one of KMS or S3_MANAGED.\"',\n ' }',\n '}',\n '',\n 'variable \"kms_key_arn\" {',\n ' description = \"ARN of an existing KMS key used to encrypt the website and distribution log buckets when encryption is KMS. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the CloudWatch Logs, S3 and CloudFront service principals the necessary permissions in its own key policy.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"create_kms_key\" {',\n ' description = \"Whether to create a KMS key for the website. Only applies when encryption is KMS. Set to false when supplying kms_key_arn.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"enable_key_rotation\" {',\n ' description = \"Whether the automatically created KMS key has rotation enabled. Only applies when encryption is KMS and create_kms_key is true.\"',\n ' type = bool',\n ' default = true',\n '}',\n].join('\\n');\n\nconst NEW_LOCALS_TEXT = [\n '',\n ' create_website_key = var.encryption == \"KMS\" && var.create_kms_key',\n ' website_kms_key_arn = (',\n ' var.encryption != \"KMS\" ? null :',\n ' local.create_website_key ? aws_kms_key.website_key[0].arn :',\n ' var.kms_key_arn',\n ' )',\n].join('\\n');\n\n/**\n * Surface the same configuration on the vended Terraform static-website\n * module.\n */\nconst migrateTerraformModule = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(TERRAFORM_STATIC_WEBSITE_FILE)) {\n return; // This workspace has no Terraform static website module.\n }\n\n if (\n await matchGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl('`variable \"enable_waf\" { $_ }`'),\n )\n ) {\n return; // Already migrated.\n }\n\n const ACM_CERT_VARIABLE = hcl('`variable \"acm_certificate_arn\" { $_ }`');\n const ACCESS_LOGS_LOCAL = hcl(\n '`access_logs_name_prefix = substr(lower(var.website_name), 0, 16)`',\n );\n const KMS_KEY_BLOCK = hcl('`resource \"aws_kms_key\" \"website_key\" { $_ }`');\n const KMS_KEY_ROTATION_LINE = withinResource(\n 'enable_key_rotation = true',\n 'aws_kms_key',\n 'website_key',\n );\n const KMS_ALIAS_BLOCK = hcl(\n '`resource \"aws_kms_alias\" \"website_key_alias\" { $_ }`',\n );\n const KMS_ALIAS_TARGET_LINE = hcl(\n '`target_key_id = aws_kms_key.website_key.key_id`',\n );\n const LOG_GROUP_KMS_LINE = hcl('`kms_key_id = aws_kms_key.website_key.arn`');\n const WEBSITE_ENCRYPTION_KEY_LINE = withinResource(\n 'kms_master_key_id = aws_kms_key.website_key.arn',\n 'aws_s3_bucket_server_side_encryption_configuration',\n 'website_encryption',\n );\n const WEBSITE_ENCRYPTION_ALGO_LINE = withinResource(\n 'sse_algorithm = \"aws:kms\"',\n 'aws_s3_bucket_server_side_encryption_configuration',\n 'website_encryption',\n );\n const DISTRIBUTION_LOGS_ENCRYPTION_KEY_LINE = withinResource(\n 'kms_master_key_id = aws_kms_key.website_key.arn',\n 'aws_s3_bucket_server_side_encryption_configuration',\n 'distribution_logs_encryption',\n );\n const DISTRIBUTION_LOGS_ENCRYPTION_ALGO_LINE = withinResource(\n 'sse_algorithm = \"aws:kms\"',\n 'aws_s3_bucket_server_side_encryption_configuration',\n 'distribution_logs_encryption',\n );\n const WAF_BLOCK = hcl(\n '`resource \"aws_wafv2_web_acl\" \"cloudfront_waf\" { $_ }`',\n );\n const WEB_ACL_ID_LINE = hcl(\n '`web_acl_id = aws_wafv2_web_acl.cloudfront_waf.arn`',\n );\n const WAF_OUTPUT_VALUE_LINE = hcl(\n '`value = aws_wafv2_web_acl.cloudfront_waf.arn` as $line where {\\n' +\n ' $line <: within `output \"waf_web_acl_arn\" { $_ }`\\n' +\n '}',\n );\n // Forces distribution replacement whenever the WAF ACL changes. Once the\n // ACL gains `count`, this can't be resolved on a greenfield apply where\n // count is 0 and there's no prior state to reference - see\n // https://github.com/awslabs/nx-plugin-for-aws/pull/1107. It's also\n // redundant: web_acl_id already references the ACL ARN directly, so a\n // replaced ACL still drives a plain distribution update.\n const LIFECYCLE_REPLACE_TRIGGERED_BY_BLOCK = hcl(\n '`lifecycle {\\n replace_triggered_by = [\\n aws_wafv2_web_acl.cloudfront_waf\\n ]\\n }`',\n );\n\n const anchors = [\n ACM_CERT_VARIABLE,\n ACCESS_LOGS_LOCAL,\n KMS_KEY_BLOCK,\n KMS_KEY_ROTATION_LINE,\n KMS_ALIAS_BLOCK,\n KMS_ALIAS_TARGET_LINE,\n LOG_GROUP_KMS_LINE,\n WEBSITE_ENCRYPTION_KEY_LINE,\n WEBSITE_ENCRYPTION_ALGO_LINE,\n DISTRIBUTION_LOGS_ENCRYPTION_KEY_LINE,\n DISTRIBUTION_LOGS_ENCRYPTION_ALGO_LINE,\n WAF_BLOCK,\n WEB_ACL_ID_LINE,\n WAF_OUTPUT_VALUE_LINE,\n LIFECYCLE_REPLACE_TRIGGERED_BY_BLOCK,\n ];\n\n const allPresent = (\n await Promise.all(\n anchors.map((pattern) =>\n matchGritQL(tree, TERRAFORM_STATIC_WEBSITE_FILE, pattern),\n ),\n )\n ).every(Boolean);\n\n if (!allPresent) {\n nextSteps.push(TERRAFORM_DIVERGED_MESSAGE);\n return;\n }\n\n // 1. New variables, after acm_certificate_arn. Routed through the\n // placeholder since the variable descriptions contain double-quoted\n // HCL string values.\n await insertViaGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`variable \"acm_certificate_arn\" { $body }` as $var => `$var\\n\\n' +\n GRIT_INSERT_PLACEHOLDER +\n '`',\n ),\n NEW_VARIABLES_TEXT,\n );\n\n // 2. New locals, after access_logs_name_prefix.\n await insertViaGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`access_logs_name_prefix = substr(lower(var.website_name), 0, 16)` as $line => `$line\\n' +\n GRIT_INSERT_PLACEHOLDER +\n '`',\n ),\n NEW_LOCALS_TEXT,\n );\n\n // 3. Make the KMS key and alias conditional on encryption/kms_key_arn.\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`resource \"aws_kms_key\" \"website_key\" { $body }` => `resource \"aws_kms_key\" \"website_key\" {\\n' +\n ' count = local.create_website_key ? 1 : 0\\n\\n' +\n ' $body\\n' +\n '}`',\n ),\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n KMS_KEY_ROTATION_LINE +\n ' => `enable_key_rotation = var.enable_key_rotation`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`resource \"aws_kms_alias\" \"website_key_alias\" { $body }` => `resource \"aws_kms_alias\" \"website_key_alias\" {\\n' +\n ' count = local.create_website_key ? 1 : 0\\n\\n' +\n ' $body\\n' +\n '}`',\n ),\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n KMS_ALIAS_TARGET_LINE +\n ' => `target_key_id = aws_kms_key.website_key[0].key_id`',\n );\n\n // 4. Resolve the key ARN through the new local everywhere it's consumed.\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n LOG_GROUP_KMS_LINE + ' => `kms_key_id = local.website_kms_key_arn`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n WEBSITE_ENCRYPTION_KEY_LINE +\n ' => `kms_master_key_id = var.encryption == \"KMS\" ? local.website_kms_key_arn : null`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n WEBSITE_ENCRYPTION_ALGO_LINE +\n ' => `sse_algorithm = var.encryption == \"KMS\" ? \"aws:kms\" : \"AES256\"`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n DISTRIBUTION_LOGS_ENCRYPTION_KEY_LINE +\n ' => `kms_master_key_id = var.encryption == \"KMS\" ? local.website_kms_key_arn : null`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n DISTRIBUTION_LOGS_ENCRYPTION_ALGO_LINE +\n ' => `sse_algorithm = var.encryption == \"KMS\" ? \"aws:kms\" : \"AES256\"`',\n );\n\n // 5. Make the WAF Web ACL conditional on enable_waf.\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n hcl(\n '`resource \"aws_wafv2_web_acl\" \"cloudfront_waf\" { $body }` => `resource \"aws_wafv2_web_acl\" \"cloudfront_waf\" {\\n' +\n ' count = var.enable_waf ? 1 : 0\\n\\n' +\n ' $body\\n' +\n '}`',\n ),\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n WEB_ACL_ID_LINE +\n ' => `web_acl_id = var.enable_waf ? aws_wafv2_web_acl.cloudfront_waf[0].arn : null`',\n );\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n WAF_OUTPUT_VALUE_LINE +\n ' => `value = var.enable_waf ? aws_wafv2_web_acl.cloudfront_waf[0].arn : null`',\n );\n\n // 6. Drop the lifecycle block that can no longer resolve once the WAF ACL\n // has a count of 0 and no prior state to reference.\n await applyGritQL(\n tree,\n TERRAFORM_STATIC_WEBSITE_FILE,\n LIFECYCLE_REPLACE_TRIGGERED_BY_BLOCK + ' => ``',\n );\n};\n\nconst NEW_APP_MODULE_VARIABLES_TEXT = [\n 'variable \"custom_domain_names\" {',\n ' description = \"Custom domain names (aliases) for the CloudFront distribution. Requires acm_certificate_arn.\"',\n ' type = list(string)',\n ' default = []',\n '}',\n '',\n 'variable \"acm_certificate_arn\" {',\n ' description = \"ARN of an ACM certificate (in us-east-1) for the custom domain names. When set, viewers are required to use TLS 1.2 or later.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"enable_waf\" {',\n ' description = \"Whether to protect the CloudFront distribution with an AWS WAF Web ACL.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"encryption\" {',\n ' description = \"Server-side encryption for the website and distribution log buckets. One of KMS or S3_MANAGED.\"',\n ' type = string',\n ' default = \"KMS\"',\n '',\n ' validation {',\n ' condition = contains([\"KMS\", \"S3_MANAGED\"], var.encryption)',\n ' error_message = \"encryption must be one of KMS or S3_MANAGED.\"',\n ' }',\n '}',\n '',\n 'variable \"kms_key_arn\" {',\n ' description = \"ARN of an existing KMS key used to encrypt the website and distribution log buckets when encryption is KMS. When not provided and create_kms_key is true, a new key is created. Note that a customer-supplied key must already grant the CloudWatch Logs, S3 and CloudFront service principals the necessary permissions in its own key policy.\"',\n ' type = string',\n ' default = null',\n '}',\n '',\n 'variable \"create_kms_key\" {',\n ' description = \"Whether to create a KMS key for the website. Only applies when encryption is KMS. Set to false when supplying kms_key_arn.\"',\n ' type = bool',\n ' default = true',\n '}',\n '',\n 'variable \"enable_key_rotation\" {',\n ' description = \"Whether the automatically created KMS key has rotation enabled. Only applies when encryption is KMS and create_kms_key is true.\"',\n ' type = bool',\n ' default = true',\n '}',\n].join('\\n');\n\nconst NEW_APP_MODULE_FORWARD_TEXT = [\n 'custom_domain_names = var.custom_domain_names',\n 'acm_certificate_arn = var.acm_certificate_arn',\n 'enable_waf = var.enable_waf',\n 'encryption = var.encryption',\n 'kms_key_arn = var.kms_key_arn',\n 'create_kms_key = var.create_kms_key',\n 'enable_key_rotation = var.enable_key_rotation',\n].join('\\n ');\n\n/**\n * Surface the same configuration as pass-through variables on each vended\n * per-website Terraform app module, matching the pass-through convention\n * every other app-wraps-core Terraform module already follows.\n */\nconst migrateTerraformAppModules = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(TERRAFORM_STATIC_WEBSITES_APP_DIR)) {\n return;\n }\n\n for (const dirName of tree.children(TERRAFORM_STATIC_WEBSITES_APP_DIR)) {\n const filePath = joinPathFragments(\n TERRAFORM_STATIC_WEBSITES_APP_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a website app module directory.\n }\n\n if (\n await matchGritQL(tree, filePath, hcl('`variable \"enable_waf\" { $_ }`'))\n ) {\n continue; // Already migrated.\n }\n\n const TERRAFORM_BLOCK = hcl('`terraform { $_ }`');\n const MODULE_BLOCK = hcl('`module \"static_website\" { $_ }`');\n const FILE_PATH_LINE = hcl(\n '`website_file_path = $val` as $line where {\\n' +\n ' $line <: within `module \"static_website\" { $_ }`\\n' +\n '}',\n );\n // custom_domain_names/acm_certificate_arn predate this migration as a\n // documented customization point (hand-editing the module block). If\n // either is already present as a literal argument, blindly forwarding\n // our own `= var.x` line would produce a duplicate argument, which is\n // invalid HCL. Treat that as diverged instead of silently corrupting the file.\n const hasExistingCustomDomainArg = async (name: string) =>\n matchGritQL(\n tree,\n filePath,\n hcl(\n `\\`${name} = $_\\` as $line where {\\n` +\n ' $line <: within `module \"static_website\" { $_ }`\\n' +\n '}',\n ),\n );\n\n const ready =\n (\n await Promise.all(\n [TERRAFORM_BLOCK, MODULE_BLOCK, FILE_PATH_LINE].map((pattern) =>\n matchGritQL(tree, filePath, pattern),\n ),\n )\n ).every(Boolean) &&\n !(await hasExistingCustomDomainArg('custom_domain_names')) &&\n !(await hasExistingCustomDomainArg('acm_certificate_arn'));\n\n if (!ready) {\n nextSteps.push(terraformAppDivergedMessage(filePath));\n continue;\n }\n\n // Inserted right after the terraform/required_providers block (and\n // before the \"Static website module\" comment), matching where the\n // vended template puts these variables.\n await insertViaGritQL(\n tree,\n filePath,\n hcl('`terraform { $body }` as $tf') +\n ` => \\`$tf\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n NEW_APP_MODULE_VARIABLES_TEXT,\n );\n\n await insertViaGritQL(\n tree,\n filePath,\n FILE_PATH_LINE + ` => \\`$line\\n\\n ${GRIT_INSERT_PLACEHOLDER}\\``,\n NEW_APP_MODULE_FORWARD_TEXT,\n );\n }\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n await migrateCdkConstruct(tree, nextSteps);\n await migrateCdkAppConstructs(tree, nextSteps);\n await migrateTerraformModule(tree, nextSteps);\n await migrateTerraformAppModules(tree, nextSteps);\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","addDestructuredImport","applyGritQL","captureGritQLVariable","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","CDK_STATIC_WEBSITE_FILE","CDK_STATIC_WEBSITES_APP_DIR","TERRAFORM_STATIC_WEBSITE_FILE","TERRAFORM_STATIC_WEBSITES_APP_DIR","CDK_DIVERGED_MESSAGE","TERRAFORM_DIVERGED_MESSAGE","terraformAppDivergedMessage","filePath","STATIC_WEBSITE_PROPS_TEXT","migrateCdkConstruct","tree","nextSteps","exists","CERTIFICATE_FIELD","DESTRUCTURE_OLD","KEY_CREATION_OLD","ADD_TO_RESOURCE_POLICY_OLD","WEBSITE_BUCKET_ENCRYPTION_OLD","DISTRIBUTION_LOGS_BUCKET_ENCRYPTION_OLD","WAF_STACK_OLD","WEB_ACL_ID_OLD","anchors","allPresent","Promise","all","map","pattern","every","Boolean","push","migrateCdkAppConstructs","fileName","children","endsWith","contents","read","includes","name","importPattern","ctorPattern","superPattern","ready","hcl","withinResource","linePattern","resourceType","resourceLabel","NEW_VARIABLES_TEXT","join","NEW_LOCALS_TEXT","migrateTerraformModule","ACM_CERT_VARIABLE","ACCESS_LOGS_LOCAL","KMS_KEY_BLOCK","KMS_KEY_ROTATION_LINE","KMS_ALIAS_BLOCK","KMS_ALIAS_TARGET_LINE","LOG_GROUP_KMS_LINE","WEBSITE_ENCRYPTION_KEY_LINE","WEBSITE_ENCRYPTION_ALGO_LINE","DISTRIBUTION_LOGS_ENCRYPTION_KEY_LINE","DISTRIBUTION_LOGS_ENCRYPTION_ALGO_LINE","WAF_BLOCK","WEB_ACL_ID_LINE","WAF_OUTPUT_VALUE_LINE","LIFECYCLE_REPLACE_TRIGGERED_BY_BLOCK","NEW_APP_MODULE_VARIABLES_TEXT","NEW_APP_MODULE_FORWARD_TEXT","migrateTerraformAppModules","dirName","TERRAFORM_BLOCK","MODULE_BLOCK","FILE_PATH_LINE","hasExistingCustomDomainArg","migration"],"mappings":"AAAA;;;CAGC,GACD,SACEA,iBAAiB,QAGZ,aAAa;AACpB,SACEC,qBAAqB,EACrBC,WAAW,EACXC,qBAAqB,EACrBC,uBAAuB,EACvBC,eAAe,EACfC,WAAW,QACN,wBAAwB;AAC/B,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SACEC,YAAY,EACZC,qBAAqB,EACrBC,oBAAoB,QACf,gDAAgD;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GAED,MAAMC,0BAA0B,GAAGH,aAAa,CAAC,EAAEC,sBAAsB,2BAA2B,CAAC;AACrG,MAAMG,8BAA8B,GAAGJ,aAAa,CAAC,EAAEC,sBAAsB,wBAAwB,CAAC;AACtG,MAAMI,gCAAgC,GAAGL,aAAa,CAAC,EAAEE,qBAAqB,0CAA0C,CAAC;AACzH,MAAMI,oCAAoC,GAAGN,aAAa,CAAC,EAAEE,qBAAqB,wBAAwB,CAAC;AAE3G,MAAMK,uBAAuB,GAAGJ,wBAAwB,8QAA8Q,CAAC;AAEvU,MAAMK,6BAA6B,GAAGH,8BAA8B,2RAA2R,CAAC;AAEhW,MAAMI,8BAA8B,CAACC,WACnC,GAAGA,SAAS,oYAAoY,CAAC;AAEnZ,2EAA2E;AAC3E,+EAA+E;AAC/E,gDAAgD;AAChD,MAAMC,4BAA4B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;sCA0BG,CAAC;AAEvC;;;CAGC,GACD,MAAMC,sBAAsB,OAC1BC,MACAC;IAEA,IAAI,CAACD,KAAKE,MAAM,CAACZ,0BAA0B;QACzC,QAAQ,sDAAsD;IAChE;IAEA,IACE,MAAML,YACJe,MACAV,yBACA,mCAEF;QACA,QAAQ,oBAAoB;IAC9B;IAEA,MAAMa,oBAAoB;IAC1B,MAAMC,kBACJ;IACF,MAAMC,mBACJ;IACF,MAAMC,6BAA6B;IACnC,MAAMC,gCACJ;IACF,MAAMC,0CACJ;IACF,MAAMC,gBAAgB;IACtB,MAAMC,iBAAiB;IAEvB,MAAMC,UAAU;QACdR;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;KACD;IAED,MAAME,aAAa,AACjB,CAAA,MAAMC,QAAQC,GAAG,CACfH,QAAQI,GAAG,CAAC,CAACC,UACX/B,YAAYe,MAAMV,yBAAyB0B,UAE/C,EACAC,KAAK,CAACC;IAER,IAAI,CAACN,YAAY;QACfX,UAAUkB,IAAI,CAACzB;QACf;IACF;IAEA,MAAMd,sBACJoB,MACAV,yBACA;QAAC;KAAO,EACR;IAGF,MAAMN,gBACJgB,MACAV,yBACA,GAAGa,kBAAkB;EACvB,EAAEpB,wBAAwB,EAAE,CAAC,EAC3Be;IAGF,MAAMjB,YACJmB,MACAV,yBACA,GAAGc,gBAAgB;;;;;;;;;2BASI,CAAC;IAG1B,MAAMvB,YACJmB,MACAV,yBACA,GAAGe,iBAAiB;;;sBAGF,CAAC;IAGrB,MAAMxB,YACJmB,MACAV,yBACA,GAAGgB,2BAA2B,8CAA8C,CAAC;IAG/E,MAAMzB,YACJmB,MACAV,yBACA,GAAGiB,8BAA8B,kBAAkB,CAAC;IAGtD,MAAM1B,YACJmB,MACAV,yBACA,GAAGkB,wCAAwC,kBAAkB,CAAC;IAGhE,MAAM3B,YACJmB,MACAV,yBACA,GAAGmB,cAAc,mFAAmF,CAAC;IAGvG,MAAM5B,YACJmB,MACAV,yBACA,GAAGoB,eAAe,kCAAkC,CAAC;AAEzD;AAEA;;;CAGC,GACD,MAAMU,0BAA0B,OAC9BpB,MACAC;IAEA,IAAI,CAACD,KAAKE,MAAM,CAACX,8BAA8B;QAC7C;IACF;IAEA,KAAK,MAAM8B,YAAYrB,KAAKsB,QAAQ,CAAC/B,6BAA8B;QACjE,IAAI8B,aAAa,cAAc,CAACA,SAASE,QAAQ,CAAC,QAAQ;YACxD;QACF;QACA,MAAM1B,WAAWlB,kBAAkBY,6BAA6B8B;QAEhE,MAAMG,WAAWxB,KAAKyB,IAAI,CAAC5B,UAAU,YAAY;QACjD,IAAI2B,SAASE,QAAQ,CAAC,uBAAuB;YAC3C,UAAU,oBAAoB;QAChC;QAEA,MAAMC,OAAO,MAAM7C,sBACjBkB,MACAH,UACA,qDACA;QAEF,IAAI,CAAC8B,MAAM;YACT,UAAU,mDAAmD;QAC/D;QAEA,MAAMC,gBAAgB;QACtB,MAAMC,cAAc;QACpB,wEAAwE;QACxE,oEAAoE;QACpE,oEAAoE;QACpE,sEAAsE;QACtE,qEAAqE;QACrE,MAAMC,eAAe,CAAC;qCACW,EAAEH,KAAK;CAC3C,CAAC;QAEE,MAAMI,QAAQ,AACZ,CAAA,MAAMlB,QAAQC,GAAG,CACf;YAACc;YAAeC;YAAaC;SAAa,CAACf,GAAG,CAAC,CAACC,UAC9C/B,YAAYe,MAAMH,UAAUmB,UAEhC,EACAC,KAAK,CAACC;QAER,IAAI,CAACa,OAAO;YACV9B,UAAUkB,IAAI,CACZ,GAAGtB,SAAS,0KAA0K,EAAE8B,KAAK,wMAAwM,CAAC;YAExY;QACF;QAEA,MAAM9C,YACJmB,MACAH,UACA,GAAG+B,cAAc,+DAA+D,CAAC;QAGnF,MAAM5C,gBACJgB,MACAH,UACA,CAAC,eAAe,EAAE8B,KAAK,gDAAgD,EAAE5C,wBAAwB;;MAEjG,CAAC,EACD,CAAC,YAAY,EAAE4C,KAAK,6EAA6E,CAAC;QAGpG,MAAM9C,YACJmB,MACAH,UACA,GAAGgC,YAAY,wDAAwD,EAAEF,KAAK,kBAAkB,CAAC;QAGnG,MAAM9C,YACJmB,MACAH,UACA,GAAGiC,aAAa,8CAA8C,CAAC;IAEnE;AACF;AAEA,uEAAuE;AACvE,mEAAmE;AACnE,qEAAqE;AACrE,MAAME,MAAM,CAAChB,UAAoB,mBAAmBA;AAEpD,MAAMiB,iBAAiB,CACrBC,aACAC,cACAC,gBAEAJ,IACE,MACEE,cACA,yBACA,kCACAC,eACA,QACAC,gBACA,gBACA;AAGN,MAAMC,qBAAqB;IACzB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAACC,IAAI,CAAC;AAEP,MAAMC,kBAAkB;IACtB;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAACD,IAAI,CAAC;AAEP;;;CAGC,GACD,MAAME,yBAAyB,OAC7BxC,MACAC;IAEA,IAAI,CAACD,KAAKE,MAAM,CAACV,gCAAgC;QAC/C,QAAQ,yDAAyD;IACnE;IAEA,IACE,MAAMP,YACJe,MACAR,+BACAwC,IAAI,oCAEN;QACA,QAAQ,oBAAoB;IAC9B;IAEA,MAAMS,oBAAoBT,IAAI;IAC9B,MAAMU,oBAAoBV,IACxB;IAEF,MAAMW,gBAAgBX,IAAI;IAC1B,MAAMY,wBAAwBX,eAC5B,8BACA,eACA;IAEF,MAAMY,kBAAkBb,IACtB;IAEF,MAAMc,wBAAwBd,IAC5B;IAEF,MAAMe,qBAAqBf,IAAI;IAC/B,MAAMgB,8BAA8Bf,eAClC,mDACA,sDACA;IAEF,MAAMgB,+BAA+BhB,eACnC,6BACA,sDACA;IAEF,MAAMiB,wCAAwCjB,eAC5C,mDACA,sDACA;IAEF,MAAMkB,yCAAyClB,eAC7C,6BACA,sDACA;IAEF,MAAMmB,YAAYpB,IAChB;IAEF,MAAMqB,kBAAkBrB,IACtB;IAEF,MAAMsB,wBAAwBtB,IAC5B,sEACE,0DACA;IAEJ,yEAAyE;IACzE,wEAAwE;IACxE,2DAA2D;IAC3D,oEAAoE;IACpE,sEAAsE;IACtE,yDAAyD;IACzD,MAAMuB,uCAAuCvB,IAC3C;IAGF,MAAMrB,UAAU;QACd8B;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;QACAC;KACD;IAED,MAAM3C,aAAa,AACjB,CAAA,MAAMC,QAAQC,GAAG,CACfH,QAAQI,GAAG,CAAC,CAACC,UACX/B,YAAYe,MAAMR,+BAA+BwB,UAErD,EACAC,KAAK,CAACC;IAER,IAAI,CAACN,YAAY;QACfX,UAAUkB,IAAI,CAACxB;QACf;IACF;IAEA,kEAAkE;IAClE,uEAAuE;IACvE,wBAAwB;IACxB,MAAMX,gBACJgB,MACAR,+BACAwC,IACE,oEACEjD,0BACA,MAEJsD;IAGF,gDAAgD;IAChD,MAAMrD,gBACJgB,MACAR,+BACAwC,IACE,4FACEjD,0BACA,MAEJwD;IAGF,uEAAuE;IACvE,MAAM1D,YACJmB,MACAR,+BACAwC,IACE,kGACE,mDACA,cACA;IAGN,MAAMnD,YACJmB,MACAR,+BACAoD,wBACE;IAEJ,MAAM/D,YACJmB,MACAR,+BACAwC,IACE,kHACE,mDACA,cACA;IAGN,MAAMnD,YACJmB,MACAR,+BACAsD,wBACE;IAGJ,yEAAyE;IACzE,MAAMjE,YACJmB,MACAR,+BACAuD,qBAAqB;IAEvB,MAAMlE,YACJmB,MACAR,+BACAwD,8BACE;IAEJ,MAAMnE,YACJmB,MACAR,+BACAyD,+BACE;IAEJ,MAAMpE,YACJmB,MACAR,+BACA0D,wCACE;IAEJ,MAAMrE,YACJmB,MACAR,+BACA2D,yCACE;IAGJ,qDAAqD;IACrD,MAAMtE,YACJmB,MACAR,+BACAwC,IACE,oHACE,yCACA,cACA;IAGN,MAAMnD,YACJmB,MACAR,+BACA6D,kBACE;IAEJ,MAAMxE,YACJmB,MACAR,+BACA8D,wBACE;IAGJ,0EAA0E;IAC1E,uDAAuD;IACvD,MAAMzE,YACJmB,MACAR,+BACA+D,uCAAuC;AAE3C;AAEA,MAAMC,gCAAgC;IACpC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAClB,IAAI,CAAC;AAEP,MAAMmB,8BAA8B;IAClC;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAACnB,IAAI,CAAC;AAEP;;;;CAIC,GACD,MAAMoB,6BAA6B,OACjC1D,MACAC;IAEA,IAAI,CAACD,KAAKE,MAAM,CAACT,oCAAoC;QACnD;IACF;IAEA,KAAK,MAAMkE,WAAW3D,KAAKsB,QAAQ,CAAC7B,mCAAoC;QACtE,MAAMI,WAAWlB,kBACfc,mCACAkE,SACA,GAAGA,QAAQ,GAAG,CAAC;QAEjB,IAAI,CAAC3D,KAAKE,MAAM,CAACL,WAAW;YAC1B,UAAU,sCAAsC;QAClD;QAEA,IACE,MAAMZ,YAAYe,MAAMH,UAAUmC,IAAI,oCACtC;YACA,UAAU,oBAAoB;QAChC;QAEA,MAAM4B,kBAAkB5B,IAAI;QAC5B,MAAM6B,eAAe7B,IAAI;QACzB,MAAM8B,iBAAiB9B,IACrB,kDACE,yDACA;QAEJ,sEAAsE;QACtE,qEAAqE;QACrE,sEAAsE;QACtE,sEAAsE;QACtE,+EAA+E;QAC/E,MAAM+B,6BAA6B,OAAOpC,OACxC1C,YACEe,MACAH,UACAmC,IACE,CAAC,EAAE,EAAEL,KAAK,0BAA0B,CAAC,GACnC,yDACA;QAIR,MAAMI,QACJ,AACE,CAAA,MAAMlB,QAAQC,GAAG,CACf;YAAC8C;YAAiBC;YAAcC;SAAe,CAAC/C,GAAG,CAAC,CAACC,UACnD/B,YAAYe,MAAMH,UAAUmB,UAEhC,EACAC,KAAK,CAACC,YACR,CAAE,MAAM6C,2BAA2B,0BACnC,CAAE,MAAMA,2BAA2B;QAErC,IAAI,CAAChC,OAAO;YACV9B,UAAUkB,IAAI,CAACvB,4BAA4BC;YAC3C;QACF;QAEA,mEAAmE;QACnE,kEAAkE;QAClE,wCAAwC;QACxC,MAAMb,gBACJgB,MACAH,UACAmC,IAAI,kCACF,CAAC,aAAa,EAAEjD,wBAAwB,EAAE,CAAC,EAC7CyE;QAGF,MAAMxE,gBACJgB,MACAH,UACAiE,iBAAiB,CAAC,iBAAiB,EAAE/E,wBAAwB,EAAE,CAAC,EAChE0E;IAEJ;AACF;AAEA,eAAe,eAAeO,UAC5BhE,IAAU;IAEV,MAAMC,YAAsB,EAAE;IAE9B,MAAMF,oBAAoBC,MAAMC;IAChC,MAAMmB,wBAAwBpB,MAAMC;IACpC,MAAMuC,uBAAuBxC,MAAMC;IACnC,MAAMyD,2BAA2B1D,MAAMC;IAEvC,MAAMf,qBAAqBc;IAE3B,OAAO;QAAEC;IAAU;AACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.79/0001-py-agent-ag-ui-hooks/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport { PY_AGENT_GENERATOR_INFO } from '../../../py/agent/generator.js';\nimport {\n addPythonDestructuredImport,\n applyGritQL,\n captureGritQLVariable,\n GRIT_INSERT_PLACEHOLDER,\n insertViaGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport type { ComponentMetadata } from '../../../utils/nx.js';\n\n/**\n * Forward a Strands py#agent's hooks to the AG-UI adapter.\n *\n * `StrandsAgent` builds a fresh `Agent` per `thread_id` by copying the template\n * Agent's kwargs, but deliberately skips `hooks` - Strands keeps only the built\n * `HookRegistry`, so the providers can't be read back off it. An AG-UI agent's\n * `log_model_errors` / `log_tool_errors` therefore never fired for served\n * requests, and model/tool failures were reported as successful runs.\n *\n * The fix hoists the hooks list out of the `Agent(...)` call into an\n * `AGENT_HOOKS` module constant in `agent.py` - whatever the user has put in it,\n * so custom hooks come along - and passes that to `StrandsAgent(...)` in\n * `main.py` as well. `agent.py` is hoisted for every protocol so the constant\n * matches what the generator vends today; only AG-UI needs the `main.py` half.\n */\n\n// `Agent`'s `hooks` list is invariant, so the hoisted constant has to declare\n// the parameter's own type - an inline literal was inferred from it instead.\nconst HOOKS_TYPE = 'list[HookProvider | HookCallback]';\nconst HOOKS_MODULE = 'strands.hooks';\n\n// Anchored on the `get_agent` context manager - the one shape shared by every\n// generated protocol and connection combination - so the constant lands\n// directly above its only use, where the generator vends it.\nconst HOIST_HOOKS_PATTERN = `language python\n\\`@contextmanager\ndef get_agent($params):\n $body\\` => raw\\`AGENT_HOOKS: ${HOOKS_TYPE} = ${GRIT_INSERT_PLACEHOLDER}\n\n\n@contextmanager\ndef get_agent($params):\n $body\\` where { $program <: not contains \\`AGENT_HOOKS\\` }`;\n\n// Scoped to the Agent constructor so a hooks list the user passes elsewhere in\n// agent.py is left alone.\nconst HOOKS_LIST_CAPTURE_PATTERN =\n 'language python\\n`hooks=[$hooks]` where { $hooks <: within `Agent($_)` }';\nconst HOOKS_LIST_REWRITE_PATTERN =\n 'language python\\n`hooks=[$_]` as $kwarg where { $kwarg <: within `Agent($_)`, $kwarg => `hooks=AGENT_HOOKS` }';\n\nconst AGUI_CONSTRUCTOR_MATCH_PATTERN =\n 'language python\\n`StrandsAgent($args)` where { $args <: contains `agent=$_` }';\nconst AGUI_HOOKS_WIRED_PATTERN =\n 'language python\\n`StrandsAgent($args)` where { $args <: contains `agent=$_`, $args <: contains `hooks=$_` }';\n// Appends after the last argument rather than re-emitting the argument list,\n// both to land `hooks=` where the template puts it and to leave the other\n// arguments' formatting and comments untouched.\nconst AGUI_HOOKS_APPEND_PATTERN =\n 'language python\\n`StrandsAgent($args)` where { $args <: contains `agent=$_`, $args <: not contains `hooks=$_`, $args <: [$..., $last], $last += `, hooks=AGENT_HOOKS` }';\n\nconst findAgentComponents = (\n components: ComponentMetadata[] | undefined,\n): ComponentMetadata[] =>\n (components ?? []).filter(\n (component) => component.generator === PY_AGENT_GENERATOR_INFO.id,\n );\n\n/**\n * Hoists `agent.py`'s `hooks=[...]` list into an `AGENT_HOOKS` module constant,\n * returning whether the file ends up exporting one.\n */\nconst hoistAgentHooks = async (\n tree: Tree,\n agentPath: string,\n): Promise<boolean> => {\n if ((tree.read(agentPath, 'utf-8') ?? '').includes('AGENT_HOOKS')) {\n return true;\n }\n\n // Nothing to hoist unless the Agent is constructed with an inline hooks list.\n const hooks = await captureGritQLVariable(\n tree,\n agentPath,\n HOOKS_LIST_CAPTURE_PATTERN,\n 'hooks',\n );\n if (!hooks) return false;\n\n if (\n !(await insertViaGritQL(tree, agentPath, HOIST_HOOKS_PATTERN, `[${hooks}]`))\n ) {\n return false;\n }\n await applyGritQL(tree, agentPath, HOOKS_LIST_REWRITE_PATTERN);\n await addPythonDestructuredImport(\n tree,\n agentPath,\n ['HookCallback', 'HookProvider'],\n HOOKS_MODULE,\n );\n return true;\n};\n\nconst migrateAgent = async (\n tree: Tree,\n agentDir: string,\n component: ComponentMetadata,\n nextSteps: string[],\n): Promise<void> => {\n // LangChain agents pass no hooks - their AG-UI adapter is handed the graph\n // itself rather than rebuilding one, so nothing is dropped.\n if (component.framework === 'langchain') return;\n\n const agentPath = joinPathFragments(agentDir, 'agent.py');\n const mainPath = joinPathFragments(agentDir, 'main.py');\n const isAgUi = component.protocol === 'ag-ui';\n\n if (!tree.exists(agentPath)) return;\n\n if (!(await hoistAgentHooks(tree, agentPath))) {\n if (isAgUi) {\n nextSteps.push(\n `${agentPath}: the hooks passed to \\`Agent(...)\\` have diverged from the generated shape - left untouched. Hoist them into an \\`AGENT_HOOKS\\` module constant and pass it to \\`StrandsAgent(...)\\` in ${mainPath} too, or AG-UI will not register them (see the py#agent generator's template).`,\n );\n }\n return;\n }\n\n // Only AG-UI rebuilds the Agent, so only its main.py needs the hooks.\n if (!isAgUi || !tree.exists(mainPath)) return;\n\n // Already passing hooks, whether from a previous run or the user's own wiring.\n if (await matchGritQL(tree, mainPath, AGUI_HOOKS_WIRED_PATTERN)) return;\n\n if (!(await matchGritQL(tree, mainPath, AGUI_CONSTRUCTOR_MATCH_PATTERN))) {\n nextSteps.push(\n `${mainPath}: the \\`StrandsAgent(...)\\` constructor has diverged from the generated shape - left untouched. Pass \\`hooks=AGENT_HOOKS\\` to it, importing \\`AGENT_HOOKS\\` from \\`.agent\\`, or AG-UI will not register the agent's hooks (see the py#agent generator's template).`,\n );\n return;\n }\n\n await applyGritQL(tree, mainPath, AGUI_HOOKS_APPEND_PATTERN);\n await addPythonDestructuredImport(tree, mainPath, ['AGENT_HOOKS'], '.agent');\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const project of getProjects(tree).values()) {\n const components = findAgentComponents(\n (project.metadata as { components?: ComponentMetadata[] })?.components,\n );\n\n for (const component of components) {\n if (!component.path) continue;\n\n // Legacy component paths point at agent.py rather than its directory.\n const componentDir = component.path.endsWith('/agent.py')\n ? component.path.slice(0, -'/agent.py'.length)\n : component.path;\n\n await migrateAgent(\n tree,\n joinPathFragments(project.root, componentDir),\n component,\n nextSteps,\n );\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["getProjects","joinPathFragments","PY_AGENT_GENERATOR_INFO","addPythonDestructuredImport","applyGritQL","captureGritQLVariable","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","HOOKS_TYPE","HOOKS_MODULE","HOIST_HOOKS_PATTERN","HOOKS_LIST_CAPTURE_PATTERN","HOOKS_LIST_REWRITE_PATTERN","AGUI_CONSTRUCTOR_MATCH_PATTERN","AGUI_HOOKS_WIRED_PATTERN","AGUI_HOOKS_APPEND_PATTERN","findAgentComponents","components","filter","component","generator","id","hoistAgentHooks","tree","agentPath","read","includes","hooks","migrateAgent","agentDir","nextSteps","framework","mainPath","isAgUi","protocol","exists","push","migration","project","values","metadata","path","componentDir","endsWith","slice","length","root"],"mappings":"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"}
|
package/src/migrations/v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path/migration.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.82/0001-terraform-load-runtime-config-src-path/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n type MigrationReturnObject,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport { TERRAFORM_WEBSITE_RUNTIME_CONFIG_FILE } from '../../../utils/shared-constructs-constants.js';\n\n/**\n * The Terraform `load-runtime-config` target copied\n * `dist/packages/common/terraform/runtime-config.json`, which nothing writes:\n * the vended runtime-config modules aggregate per namespace into the\n * `runtime-config` *directory*, so the website's config lands at\n * `runtime-config/connection.json`. Copying the directory path always failed\n * with ENOENT, so point it at the file the aggregation actually writes.\n *\n * Both target names are rewritten — `load-runtime-config` and the\n * `load:runtime-config` it was cloned from, which is left in place for\n * workspaces that still reference it.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n */\n\nconst TARGETS = ['load-runtime-config', 'load:runtime-config'];\n\n/** The path the target used to name, which never existed on disk. */\nconst BROKEN_SRC_FILE = 'dist/packages/common/terraform/runtime-config.json';\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n for (const [projectName, project] of getProjects(tree)) {\n let updated = false;\n\n for (const targetName of TARGETS) {\n const env = project.targets?.[targetName]?.options?.env;\n // Only the broken path is rewritten, so a workspace that pointed the\n // target somewhere of its own keeps it.\n if (env?.SRC_FILE !== BROKEN_SRC_FILE) {\n continue;\n }\n env.SRC_FILE = TERRAFORM_WEBSITE_RUNTIME_CONFIG_FILE;\n updated = true;\n }\n\n if (updated) {\n updateProjectConfiguration(tree, projectName, project);\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return {};\n}\n"],"names":["getProjects","updateProjectConfiguration","formatFilesInSubtree","TERRAFORM_WEBSITE_RUNTIME_CONFIG_FILE","TARGETS","BROKEN_SRC_FILE","migration","tree","projectName","project","updated","targetName","env","targets","options","SRC_FILE"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EAGXC,0BAA0B,QACrB,aAAa;AACpB,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SAASC,qCAAqC,QAAQ,gDAAgD;AAEtG;;;;;;;;;;;;;;;CAeC,GAED,MAAMC,UAAU;IAAC;IAAuB;CAAsB;AAE9D,mEAAmE,GACnE,MAAMC,kBAAkB;AAExB,eAAe,eAAeC,UAC5BC,IAAU;IAEV,KAAK,MAAM,CAACC,aAAaC,QAAQ,IAAIT,YAAYO,MAAO;QACtD,IAAIG,UAAU;QAEd,KAAK,MAAMC,cAAcP,QAAS;YAChC,MAAMQ,MAAMH,QAAQI,OAAO,EAAE,CAACF,WAAW,EAAEG,SAASF;YACpD,qEAAqE;YACrE,wCAAwC;YACxC,IAAIA,KAAKG,aAAaV,iBAAiB;gBACrC;YACF;YACAO,IAAIG,QAAQ,GAAGZ;YACfO,UAAU;QACZ;QAEA,IAAIA,SAAS;YACXT,2BAA2BM,MAAMC,aAAaC;QAChD;IACF;IAEA,MAAMP,qBAAqBK;IAE3B,OAAO,CAAC;AACV"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.82/0002-runtime-config-namespace-defaults/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n type MigrationReturnObject,\n type Tree,\n visitNotIgnoredFiles,\n} from '@nx/devkit';\nimport { applyGritQL, matchGritQL } 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 * Provision AppConfig configuration profiles for every namespace the vended terraform modules write to\n *\n * `core/runtime-config/appconfig` creates one configuration profile per entry in\n * its `namespaces` list, and `appconfig-deployment` aggregates and deploys the\n * same list. A module contributing an entry to a namespace outside that list\n * writes its entry file to disk and nothing more — the value never reaches\n * AppConfig, so a consumer reading it back fails at runtime. The vended defaults\n * now cover `dynamodb` and `database` alongside `connection` and `agentcore`.\n *\n * Root modules that pass `namespaces` explicitly are updated too, since an\n * explicit list overrides the vended default.\n *\n * How to write a migration:\n * - https://nx.dev/docs/kb/migration-generators\n * - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject\n *\n * Guardrails:\n * - Transform source with GritQL (`applyGritQL`, `matchGritQL`), not regexes or\n * string replacements: it matches the AST, so it holds up against however the\n * user's copy has been formatted. Use `updateJson` for JSON.\n * - Pattern-match before writing: skip files that have diverged from the shape\n * your generators produce and report them via `nextSteps`, or consider a\n * hybrid migration, rather than clobbering the user's changes.\n * - `nextSteps` is for work left for the user to do by hand, not a log of the\n * edits you made: an edit that applied cleanly needs no entry.\n * - Idempotent: re-running must be a no-op.\n * - Format what you write: finish with `formatFilesInSubtree` so the files your\n * migration wrote are formatted correctly.\n */\n\n/** Namespaces the vended modules write to that the old defaults omitted. */\nconst ADDED_NAMESPACES = ['database', 'dynamodb'];\n\nconst RUNTIME_CONFIG_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/runtime-config`;\nconst APPCONFIG_FILE = `${RUNTIME_CONFIG_DIR}/appconfig/appconfig.tf`;\nconst APPCONFIG_DEPLOYMENT_FILE = `${RUNTIME_CONFIG_DIR}/appconfig-deployment/appconfig-deployment.tf`;\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\n/**\n * Where a `namespaces` list lives, as the block that encloses it and the\n * attribute holding the list. `clauses` narrows the block further, keeping an\n * unrelated list elsewhere in the file out of scope; `$body` is the block's body\n * in every case, so the clauses below can all be written against it.\n */\ninterface NamespaceList {\n readonly block: string;\n readonly attribute: string;\n readonly clauses?: string;\n}\n\n/** The `namespaces` variable declaration in a vended appconfig module. */\nconst VENDED_DEFAULT: NamespaceList = {\n block: 'variable \"namespaces\" { $body }',\n attribute: 'default',\n};\n\n/**\n * A call to the vended appconfig module that passes `namespaces` explicitly.\n * `$src` binds the quoted string, so the regex ends at the closing quote — that\n * anchor is what keeps the sibling `appconfig-deployment` call, which takes its\n * namespaces from this module's output, out of scope.\n */\nconst ROOT_MODULE_ARGUMENT: NamespaceList = {\n block: 'module $name { $body }',\n attribute: 'namespaces',\n clauses:\n '$body <: contains `source = $src`, $src <: r\".*runtime-config/appconfig\\\\\"\", $body <: contains `namespaces = $_`',\n};\n\n/** A GritQL pattern matching the enclosing block, plus any extra clauses. */\nconst scopePattern = (list: NamespaceList, extraClauses?: string) => {\n const clauses = [list.clauses, extraClauses].filter(Boolean).join(', ');\n return hcl(`\\`${list.block}\\`${clauses ? ` where { ${clauses} }` : ''}`);\n};\n\n/** Whether a namespace already appears in the list. */\nconst listsNamespace = (\n tree: Tree,\n filePath: string,\n list: NamespaceList,\n namespace: string,\n) =>\n matchGritQL(\n tree,\n filePath,\n scopePattern(list, `$body <: contains \\`\"${namespace}\"\\``),\n );\n\n/**\n * Append a namespace to the list. Appended within the list rather than\n * re-emitting it, so no trailing hole is introduced.\n */\nconst appendNamespace = (\n tree: Tree,\n filePath: string,\n list: NamespaceList,\n namespace: string,\n) =>\n applyGritQL(\n tree,\n filePath,\n scopePattern(\n list,\n `$body <: contains bubble \\`${list.attribute} = [$items]\\` where { $items <: [$..., $last], $last += \\`, \"${namespace}\"\\` }`,\n ),\n );\n\n/**\n * Add every missing namespace to a list, returning false when one could not be\n * added. Already-listed namespaces are skipped, which is what makes a re-run a\n * no-op.\n */\nconst addMissingNamespaces = async (\n tree: Tree,\n filePath: string,\n list: NamespaceList,\n): Promise<boolean> => {\n for (const namespace of ADDED_NAMESPACES) {\n if (await listsNamespace(tree, filePath, list, namespace)) {\n continue;\n }\n if (!(await appendNamespace(tree, filePath, list, namespace))) {\n return false;\n }\n }\n return true;\n};\n\n/** The vended module's own description, updated to document the new defaults. */\nconst DESCRIPTION_REWRITE = scopePattern(\n VENDED_DEFAULT,\n '$body <: contains bubble `description = $desc` where { $desc <: r\".*one Configuration Profile is created per namespace.*\", $desc => `\"List of runtime-config namespaces this AppConfig application should expose (one Configuration Profile is created per namespace). The default covers every namespace the generated modules write to, and a namespace with no contributions deploys an empty profile — when adding namespaces of your own, keep the defaults alongside them.\"` }',\n);\n\nconst missing = ADDED_NAMESPACES.map((n) => `\"${n}\"`).join(' and ');\n\nconst divergedNextStep = (filePath: string) =>\n `${filePath}: the \\`namespaces\\` variable has diverged from the generated shape - left untouched. Add ${missing} to its \\`default\\` list, otherwise no configuration profile is created for them and the entries the vended dynamodb/database modules contribute are never deployed.`;\n\nconst rootModuleNextStep = (filePath: string) =>\n `${filePath}: passes \\`namespaces\\` to the runtime-config appconfig module in a shape this migration could not update - left untouched. Add ${missing} to that list, or drop the argument to take the module's defaults, otherwise the entries the vended dynamodb/database modules contribute are never deployed.`;\n\n/** Bring one vended appconfig module's `namespaces` default up to date. */\nconst migrateVendedModule = async (\n tree: Tree,\n filePath: string,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(filePath)) {\n return; // This workspace has no Terraform runtime config.\n }\n\n if (!(await addMissingNamespaces(tree, filePath, VENDED_DEFAULT))) {\n nextSteps.push(divergedNextStep(filePath));\n }\n};\n\n/**\n * Root modules that pass `namespaces` explicitly override the vended default, so\n * they need the same additions.\n */\nconst migrateRootModules = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n const terraformFiles: string[] = [];\n visitNotIgnoredFiles(tree, '', (filePath) => {\n if (filePath.endsWith('.tf') && !filePath.startsWith(RUNTIME_CONFIG_DIR)) {\n terraformFiles.push(filePath);\n }\n });\n\n for (const filePath of terraformFiles) {\n if (\n !(await matchGritQL(tree, filePath, scopePattern(ROOT_MODULE_ARGUMENT)))\n ) {\n continue; // Takes the module's defaults, so nothing to update.\n }\n\n if (!(await addMissingNamespaces(tree, filePath, ROOT_MODULE_ARGUMENT))) {\n nextSteps.push(rootModuleNextStep(filePath));\n }\n }\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n await migrateVendedModule(tree, APPCONFIG_FILE, nextSteps);\n await migrateVendedModule(tree, APPCONFIG_DEPLOYMENT_FILE, nextSteps);\n\n if (tree.exists(APPCONFIG_FILE)) {\n await applyGritQL(tree, APPCONFIG_FILE, DESCRIPTION_REWRITE);\n }\n\n await migrateRootModules(tree, nextSteps);\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["visitNotIgnoredFiles","applyGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","ADDED_NAMESPACES","RUNTIME_CONFIG_DIR","APPCONFIG_FILE","APPCONFIG_DEPLOYMENT_FILE","hcl","pattern","VENDED_DEFAULT","block","attribute","ROOT_MODULE_ARGUMENT","clauses","scopePattern","list","extraClauses","filter","Boolean","join","listsNamespace","tree","filePath","namespace","appendNamespace","addMissingNamespaces","DESCRIPTION_REWRITE","missing","map","n","divergedNextStep","rootModuleNextStep","migrateVendedModule","nextSteps","exists","push","migrateRootModules","terraformFiles","endsWith","startsWith","migration"],"mappings":"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"}
|
package/src/migrations/v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs/migration.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.83/0001-dynamodb-terraform-app-module-outputs/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 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 * Re-export the core dynamodb module's `table_name`, `table_arn` and\n * `kms_key_arn` from each vended per-table Terraform app module\n * (`app/dynamodb/<name>/<name>.tf`).\n *\n * The app module is what a root configuration instantiates, so without these\n * outputs a root module cannot reference the table or its encryption key at\n * all — for example to grant a consumer access to it.\n *\n * These files are generated with `KeepExisting`, so an upgraded workspace keeps\n * a module with no outputs until this runs. Diverged files are left untouched\n * and reported via `nextSteps`.\n */\n\nconst TERRAFORM_DYNAMODB_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/dynamodb`;\n\nconst divergedMessage = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To reference the table from your root Terraform configuration, add table_name, table_arn and kms_key_arn outputs here forwarding the equivalent module.dynamodb_table attributes (see the ts#dynamodb generator's dynamodb app template).`;\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\nconst OUTPUTS_TEXT = [\n 'output \"table_name\" {',\n ' description = \"Name of the DynamoDB table\"',\n ' value = module.dynamodb_table.table_name',\n '}',\n '',\n 'output \"table_arn\" {',\n ' description = \"ARN of the DynamoDB table, for use in IAM policies granting access to the table. Suffix with /index/* to cover its global secondary indexes.\"',\n ' value = module.dynamodb_table.table_arn',\n '}',\n '',\n 'output \"kms_key_arn\" {',\n ' description = \"ARN of the KMS key used to encrypt the DynamoDB table, for use in IAM policies granting access to the table. Null when using the AWS owned key (encryption = DEFAULT).\"',\n ' value = module.dynamodb_table.kms_key_arn',\n '}',\n].join('\\n');\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (!tree.exists(TERRAFORM_DYNAMODB_APP_DIR)) {\n return { nextSteps }; // This workspace has no Terraform dynamodb modules.\n }\n\n for (const dirName of tree.children(TERRAFORM_DYNAMODB_APP_DIR)) {\n const filePath = joinPathFragments(\n TERRAFORM_DYNAMODB_APP_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a dynamodb app module directory.\n }\n\n if (await matchGritQL(tree, filePath, hcl('`output \"table_arn\" { $_ }`'))) {\n continue; // Already migrated.\n }\n\n const RUNTIME_CONFIG_MODULE_BLOCK = hcl(\n '`module \"add_to_runtime_config\" { $_ }`',\n );\n if (!(await matchGritQL(tree, filePath, RUNTIME_CONFIG_MODULE_BLOCK))) {\n nextSteps.push(divergedMessage(filePath));\n continue;\n }\n\n // Appended after the runtime config module call, which the template ends\n // with, matching where the generator now writes the outputs.\n await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`module \"add_to_runtime_config\" { $body }\\` => \\`module \"add_to_runtime_config\" {\\n $body\\n}\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n OUTPUTS_TEXT,\n );\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TERRAFORM_DYNAMODB_APP_DIR","divergedMessage","filePath","hcl","pattern","OUTPUTS_TEXT","join","migration","tree","nextSteps","exists","dirName","children","RUNTIME_CONFIG_MODULE_BLOCK","push"],"mappings":"AAAA;;;CAGC,GACD,SACEA,iBAAiB,QAGZ,aAAa;AACpB,SACEC,uBAAuB,EACvBC,eAAe,EACfC,WAAW,QACN,wBAAwB;AAC/B,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SACEC,YAAY,EACZC,oBAAoB,QACf,gDAAgD;AAEvD;;;;;;;;;;;;CAYC,GAED,MAAMC,6BAA6B,GAAGF,aAAa,CAAC,EAAEC,qBAAqB,iBAAiB,CAAC;AAE7F,MAAME,kBAAkB,CAACC,WACvB,GAAGA,SAAS,mSAAmS,CAAC;AAElT,MAAMC,MAAM,CAACC,UAAoB,CAAC,cAAc,EAAEA,SAAS;AAE3D,MAAMC,eAAe;IACnB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAACC,IAAI,CAAC;AAEP,eAAe,eAAeC,UAC5BC,IAAU;IAEV,MAAMC,YAAsB,EAAE;IAE9B,IAAI,CAACD,KAAKE,MAAM,CAACV,6BAA6B;QAC5C,OAAO;YAAES;QAAU,GAAG,oDAAoD;IAC5E;IAEA,KAAK,MAAME,WAAWH,KAAKI,QAAQ,CAACZ,4BAA6B;QAC/D,MAAME,WAAWT,kBACfO,4BACAW,SACA,GAAGA,QAAQ,GAAG,CAAC;QAEjB,IAAI,CAACH,KAAKE,MAAM,CAACR,WAAW;YAC1B,UAAU,uCAAuC;QACnD;QAEA,IAAI,MAAMN,YAAYY,MAAMN,UAAUC,IAAI,iCAAiC;YACzE,UAAU,oBAAoB;QAChC;QAEA,MAAMU,8BAA8BV,IAClC;QAEF,IAAI,CAAE,MAAMP,YAAYY,MAAMN,UAAUW,8BAA+B;YACrEJ,UAAUK,IAAI,CAACb,gBAAgBC;YAC/B;QACF;QAEA,yEAAyE;QACzE,6DAA6D;QAC7D,MAAMP,gBACJa,MACAN,UACAC,IACE,CAAC,kGAAkG,EAAET,wBAAwB,EAAE,CAAC,GAElIW;IAEJ;IAEA,MAAMR,qBAAqBW;IAE3B,OAAO;QAAEC;IAAU;AACrB"}
|
package/src/migrations/v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection/migration.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.83/0002-terraform-data-resource-destroy-protection/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport type { MigrationReturnObject, Tree } from '@nx/devkit';\nimport { applyGritQL, matchGritQL } 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 existing workspaces up to the destroy protection the generators now\n * vend on the data-bearing Terraform resources:\n *\n * - The DynamoDB table and the Aurora cluster gain\n * `lifecycle { prevent_destroy = true }`. This is a second, independent layer\n * on top of the service-side `deletion_protection_enabled` /\n * `deletion_protection` flags, matching the two guards the CDK constructs\n * already have (`deletionProtection` plus `RemovalPolicy.RETAIN`). Without it,\n * clearing the service-side flag destroys the data in the same apply.\n * `prevent_destroy` cannot reference a variable, so it is vended as a literal\n * and removed to tear down.\n * - Their KMS keys pin `deletion_window_in_days = 30`, the provider maximum and\n * the CDK default. Restoring either resource from a point-in-time backup or\n * snapshot requires its key, so the window in which a scheduled key deletion\n * can still be cancelled matters as much as the backup itself.\n *\n * These files are generated with `KeepExisting`, so without this an upgraded\n * workspace keeps the weaker single-layer protection. Diverged files are left\n * untouched and reported via `nextSteps`.\n */\n\nconst TERRAFORM_DYNAMODB_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/dynamodb/dynamodb.tf`;\nconst TERRAFORM_AURORA_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/rdb/aurora/aurora.tf`;\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\nconst divergedMessage = (filePath: string, resource: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To protect the ${resource} from being destroyed when the service-side deletion protection flag is cleared, add \\`lifecycle { prevent_destroy = true }\\` to it, and set \\`deletion_window_in_days = 30\\` on its KMS key.`;\n\n/**\n * Add the maximum KMS pending window to a key that omits it, so a scheduled\n * deletion stays cancellable for as long as the provider allows.\n */\nconst migrateKmsKey = async (\n tree: Tree,\n filePath: string,\n keyLabel: string,\n): Promise<void> => {\n const keyBlock = hcl(`\\`resource \"aws_kms_key\" \"${keyLabel}\" { $props }\\``);\n\n // Already pinned - nothing to do.\n if (\n await matchGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"aws_kms_key\" \"${keyLabel}\" { $props }\\` where { $props <: contains \\`deletion_window_in_days = $_\\` }`,\n ),\n )\n ) {\n return;\n }\n\n if (!(await matchGritQL(tree, filePath, keyBlock))) {\n return; // Reported by the caller, which matches the data resource too.\n }\n\n await applyGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"aws_kms_key\" \"${keyLabel}\" { $props }\\` where { $props <: contains \\`enable_key_rotation = $rotation\\` => \\`enable_key_rotation = $rotation\\n\\n deletion_window_in_days = 30\\` }`,\n ),\n );\n};\n\n/**\n * Add `lifecycle { prevent_destroy = true }` to a data-bearing resource.\n */\nconst migrateDataResource = async (\n tree: Tree,\n filePath: string,\n resourceType: string,\n resourceLabel: string,\n nextSteps: string[],\n description: string,\n): Promise<boolean> => {\n const resourceBlock = hcl(\n `\\`resource \"${resourceType}\" \"${resourceLabel}\" { $props }\\``,\n );\n\n if (!(await matchGritQL(tree, filePath, resourceBlock))) {\n nextSteps.push(divergedMessage(filePath, description));\n return false;\n }\n\n // Already guarded - keep the user's block, whatever it is set to.\n if (\n await matchGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"${resourceType}\" \"${resourceLabel}\" { $props }\\` where { $props <: contains \\`prevent_destroy = $_\\` }`,\n ),\n )\n ) {\n return true;\n }\n\n await applyGritQL(\n tree,\n filePath,\n hcl(\n `\\`resource \"${resourceType}\" \"${resourceLabel}\" { $props }\\` => \\`resource \"${resourceType}\" \"${resourceLabel}\" {\\n $props\\n\\n lifecycle {\\n prevent_destroy = true\\n }\\n}\\``,\n ),\n );\n\n return true;\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (tree.exists(TERRAFORM_DYNAMODB_FILE)) {\n if (\n await migrateDataResource(\n tree,\n TERRAFORM_DYNAMODB_FILE,\n 'aws_dynamodb_table',\n 'table',\n nextSteps,\n 'DynamoDB table',\n )\n ) {\n await migrateKmsKey(tree, TERRAFORM_DYNAMODB_FILE, 'table');\n }\n }\n\n if (tree.exists(TERRAFORM_AURORA_FILE)) {\n if (\n await migrateDataResource(\n tree,\n TERRAFORM_AURORA_FILE,\n 'aws_rds_cluster',\n 'database',\n nextSteps,\n 'Aurora cluster',\n )\n ) {\n await migrateKmsKey(tree, TERRAFORM_AURORA_FILE, 'database');\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["applyGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TERRAFORM_DYNAMODB_FILE","TERRAFORM_AURORA_FILE","hcl","pattern","divergedMessage","filePath","resource","migrateKmsKey","tree","keyLabel","keyBlock","migrateDataResource","resourceType","resourceLabel","nextSteps","description","resourceBlock","push","migration","exists"],"mappings":"AAAA;;;CAGC,GAED,SAASA,WAAW,EAAEC,WAAW,QAAQ,wBAAwB;AACjE,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SACEC,YAAY,EACZC,oBAAoB,QACf,gDAAgD;AAEvD;;;;;;;;;;;;;;;;;;;;CAoBC,GAED,MAAMC,0BAA0B,GAAGF,aAAa,CAAC,EAAEC,qBAAqB,8BAA8B,CAAC;AACvG,MAAME,wBAAwB,GAAGH,aAAa,CAAC,EAAEC,qBAAqB,8BAA8B,CAAC;AAErG,MAAMG,MAAM,CAACC,UAAoB,CAAC,cAAc,EAAEA,SAAS;AAE3D,MAAMC,kBAAkB,CAACC,UAAkBC,WACzC,GAAGD,SAAS,yEAAyE,EAAEC,SAAS,6LAA6L,CAAC;AAEhS;;;CAGC,GACD,MAAMC,gBAAgB,OACpBC,MACAH,UACAI;IAEA,MAAMC,WAAWR,IAAI,CAAC,0BAA0B,EAAEO,SAAS,cAAc,CAAC;IAE1E,kCAAkC;IAClC,IACE,MAAMb,YACJY,MACAH,UACAH,IACE,CAAC,0BAA0B,EAAEO,SAAS,4EAA4E,CAAC,IAGvH;QACA;IACF;IAEA,IAAI,CAAE,MAAMb,YAAYY,MAAMH,UAAUK,WAAY;QAClD,QAAQ,+DAA+D;IACzE;IAEA,MAAMf,YACJa,MACAH,UACAH,IACE,CAAC,0BAA0B,EAAEO,SAAS,wJAAwJ,CAAC;AAGrM;AAEA;;CAEC,GACD,MAAME,sBAAsB,OAC1BH,MACAH,UACAO,cACAC,eACAC,WACAC;IAEA,MAAMC,gBAAgBd,IACpB,CAAC,YAAY,EAAEU,aAAa,GAAG,EAAEC,cAAc,cAAc,CAAC;IAGhE,IAAI,CAAE,MAAMjB,YAAYY,MAAMH,UAAUW,gBAAiB;QACvDF,UAAUG,IAAI,CAACb,gBAAgBC,UAAUU;QACzC,OAAO;IACT;IAEA,kEAAkE;IAClE,IACE,MAAMnB,YACJY,MACAH,UACAH,IACE,CAAC,YAAY,EAAEU,aAAa,GAAG,EAAEC,cAAc,oEAAoE,CAAC,IAGxH;QACA,OAAO;IACT;IAEA,MAAMlB,YACJa,MACAH,UACAH,IACE,CAAC,YAAY,EAAEU,aAAa,GAAG,EAAEC,cAAc,8BAA8B,EAAED,aAAa,GAAG,EAAEC,cAAc,oEAAoE,CAAC;IAIxL,OAAO;AACT;AAEA,eAAe,eAAeK,UAC5BV,IAAU;IAEV,MAAMM,YAAsB,EAAE;IAE9B,IAAIN,KAAKW,MAAM,CAACnB,0BAA0B;QACxC,IACE,MAAMW,oBACJH,MACAR,yBACA,sBACA,SACAc,WACA,mBAEF;YACA,MAAMP,cAAcC,MAAMR,yBAAyB;QACrD;IACF;IAEA,IAAIQ,KAAKW,MAAM,CAAClB,wBAAwB;QACtC,IACE,MAAMU,oBACJH,MACAP,uBACA,mBACA,YACAa,WACA,mBAEF;YACA,MAAMP,cAAcC,MAAMP,uBAAuB;QACnD;IACF;IAEA,MAAMJ,qBAAqBW;IAE3B,OAAO;QAAEM;IAAU;AACrB"}
|
package/src/migrations/v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs/migration.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.83/0003-terraform-gateway-cedar-render-root-ejs/migration.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n getProjects,\n joinPathFragments,\n type MigrationReturnObject,\n type Tree,\n} from '@nx/devkit';\nimport { AGENTCORE_GATEWAY_GENERATOR_INFO } from '../../../agentcore-gateway/generator.js';\nimport { addDependenciesToPackageJson } from '../../../utils/dependencies.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\nimport { TS_VERSIONS } from '../../../utils/versions.js';\n\n/**\n * Declare `ejs` at the workspace root for Terraform gateways with Cedar\n * policies, which is where `render-cedar.cjs` resolves it from: the script runs\n * in the shared terraform project, which has no `package.json` of its own.\n */\n\nconst RENDER_SCRIPT = 'render-cedar.cjs';\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const gatewaysDir = joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n 'app',\n 'gateways',\n );\n let needsRootEjs = false;\n\n for (const project of getProjects(tree).values()) {\n const metadata = project.metadata as\n | { generator?: string; name?: string }\n | undefined;\n if (metadata?.generator !== AGENTCORE_GATEWAY_GENERATOR_INFO.id) {\n continue;\n }\n\n // Only a Terraform gateway with Cedar policies vends the render script; a\n // CDK one resolves ejs from the shared constructs project instead.\n if (\n tree.exists(\n joinPathFragments(gatewaysDir, metadata.name ?? '', RENDER_SCRIPT),\n )\n ) {\n needsRootEjs = true;\n }\n }\n\n if (needsRootEjs) {\n addDependenciesToPackageJson(tree, {}, { ejs: TS_VERSIONS.ejs });\n }\n\n await formatFilesInSubtree(tree);\n\n return {};\n}\n"],"names":["getProjects","joinPathFragments","AGENTCORE_GATEWAY_GENERATOR_INFO","addDependenciesToPackageJson","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TS_VERSIONS","RENDER_SCRIPT","migration","tree","gatewaysDir","needsRootEjs","project","values","metadata","generator","id","exists","name","ejs"],"mappings":"AAAA;;;CAGC,GACD,SACEA,WAAW,EACXC,iBAAiB,QAGZ,aAAa;AACpB,SAASC,gCAAgC,QAAQ,0CAA0C;AAC3F,SAASC,4BAA4B,QAAQ,iCAAiC;AAC9E,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SACEC,YAAY,EACZC,oBAAoB,QACf,gDAAgD;AACvD,SAASC,WAAW,QAAQ,6BAA6B;AAEzD;;;;CAIC,GAED,MAAMC,gBAAgB;AAEtB,eAAe,eAAeC,UAC5BC,IAAU;IAEV,MAAMC,cAAcV,kBAClBI,cACAC,sBACA,OACA,OACA;IAEF,IAAIM,eAAe;IAEnB,KAAK,MAAMC,WAAWb,YAAYU,MAAMI,MAAM,GAAI;QAChD,MAAMC,WAAWF,QAAQE,QAAQ;QAGjC,IAAIA,UAAUC,cAAcd,iCAAiCe,EAAE,EAAE;YAC/D;QACF;QAEA,0EAA0E;QAC1E,mEAAmE;QACnE,IACEP,KAAKQ,MAAM,CACTjB,kBAAkBU,aAAaI,SAASI,IAAI,IAAI,IAAIX,iBAEtD;YACAI,eAAe;QACjB;IACF;IAEA,IAAIA,cAAc;QAChBT,6BAA6BO,MAAM,CAAC,GAAG;YAAEU,KAAKb,YAAYa,GAAG;QAAC;IAChE;IAEA,MAAMhB,qBAAqBM;IAE3B,OAAO,CAAC;AACV"}
|
package/src/migrations/v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults/migration.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.84/0001-rdb-terraform-aurora-app-module-defaults/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 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 * Align each vended per-database Terraform app module\n * (`app/dbs/<name>/<name>.tf`) with the core Aurora module it wraps.\n *\n * The app module is what a root configuration instantiates and it forwards its\n * own value down, so its default is the one that takes effect:\n *\n * - `enable_cloudwatch_logs` defaulted to `false` against the core module's\n * `true`, so Aurora exported no engine logs and the cluster parameter group\n * that configures them (`log_statement=ddl` on PostgreSQL,\n * `server_audit_events=CONNECT,QUERY_DDL` on MySQL) was never created —\n * the CDK construct enabled both.\n * - `enable_key_rotation` was never declared, leaving the core module's\n * variable unreachable from a root configuration.\n *\n * These files are generated with `KeepExisting`, so an upgraded workspace keeps\n * the diverged defaults until this runs. Diverged files are left untouched and\n * reported via `nextSteps`.\n */\n\nconst TERRAFORM_DBS_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/dbs`;\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\nconst KEY_ROTATION_VARIABLE = [\n 'variable \"enable_key_rotation\" {',\n ' description = \"Whether to enable automatic key rotation on the KMS key used to encrypt the Aurora cluster and its credentials secret.\"',\n ' type = bool',\n ' default = true',\n '}',\n].join('\\n');\n\nconst divergedMessage = (filePath: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. To export Aurora engine logs to CloudWatch as the CDK construct and the core Terraform module do, default \\`enable_cloudwatch_logs\\` to true here, and declare an \\`enable_key_rotation\\` variable (bool, default true) forwarded to the aurora module (see the ts#rdb generator's dbs app template).`;\n\nconst OLD_LOGS_DESCRIPTION =\n 'Whether to export Aurora engine logs to CloudWatch. PostgreSQL also enables verbose statement logging.';\nconst NEW_LOGS_DESCRIPTION =\n 'Whether to export Aurora engine logs to CloudWatch. PostgreSQL logs DDL statements only (log_statement=ddl); MySQL enables Advanced Auditing scoped to connections and DDL (server_audit_events=CONNECT,QUERY_DDL). Neither logs statement parameter values, to avoid leaking PII into log data.';\n\n/**\n * Flip the app module's `enable_cloudwatch_logs` default to match the core\n * module, and replace the description that documented the old behaviour.\n *\n * Keyed on that description, which is the only thing distinguishing the vended\n * `default = false` from a user who deliberately turned log export off.\n */\nconst migrateCloudwatchLogsDefault = (tree: Tree, filePath: string) =>\n applyGritQL(\n tree,\n filePath,\n hcl(\n `\\`variable \"enable_cloudwatch_logs\" { $props }\\` where {` +\n ` $props <: contains \\`description = \"${OLD_LOGS_DESCRIPTION}\"\\` => \\`description = \"${NEW_LOGS_DESCRIPTION}\"\\`,` +\n ` $props <: contains \\`default = false\\` => \\`default = true\\`` +\n ` }`,\n ),\n );\n\n/**\n * Declare `enable_key_rotation` and forward it to the core aurora module, so a\n * root configuration can reach the variable the core module already exposes.\n */\nconst migrateKeyRotationVariable = async (\n tree: Tree,\n filePath: string,\n): Promise<void> => {\n if (\n await matchGritQL(\n tree,\n filePath,\n hcl('`variable \"enable_key_rotation\" { $_ }`'),\n )\n ) {\n return; // Already declared.\n }\n\n const declared = await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`variable \"enable_backup\" { $props }\\` => \\`variable \"enable_backup\" {\\n $props\\n}\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n ),\n KEY_ROTATION_VARIABLE,\n );\n if (!declared) return;\n\n // Appended after the existing arguments so their alignment is preserved;\n // `terraform fmt` re-aligns the block.\n await applyGritQL(\n tree,\n filePath,\n hcl(\n `\\`module \"aurora\" { $props }\\` => \\`module \"aurora\" {\\n $props\\n\\n enable_key_rotation = var.enable_key_rotation\\n}\\``,\n ),\n );\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (!tree.exists(TERRAFORM_DBS_APP_DIR)) {\n return { nextSteps }; // This workspace has no Terraform database modules.\n }\n\n for (const dirName of tree.children(TERRAFORM_DBS_APP_DIR)) {\n const filePath = joinPathFragments(\n TERRAFORM_DBS_APP_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a database app module directory.\n }\n\n // Both edits hang off the shape the generator produces: the two variables\n // and the module call that forwards them.\n const recognised =\n (await matchGritQL(\n tree,\n filePath,\n hcl('`variable \"enable_cloudwatch_logs\" { $_ }`'),\n )) &&\n (await matchGritQL(\n tree,\n filePath,\n hcl('`variable \"enable_backup\" { $_ }`'),\n )) &&\n (await matchGritQL(\n tree,\n filePath,\n hcl(\n '`module \"aurora\" { $props }` where { $props <: contains `enable_cloudwatch_logs = var.enable_cloudwatch_logs` }',\n ),\n ));\n\n if (!recognised) {\n nextSteps.push(divergedMessage(filePath));\n continue;\n }\n\n await migrateCloudwatchLogsDefault(tree, filePath);\n await migrateKeyRotationVariable(tree, filePath);\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","applyGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TERRAFORM_DBS_APP_DIR","hcl","pattern","KEY_ROTATION_VARIABLE","join","divergedMessage","filePath","OLD_LOGS_DESCRIPTION","NEW_LOGS_DESCRIPTION","migrateCloudwatchLogsDefault","tree","migrateKeyRotationVariable","declared","migration","nextSteps","exists","dirName","children","recognised","push"],"mappings":"AAAA;;;CAGC,GACD,SACEA,iBAAiB,QAGZ,aAAa;AACpB,SACEC,WAAW,EACXC,uBAAuB,EACvBC,eAAe,EACfC,WAAW,QACN,wBAAwB;AAC/B,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SACEC,YAAY,EACZC,oBAAoB,QACf,gDAAgD;AAEvD;;;;;;;;;;;;;;;;;;CAkBC,GAED,MAAMC,wBAAwB,GAAGF,aAAa,CAAC,EAAEC,qBAAqB,YAAY,CAAC;AAEnF,MAAME,MAAM,CAACC,UAAoB,CAAC,cAAc,EAAEA,SAAS;AAE3D,MAAMC,wBAAwB;IAC5B;IACA;IACA;IACA;IACA;CACD,CAACC,IAAI,CAAC;AAEP,MAAMC,kBAAkB,CAACC,WACvB,GAAGA,SAAS,+VAA+V,CAAC;AAE9W,MAAMC,uBACJ;AACF,MAAMC,uBACJ;AAEF;;;;;;CAMC,GACD,MAAMC,+BAA+B,CAACC,MAAYJ,WAChDb,YACEiB,MACAJ,UACAL,IACE,CAAC,wDAAwD,CAAC,GACxD,CAAC,qCAAqC,EAAEM,qBAAqB,wBAAwB,EAAEC,qBAAqB,IAAI,CAAC,GACjH,CAAC,6DAA6D,CAAC,GAC/D,CAAC,EAAE,CAAC;AAIZ;;;CAGC,GACD,MAAMG,6BAA6B,OACjCD,MACAJ;IAEA,IACE,MAAMV,YACJc,MACAJ,UACAL,IAAI,6CAEN;QACA,QAAQ,oBAAoB;IAC9B;IAEA,MAAMW,WAAW,MAAMjB,gBACrBe,MACAJ,UACAL,IACE,CAAC,wFAAwF,EAAEP,wBAAwB,EAAE,CAAC,GAExHS;IAEF,IAAI,CAACS,UAAU;IAEf,yEAAyE;IACzE,uCAAuC;IACvC,MAAMnB,YACJiB,MACAJ,UACAL,IACE,CAAC,uHAAuH,CAAC;AAG/H;AAEA,eAAe,eAAeY,UAC5BH,IAAU;IAEV,MAAMI,YAAsB,EAAE;IAE9B,IAAI,CAACJ,KAAKK,MAAM,CAACf,wBAAwB;QACvC,OAAO;YAAEc;QAAU,GAAG,oDAAoD;IAC5E;IAEA,KAAK,MAAME,WAAWN,KAAKO,QAAQ,CAACjB,uBAAwB;QAC1D,MAAMM,WAAWd,kBACfQ,uBACAgB,SACA,GAAGA,QAAQ,GAAG,CAAC;QAEjB,IAAI,CAACN,KAAKK,MAAM,CAACT,WAAW;YAC1B,UAAU,uCAAuC;QACnD;QAEA,0EAA0E;QAC1E,0CAA0C;QAC1C,MAAMY,aACJ,AAAC,MAAMtB,YACLc,MACAJ,UACAL,IAAI,kDAEL,MAAML,YACLc,MACAJ,UACAL,IAAI,yCAEL,MAAML,YACLc,MACAJ,UACAL,IACE;QAIN,IAAI,CAACiB,YAAY;YACfJ,UAAUK,IAAI,CAACd,gBAAgBC;YAC/B;QACF;QAEA,MAAMG,6BAA6BC,MAAMJ;QACzC,MAAMK,2BAA2BD,MAAMJ;IACzC;IAEA,MAAMT,qBAAqBa;IAE3B,OAAO;QAAEI;IAAU;AACrB"}
|
package/src/migrations/v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default/migration.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.84/0002-agentcore-harness-no-tools-by-default/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 GRIT_INSERT_PLACEHOLDER,\n insertViaGritQL,\n matchGritQL,\n} from '../../../utils/ast.js';\nimport { formatFilesInSubtree } from '../../../utils/format.js';\nimport {\n PACKAGES_DIR,\n SHARED_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * Pin each vended Harness to no tools by default.\n *\n * AgentCore treats an absent `allowedTools` as every tool, so a Harness that\n * never set it deployed with `allowedTools: [\"*\"]` — shell execution and file\n * read/write included. Both providers now send the field explicitly, defaulting\n * to none.\n *\n * These files are generated with `KeepExisting`, so an upgraded workspace keeps\n * the implicit every-tool default until this runs. Diverged files are left\n * untouched and reported via `nextSteps`.\n */\n\nconst CDK_HARNESSES_DIR = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src/app/harnesses`;\nconst TERRAFORM_HARNESSES_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/harnesses`;\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\nconst ALLOWED_TOOLS_VARIABLE = [\n 'variable \"allowed_tools\" {',\n ' description = \"Tools the Harness may use. Defaults to none; set [\\\\\"@builtin\\\\\"] for every built-in tool, or name individual tools. Always sent explicitly, since the service treats an absent value as every tool.\"',\n ' type = list(string)',\n ' default = []',\n '}',\n].join('\\n');\n\nconst divergedMessage = (filePath: string, remedy: string) =>\n `${filePath}: has diverged from the generated shape - left untouched. AgentCore treats an absent allowedTools as every tool, so this Harness deploys with all built-in tools (including shell and file access). ${remedy}`;\n\n/**\n * Default the construct's `allowedTools` to none and pass it to the resource\n * after the props spread, so an undefined caller value cannot reinstate the\n * service's every-tool default.\n */\nconst migrateCdkConstruct = async (\n tree: Tree,\n filePath: string,\n): Promise<boolean> => {\n const destructured = await insertViaGritQL(\n tree,\n filePath,\n `\\`const { $fields } = props ?? {};\\` => \\`const {\\n ${GRIT_INSERT_PLACEHOLDER}\\n $fields\\n } = props ?? {};\\``,\n [\n '// The service treats an absent allowedTools as every tool, so this',\n '// defaults to none and is always passed explicitly.',\n 'allowedTools = [],',\n ].join('\\n '),\n );\n if (!destructured) return false;\n\n return await applyGritQL(\n tree,\n filePath,\n '`new agentcore.CfnHarness($scope, $id, { $props })` where {' +\n ' $props <: contains spread_element() as $spread,' +\n ' $spread => `$spread,\\n allowedTools`' +\n ' }',\n );\n};\n\n/**\n * Declare `allowed_tools` and assign it to the Harness resource, so the module\n * always sends the field.\n */\nconst migrateTerraformModule = async (\n tree: Tree,\n filePath: string,\n): Promise<boolean> => {\n const declared = await insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`variable \"model_resource_arns\" { $props }\\` => \\`${GRIT_INSERT_PLACEHOLDER}\\n\\nvariable \"model_resource_arns\" {\\n $props\\n}\\``,\n ),\n ALLOWED_TOOLS_VARIABLE,\n );\n if (!declared) return false;\n\n // Appended after the existing arguments so their alignment is preserved;\n // `terraform fmt` re-aligns the block.\n return await applyGritQL(\n tree,\n filePath,\n hcl(\n '`execution_role_arn = aws_iam_role.execution_role.arn` => `execution_role_arn = aws_iam_role.execution_role.arn\\n allowed_tools = var.allowed_tools`',\n ),\n );\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const dirName of tree.exists(CDK_HARNESSES_DIR)\n ? tree.children(CDK_HARNESSES_DIR)\n : []) {\n const filePath = joinPathFragments(\n CDK_HARNESSES_DIR,\n dirName,\n `${dirName}.ts`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a Harness construct directory.\n }\n\n // Already pinned, by this migration or by hand.\n if (tree.read(filePath, 'utf-8')?.includes('allowedTools = [],')) {\n continue;\n }\n\n if (!(await migrateCdkConstruct(tree, filePath))) {\n nextSteps.push(\n divergedMessage(\n filePath,\n 'Default `allowedTools` to `[]` when destructuring props, and pass `allowedTools` to the CfnHarness after the `...harnessProps` spread.',\n ),\n );\n }\n }\n\n for (const dirName of tree.exists(TERRAFORM_HARNESSES_DIR)\n ? tree.children(TERRAFORM_HARNESSES_DIR)\n : []) {\n const filePath = joinPathFragments(\n TERRAFORM_HARNESSES_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (!tree.exists(filePath)) {\n continue; // Not a Harness module directory.\n }\n\n // Already assigned, by this migration or by hand.\n if (await matchGritQL(tree, filePath, hcl('`allowed_tools = $_`'))) {\n continue;\n }\n\n if (!(await migrateTerraformModule(tree, filePath))) {\n nextSteps.push(\n divergedMessage(\n filePath,\n 'Declare an `allowed_tools` variable (list(string), default []) and assign `allowed_tools = var.allowed_tools` on the `aws_bedrockagentcore_harness` resource.',\n ),\n );\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","applyGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","CDK_HARNESSES_DIR","TERRAFORM_HARNESSES_DIR","hcl","pattern","ALLOWED_TOOLS_VARIABLE","join","divergedMessage","filePath","remedy","migrateCdkConstruct","tree","destructured","migrateTerraformModule","declared","migration","nextSteps","dirName","exists","children","read","includes","push"],"mappings":"AAAA;;;CAGC,GACD,SACEA,iBAAiB,QAGZ,aAAa;AACpB,SACEC,WAAW,EACXC,uBAAuB,EACvBC,eAAe,EACfC,WAAW,QACN,wBAAwB;AAC/B,SAASC,oBAAoB,QAAQ,2BAA2B;AAChE,SACEC,YAAY,EACZC,qBAAqB,EACrBC,oBAAoB,QACf,gDAAgD;AAEvD;;;;;;;;;;;CAWC,GAED,MAAMC,oBAAoB,GAAGH,aAAa,CAAC,EAAEC,sBAAsB,kBAAkB,CAAC;AACtF,MAAMG,0BAA0B,GAAGJ,aAAa,CAAC,EAAEE,qBAAqB,kBAAkB,CAAC;AAE3F,MAAMG,MAAM,CAACC,UAAoB,CAAC,cAAc,EAAEA,SAAS;AAE3D,MAAMC,yBAAyB;IAC7B;IACA;IACA;IACA;IACA;CACD,CAACC,IAAI,CAAC;AAEP,MAAMC,kBAAkB,CAACC,UAAkBC,SACzC,GAAGD,SAAS,oMAAoM,EAAEC,QAAQ;AAE5N;;;;CAIC,GACD,MAAMC,sBAAsB,OAC1BC,MACAH;IAEA,MAAMI,eAAe,MAAMjB,gBACzBgB,MACAH,UACA,CAAC,yDAAyD,EAAEd,wBAAwB,uCAAuC,CAAC,EAC5H;QACE;QACA;QACA;KACD,CAACY,IAAI,CAAC;IAET,IAAI,CAACM,cAAc,OAAO;IAE1B,OAAO,MAAMnB,YACXkB,MACAH,UACA,gEACE,qDACA,+CACA;AAEN;AAEA;;;CAGC,GACD,MAAMK,yBAAyB,OAC7BF,MACAH;IAEA,MAAMM,WAAW,MAAMnB,gBACrBgB,MACAH,UACAL,IACE,CAAC,mDAAmD,EAAET,wBAAwB,mDAAmD,CAAC,GAEpIW;IAEF,IAAI,CAACS,UAAU,OAAO;IAEtB,yEAAyE;IACzE,uCAAuC;IACvC,OAAO,MAAMrB,YACXkB,MACAH,UACAL,IACE;AAGN;AAEA,eAAe,eAAeY,UAC5BJ,IAAU;IAEV,MAAMK,YAAsB,EAAE;IAE9B,KAAK,MAAMC,WAAWN,KAAKO,MAAM,CAACjB,qBAC9BU,KAAKQ,QAAQ,CAAClB,qBACd,EAAE,CAAE;QACN,MAAMO,WAAWhB,kBACfS,mBACAgB,SACA,GAAGA,QAAQ,GAAG,CAAC;QAEjB,IAAI,CAACN,KAAKO,MAAM,CAACV,WAAW;YAC1B,UAAU,qCAAqC;QACjD;QAEA,gDAAgD;QAChD,IAAIG,KAAKS,IAAI,CAACZ,UAAU,UAAUa,SAAS,uBAAuB;YAChE;QACF;QAEA,IAAI,CAAE,MAAMX,oBAAoBC,MAAMH,WAAY;YAChDQ,UAAUM,IAAI,CACZf,gBACEC,UACA;QAGN;IACF;IAEA,KAAK,MAAMS,WAAWN,KAAKO,MAAM,CAAChB,2BAC9BS,KAAKQ,QAAQ,CAACjB,2BACd,EAAE,CAAE;QACN,MAAMM,WAAWhB,kBACfU,yBACAe,SACA,GAAGA,QAAQ,GAAG,CAAC;QAEjB,IAAI,CAACN,KAAKO,MAAM,CAACV,WAAW;YAC1B,UAAU,kCAAkC;QAC9C;QAEA,kDAAkD;QAClD,IAAI,MAAMZ,YAAYe,MAAMH,UAAUL,IAAI,0BAA0B;YAClE;QACF;QAEA,IAAI,CAAE,MAAMU,uBAAuBF,MAAMH,WAAY;YACnDQ,UAAUM,IAAI,CACZf,gBACEC,UACA;QAGN;IACF;IAEA,MAAMX,qBAAqBc;IAE3B,OAAO;QAAEK;IAAU;AACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.84/0003-terraform-declare-used-providers/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 visitNotIgnoredFiles,\n} from '@nx/devkit';\nimport {\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';\nimport { TERRAFORM_VERSIONS } from '../../../utils/versions.js';\n\n/**\n * Declare every provider each vended Terraform module uses in its own\n * `required_providers`.\n *\n * A provider a module uses but never declares is resolved by implicit\n * inheritance from the root module, unpinned — so `terraform init` on that\n * module takes whatever the registry serves, bypassing the version pinning the\n * generators vend. Several vended modules referenced `random`, `archive`,\n * `null`, `external` and `aws` without declaring them.\n *\n * These files are generated with `KeepExisting`, so an upgraded workspace keeps\n * the unpinned shape until this runs. Each missing provider is added to the\n * module's existing `required_providers`; a module with no `terraform` block at\n * all is reported via `nextSteps` rather than having one inserted, since where\n * it belongs depends on how the user has arranged the file.\n */\n\nconst TERRAFORM_SRC = joinPathFragments(\n PACKAGES_DIR,\n SHARED_TERRAFORM_DIR,\n 'src',\n);\n\nconst hcl = (pattern: string) => `language hcl\\n${pattern}`;\n\n/** Providers built in to Terraform, which have no entry and no version. */\nconst BUILTIN_TYPES = new Set(['terraform_data', 'terraform_remote_state']);\n\n/**\n * The provider a resource or data source type resolves to. Terraform derives\n * this from the type's first underscore-delimited word; a type it can't\n * attribute to a provider the plugin vends is not this migration's to touch.\n */\nconst providerOf = (type: string): string | undefined => {\n // `null_resource` and `external` are the whole type, not a prefixed one.\n if (type === 'null_resource') {\n return 'null';\n }\n if (type === 'external') {\n return 'external';\n }\n const [prefix] = type.split('_');\n return prefix in TERRAFORM_VERSIONS ? prefix : undefined;\n};\n\n/**\n * The providers a module requires, read from its `resource` and `data` blocks.\n *\n * Only declarations are read, because Terraform resolves a resource reference\n * within the module that declares it — so a provider a module references is one\n * it also declares a block for, and the blocks alone are complete. Matching the\n * syntax tree also means a type named in a comment, in string prose or inside a\n * `local-exec` heredoc's embedded Python is not a use, since none of those parse\n * as a block.\n */\nconst usedProviders = async (\n tree: Tree,\n filePath: string,\n): Promise<Set<string>> => {\n const used = new Set<string>();\n for (const kind of ['resource', 'data']) {\n for (const block of await captureAllGritQL(\n tree,\n filePath,\n hcl(`\\`${kind} $type $name { $_ }\\``),\n )) {\n // The block's own type, e.g. `resource \"random_string\" \"suffix\" {`.\n const type = /\"([A-Za-z0-9_]+)\"/.exec(block)?.[1];\n if (!type || BUILTIN_TYPES.has(type)) {\n continue;\n }\n const provider = providerOf(type);\n if (provider) {\n used.add(provider);\n }\n }\n }\n return used;\n};\n\n/** Every `.tf` file in the workspace's shared terraform library, by directory. */\nconst terraformModules = (tree: Tree): Map<string, string[]> => {\n const modules = new Map<string, string[]>();\n if (!tree.exists(TERRAFORM_SRC)) {\n return modules;\n }\n visitNotIgnoredFiles(tree, TERRAFORM_SRC, (filePath) => {\n if (!filePath.endsWith('.tf')) {\n return;\n }\n const dir = filePath.slice(0, filePath.lastIndexOf('/'));\n modules.set(dir, [...(modules.get(dir) ?? []), filePath]);\n });\n return modules;\n};\n\n/** The provider entry to vend, at the version this release pins. */\nconst providerEntry = (provider: string) =>\n [\n `${provider} = {`,\n ` source = \"hashicorp/${provider}\"`,\n ` version = \"${TERRAFORM_VERSIONS[provider as keyof typeof TERRAFORM_VERSIONS]}\"`,\n '}',\n ].join('\\n');\n\n/**\n * Add a provider to a file's `required_providers`. Appended after the block's\n * existing body, so an entry the user has customised is left as it is.\n */\nconst declareProvider = (tree: Tree, filePath: string, provider: string) =>\n insertViaGritQL(\n tree,\n filePath,\n hcl(\n `\\`required_providers { $body }\\` => \\`required_providers {\\n $body\\n\\n ${GRIT_INSERT_PLACEHOLDER}\\n}\\`` +\n ` where { $body <: not contains \\`${provider} = { $_ }\\` }`,\n ),\n providerEntry(provider),\n );\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n for (const [dir, filePaths] of terraformModules(tree)) {\n const used = new Set<string>();\n // The file holding the module's `required_providers`, which is where the\n // missing declarations are added — Terraform merges every `.tf` in a\n // directory, so one block covers the module.\n let blockFile: string | undefined;\n const declared = new Set<string>();\n\n for (const filePath of filePaths) {\n for (const provider of await usedProviders(tree, filePath)) {\n used.add(provider);\n }\n const contents = tree.read(filePath, 'utf-8') ?? '';\n if (!contents.includes('required_providers')) {\n continue;\n }\n blockFile ??= filePath;\n for (const provider of Object.keys(TERRAFORM_VERSIONS)) {\n if (\n await matchGritQL(\n tree,\n filePath,\n hcl(\n `\\`required_providers { $body }\\` where { $body <: contains \\`${provider} = { $_ }\\` }`,\n ),\n )\n ) {\n declared.add(provider);\n }\n }\n }\n\n const missing = [...used].filter((p) => !declared.has(p)).sort();\n if (missing.length === 0) {\n continue;\n }\n\n if (!blockFile) {\n nextSteps.push(\n `${dir}: has no terraform required_providers block - left untouched. Add one declaring ${missing.join(', ')}, so the module pins the providers it uses rather than inheriting them unpinned from the root module.`,\n );\n continue;\n }\n\n for (const provider of missing) {\n if (!(await declareProvider(tree, blockFile, provider))) {\n nextSteps.push(\n `${blockFile}: its required_providers block has diverged from the generated shape - left untouched. Add a ${provider} entry to it, sourced from hashicorp/${provider}.`,\n );\n }\n }\n }\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","visitNotIgnoredFiles","captureAllGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_TERRAFORM_DIR","TERRAFORM_VERSIONS","TERRAFORM_SRC","hcl","pattern","BUILTIN_TYPES","Set","providerOf","type","prefix","split","undefined","usedProviders","tree","filePath","used","kind","block","exec","has","provider","add","terraformModules","modules","Map","exists","endsWith","dir","slice","lastIndexOf","set","get","providerEntry","join","declareProvider","migration","nextSteps","filePaths","blockFile","declared","contents","read","includes","Object","keys","missing","filter","p","sort","length","push"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.84/0004-waf-log-group-cmk-encryption/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 addDestructuredImport,\n applyGritQL,\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_CONSTRUCTS_DIR,\n SHARED_TERRAFORM_DIR,\n} from '../../../utils/shared-constructs-constants.js';\n\n/**\n * WAF request logs carry full request metadata - URIs, headers and client IPs -\n * so the vended WAF log groups are encrypted with a customer-managed KMS key,\n * matching how the vended access log groups beside them already are. Three were\n * plaintext: the Terraform REST API and user-pool WAF log groups, the Terraform\n * AgentCore gateway WAF log group, and the CDK user-pool WAF log group. Each now\n * gets a key whose policy grants CloudWatch Logs use of it, scoped by\n * `kms:EncryptionContext:aws:logs:arn` to that log group alone.\n *\n * CloudWatch Logs cannot encrypt a log group that already exists in place, so\n * this only rewrites the vended templates. On the next deploy Terraform sets\n * `kms_key_id` on the existing group (an in-place update) and CloudFormation\n * replaces the CDK one, which the vended `RemovalPolicy.DESTROY` already allows.\n * Events written before then stay unencrypted; the `nextSteps` note says so.\n */\n\nconst CONSTRUCTS_SRC = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src`;\nconst TERRAFORM_SRC = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src`;\n\nconst CDK_USER_IDENTITY_FILE = `${CONSTRUCTS_SRC}/core/user-identity.ts`;\nconst TERRAFORM_REST_API_FILE = `${TERRAFORM_SRC}/core/api/rest-api/rest-api.tf`;\nconst TERRAFORM_USER_IDENTITY_FILE = `${TERRAFORM_SRC}/core/user-identity/identity/identity.tf`;\nconst TERRAFORM_GATEWAYS_DIR = `${TERRAFORM_SRC}/app/gateways`;\n\nconst divergedMessage = (file: string) =>\n `${file}: the WAF log group has diverged from the generated shape - left untouched. Its request logs stay unencrypted. To encrypt them, add a customer-managed KMS key granting the \\`logs.<region>.amazonaws.com\\` service principal kms:Encrypt/Decrypt/ReEncrypt*/GenerateDataKey*/DescribeKey, conditioned on \\`kms:EncryptionContext:aws:logs:arn\\` matching the log group, and point the log group at it (see the vended WAF log group template).`;\n\nconst ALREADY_ENCRYPTED_TERRAFORM = 'aws_kms_key.';\n\n// Terraform (HCL) patterns are built from plain strings rather than JS template\n// literals, since the HCL text itself is full of `${...}` interpolations that\n// would otherwise be parsed as JS interpolation.\nconst hcl = (pattern: string) => 'language hcl\\n' + pattern;\n\n/** A vended Terraform WAF log group and the key this migration gives it. */\ninterface TerraformWafLogGroup {\n /** The `aws_cloudwatch_log_group` resource label. */\n readonly logGroupLabel: string;\n /** The `aws_kms_key` / `aws_kms_alias` label to create. */\n readonly keyLabel: string;\n /** The `locals` entry the log group name moves into. */\n readonly nameLocal: string;\n /** The log group's name expression, which becomes the local's value. */\n readonly nameExpression: string;\n /** The KMS key description. */\n readonly keyDescription: string;\n /** The KMS alias name expression. */\n readonly aliasName: string;\n /** How the account id is referenced in this module. */\n readonly accountId: string;\n /** How the region is referenced in this module. */\n readonly region: string;\n /** Whether the module tags its resources. */\n readonly tagged: boolean;\n}\n\nconst REST_API_LOG_GROUP: TerraformWafLogGroup = {\n logGroupLabel: 'api_waf_logs',\n keyLabel: 'waf_logs',\n nameLocal: 'waf_log_group_name',\n nameExpression:\n 'var.enable_waf ? \"aws-waf-logs-${var.api_name}-${random_id.waf_log_suffix[0].hex}\" : null',\n keyDescription: '\"${var.api_name} API WAF log encryption\"',\n aliasName:\n '\"alias/${var.api_name}-${random_id.waf_log_suffix[0].hex}-waf-logs\"',\n accountId: 'data.aws_caller_identity.current.account_id',\n region: 'data.aws_region.current.region',\n tagged: true,\n};\n\nconst USER_IDENTITY_LOG_GROUP: TerraformWafLogGroup = {\n logGroupLabel: 'user_pool_waf_logs',\n keyLabel: 'user_pool_waf_logs',\n nameLocal: 'user_pool_waf_log_group_name',\n nameExpression:\n 'var.enable_waf ? \"aws-waf-logs-UserPool-${random_id.unique_suffix.hex}\" : null',\n keyDescription: '\"User pool WAF log encryption\"',\n aliasName: '\"alias/user-pool-${random_id.unique_suffix.hex}-waf-logs\"',\n accountId: 'data.aws_caller_identity.current.account_id',\n region: 'data.aws_region.current.region',\n tagged: false,\n};\n\nconst gatewayLogGroup = (nameKebabCase: string): TerraformWafLogGroup => ({\n logGroupLabel: 'gateway_waf_logs',\n keyLabel: 'gateway_waf_logs',\n nameLocal: 'gateway_waf_log_group_name',\n nameExpression: `var.enable_waf ? \"aws-waf-logs-${nameKebabCase}-\\${random_id.unique_suffix.hex}\" : null`,\n keyDescription: `\"${nameKebabCase} gateway WAF log encryption\"`,\n aliasName: `\"alias/${nameKebabCase}-\\${random_id.unique_suffix.hex}-waf-logs\"`,\n accountId: 'local.aws_account_id',\n region: 'local.aws_region',\n tagged: false,\n});\n\n/** The KMS key and alias resources, as the templates now vend them. */\nconst keyResourcesText = (group: TerraformWafLogGroup): string =>\n [\n '# KMS key for encrypting WAF request logs at rest',\n `resource \"aws_kms_key\" \"${group.keyLabel}\" {`,\n ' count = var.enable_waf ? 1 : 0',\n '',\n ` description = ${group.keyDescription}`,\n ' deletion_window_in_days = 7',\n ' enable_key_rotation = true',\n '',\n ' policy = jsonencode({',\n ' Version = \"2012-10-17\"',\n ' Statement = [',\n ' {',\n ' Sid = \"Enable IAM User Permissions\"',\n ' Effect = \"Allow\"',\n ` Principal = { AWS = \"arn:aws:iam::\\${${group.accountId}}:root\" }`,\n ' Action = \"kms:*\"',\n ' Resource = \"*\"',\n ' },',\n ' {',\n ' Sid = \"Allow CloudWatch Logs\"',\n ' Effect = \"Allow\"',\n ` Principal = { Service = \"logs.\\${${group.region}}.amazonaws.com\" }`,\n ' Action = [',\n ' \"kms:Encrypt\",',\n ' \"kms:Decrypt\",',\n ' \"kms:ReEncrypt*\",',\n ' \"kms:GenerateDataKey*\",',\n ' \"kms:DescribeKey\"',\n ' ]',\n ' Resource = \"*\"',\n ' Condition = {',\n ' ArnEquals = {',\n ` \"kms:EncryptionContext:aws:logs:arn\" = \"arn:aws:logs:\\${${group.region}}:\\${${group.accountId}}:log-group:\\${local.${group.nameLocal}}\"`,\n ' }',\n ' }',\n ' }',\n ' ]',\n ' })',\n ...(group.tagged ? ['', ' tags = var.tags'] : []),\n '}',\n '',\n `resource \"aws_kms_alias\" \"${group.keyLabel}\" {`,\n ' count = var.enable_waf ? 1 : 0',\n '',\n ` name = ${group.aliasName}`,\n ` target_key_id = aws_kms_key.${group.keyLabel}[0].key_id`,\n '}',\n ].join('\\n');\n\nconst migrateTerraformLogGroup = async (\n tree: Tree,\n file: string,\n group: TerraformWafLogGroup,\n nextSteps: string[],\n): Promise<void> => {\n const contents = tree.read(file, 'utf-8') ?? '';\n\n // The WAF log group is only vended when WAF is enabled, so it may be absent.\n if (\n !contents.includes(`\"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\"`)\n ) {\n return;\n }\n\n const LOG_GROUP_BLOCK = hcl(\n `\\`resource \"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\" { $body }\\``,\n );\n const NAME_LINE = hcl(\n `\\`name = ${group.nameExpression.replace(/^var\\.enable_waf \\? /, '').replace(/ : null$/, '')}\\` as $line where {\\n` +\n ` $line <: within \\`resource \"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\" { $_ }\\`\\n` +\n '}',\n );\n const SUPPRESSION_LINE =\n '#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption';\n const LOCALS_BLOCK = hcl('`locals { $body }`');\n\n if (contents.includes(ALREADY_ENCRYPTED_TERRAFORM + group.keyLabel)) {\n return; // Already migrated.\n }\n\n const ready =\n (await matchGritQL(tree, file, LOG_GROUP_BLOCK)) &&\n (await matchGritQL(tree, file, NAME_LINE)) &&\n (await matchGritQL(tree, file, LOCALS_BLOCK)) &&\n contents.includes(SUPPRESSION_LINE);\n\n if (!ready) {\n nextSteps.push(divergedMessage(file));\n return;\n }\n\n // 1. Route the log group name through a local, so the key policy's encryption\n // context condition can name the same log group.\n await insertViaGritQL(\n tree,\n file,\n LOCALS_BLOCK +\n ` as $l => \\`locals {\\n $body\\n ${GRIT_INSERT_PLACEHOLDER}\\n}\\``,\n `${group.nameLocal} = ${group.nameExpression}`,\n );\n await applyGritQL(\n tree,\n file,\n NAME_LINE + ` => \\`name = local.${group.nameLocal}\\``,\n );\n\n // 2. Point the log group at a new key, added after it so the log group keeps\n // the comment above it.\n await insertViaGritQL(\n tree,\n file,\n hcl(\n `\\`name = local.${group.nameLocal}\\` as $line where {\\n` +\n ` $line <: within \\`resource \"aws_cloudwatch_log_group\" \"${group.logGroupLabel}\" { $_ }\\`\\n` +\n '}',\n ) + ` => \\`$line\\n ${GRIT_INSERT_PLACEHOLDER}\\``,\n `kms_key_id = aws_kms_key.${group.keyLabel}[0].arn`,\n );\n await insertViaGritQL(\n tree,\n file,\n LOG_GROUP_BLOCK + ` as $lg => \\`$lg\\n\\n${GRIT_INSERT_PLACEHOLDER}\\``,\n keyResourcesText(group),\n );\n\n // 3. Drop the suppression the encryption makes redundant.\n const updated = (tree.read(file, 'utf-8') ?? '')\n .split('\\n')\n .filter((line) => line.trim() !== SUPPRESSION_LINE)\n .join('\\n');\n tree.write(file, updated);\n};\n\n/**\n * The vended CDK user-pool WAF log group gains an `encryptionKey`, matching the\n * eight sibling CDK WAF log groups which already pass one.\n */\nconst migrateCdkUserIdentity = async (\n tree: Tree,\n nextSteps: string[],\n): Promise<void> => {\n if (!tree.exists(CDK_USER_IDENTITY_FILE)) {\n return; // This workspace has no CDK user identity construct.\n }\n\n const contents = tree.read(CDK_USER_IDENTITY_FILE, 'utf-8') ?? '';\n\n // The WAF web ACL (and its log group) is optional, so it may be absent.\n if (!contents.includes(`new LogGroup(this, 'WebAclLogs'`)) {\n return;\n }\n\n if (contents.includes('WebAclLogsKey')) {\n return; // Already migrated.\n }\n\n // Already encrypted, whether by this migration or by the user's own key.\n if (\n await matchGritQL(\n tree,\n CDK_USER_IDENTITY_FILE,\n \"`new LogGroup(this, 'WebAclLogs', { $props })` where { $props <: contains `encryptionKey` }\",\n )\n ) {\n return;\n }\n\n const LOG_GROUP =\n \"`new LogGroup(this, 'WebAclLogs', { $props })` as $lg where { $props <: contains `logGroupName: $name` }\";\n const SUPPRESS_CALL =\n \"`suppressRules(wafLogGroup, ['CKV_AWS_158'], $reason)` as $call\";\n\n const ready =\n (await matchGritQL(tree, CDK_USER_IDENTITY_FILE, LOG_GROUP)) &&\n (await matchGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL));\n\n if (!ready) {\n nextSteps.push(divergedMessage(CDK_USER_IDENTITY_FILE));\n return;\n }\n\n await addDestructuredImport(\n tree,\n CDK_USER_IDENTITY_FILE,\n ['Key'],\n 'aws-cdk-lib/aws-kms',\n );\n await addDestructuredImport(\n tree,\n CDK_USER_IDENTITY_FILE,\n ['Effect', 'PolicyStatement', 'ServicePrincipal'],\n 'aws-cdk-lib/aws-iam',\n );\n\n // Hoist the log group name into a const so the key policy's encryption context\n // condition can name the same log group, then create the key above the group.\n await insertViaGritQL(\n tree,\n CDK_USER_IDENTITY_FILE,\n \"`const wafLogGroup = new LogGroup(this, 'WebAclLogs', { $props })` as $decl => \" +\n `\\`${GRIT_INSERT_PLACEHOLDER}\\n\\n$decl\\``,\n `const stack = Stack.of(this);\n const wafLogGroupName = \\`aws-waf-logs-\\${id}-\\${this.node.addr.slice(-8)}\\`;\n\n // KMS key for encrypting WAF logs at rest, usable by CloudWatch Logs\n const logsKey = new Key(this, 'WebAclLogsKey', {\n enableKeyRotation: true,\n });\n logsKey.addToResourcePolicy(\n new PolicyStatement({\n effect: Effect.ALLOW,\n principals: [new ServicePrincipal(\\`logs.\\${stack.region}.amazonaws.com\\`)],\n actions: [\n 'kms:Encrypt',\n 'kms:Decrypt',\n 'kms:ReEncrypt*',\n 'kms:GenerateDataKey*',\n 'kms:DescribeKey',\n ],\n resources: ['*'],\n conditions: {\n ArnEquals: {\n 'kms:EncryptionContext:aws:logs:arn': \\`arn:aws:logs:\\${stack.region}:\\${stack.account}:log-group:\\${wafLogGroupName}\\`,\n },\n },\n }),\n );`,\n );\n\n await applyGritQL(\n tree,\n CDK_USER_IDENTITY_FILE,\n \"`logGroupName: $name` as $prop where { $prop <: within `new LogGroup(this, 'WebAclLogs', $_)` } => `logGroupName: wafLogGroupName,\\n encryptionKey: logsKey`\",\n );\n\n // Drop the suppression the encryption makes redundant.\n await applyGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL + ' => ``');\n};\n\nexport default async function migration(\n tree: Tree,\n): Promise<MigrationReturnObject> {\n const nextSteps: string[] = [];\n\n if (tree.exists(TERRAFORM_REST_API_FILE)) {\n await migrateTerraformLogGroup(\n tree,\n TERRAFORM_REST_API_FILE,\n REST_API_LOG_GROUP,\n nextSteps,\n );\n }\n if (tree.exists(TERRAFORM_USER_IDENTITY_FILE)) {\n await migrateTerraformLogGroup(\n tree,\n TERRAFORM_USER_IDENTITY_FILE,\n USER_IDENTITY_LOG_GROUP,\n nextSteps,\n );\n }\n if (tree.exists(TERRAFORM_GATEWAYS_DIR)) {\n for (const dirName of tree.children(TERRAFORM_GATEWAYS_DIR)) {\n const file = joinPathFragments(\n TERRAFORM_GATEWAYS_DIR,\n dirName,\n `${dirName}.tf`,\n );\n if (tree.exists(file)) {\n await migrateTerraformLogGroup(\n tree,\n file,\n gatewayLogGroup(dirName),\n nextSteps,\n );\n }\n }\n }\n\n await migrateCdkUserIdentity(tree, nextSteps);\n\n await formatFilesInSubtree(tree);\n\n return { nextSteps };\n}\n"],"names":["joinPathFragments","addDestructuredImport","applyGritQL","GRIT_INSERT_PLACEHOLDER","insertViaGritQL","matchGritQL","formatFilesInSubtree","PACKAGES_DIR","SHARED_CONSTRUCTS_DIR","SHARED_TERRAFORM_DIR","CONSTRUCTS_SRC","TERRAFORM_SRC","CDK_USER_IDENTITY_FILE","TERRAFORM_REST_API_FILE","TERRAFORM_USER_IDENTITY_FILE","TERRAFORM_GATEWAYS_DIR","divergedMessage","file","ALREADY_ENCRYPTED_TERRAFORM","hcl","pattern","REST_API_LOG_GROUP","logGroupLabel","keyLabel","nameLocal","nameExpression","keyDescription","aliasName","accountId","region","tagged","USER_IDENTITY_LOG_GROUP","gatewayLogGroup","nameKebabCase","keyResourcesText","group","join","migrateTerraformLogGroup","tree","nextSteps","contents","read","includes","LOG_GROUP_BLOCK","NAME_LINE","replace","SUPPRESSION_LINE","LOCALS_BLOCK","ready","push","updated","split","filter","line","trim","write","migrateCdkUserIdentity","exists","LOG_GROUP","SUPPRESS_CALL","migration","dirName","children"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/v1.0.0-rc.84/0005-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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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type MigrationReturnObject, type Tree } from '@nx/devkit';
|
|
6
|
+
export default function migration(tree: Tree): Promise<MigrationReturnObject>;
|