@aws/nx-plugin 1.0.0-rc.81 → 1.0.0-rc.83
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/migrations.json +26 -1
- package/package.json +1 -1
- package/src/agentcore-gateway/generator.d.ts +7 -6
- package/src/agentcore-gateway/generator.js +2 -12
- package/src/agentcore-gateway/generator.js.map +1 -1
- package/src/migrations/latest/dynamodb-terraform-app-module-outputs/metadata.json +3 -0
- package/src/migrations/latest/dynamodb-terraform-app-module-outputs/migration.d.ts +6 -0
- package/src/migrations/latest/dynamodb-terraform-app-module-outputs/migration.js +69 -0
- package/src/migrations/latest/dynamodb-terraform-app-module-outputs/migration.js.map +1 -0
- package/src/migrations/latest/runtime-config-namespace-defaults/metadata.json +3 -0
- package/src/migrations/latest/runtime-config-namespace-defaults/migration.d.ts +6 -0
- package/src/migrations/latest/runtime-config-namespace-defaults/migration.js +131 -0
- package/src/migrations/latest/runtime-config-namespace-defaults/migration.js.map +1 -0
- package/src/migrations/latest/terraform-data-resource-destroy-protection/__snapshots__/migration.spec.ts.snap +75 -0
- package/src/migrations/latest/terraform-data-resource-destroy-protection/metadata.json +3 -0
- package/src/migrations/latest/terraform-data-resource-destroy-protection/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-data-resource-destroy-protection/migration.js +78 -0
- package/src/migrations/latest/terraform-data-resource-destroy-protection/migration.js.map +1 -0
- package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/metadata.json +3 -0
- package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration.js +38 -0
- package/src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration.js.map +1 -0
- package/src/migrations/latest/terraform-load-runtime-config-src-path/metadata.json +3 -0
- package/src/migrations/latest/terraform-load-runtime-config-src-path/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-load-runtime-config-src-path/migration.js +48 -0
- package/src/migrations/latest/terraform-load-runtime-config-src-path/migration.js.map +1 -0
- package/src/py/dynamodb/__snapshots__/generator.spec.ts.snap +21 -0
- package/src/ts/dynamodb/__snapshots__/generator.spec.ts.snap +21 -0
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +12 -0
- package/src/ts/react-website/app/generator.js +2 -2
- package/src/ts/react-website/app/generator.js.map +1 -1
- package/src/utils/agent-core-constructs/agent-core-constructs.d.ts +9 -2
- package/src/utils/agent-core-constructs/agent-core-constructs.js +22 -8
- package/src/utils/agent-core-constructs/agent-core-constructs.js.map +1 -1
- package/src/utils/dynamodb-constructs/files/terraform/app/dynamodb/__nameKebabCase__/__nameKebabCase__.tf.template +15 -0
- package/src/utils/dynamodb-constructs/files/terraform/core/dynamodb/dynamodb.tf.template +6 -0
- package/src/utils/files/terraform/src/core/runtime-config/appconfig/appconfig.tf.template +2 -2
- package/src/utils/files/terraform/src/core/runtime-config/appconfig-deployment/appconfig-deployment.tf.template +1 -1
- package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +6 -0
- package/src/utils/shared-constructs-constants.d.ts +22 -0
- package/src/utils/shared-constructs-constants.js +18 -0
- package/src/utils/shared-constructs-constants.js.map +1 -1
package/migrations.json
CHANGED
|
@@ -22,6 +22,31 @@
|
|
|
22
22
|
"description": "Forward the py#agent Strands hooks to the AG-UI adapter so model and tool errors are reported",
|
|
23
23
|
"implementation": "./src/migrations/latest/py-agent-ag-ui-hooks/migration"
|
|
24
24
|
},
|
|
25
|
+
"latest-terraform-load-runtime-config-src-path": {
|
|
26
|
+
"version": "1.0.0-rc.82",
|
|
27
|
+
"description": "Point the react website's terraform load-runtime-config target at the runtime-config file the terraform aggregation actually writes",
|
|
28
|
+
"implementation": "./src/migrations/latest/terraform-load-runtime-config-src-path/migration"
|
|
29
|
+
},
|
|
30
|
+
"latest-runtime-config-namespace-defaults": {
|
|
31
|
+
"version": "1.0.0-rc.82",
|
|
32
|
+
"description": "Provision AppConfig configuration profiles for every namespace the vended terraform modules write to",
|
|
33
|
+
"implementation": "./src/migrations/latest/runtime-config-namespace-defaults/migration"
|
|
34
|
+
},
|
|
35
|
+
"latest-dynamodb-terraform-app-module-outputs": {
|
|
36
|
+
"version": "1.0.0-rc.83",
|
|
37
|
+
"description": "Re-export the DynamoDB table and KMS key attributes from the vended Terraform per-table app module",
|
|
38
|
+
"implementation": "./src/migrations/latest/dynamodb-terraform-app-module-outputs/migration"
|
|
39
|
+
},
|
|
40
|
+
"latest-terraform-data-resource-destroy-protection": {
|
|
41
|
+
"version": "1.0.0-rc.83",
|
|
42
|
+
"description": "Add prevent_destroy and a 30 day KMS deletion window to the vended Terraform DynamoDB table and Aurora cluster modules",
|
|
43
|
+
"implementation": "./src/migrations/latest/terraform-data-resource-destroy-protection/migration"
|
|
44
|
+
},
|
|
45
|
+
"latest-terraform-gateway-cedar-render-root-ejs": {
|
|
46
|
+
"version": "1.0.0-rc.83",
|
|
47
|
+
"description": "Declare ejs at the workspace root so the Terraform gateway's Cedar render script resolves it",
|
|
48
|
+
"implementation": "./src/migrations/latest/terraform-gateway-cedar-render-root-ejs/migration"
|
|
49
|
+
},
|
|
25
50
|
"v1.0.0-rc.50-0001-modernize-function-props-cast": {
|
|
26
51
|
"version": "1.0.0-rc.50",
|
|
27
52
|
"description": "Replace the legacy angle-bracket FunctionProps type assertion with the modern as syntax in generated API constructs",
|
|
@@ -163,7 +188,7 @@
|
|
|
163
188
|
"implementation": "./src/migrations/v1.0.0-rc.75/0001-ts-agent-session-id-middleware-extraction/migration"
|
|
164
189
|
},
|
|
165
190
|
"sync-vended-versions": {
|
|
166
|
-
"version": "1.0.0-rc.
|
|
191
|
+
"version": "1.0.0-rc.83",
|
|
167
192
|
"description": "Sync vended dependency versions and the tracked plugin version to those vended by this release",
|
|
168
193
|
"implementation": "./src/utils/version-upgrade-migration/migration"
|
|
169
194
|
}
|
package/package.json
CHANGED
|
@@ -14,12 +14,6 @@ export declare const DEPENDENCIES: import("../utils/declared-dependencies.js").D
|
|
|
14
14
|
}, {
|
|
15
15
|
readonly name: "@types/express";
|
|
16
16
|
readonly dev: true;
|
|
17
|
-
}, {
|
|
18
|
-
readonly name: "ejs";
|
|
19
|
-
readonly dev: true;
|
|
20
|
-
}, {
|
|
21
|
-
readonly name: "@types/ejs";
|
|
22
|
-
readonly dev: true;
|
|
23
17
|
}, {
|
|
24
18
|
readonly name: "tsx";
|
|
25
19
|
readonly dev: true;
|
|
@@ -39,6 +33,13 @@ export declare const DEPENDENCIES: import("../utils/declared-dependencies.js").D
|
|
|
39
33
|
readonly when: (m: IacMetadata) => boolean;
|
|
40
34
|
} & {
|
|
41
35
|
versionOnly: true;
|
|
36
|
+
}, {
|
|
37
|
+
readonly name: "ejs";
|
|
38
|
+
readonly when: (m: IacMetadata) => boolean;
|
|
39
|
+
readonly dev: true;
|
|
40
|
+
readonly root: true;
|
|
41
|
+
} & {
|
|
42
|
+
versionOnly: true;
|
|
42
43
|
}, {
|
|
43
44
|
readonly name: "constructs";
|
|
44
45
|
readonly when: (m: IacMetadata) => boolean;
|
|
@@ -15,9 +15,8 @@ import { addGeneratorMetadata, getGeneratorInfo, projectExists, readProjectConfi
|
|
|
15
15
|
import { assignPort } from "../utils/port.js";
|
|
16
16
|
import { ensureProjectPackageJson } from "../utils/project-package-json.js";
|
|
17
17
|
import { SHARED_CONSTRUCTS_DEPENDENCIES, sharedConstructsGenerator } from "../utils/shared-constructs.js";
|
|
18
|
-
// The gateway's local-dev server
|
|
19
|
-
//
|
|
20
|
-
// gateway is a plain proxy).
|
|
18
|
+
// The gateway's local-dev server needs these whatever the auth; only the MCP
|
|
19
|
+
// SDK is protocol-specific (the http local gateway is a plain proxy).
|
|
21
20
|
export const DEPENDENCIES = declareDependencies()({
|
|
22
21
|
ts: [
|
|
23
22
|
{
|
|
@@ -31,15 +30,6 @@ export const DEPENDENCIES = declareDependencies()({
|
|
|
31
30
|
name: '@types/express',
|
|
32
31
|
dev: true
|
|
33
32
|
},
|
|
34
|
-
// ejs renders the Cedar policy in the shared gateway construct.
|
|
35
|
-
{
|
|
36
|
-
name: 'ejs',
|
|
37
|
-
dev: true
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: '@types/ejs',
|
|
41
|
-
dev: true
|
|
42
|
-
},
|
|
43
33
|
// local-dev.ts runs via tsx, which is shared tooling.
|
|
44
34
|
{
|
|
45
35
|
name: 'tsx',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../packages/nx-plugin/src/agentcore-gateway/generator.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n addProjectConfiguration,\n type GeneratorCallback,\n generateFiles,\n joinPathFragments,\n logger,\n OverwriteStrategy,\n type ProjectConfiguration,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { addTsDependencies } from '../utils/add-dependencies.js';\nimport {\n AGENT_CORE_CONSTRUCTS_DEPENDENCIES,\n AGENT_CORE_CONSTRUCTS_PY_DEPENDENCIES,\n addAgentCoreGatewayInfra,\n} from '../utils/agent-core-constructs/agent-core-constructs.js';\nimport {\n declareDependencies,\n ownedElsewhere,\n} from '../utils/declared-dependencies.js';\nimport { formatFilesInSubtree } from '../utils/format.js';\nimport { resolveIac } from '../utils/iac.js';\nimport { installDependencies } from '../utils/install.js';\nimport { addGeneratorMetricsIfApplicable } from '../utils/metrics.js';\nimport { kebabCase, toClassName } from '../utils/names.js';\nimport { getNpmScopePrefix } from '../utils/npm-scope.js';\nimport {\n addGeneratorMetadata,\n getGeneratorInfo,\n type NxGeneratorInfo,\n projectExists,\n readProjectConfigurationUnqualified,\n} from '../utils/nx.js';\nimport { assignPort } from '../utils/port.js';\nimport { ensureProjectPackageJson } from '../utils/project-package-json.js';\nimport {\n SHARED_CONSTRUCTS_DEPENDENCIES,\n sharedConstructsGenerator,\n} from '../utils/shared-constructs.js';\nimport type { IacMetadata } from '../utils/shared-constructs-constants.js';\nimport type { AgentcoreGatewayGeneratorSchema } from './schema';\n\n// The gateway's local-dev server and Cedar policy rendering need these\n// whatever the auth; only the MCP SDK is protocol-specific (the http local\n// gateway is a plain proxy).\nexport const DEPENDENCIES = declareDependencies<AgentCoreGatewayMetadata>()({\n ts: [\n { name: '@modelcontextprotocol/sdk', when: (m) => m.protocol === 'mcp' },\n { name: 'express' },\n { name: '@types/express', dev: true },\n // ejs renders the Cedar policy in the shared gateway construct.\n { name: 'ejs', dev: true },\n { name: '@types/ejs', dev: true },\n // local-dev.ts runs via tsx, which is shared tooling.\n { name: 'tsx', dev: true, root: true },\n ...ownedElsewhere(AGENT_CORE_CONSTRUCTS_DEPENDENCIES),\n ...ownedElsewhere(SHARED_CONSTRUCTS_DEPENDENCIES),\n ],\n py: ownedElsewhere(AGENT_CORE_CONSTRUCTS_PY_DEPENDENCIES),\n});\n\nexport const AGENTCORE_GATEWAY_GENERATOR_INFO: NxGeneratorInfo =\n getGeneratorInfo(import.meta.filename);\n\nexport const agentcoreGatewayGenerator = async (\n tree: Tree,\n options: AgentcoreGatewayGeneratorSchema,\n): Promise<GeneratorCallback> => {\n const name = kebabCase(options.name);\n const nameClassName = toClassName(name);\n const parentDir = options.directory ?? 'packages';\n const subDir = options.subDirectory ?? name;\n const projectRoot = joinPathFragments(parentDir, subDir);\n const fullyQualifiedName = `${getNpmScopePrefix(tree)}${name}`;\n\n // Protocol is mcp (aggregate MCP server targets into one MCP endpoint) or\n // http (proxy agent runtime targets via path-based routing). Persisted in\n // metadata so connection generators can validate it.\n const protocol = options.protocol ?? 'mcp';\n // Inbound auth is iam (default) or cognito. Persisted in metadata so\n // connection generators can validate it (agent -> gateway connections\n // require an IAM gateway).\n const auth = options.auth ?? 'iam';\n // Cedar policies authorize MCP tool actions, which an http gateway has none\n // of — its runtime targets are plain proxied endpoints — so the policy\n // engine only applies to mcp gateways. cedarPolicy defaults to true in the\n // schema, so an http gateway quietly drops it rather than requiring\n // --cedarPolicy=false.\n if (protocol === 'http' && options.cedarPolicy) {\n logger.warn(\n 'Cedar policies authorize MCP tool actions, so cedarPolicy is ignored for http-protocol gateways.',\n );\n }\n const cedarPolicy = protocol === 'mcp' && (options.cedarPolicy ?? true);\n const infra = options.infra ?? 'agentcore';\n\n // local-dev.ts is the local gateway: a continuous MCP aggregator chaining\n // onto attached MCP servers' dev targets (added by the mcp-connection\n // generator), so a single `nx dev <gateway>` boots the gateway and every\n // attached MCP server together.\n //\n // `serve` exists for parity with other generators; the deployed Gateway is\n // a managed AWS resource, so `serve`-mode agents talk to it via runtime\n // config and the local gateway sits idle.\n const localGatewayTarget = (port: number) => ({\n executor: 'nx:run-commands' as const,\n continuous: true,\n options: {\n command: 'tsx local-dev.ts',\n cwd: '{projectRoot}',\n env: { PORT: `${port}` },\n },\n dependsOn: [] as Array<string | { projects: string[]; target: string }>,\n });\n let project: ProjectConfiguration;\n if (projectExists(tree, fullyQualifiedName)) {\n project = readProjectConfigurationUnqualified(tree, fullyQualifiedName);\n } else {\n addProjectConfiguration(tree, fullyQualifiedName, {\n name: fullyQualifiedName,\n root: projectRoot,\n projectType: 'library',\n sourceRoot: projectRoot,\n tags: [],\n targets: {},\n metadata: {\n generator: AGENTCORE_GATEWAY_GENERATOR_INFO.id,\n } as any,\n });\n project = readProjectConfigurationUnqualified(tree, fullyQualifiedName);\n }\n // Ensure the project has its own package.json so its runtime dependencies\n // (added below) are declared in it rather than the workspace root.\n ensureProjectPackageJson(tree, {\n dir: projectRoot,\n fullyQualifiedName,\n });\n const port = assignPort(tree, project, 8100);\n // A gateway is its own standalone project, so it uses plain `serve` / `dev`\n // targets. Re-run: keep existing targets (their dependsOn may have been\n // populated by the mcp-connection generator), just ensure both exist.\n project.targets ??= {};\n project.targets['serve'] ??= localGatewayTarget(port);\n project.targets['dev'] ??= localGatewayTarget(port);\n updateProjectConfiguration(tree, project.name, project);\n\n // Scaffold the gateway project: local-dev.ts (+ Cedar policies if requested)\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'project', protocol),\n projectRoot,\n {\n nameClassName,\n nameKebabCase: name,\n port,\n attachedMcpServers: [],\n attachedAgents: [],\n },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n if (cedarPolicy) {\n // The default permit-all policy differs by auth type: AgentCore represents\n // IAM callers as `AgentCore::IamEntity` and Cognito/JWT callers as\n // `AgentCore::OAuthUser`, so the two policies match different principals.\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'cedar'),\n projectRoot,\n { nameClassName, auth },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n }\n\n // Recorded in the metadata below so the version sync can tell a CDK\n // project from a Terraform one; undefined when no infrastructure was\n // generated, in which case neither provider's packages were added.\n const iac =\n infra !== 'none' ? await resolveIac(tree, options.iac) : undefined;\n\n // Recorded below and read by the declaration's predicates, so the packages\n // added here are exactly the ones the version sync will own.\n const metadata: AgentCoreGatewayMetadata = {\n name,\n rc: nameClassName,\n protocol,\n auth,\n port,\n ...(iac ? { iac } : {}),\n };\n\n addGeneratorMetadata(\n tree,\n fullyQualifiedName,\n AGENTCORE_GATEWAY_GENERATOR_INFO,\n metadata,\n );\n\n addTsDependencies(tree, DEPENDENCIES, { metadata, projectRoot });\n\n // Wire up infra (CDK or Terraform); re-running with infra=agentcore adds\n // the infrastructure to a previously infra-less gateway.\n\n if (infra === 'agentcore') {\n await sharedConstructsGenerator(tree, { iac }, DEPENDENCIES);\n\n await addAgentCoreGatewayInfra(\n tree,\n {\n gatewayNameClassName: nameClassName,\n gatewayNameKebabCase: name,\n projectName: fullyQualifiedName,\n projectDirectory: projectRoot,\n cedarPolicy,\n auth,\n protocol,\n iac,\n },\n DEPENDENCIES,\n );\n }\n\n await addGeneratorMetricsIfApplicable(tree, [\n AGENTCORE_GATEWAY_GENERATOR_INFO,\n ]);\n\n await formatFilesInSubtree(tree);\n return () =>\n installDependencies(tree, options.preferInstallDependencies, {\n languages: ['typescript'],\n });\n};\n\n/**\n * Gateway details stored in the gateway project's metadata.\n */\nexport interface AgentCoreGatewayMetadata extends IacMetadata {\n name: string;\n rc: string;\n protocol: string;\n auth: string;\n port: number;\n}\n\n/**\n * Read a gateway project's metadata, validating it was generated by the\n * agentcore-gateway generator.\n */\nexport const readAgentCoreGatewayMetadata = (\n project: ProjectConfiguration,\n): AgentCoreGatewayMetadata => {\n const metadata = project.metadata as any;\n if (metadata?.generator !== AGENTCORE_GATEWAY_GENERATOR_INFO.id) {\n throw new Error(\n `Project '${project.name}' was not generated by the '${AGENTCORE_GATEWAY_GENERATOR_INFO.id}' generator.`,\n );\n }\n return metadata as AgentCoreGatewayMetadata;\n};\n\nexport default agentcoreGatewayGenerator;\n"],"names":["addProjectConfiguration","generateFiles","joinPathFragments","logger","OverwriteStrategy","updateProjectConfiguration","addTsDependencies","AGENT_CORE_CONSTRUCTS_DEPENDENCIES","AGENT_CORE_CONSTRUCTS_PY_DEPENDENCIES","addAgentCoreGatewayInfra","declareDependencies","ownedElsewhere","formatFilesInSubtree","resolveIac","installDependencies","addGeneratorMetricsIfApplicable","kebabCase","toClassName","getNpmScopePrefix","addGeneratorMetadata","getGeneratorInfo","projectExists","readProjectConfigurationUnqualified","assignPort","ensureProjectPackageJson","SHARED_CONSTRUCTS_DEPENDENCIES","sharedConstructsGenerator","DEPENDENCIES","ts","name","when","m","protocol","dev","root","py","AGENTCORE_GATEWAY_GENERATOR_INFO","filename","agentcoreGatewayGenerator","tree","options","nameClassName","parentDir","directory","subDir","subDirectory","projectRoot","fullyQualifiedName","auth","cedarPolicy","warn","infra","localGatewayTarget","port","executor","continuous","command","cwd","env","PORT","dependsOn","project","projectType","sourceRoot","tags","targets","metadata","generator","id","dir","dirname","nameKebabCase","attachedMcpServers","attachedAgents","overwriteStrategy","KeepExisting","iac","undefined","rc","gatewayNameClassName","gatewayNameKebabCase","projectName","projectDirectory","preferInstallDependencies","languages","readAgentCoreGatewayMetadata","Error"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../packages/nx-plugin/src/agentcore-gateway/generator.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport {\n addProjectConfiguration,\n type GeneratorCallback,\n generateFiles,\n joinPathFragments,\n logger,\n OverwriteStrategy,\n type ProjectConfiguration,\n type Tree,\n updateProjectConfiguration,\n} from '@nx/devkit';\nimport { addTsDependencies } from '../utils/add-dependencies.js';\nimport {\n AGENT_CORE_CONSTRUCTS_DEPENDENCIES,\n AGENT_CORE_CONSTRUCTS_PY_DEPENDENCIES,\n addAgentCoreGatewayInfra,\n} from '../utils/agent-core-constructs/agent-core-constructs.js';\nimport {\n declareDependencies,\n ownedElsewhere,\n} from '../utils/declared-dependencies.js';\nimport { formatFilesInSubtree } from '../utils/format.js';\nimport { resolveIac } from '../utils/iac.js';\nimport { installDependencies } from '../utils/install.js';\nimport { addGeneratorMetricsIfApplicable } from '../utils/metrics.js';\nimport { kebabCase, toClassName } from '../utils/names.js';\nimport { getNpmScopePrefix } from '../utils/npm-scope.js';\nimport {\n addGeneratorMetadata,\n getGeneratorInfo,\n type NxGeneratorInfo,\n projectExists,\n readProjectConfigurationUnqualified,\n} from '../utils/nx.js';\nimport { assignPort } from '../utils/port.js';\nimport { ensureProjectPackageJson } from '../utils/project-package-json.js';\nimport {\n SHARED_CONSTRUCTS_DEPENDENCIES,\n sharedConstructsGenerator,\n} from '../utils/shared-constructs.js';\nimport type { IacMetadata } from '../utils/shared-constructs-constants.js';\nimport type { AgentcoreGatewayGeneratorSchema } from './schema';\n\n// The gateway's local-dev server needs these whatever the auth; only the MCP\n// SDK is protocol-specific (the http local gateway is a plain proxy).\nexport const DEPENDENCIES = declareDependencies<AgentCoreGatewayMetadata>()({\n ts: [\n { name: '@modelcontextprotocol/sdk', when: (m) => m.protocol === 'mcp' },\n { name: 'express' },\n { name: '@types/express', dev: true },\n // local-dev.ts runs via tsx, which is shared tooling.\n { name: 'tsx', dev: true, root: true },\n ...ownedElsewhere(AGENT_CORE_CONSTRUCTS_DEPENDENCIES),\n ...ownedElsewhere(SHARED_CONSTRUCTS_DEPENDENCIES),\n ],\n py: ownedElsewhere(AGENT_CORE_CONSTRUCTS_PY_DEPENDENCIES),\n});\n\nexport const AGENTCORE_GATEWAY_GENERATOR_INFO: NxGeneratorInfo =\n getGeneratorInfo(import.meta.filename);\n\nexport const agentcoreGatewayGenerator = async (\n tree: Tree,\n options: AgentcoreGatewayGeneratorSchema,\n): Promise<GeneratorCallback> => {\n const name = kebabCase(options.name);\n const nameClassName = toClassName(name);\n const parentDir = options.directory ?? 'packages';\n const subDir = options.subDirectory ?? name;\n const projectRoot = joinPathFragments(parentDir, subDir);\n const fullyQualifiedName = `${getNpmScopePrefix(tree)}${name}`;\n\n // Protocol is mcp (aggregate MCP server targets into one MCP endpoint) or\n // http (proxy agent runtime targets via path-based routing). Persisted in\n // metadata so connection generators can validate it.\n const protocol = options.protocol ?? 'mcp';\n // Inbound auth is iam (default) or cognito. Persisted in metadata so\n // connection generators can validate it (agent -> gateway connections\n // require an IAM gateway).\n const auth = options.auth ?? 'iam';\n // Cedar policies authorize MCP tool actions, which an http gateway has none\n // of — its runtime targets are plain proxied endpoints — so the policy\n // engine only applies to mcp gateways. cedarPolicy defaults to true in the\n // schema, so an http gateway quietly drops it rather than requiring\n // --cedarPolicy=false.\n if (protocol === 'http' && options.cedarPolicy) {\n logger.warn(\n 'Cedar policies authorize MCP tool actions, so cedarPolicy is ignored for http-protocol gateways.',\n );\n }\n const cedarPolicy = protocol === 'mcp' && (options.cedarPolicy ?? true);\n const infra = options.infra ?? 'agentcore';\n\n // local-dev.ts is the local gateway: a continuous MCP aggregator chaining\n // onto attached MCP servers' dev targets (added by the mcp-connection\n // generator), so a single `nx dev <gateway>` boots the gateway and every\n // attached MCP server together.\n //\n // `serve` exists for parity with other generators; the deployed Gateway is\n // a managed AWS resource, so `serve`-mode agents talk to it via runtime\n // config and the local gateway sits idle.\n const localGatewayTarget = (port: number) => ({\n executor: 'nx:run-commands' as const,\n continuous: true,\n options: {\n command: 'tsx local-dev.ts',\n cwd: '{projectRoot}',\n env: { PORT: `${port}` },\n },\n dependsOn: [] as Array<string | { projects: string[]; target: string }>,\n });\n let project: ProjectConfiguration;\n if (projectExists(tree, fullyQualifiedName)) {\n project = readProjectConfigurationUnqualified(tree, fullyQualifiedName);\n } else {\n addProjectConfiguration(tree, fullyQualifiedName, {\n name: fullyQualifiedName,\n root: projectRoot,\n projectType: 'library',\n sourceRoot: projectRoot,\n tags: [],\n targets: {},\n metadata: {\n generator: AGENTCORE_GATEWAY_GENERATOR_INFO.id,\n } as any,\n });\n project = readProjectConfigurationUnqualified(tree, fullyQualifiedName);\n }\n // Ensure the project has its own package.json so its runtime dependencies\n // (added below) are declared in it rather than the workspace root.\n ensureProjectPackageJson(tree, {\n dir: projectRoot,\n fullyQualifiedName,\n });\n const port = assignPort(tree, project, 8100);\n // A gateway is its own standalone project, so it uses plain `serve` / `dev`\n // targets. Re-run: keep existing targets (their dependsOn may have been\n // populated by the mcp-connection generator), just ensure both exist.\n project.targets ??= {};\n project.targets['serve'] ??= localGatewayTarget(port);\n project.targets['dev'] ??= localGatewayTarget(port);\n updateProjectConfiguration(tree, project.name, project);\n\n // Scaffold the gateway project: local-dev.ts (+ Cedar policies if requested)\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'project', protocol),\n projectRoot,\n {\n nameClassName,\n nameKebabCase: name,\n port,\n attachedMcpServers: [],\n attachedAgents: [],\n },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n if (cedarPolicy) {\n // The default permit-all policy differs by auth type: AgentCore represents\n // IAM callers as `AgentCore::IamEntity` and Cognito/JWT callers as\n // `AgentCore::OAuthUser`, so the two policies match different principals.\n generateFiles(\n tree,\n joinPathFragments(import.meta.dirname, 'files', 'cedar'),\n projectRoot,\n { nameClassName, auth },\n { overwriteStrategy: OverwriteStrategy.KeepExisting },\n );\n }\n\n // Recorded in the metadata below so the version sync can tell a CDK\n // project from a Terraform one; undefined when no infrastructure was\n // generated, in which case neither provider's packages were added.\n const iac =\n infra !== 'none' ? await resolveIac(tree, options.iac) : undefined;\n\n // Recorded below and read by the declaration's predicates, so the packages\n // added here are exactly the ones the version sync will own.\n const metadata: AgentCoreGatewayMetadata = {\n name,\n rc: nameClassName,\n protocol,\n auth,\n port,\n ...(iac ? { iac } : {}),\n };\n\n addGeneratorMetadata(\n tree,\n fullyQualifiedName,\n AGENTCORE_GATEWAY_GENERATOR_INFO,\n metadata,\n );\n\n addTsDependencies(tree, DEPENDENCIES, { metadata, projectRoot });\n\n // Wire up infra (CDK or Terraform); re-running with infra=agentcore adds\n // the infrastructure to a previously infra-less gateway.\n\n if (infra === 'agentcore') {\n await sharedConstructsGenerator(tree, { iac }, DEPENDENCIES);\n\n await addAgentCoreGatewayInfra(\n tree,\n {\n gatewayNameClassName: nameClassName,\n gatewayNameKebabCase: name,\n projectName: fullyQualifiedName,\n projectDirectory: projectRoot,\n cedarPolicy,\n auth,\n protocol,\n iac,\n },\n DEPENDENCIES,\n );\n }\n\n await addGeneratorMetricsIfApplicable(tree, [\n AGENTCORE_GATEWAY_GENERATOR_INFO,\n ]);\n\n await formatFilesInSubtree(tree);\n return () =>\n installDependencies(tree, options.preferInstallDependencies, {\n languages: ['typescript'],\n });\n};\n\n/**\n * Gateway details stored in the gateway project's metadata.\n */\nexport interface AgentCoreGatewayMetadata extends IacMetadata {\n name: string;\n rc: string;\n protocol: string;\n auth: string;\n port: number;\n}\n\n/**\n * Read a gateway project's metadata, validating it was generated by the\n * agentcore-gateway generator.\n */\nexport const readAgentCoreGatewayMetadata = (\n project: ProjectConfiguration,\n): AgentCoreGatewayMetadata => {\n const metadata = project.metadata as any;\n if (metadata?.generator !== AGENTCORE_GATEWAY_GENERATOR_INFO.id) {\n throw new Error(\n `Project '${project.name}' was not generated by the '${AGENTCORE_GATEWAY_GENERATOR_INFO.id}' generator.`,\n );\n }\n return metadata as AgentCoreGatewayMetadata;\n};\n\nexport default agentcoreGatewayGenerator;\n"],"names":["addProjectConfiguration","generateFiles","joinPathFragments","logger","OverwriteStrategy","updateProjectConfiguration","addTsDependencies","AGENT_CORE_CONSTRUCTS_DEPENDENCIES","AGENT_CORE_CONSTRUCTS_PY_DEPENDENCIES","addAgentCoreGatewayInfra","declareDependencies","ownedElsewhere","formatFilesInSubtree","resolveIac","installDependencies","addGeneratorMetricsIfApplicable","kebabCase","toClassName","getNpmScopePrefix","addGeneratorMetadata","getGeneratorInfo","projectExists","readProjectConfigurationUnqualified","assignPort","ensureProjectPackageJson","SHARED_CONSTRUCTS_DEPENDENCIES","sharedConstructsGenerator","DEPENDENCIES","ts","name","when","m","protocol","dev","root","py","AGENTCORE_GATEWAY_GENERATOR_INFO","filename","agentcoreGatewayGenerator","tree","options","nameClassName","parentDir","directory","subDir","subDirectory","projectRoot","fullyQualifiedName","auth","cedarPolicy","warn","infra","localGatewayTarget","port","executor","continuous","command","cwd","env","PORT","dependsOn","project","projectType","sourceRoot","tags","targets","metadata","generator","id","dir","dirname","nameKebabCase","attachedMcpServers","attachedAgents","overwriteStrategy","KeepExisting","iac","undefined","rc","gatewayNameClassName","gatewayNameKebabCase","projectName","projectDirectory","preferInstallDependencies","languages","readAgentCoreGatewayMetadata","Error"],"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>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { joinPathFragments } from "@nx/devkit";
|
|
5
|
+
import { GRIT_INSERT_PLACEHOLDER, insertViaGritQL, matchGritQL } from "../../../utils/ast.js";
|
|
6
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
7
|
+
import { PACKAGES_DIR, SHARED_TERRAFORM_DIR } from "../../../utils/shared-constructs-constants.js";
|
|
8
|
+
/**
|
|
9
|
+
* Re-export the core dynamodb module's `table_name`, `table_arn` and
|
|
10
|
+
* `kms_key_arn` from each vended per-table Terraform app module
|
|
11
|
+
* (`app/dynamodb/<name>/<name>.tf`).
|
|
12
|
+
*
|
|
13
|
+
* The app module is what a root configuration instantiates, so without these
|
|
14
|
+
* outputs a root module cannot reference the table or its encryption key at
|
|
15
|
+
* all — for example to grant a consumer access to it.
|
|
16
|
+
*
|
|
17
|
+
* These files are generated with `KeepExisting`, so an upgraded workspace keeps
|
|
18
|
+
* a module with no outputs until this runs. Diverged files are left untouched
|
|
19
|
+
* and reported via `nextSteps`.
|
|
20
|
+
*/ const TERRAFORM_DYNAMODB_APP_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/app/dynamodb`;
|
|
21
|
+
const divergedMessage = (filePath)=>`${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).`;
|
|
22
|
+
const hcl = (pattern)=>`language hcl\n${pattern}`;
|
|
23
|
+
const OUTPUTS_TEXT = [
|
|
24
|
+
'output "table_name" {',
|
|
25
|
+
' description = "Name of the DynamoDB table"',
|
|
26
|
+
' value = module.dynamodb_table.table_name',
|
|
27
|
+
'}',
|
|
28
|
+
'',
|
|
29
|
+
'output "table_arn" {',
|
|
30
|
+
' 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."',
|
|
31
|
+
' value = module.dynamodb_table.table_arn',
|
|
32
|
+
'}',
|
|
33
|
+
'',
|
|
34
|
+
'output "kms_key_arn" {',
|
|
35
|
+
' 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)."',
|
|
36
|
+
' value = module.dynamodb_table.kms_key_arn',
|
|
37
|
+
'}'
|
|
38
|
+
].join('\n');
|
|
39
|
+
export default async function migration(tree) {
|
|
40
|
+
const nextSteps = [];
|
|
41
|
+
if (!tree.exists(TERRAFORM_DYNAMODB_APP_DIR)) {
|
|
42
|
+
return {
|
|
43
|
+
nextSteps
|
|
44
|
+
}; // This workspace has no Terraform dynamodb modules.
|
|
45
|
+
}
|
|
46
|
+
for (const dirName of tree.children(TERRAFORM_DYNAMODB_APP_DIR)){
|
|
47
|
+
const filePath = joinPathFragments(TERRAFORM_DYNAMODB_APP_DIR, dirName, `${dirName}.tf`);
|
|
48
|
+
if (!tree.exists(filePath)) {
|
|
49
|
+
continue; // Not a dynamodb app module directory.
|
|
50
|
+
}
|
|
51
|
+
if (await matchGritQL(tree, filePath, hcl('`output "table_arn" { $_ }`'))) {
|
|
52
|
+
continue; // Already migrated.
|
|
53
|
+
}
|
|
54
|
+
const RUNTIME_CONFIG_MODULE_BLOCK = hcl('`module "add_to_runtime_config" { $_ }`');
|
|
55
|
+
if (!await matchGritQL(tree, filePath, RUNTIME_CONFIG_MODULE_BLOCK)) {
|
|
56
|
+
nextSteps.push(divergedMessage(filePath));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Appended after the runtime config module call, which the template ends
|
|
60
|
+
// with, matching where the generator now writes the outputs.
|
|
61
|
+
await insertViaGritQL(tree, filePath, hcl(`\`module "add_to_runtime_config" { $body }\` => \`module "add_to_runtime_config" {\n $body\n}\n\n${GRIT_INSERT_PLACEHOLDER}\``), OUTPUTS_TEXT);
|
|
62
|
+
}
|
|
63
|
+
await formatFilesInSubtree(tree);
|
|
64
|
+
return {
|
|
65
|
+
nextSteps
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/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"}
|
|
@@ -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>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { visitNotIgnoredFiles } from "@nx/devkit";
|
|
5
|
+
import { applyGritQL, matchGritQL } from "../../../utils/ast.js";
|
|
6
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
7
|
+
import { PACKAGES_DIR, SHARED_TERRAFORM_DIR } from "../../../utils/shared-constructs-constants.js";
|
|
8
|
+
/**
|
|
9
|
+
* Provision AppConfig configuration profiles for every namespace the vended terraform modules write to
|
|
10
|
+
*
|
|
11
|
+
* `core/runtime-config/appconfig` creates one configuration profile per entry in
|
|
12
|
+
* its `namespaces` list, and `appconfig-deployment` aggregates and deploys the
|
|
13
|
+
* same list. A module contributing an entry to a namespace outside that list
|
|
14
|
+
* writes its entry file to disk and nothing more — the value never reaches
|
|
15
|
+
* AppConfig, so a consumer reading it back fails at runtime. The vended defaults
|
|
16
|
+
* now cover `dynamodb` and `database` alongside `connection` and `agentcore`.
|
|
17
|
+
*
|
|
18
|
+
* Root modules that pass `namespaces` explicitly are updated too, since an
|
|
19
|
+
* explicit list overrides the vended default.
|
|
20
|
+
*
|
|
21
|
+
* How to write a migration:
|
|
22
|
+
* - https://nx.dev/docs/kb/migration-generators
|
|
23
|
+
* - What `nextSteps` means: https://nx.dev/docs/reference/devkit/MigrationReturnObject
|
|
24
|
+
*
|
|
25
|
+
* Guardrails:
|
|
26
|
+
* - Transform source with GritQL (`applyGritQL`, `matchGritQL`), not regexes or
|
|
27
|
+
* string replacements: it matches the AST, so it holds up against however the
|
|
28
|
+
* user's copy has been formatted. Use `updateJson` for JSON.
|
|
29
|
+
* - Pattern-match before writing: skip files that have diverged from the shape
|
|
30
|
+
* your generators produce and report them via `nextSteps`, or consider a
|
|
31
|
+
* hybrid migration, rather than clobbering the user's changes.
|
|
32
|
+
* - `nextSteps` is for work left for the user to do by hand, not a log of the
|
|
33
|
+
* edits you made: an edit that applied cleanly needs no entry.
|
|
34
|
+
* - Idempotent: re-running must be a no-op.
|
|
35
|
+
* - Format what you write: finish with `formatFilesInSubtree` so the files your
|
|
36
|
+
* migration wrote are formatted correctly.
|
|
37
|
+
*/ /** Namespaces the vended modules write to that the old defaults omitted. */ const ADDED_NAMESPACES = [
|
|
38
|
+
'database',
|
|
39
|
+
'dynamodb'
|
|
40
|
+
];
|
|
41
|
+
const RUNTIME_CONFIG_DIR = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/runtime-config`;
|
|
42
|
+
const APPCONFIG_FILE = `${RUNTIME_CONFIG_DIR}/appconfig/appconfig.tf`;
|
|
43
|
+
const APPCONFIG_DEPLOYMENT_FILE = `${RUNTIME_CONFIG_DIR}/appconfig-deployment/appconfig-deployment.tf`;
|
|
44
|
+
const hcl = (pattern)=>`language hcl\n${pattern}`;
|
|
45
|
+
/** The `namespaces` variable declaration in a vended appconfig module. */ const VENDED_DEFAULT = {
|
|
46
|
+
block: 'variable "namespaces" { $body }',
|
|
47
|
+
attribute: 'default'
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* A call to the vended appconfig module that passes `namespaces` explicitly.
|
|
51
|
+
* `$src` binds the quoted string, so the regex ends at the closing quote — that
|
|
52
|
+
* anchor is what keeps the sibling `appconfig-deployment` call, which takes its
|
|
53
|
+
* namespaces from this module's output, out of scope.
|
|
54
|
+
*/ const ROOT_MODULE_ARGUMENT = {
|
|
55
|
+
block: 'module $name { $body }',
|
|
56
|
+
attribute: 'namespaces',
|
|
57
|
+
clauses: '$body <: contains `source = $src`, $src <: r".*runtime-config/appconfig\\"", $body <: contains `namespaces = $_`'
|
|
58
|
+
};
|
|
59
|
+
/** A GritQL pattern matching the enclosing block, plus any extra clauses. */ const scopePattern = (list, extraClauses)=>{
|
|
60
|
+
const clauses = [
|
|
61
|
+
list.clauses,
|
|
62
|
+
extraClauses
|
|
63
|
+
].filter(Boolean).join(', ');
|
|
64
|
+
return hcl(`\`${list.block}\`${clauses ? ` where { ${clauses} }` : ''}`);
|
|
65
|
+
};
|
|
66
|
+
/** Whether a namespace already appears in the list. */ const listsNamespace = (tree, filePath, list, namespace)=>matchGritQL(tree, filePath, scopePattern(list, `$body <: contains \`"${namespace}"\``));
|
|
67
|
+
/**
|
|
68
|
+
* Append a namespace to the list. Appended within the list rather than
|
|
69
|
+
* re-emitting it, so no trailing hole is introduced.
|
|
70
|
+
*/ const appendNamespace = (tree, filePath, list, namespace)=>applyGritQL(tree, filePath, scopePattern(list, `$body <: contains bubble \`${list.attribute} = [$items]\` where { $items <: [$..., $last], $last += \`, "${namespace}"\` }`));
|
|
71
|
+
/**
|
|
72
|
+
* Add every missing namespace to a list, returning false when one could not be
|
|
73
|
+
* added. Already-listed namespaces are skipped, which is what makes a re-run a
|
|
74
|
+
* no-op.
|
|
75
|
+
*/ const addMissingNamespaces = async (tree, filePath, list)=>{
|
|
76
|
+
for (const namespace of ADDED_NAMESPACES){
|
|
77
|
+
if (await listsNamespace(tree, filePath, list, namespace)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!await appendNamespace(tree, filePath, list, namespace)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
/** The vended module's own description, updated to document the new defaults. */ const DESCRIPTION_REWRITE = scopePattern(VENDED_DEFAULT, '$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."` }');
|
|
87
|
+
const missing = ADDED_NAMESPACES.map((n)=>`"${n}"`).join(' and ');
|
|
88
|
+
const divergedNextStep = (filePath)=>`${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.`;
|
|
89
|
+
const rootModuleNextStep = (filePath)=>`${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.`;
|
|
90
|
+
/** Bring one vended appconfig module's `namespaces` default up to date. */ const migrateVendedModule = async (tree, filePath, nextSteps)=>{
|
|
91
|
+
if (!tree.exists(filePath)) {
|
|
92
|
+
return; // This workspace has no Terraform runtime config.
|
|
93
|
+
}
|
|
94
|
+
if (!await addMissingNamespaces(tree, filePath, VENDED_DEFAULT)) {
|
|
95
|
+
nextSteps.push(divergedNextStep(filePath));
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Root modules that pass `namespaces` explicitly override the vended default, so
|
|
100
|
+
* they need the same additions.
|
|
101
|
+
*/ const migrateRootModules = async (tree, nextSteps)=>{
|
|
102
|
+
const terraformFiles = [];
|
|
103
|
+
visitNotIgnoredFiles(tree, '', (filePath)=>{
|
|
104
|
+
if (filePath.endsWith('.tf') && !filePath.startsWith(RUNTIME_CONFIG_DIR)) {
|
|
105
|
+
terraformFiles.push(filePath);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
for (const filePath of terraformFiles){
|
|
109
|
+
if (!await matchGritQL(tree, filePath, scopePattern(ROOT_MODULE_ARGUMENT))) {
|
|
110
|
+
continue; // Takes the module's defaults, so nothing to update.
|
|
111
|
+
}
|
|
112
|
+
if (!await addMissingNamespaces(tree, filePath, ROOT_MODULE_ARGUMENT)) {
|
|
113
|
+
nextSteps.push(rootModuleNextStep(filePath));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
export default async function migration(tree) {
|
|
118
|
+
const nextSteps = [];
|
|
119
|
+
await migrateVendedModule(tree, APPCONFIG_FILE, nextSteps);
|
|
120
|
+
await migrateVendedModule(tree, APPCONFIG_DEPLOYMENT_FILE, nextSteps);
|
|
121
|
+
if (tree.exists(APPCONFIG_FILE)) {
|
|
122
|
+
await applyGritQL(tree, APPCONFIG_FILE, DESCRIPTION_REWRITE);
|
|
123
|
+
}
|
|
124
|
+
await migrateRootModules(tree, nextSteps);
|
|
125
|
+
await formatFilesInSubtree(tree);
|
|
126
|
+
return {
|
|
127
|
+
nextSteps
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/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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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`terraform-data-resource-destroy-protection migration > should protect the aurora cluster and pin its key window 1`] = `
|
|
4
|
+
"resource "aws_kms_key" "database" {
|
|
5
|
+
description = "KMS key for Aurora cluster \${var.name}"
|
|
6
|
+
enable_key_rotation = var.enable_key_rotation
|
|
7
|
+
|
|
8
|
+
deletion_window_in_days = 30
|
|
9
|
+
|
|
10
|
+
tags = merge(var.tags, {
|
|
11
|
+
Name = "\${var.name}-aurora"
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
resource "aws_rds_cluster" "database" {
|
|
16
|
+
cluster_identifier = local.cluster_name
|
|
17
|
+
engine = var.engine
|
|
18
|
+
storage_encrypted = true
|
|
19
|
+
kms_key_id = aws_kms_key.database.arn
|
|
20
|
+
deletion_protection = var.deletion_protection
|
|
21
|
+
skip_final_snapshot = var.skip_final_snapshot
|
|
22
|
+
|
|
23
|
+
serverlessv2_scaling_configuration {
|
|
24
|
+
min_capacity = var.serverless_min_capacity
|
|
25
|
+
max_capacity = var.serverless_max_capacity
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
tags = merge(var.tags, {
|
|
29
|
+
Name = "\${var.name}-aurora"
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
lifecycle {
|
|
33
|
+
prevent_destroy = true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
"
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
exports[`terraform-data-resource-destroy-protection migration > should protect the dynamodb table and pin its key window 1`] = `
|
|
40
|
+
"resource "aws_kms_key" "table" {
|
|
41
|
+
count = local.create_table_key ? 1 : 0
|
|
42
|
+
|
|
43
|
+
description = "KMS key for DynamoDB table \${var.name}"
|
|
44
|
+
enable_key_rotation = var.enable_key_rotation
|
|
45
|
+
|
|
46
|
+
deletion_window_in_days = 30
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
resource "aws_dynamodb_table" "table" {
|
|
50
|
+
name = var.name
|
|
51
|
+
billing_mode = var.billing_mode
|
|
52
|
+
hash_key = "pk"
|
|
53
|
+
range_key = "sk"
|
|
54
|
+
deletion_protection_enabled = var.deletion_protection_enabled
|
|
55
|
+
|
|
56
|
+
attribute {
|
|
57
|
+
name = "pk"
|
|
58
|
+
type = "S"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
point_in_time_recovery {
|
|
62
|
+
enabled = var.point_in_time_recovery_enabled
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
server_side_encryption {
|
|
66
|
+
enabled = var.encryption != "DEFAULT"
|
|
67
|
+
kms_key_arn = local.table_kms_key_arn
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
lifecycle {
|
|
71
|
+
prevent_destroy = true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
"
|
|
75
|
+
`;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/ import { applyGritQL, matchGritQL } from "../../../utils/ast.js";
|
|
5
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
6
|
+
import { PACKAGES_DIR, SHARED_TERRAFORM_DIR } from "../../../utils/shared-constructs-constants.js";
|
|
7
|
+
/**
|
|
8
|
+
* Bring existing workspaces up to the destroy protection the generators now
|
|
9
|
+
* vend on the data-bearing Terraform resources:
|
|
10
|
+
*
|
|
11
|
+
* - The DynamoDB table and the Aurora cluster gain
|
|
12
|
+
* `lifecycle { prevent_destroy = true }`. This is a second, independent layer
|
|
13
|
+
* on top of the service-side `deletion_protection_enabled` /
|
|
14
|
+
* `deletion_protection` flags, matching the two guards the CDK constructs
|
|
15
|
+
* already have (`deletionProtection` plus `RemovalPolicy.RETAIN`). Without it,
|
|
16
|
+
* clearing the service-side flag destroys the data in the same apply.
|
|
17
|
+
* `prevent_destroy` cannot reference a variable, so it is vended as a literal
|
|
18
|
+
* and removed to tear down.
|
|
19
|
+
* - Their KMS keys pin `deletion_window_in_days = 30`, the provider maximum and
|
|
20
|
+
* the CDK default. Restoring either resource from a point-in-time backup or
|
|
21
|
+
* snapshot requires its key, so the window in which a scheduled key deletion
|
|
22
|
+
* can still be cancelled matters as much as the backup itself.
|
|
23
|
+
*
|
|
24
|
+
* These files are generated with `KeepExisting`, so without this an upgraded
|
|
25
|
+
* workspace keeps the weaker single-layer protection. Diverged files are left
|
|
26
|
+
* untouched and reported via `nextSteps`.
|
|
27
|
+
*/ const TERRAFORM_DYNAMODB_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/dynamodb/dynamodb.tf`;
|
|
28
|
+
const TERRAFORM_AURORA_FILE = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src/core/rdb/aurora/aurora.tf`;
|
|
29
|
+
const hcl = (pattern)=>`language hcl\n${pattern}`;
|
|
30
|
+
const divergedMessage = (filePath, resource)=>`${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.`;
|
|
31
|
+
/**
|
|
32
|
+
* Add the maximum KMS pending window to a key that omits it, so a scheduled
|
|
33
|
+
* deletion stays cancellable for as long as the provider allows.
|
|
34
|
+
*/ const migrateKmsKey = async (tree, filePath, keyLabel)=>{
|
|
35
|
+
const keyBlock = hcl(`\`resource "aws_kms_key" "${keyLabel}" { $props }\``);
|
|
36
|
+
// Already pinned - nothing to do.
|
|
37
|
+
if (await matchGritQL(tree, filePath, hcl(`\`resource "aws_kms_key" "${keyLabel}" { $props }\` where { $props <: contains \`deletion_window_in_days = $_\` }`))) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!await matchGritQL(tree, filePath, keyBlock)) {
|
|
41
|
+
return; // Reported by the caller, which matches the data resource too.
|
|
42
|
+
}
|
|
43
|
+
await applyGritQL(tree, filePath, hcl(`\`resource "aws_kms_key" "${keyLabel}" { $props }\` where { $props <: contains \`enable_key_rotation = $rotation\` => \`enable_key_rotation = $rotation\n\n deletion_window_in_days = 30\` }`));
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Add `lifecycle { prevent_destroy = true }` to a data-bearing resource.
|
|
47
|
+
*/ const migrateDataResource = async (tree, filePath, resourceType, resourceLabel, nextSteps, description)=>{
|
|
48
|
+
const resourceBlock = hcl(`\`resource "${resourceType}" "${resourceLabel}" { $props }\``);
|
|
49
|
+
if (!await matchGritQL(tree, filePath, resourceBlock)) {
|
|
50
|
+
nextSteps.push(divergedMessage(filePath, description));
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// Already guarded - keep the user's block, whatever it is set to.
|
|
54
|
+
if (await matchGritQL(tree, filePath, hcl(`\`resource "${resourceType}" "${resourceLabel}" { $props }\` where { $props <: contains \`prevent_destroy = $_\` }`))) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
await applyGritQL(tree, filePath, hcl(`\`resource "${resourceType}" "${resourceLabel}" { $props }\` => \`resource "${resourceType}" "${resourceLabel}" {\n $props\n\n lifecycle {\n prevent_destroy = true\n }\n}\``));
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
export default async function migration(tree) {
|
|
61
|
+
const nextSteps = [];
|
|
62
|
+
if (tree.exists(TERRAFORM_DYNAMODB_FILE)) {
|
|
63
|
+
if (await migrateDataResource(tree, TERRAFORM_DYNAMODB_FILE, 'aws_dynamodb_table', 'table', nextSteps, 'DynamoDB table')) {
|
|
64
|
+
await migrateKmsKey(tree, TERRAFORM_DYNAMODB_FILE, 'table');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (tree.exists(TERRAFORM_AURORA_FILE)) {
|
|
68
|
+
if (await migrateDataResource(tree, TERRAFORM_AURORA_FILE, 'aws_rds_cluster', 'database', nextSteps, 'Aurora cluster')) {
|
|
69
|
+
await migrateKmsKey(tree, TERRAFORM_AURORA_FILE, 'database');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
await formatFilesInSubtree(tree);
|
|
73
|
+
return {
|
|
74
|
+
nextSteps
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=migration.js.map
|