@aws/nx-plugin 1.0.0-rc.83 → 1.0.0-rc.85
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/generators.json +7 -0
- package/migrations.json +31 -1
- package/package.json +1 -1
- package/src/agentcore-gateway/__snapshots__/generator.spec.ts.snap +51 -2
- package/src/agentcore-harness/__snapshots__/generator.spec.ts.snap +4 -5
- package/src/agentcore-harness/files/project/README.md.template +4 -5
- package/src/internal/test-matrix/generator.js +16 -0
- package/src/internal/test-matrix/generator.js.map +1 -1
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/__snapshots__/migration.spec.ts.snap +79 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/metadata.json +3 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.d.ts +6 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js +87 -0
- package/src/migrations/latest/agentcore-harness-no-tools-by-default/migration.js.map +1 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/__snapshots__/migration.spec.ts.snap +54 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/metadata.json +3 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.d.ts +6 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js +86 -0
- package/src/migrations/latest/rdb-terraform-aurora-app-module-defaults/migration.js.map +1 -0
- package/src/migrations/latest/terraform-declare-used-providers/metadata.json +3 -0
- package/src/migrations/latest/terraform-declare-used-providers/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-declare-used-providers/migration.js +147 -0
- package/src/migrations/latest/terraform-declare-used-providers/migration.js.map +1 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/__snapshots__/migration.spec.ts.snap +521 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/metadata.json +3 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js +504 -0
- package/src/migrations/latest/terraform-harness-vpc-role-and-memory-grant/migration.js.map +1 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/metadata.json +3 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.d.ts +6 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js +146 -0
- package/src/migrations/latest/terraform-project-checkov-target-and-bootstrap-destroy/migration.js.map +1 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/__snapshots__/migration.spec.ts.snap +286 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/metadata.json +3 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.d.ts +6 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js +238 -0
- package/src/migrations/latest/waf-log-group-cmk-encryption/migration.js.map +1 -0
- package/src/open-api/json-metadata/generator.d.ts +24 -0
- package/src/open-api/json-metadata/generator.js +37 -0
- package/src/open-api/json-metadata/generator.js.map +1 -0
- package/src/open-api/json-metadata/schema.d.js +6 -0
- package/src/open-api/json-metadata/schema.d.js.map +1 -0
- package/src/open-api/json-metadata/schema.d.ts +9 -0
- package/src/open-api/json-metadata/schema.json +20 -0
- package/src/py/agent/__snapshots__/generator.protocols.spec.ts.snap +12 -0
- package/src/py/fast-api/__snapshots__/generator.terraform.spec.ts.snap +1875 -623
- package/src/py/fast-api/generator.js +2 -1
- package/src/py/fast-api/generator.js.map +1 -1
- package/src/py/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
- package/src/py/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/py/rdb/__snapshots__/generator.spec.ts.snap +31 -2
- package/src/sdk/open-api.d.ts +2 -0
- package/src/sdk/open-api.js +2 -1
- package/src/sdk/open-api.js.map +1 -1
- package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +1756 -457
- package/src/smithy/ts/api/generator.js +2 -1
- package/src/smithy/ts/api/generator.js.map +1 -1
- package/src/terraform/project/files/application/bootstrap/providers.tf.template +11 -0
- package/src/terraform/project/files/application/scripts/bootstrap-destroy.ts.template +111 -0
- package/src/terraform/project/files/application/src/providers.tf.template +6 -0
- package/src/terraform/project/files/checkov/checkov.yml.template +8 -0
- package/src/terraform/project/files/library/src/main.tf.template +9 -0
- package/src/terraform/project/generator.js +47 -4
- package/src/terraform/project/generator.js.map +1 -1
- package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +1848 -614
- package/src/trpc/backend/files-operations/scripts/generate-operations.ts.template +40 -0
- package/src/trpc/backend/generator.js +10 -0
- package/src/trpc/backend/generator.js.map +1 -1
- package/src/ts/agent/__snapshots__/generator.spec.ts.snap +8 -0
- package/src/ts/dcr-proxy/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/ts/lambda-function/__snapshots__/generator.spec.ts.snap +8 -0
- package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/ts/rdb/__snapshots__/generator.spec.ts.snap +62 -4
- package/src/ts/react-website/app/__snapshots__/generator.spec.ts.snap +4 -0
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.spec.ts.snap +35 -8
- package/src/ts/react-website/cognito-auth/__snapshots__/generator.terraform.spec.ts.snap +4 -0
- package/src/utils/agent-core-constructs/files/cdk/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.ts.template +39 -1
- package/src/utils/agent-core-constructs/files/terraform/app/agent-core/__nameKebabCase__/__nameKebabCase__.tf.template +8 -1
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-gateway/__nameKebabCase__/__nameKebabCase__.tf.template +57 -2
- package/src/utils/agent-core-constructs/files/terraform/app/agentcore-harness/__nameKebabCase__/__nameKebabCase__.tf.template +258 -24
- package/src/utils/agent-core-constructs/files/terraform/core/agent-core/runtime.tf.template +4 -0
- package/src/utils/api-constructs/api-constructs.js +11 -0
- package/src/utils/api-constructs/api-constructs.js.map +1 -1
- package/src/utils/api-constructs/files/terraform/app/apis/http/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +251 -3
- package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +437 -2
- package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +60 -7
- package/src/utils/api-constructs/open-api-metadata.d.ts +20 -3
- package/src/utils/api-constructs/open-api-metadata.js +64 -8
- package/src/utils/api-constructs/open-api-metadata.js.map +1 -1
- package/src/utils/api-constructs/trpc-operations-metadata.d.ts +21 -0
- package/src/utils/api-constructs/trpc-operations-metadata.js +55 -0
- package/src/utils/api-constructs/trpc-operations-metadata.js.map +1 -0
- package/src/utils/dcr-proxy-constructs/files/terraform/app/dcr-proxies/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
- package/src/utils/files/terraform/src/metrics/providers.tf.template +12 -0
- package/src/utils/function-constructs/files/terraform/app/lambda-functions/__nameKebabCase__/__nameKebabCase__.tf.template +4 -0
- package/src/utils/identity-constructs/files/cdk/core/user-identity.ts.template +33 -8
- package/src/utils/identity-constructs/files/terraform/core/user-identity/add-callback-url/add-callback-url.tf.template +4 -0
- package/src/utils/identity-constructs/files/terraform/core/user-identity/identity/identity.tf.template +51 -2
- package/src/utils/rdb-constructs/files/cdk/core/rdb/aurora.ts.template +18 -0
- package/src/utils/rdb-constructs/files/terraform/app/dbs/__nameKebabCase__/__nameKebabCase__.tf.template +15 -4
- package/src/utils/rdb-constructs/files/terraform/core/rdb/aurora/aurora.tf.template +2 -2
- package/src/utils/rdb-constructs/rdb-constructs.d.ts +12 -0
- package/src/utils/rdb-constructs/rdb-constructs.js +24 -3
- package/src/utils/rdb-constructs/rdb-constructs.js.map +1 -1
- package/src/utils/website-constructs/files/terraform/core/static-website/static-website.tf.template +4 -0
|
@@ -0,0 +1,238 @@
|
|
|
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 { addDestructuredImport, applyGritQL, GRIT_INSERT_PLACEHOLDER, insertViaGritQL, matchGritQL } from "../../../utils/ast.js";
|
|
6
|
+
import { formatFilesInSubtree } from "../../../utils/format.js";
|
|
7
|
+
import { PACKAGES_DIR, SHARED_CONSTRUCTS_DIR, SHARED_TERRAFORM_DIR } from "../../../utils/shared-constructs-constants.js";
|
|
8
|
+
/**
|
|
9
|
+
* WAF request logs carry full request metadata - URIs, headers and client IPs -
|
|
10
|
+
* so the vended WAF log groups are encrypted with a customer-managed KMS key,
|
|
11
|
+
* matching how the vended access log groups beside them already are. Three were
|
|
12
|
+
* plaintext: the Terraform REST API and user-pool WAF log groups, the Terraform
|
|
13
|
+
* AgentCore gateway WAF log group, and the CDK user-pool WAF log group. Each now
|
|
14
|
+
* gets a key whose policy grants CloudWatch Logs use of it, scoped by
|
|
15
|
+
* `kms:EncryptionContext:aws:logs:arn` to that log group alone.
|
|
16
|
+
*
|
|
17
|
+
* CloudWatch Logs cannot encrypt a log group that already exists in place, so
|
|
18
|
+
* this only rewrites the vended templates. On the next deploy Terraform sets
|
|
19
|
+
* `kms_key_id` on the existing group (an in-place update) and CloudFormation
|
|
20
|
+
* replaces the CDK one, which the vended `RemovalPolicy.DESTROY` already allows.
|
|
21
|
+
* Events written before then stay unencrypted; the `nextSteps` note says so.
|
|
22
|
+
*/ const CONSTRUCTS_SRC = `${PACKAGES_DIR}/${SHARED_CONSTRUCTS_DIR}/src`;
|
|
23
|
+
const TERRAFORM_SRC = `${PACKAGES_DIR}/${SHARED_TERRAFORM_DIR}/src`;
|
|
24
|
+
const CDK_USER_IDENTITY_FILE = `${CONSTRUCTS_SRC}/core/user-identity.ts`;
|
|
25
|
+
const TERRAFORM_REST_API_FILE = `${TERRAFORM_SRC}/core/api/rest-api/rest-api.tf`;
|
|
26
|
+
const TERRAFORM_USER_IDENTITY_FILE = `${TERRAFORM_SRC}/core/user-identity/identity/identity.tf`;
|
|
27
|
+
const TERRAFORM_GATEWAYS_DIR = `${TERRAFORM_SRC}/app/gateways`;
|
|
28
|
+
const divergedMessage = (file)=>`${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).`;
|
|
29
|
+
const ALREADY_ENCRYPTED_TERRAFORM = 'aws_kms_key.';
|
|
30
|
+
// Terraform (HCL) patterns are built from plain strings rather than JS template
|
|
31
|
+
// literals, since the HCL text itself is full of `${...}` interpolations that
|
|
32
|
+
// would otherwise be parsed as JS interpolation.
|
|
33
|
+
const hcl = (pattern)=>'language hcl\n' + pattern;
|
|
34
|
+
const REST_API_LOG_GROUP = {
|
|
35
|
+
logGroupLabel: 'api_waf_logs',
|
|
36
|
+
keyLabel: 'waf_logs',
|
|
37
|
+
nameLocal: 'waf_log_group_name',
|
|
38
|
+
nameExpression: 'var.enable_waf ? "aws-waf-logs-${var.api_name}-${random_id.waf_log_suffix[0].hex}" : null',
|
|
39
|
+
keyDescription: '"${var.api_name} API WAF log encryption"',
|
|
40
|
+
aliasName: '"alias/${var.api_name}-${random_id.waf_log_suffix[0].hex}-waf-logs"',
|
|
41
|
+
accountId: 'data.aws_caller_identity.current.account_id',
|
|
42
|
+
region: 'data.aws_region.current.region',
|
|
43
|
+
tagged: true
|
|
44
|
+
};
|
|
45
|
+
const USER_IDENTITY_LOG_GROUP = {
|
|
46
|
+
logGroupLabel: 'user_pool_waf_logs',
|
|
47
|
+
keyLabel: 'user_pool_waf_logs',
|
|
48
|
+
nameLocal: 'user_pool_waf_log_group_name',
|
|
49
|
+
nameExpression: 'var.enable_waf ? "aws-waf-logs-UserPool-${random_id.unique_suffix.hex}" : null',
|
|
50
|
+
keyDescription: '"User pool WAF log encryption"',
|
|
51
|
+
aliasName: '"alias/user-pool-${random_id.unique_suffix.hex}-waf-logs"',
|
|
52
|
+
accountId: 'data.aws_caller_identity.current.account_id',
|
|
53
|
+
region: 'data.aws_region.current.region',
|
|
54
|
+
tagged: false
|
|
55
|
+
};
|
|
56
|
+
const gatewayLogGroup = (nameKebabCase)=>({
|
|
57
|
+
logGroupLabel: 'gateway_waf_logs',
|
|
58
|
+
keyLabel: 'gateway_waf_logs',
|
|
59
|
+
nameLocal: 'gateway_waf_log_group_name',
|
|
60
|
+
nameExpression: `var.enable_waf ? "aws-waf-logs-${nameKebabCase}-\${random_id.unique_suffix.hex}" : null`,
|
|
61
|
+
keyDescription: `"${nameKebabCase} gateway WAF log encryption"`,
|
|
62
|
+
aliasName: `"alias/${nameKebabCase}-\${random_id.unique_suffix.hex}-waf-logs"`,
|
|
63
|
+
accountId: 'local.aws_account_id',
|
|
64
|
+
region: 'local.aws_region',
|
|
65
|
+
tagged: false
|
|
66
|
+
});
|
|
67
|
+
/** The KMS key and alias resources, as the templates now vend them. */ const keyResourcesText = (group)=>[
|
|
68
|
+
'# KMS key for encrypting WAF request logs at rest',
|
|
69
|
+
`resource "aws_kms_key" "${group.keyLabel}" {`,
|
|
70
|
+
' count = var.enable_waf ? 1 : 0',
|
|
71
|
+
'',
|
|
72
|
+
` description = ${group.keyDescription}`,
|
|
73
|
+
' deletion_window_in_days = 7',
|
|
74
|
+
' enable_key_rotation = true',
|
|
75
|
+
'',
|
|
76
|
+
' policy = jsonencode({',
|
|
77
|
+
' Version = "2012-10-17"',
|
|
78
|
+
' Statement = [',
|
|
79
|
+
' {',
|
|
80
|
+
' Sid = "Enable IAM User Permissions"',
|
|
81
|
+
' Effect = "Allow"',
|
|
82
|
+
` Principal = { AWS = "arn:aws:iam::\${${group.accountId}}:root" }`,
|
|
83
|
+
' Action = "kms:*"',
|
|
84
|
+
' Resource = "*"',
|
|
85
|
+
' },',
|
|
86
|
+
' {',
|
|
87
|
+
' Sid = "Allow CloudWatch Logs"',
|
|
88
|
+
' Effect = "Allow"',
|
|
89
|
+
` Principal = { Service = "logs.\${${group.region}}.amazonaws.com" }`,
|
|
90
|
+
' Action = [',
|
|
91
|
+
' "kms:Encrypt",',
|
|
92
|
+
' "kms:Decrypt",',
|
|
93
|
+
' "kms:ReEncrypt*",',
|
|
94
|
+
' "kms:GenerateDataKey*",',
|
|
95
|
+
' "kms:DescribeKey"',
|
|
96
|
+
' ]',
|
|
97
|
+
' Resource = "*"',
|
|
98
|
+
' Condition = {',
|
|
99
|
+
' ArnEquals = {',
|
|
100
|
+
` "kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:\${${group.region}}:\${${group.accountId}}:log-group:\${local.${group.nameLocal}}"`,
|
|
101
|
+
' }',
|
|
102
|
+
' }',
|
|
103
|
+
' }',
|
|
104
|
+
' ]',
|
|
105
|
+
' })',
|
|
106
|
+
...group.tagged ? [
|
|
107
|
+
'',
|
|
108
|
+
' tags = var.tags'
|
|
109
|
+
] : [],
|
|
110
|
+
'}',
|
|
111
|
+
'',
|
|
112
|
+
`resource "aws_kms_alias" "${group.keyLabel}" {`,
|
|
113
|
+
' count = var.enable_waf ? 1 : 0',
|
|
114
|
+
'',
|
|
115
|
+
` name = ${group.aliasName}`,
|
|
116
|
+
` target_key_id = aws_kms_key.${group.keyLabel}[0].key_id`,
|
|
117
|
+
'}'
|
|
118
|
+
].join('\n');
|
|
119
|
+
const migrateTerraformLogGroup = async (tree, file, group, nextSteps)=>{
|
|
120
|
+
const contents = tree.read(file, 'utf-8') ?? '';
|
|
121
|
+
// The WAF log group is only vended when WAF is enabled, so it may be absent.
|
|
122
|
+
if (!contents.includes(`"aws_cloudwatch_log_group" "${group.logGroupLabel}"`)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const LOG_GROUP_BLOCK = hcl(`\`resource "aws_cloudwatch_log_group" "${group.logGroupLabel}" { $body }\``);
|
|
126
|
+
const NAME_LINE = hcl(`\`name = ${group.nameExpression.replace(/^var\.enable_waf \? /, '').replace(/ : null$/, '')}\` as $line where {\n` + ` $line <: within \`resource "aws_cloudwatch_log_group" "${group.logGroupLabel}" { $_ }\`\n` + '}');
|
|
127
|
+
const SUPPRESSION_LINE = '#checkov:skip=CKV_AWS_158:Using default CloudWatch log encryption';
|
|
128
|
+
const LOCALS_BLOCK = hcl('`locals { $body }`');
|
|
129
|
+
if (contents.includes(ALREADY_ENCRYPTED_TERRAFORM + group.keyLabel)) {
|
|
130
|
+
return; // Already migrated.
|
|
131
|
+
}
|
|
132
|
+
const ready = await matchGritQL(tree, file, LOG_GROUP_BLOCK) && await matchGritQL(tree, file, NAME_LINE) && await matchGritQL(tree, file, LOCALS_BLOCK) && contents.includes(SUPPRESSION_LINE);
|
|
133
|
+
if (!ready) {
|
|
134
|
+
nextSteps.push(divergedMessage(file));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// 1. Route the log group name through a local, so the key policy's encryption
|
|
138
|
+
// context condition can name the same log group.
|
|
139
|
+
await insertViaGritQL(tree, file, LOCALS_BLOCK + ` as $l => \`locals {\n $body\n ${GRIT_INSERT_PLACEHOLDER}\n}\``, `${group.nameLocal} = ${group.nameExpression}`);
|
|
140
|
+
await applyGritQL(tree, file, NAME_LINE + ` => \`name = local.${group.nameLocal}\``);
|
|
141
|
+
// 2. Point the log group at a new key, added after it so the log group keeps
|
|
142
|
+
// the comment above it.
|
|
143
|
+
await insertViaGritQL(tree, file, hcl(`\`name = local.${group.nameLocal}\` as $line where {\n` + ` $line <: within \`resource "aws_cloudwatch_log_group" "${group.logGroupLabel}" { $_ }\`\n` + '}') + ` => \`$line\n ${GRIT_INSERT_PLACEHOLDER}\``, `kms_key_id = aws_kms_key.${group.keyLabel}[0].arn`);
|
|
144
|
+
await insertViaGritQL(tree, file, LOG_GROUP_BLOCK + ` as $lg => \`$lg\n\n${GRIT_INSERT_PLACEHOLDER}\``, keyResourcesText(group));
|
|
145
|
+
// 3. Drop the suppression the encryption makes redundant.
|
|
146
|
+
const updated = (tree.read(file, 'utf-8') ?? '').split('\n').filter((line)=>line.trim() !== SUPPRESSION_LINE).join('\n');
|
|
147
|
+
tree.write(file, updated);
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* The vended CDK user-pool WAF log group gains an `encryptionKey`, matching the
|
|
151
|
+
* eight sibling CDK WAF log groups which already pass one.
|
|
152
|
+
*/ const migrateCdkUserIdentity = async (tree, nextSteps)=>{
|
|
153
|
+
if (!tree.exists(CDK_USER_IDENTITY_FILE)) {
|
|
154
|
+
return; // This workspace has no CDK user identity construct.
|
|
155
|
+
}
|
|
156
|
+
const contents = tree.read(CDK_USER_IDENTITY_FILE, 'utf-8') ?? '';
|
|
157
|
+
// The WAF web ACL (and its log group) is optional, so it may be absent.
|
|
158
|
+
if (!contents.includes(`new LogGroup(this, 'WebAclLogs'`)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (contents.includes('WebAclLogsKey')) {
|
|
162
|
+
return; // Already migrated.
|
|
163
|
+
}
|
|
164
|
+
// Already encrypted, whether by this migration or by the user's own key.
|
|
165
|
+
if (await matchGritQL(tree, CDK_USER_IDENTITY_FILE, "`new LogGroup(this, 'WebAclLogs', { $props })` where { $props <: contains `encryptionKey` }")) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const LOG_GROUP = "`new LogGroup(this, 'WebAclLogs', { $props })` as $lg where { $props <: contains `logGroupName: $name` }";
|
|
169
|
+
const SUPPRESS_CALL = "`suppressRules(wafLogGroup, ['CKV_AWS_158'], $reason)` as $call";
|
|
170
|
+
const ready = await matchGritQL(tree, CDK_USER_IDENTITY_FILE, LOG_GROUP) && await matchGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL);
|
|
171
|
+
if (!ready) {
|
|
172
|
+
nextSteps.push(divergedMessage(CDK_USER_IDENTITY_FILE));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
await addDestructuredImport(tree, CDK_USER_IDENTITY_FILE, [
|
|
176
|
+
'Key'
|
|
177
|
+
], 'aws-cdk-lib/aws-kms');
|
|
178
|
+
await addDestructuredImport(tree, CDK_USER_IDENTITY_FILE, [
|
|
179
|
+
'Effect',
|
|
180
|
+
'PolicyStatement',
|
|
181
|
+
'ServicePrincipal'
|
|
182
|
+
], 'aws-cdk-lib/aws-iam');
|
|
183
|
+
// Hoist the log group name into a const so the key policy's encryption context
|
|
184
|
+
// condition can name the same log group, then create the key above the group.
|
|
185
|
+
await insertViaGritQL(tree, CDK_USER_IDENTITY_FILE, "`const wafLogGroup = new LogGroup(this, 'WebAclLogs', { $props })` as $decl => " + `\`${GRIT_INSERT_PLACEHOLDER}\n\n$decl\``, `const stack = Stack.of(this);
|
|
186
|
+
const wafLogGroupName = \`aws-waf-logs-\${id}-\${this.node.addr.slice(-8)}\`;
|
|
187
|
+
|
|
188
|
+
// KMS key for encrypting WAF logs at rest, usable by CloudWatch Logs
|
|
189
|
+
const logsKey = new Key(this, 'WebAclLogsKey', {
|
|
190
|
+
enableKeyRotation: true,
|
|
191
|
+
});
|
|
192
|
+
logsKey.addToResourcePolicy(
|
|
193
|
+
new PolicyStatement({
|
|
194
|
+
effect: Effect.ALLOW,
|
|
195
|
+
principals: [new ServicePrincipal(\`logs.\${stack.region}.amazonaws.com\`)],
|
|
196
|
+
actions: [
|
|
197
|
+
'kms:Encrypt',
|
|
198
|
+
'kms:Decrypt',
|
|
199
|
+
'kms:ReEncrypt*',
|
|
200
|
+
'kms:GenerateDataKey*',
|
|
201
|
+
'kms:DescribeKey',
|
|
202
|
+
],
|
|
203
|
+
resources: ['*'],
|
|
204
|
+
conditions: {
|
|
205
|
+
ArnEquals: {
|
|
206
|
+
'kms:EncryptionContext:aws:logs:arn': \`arn:aws:logs:\${stack.region}:\${stack.account}:log-group:\${wafLogGroupName}\`,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}),
|
|
210
|
+
);`);
|
|
211
|
+
await applyGritQL(tree, CDK_USER_IDENTITY_FILE, "`logGroupName: $name` as $prop where { $prop <: within `new LogGroup(this, 'WebAclLogs', $_)` } => `logGroupName: wafLogGroupName,\n encryptionKey: logsKey`");
|
|
212
|
+
// Drop the suppression the encryption makes redundant.
|
|
213
|
+
await applyGritQL(tree, CDK_USER_IDENTITY_FILE, SUPPRESS_CALL + ' => ``');
|
|
214
|
+
};
|
|
215
|
+
export default async function migration(tree) {
|
|
216
|
+
const nextSteps = [];
|
|
217
|
+
if (tree.exists(TERRAFORM_REST_API_FILE)) {
|
|
218
|
+
await migrateTerraformLogGroup(tree, TERRAFORM_REST_API_FILE, REST_API_LOG_GROUP, nextSteps);
|
|
219
|
+
}
|
|
220
|
+
if (tree.exists(TERRAFORM_USER_IDENTITY_FILE)) {
|
|
221
|
+
await migrateTerraformLogGroup(tree, TERRAFORM_USER_IDENTITY_FILE, USER_IDENTITY_LOG_GROUP, nextSteps);
|
|
222
|
+
}
|
|
223
|
+
if (tree.exists(TERRAFORM_GATEWAYS_DIR)) {
|
|
224
|
+
for (const dirName of tree.children(TERRAFORM_GATEWAYS_DIR)){
|
|
225
|
+
const file = joinPathFragments(TERRAFORM_GATEWAYS_DIR, dirName, `${dirName}.tf`);
|
|
226
|
+
if (tree.exists(file)) {
|
|
227
|
+
await migrateTerraformLogGroup(tree, file, gatewayLogGroup(dirName), nextSteps);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
await migrateCdkUserIdentity(tree, nextSteps);
|
|
232
|
+
await formatFilesInSubtree(tree);
|
|
233
|
+
return {
|
|
234
|
+
nextSteps
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../packages/nx-plugin/src/migrations/latest/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,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { type Tree } from '@nx/devkit';
|
|
6
|
+
import type { CodeGenData } from '../utils/codegen-data/types.js';
|
|
7
|
+
import type { OpenApiJsonMetadataGeneratorSchema } from './schema';
|
|
8
|
+
/**
|
|
9
|
+
* File name the operations metadata is written to. The Terraform modules read
|
|
10
|
+
* this by name, so both the producing targets and the modules refer to it here.
|
|
11
|
+
*/
|
|
12
|
+
export declare const OPERATIONS_METADATA_FILE_NAME = "operations.json";
|
|
13
|
+
/**
|
|
14
|
+
* Generates an operations metadata JSON file from an OpenAPI specification,
|
|
15
|
+
* read by the vended Terraform modules to define one integration per operation.
|
|
16
|
+
*/
|
|
17
|
+
export declare const openApiJsonMetadataGenerator: (tree: Tree, options: OpenApiJsonMetadataGeneratorSchema) => Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Write the operations metadata for the given spec to the target directory,
|
|
20
|
+
* sorted by operation name so the file is stable across runs and does not churn
|
|
21
|
+
* the Terraform plan.
|
|
22
|
+
*/
|
|
23
|
+
export declare const generateOpenApiJsonMetadata: (tree: Tree, data: CodeGenData, outputPath: string) => void;
|
|
24
|
+
export default openApiJsonMetadataGenerator;
|
|
@@ -0,0 +1,37 @@
|
|
|
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 { buildOpenApiCodeGenerationData } from "../ts-client/generator.js";
|
|
6
|
+
/**
|
|
7
|
+
* File name the operations metadata is written to. The Terraform modules read
|
|
8
|
+
* this by name, so both the producing targets and the modules refer to it here.
|
|
9
|
+
*/ export const OPERATIONS_METADATA_FILE_NAME = 'operations.json';
|
|
10
|
+
/**
|
|
11
|
+
* Generates an operations metadata JSON file from an OpenAPI specification,
|
|
12
|
+
* read by the vended Terraform modules to define one integration per operation.
|
|
13
|
+
*/ export const openApiJsonMetadataGenerator = async (tree, options)=>{
|
|
14
|
+
const data = await buildOpenApiCodeGenerationData(tree, options.openApiSpecPath);
|
|
15
|
+
generateOpenApiJsonMetadata(tree, data, options.outputPath);
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Write the operations metadata for the given spec to the target directory,
|
|
19
|
+
* sorted by operation name so the file is stable across runs and does not churn
|
|
20
|
+
* the Terraform plan.
|
|
21
|
+
*/ export const generateOpenApiJsonMetadata = (tree, data, outputPath)=>{
|
|
22
|
+
const operations = Object.fromEntries(data.allOperations.map((op)=>({
|
|
23
|
+
name: op.dotNotationName ?? op.name,
|
|
24
|
+
path: op.path,
|
|
25
|
+
method: op.method.toUpperCase()
|
|
26
|
+
})).sort((a, b)=>a.name.localeCompare(b.name)).map(({ name, path, method })=>[
|
|
27
|
+
name,
|
|
28
|
+
{
|
|
29
|
+
path,
|
|
30
|
+
method
|
|
31
|
+
}
|
|
32
|
+
]));
|
|
33
|
+
tree.write(joinPathFragments(outputPath, OPERATIONS_METADATA_FILE_NAME), `${JSON.stringify(operations, null, 2)}\n`);
|
|
34
|
+
};
|
|
35
|
+
export default openApiJsonMetadataGenerator;
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../packages/nx-plugin/src/open-api/json-metadata/generator.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\nimport { joinPathFragments, type Tree } from '@nx/devkit';\nimport { buildOpenApiCodeGenerationData } from '../ts-client/generator.js';\nimport type { CodeGenData } from '../utils/codegen-data/types.js';\nimport type { OpenApiJsonMetadataGeneratorSchema } from './schema';\n\n/**\n * File name the operations metadata is written to. The Terraform modules read\n * this by name, so both the producing targets and the modules refer to it here.\n */\nexport const OPERATIONS_METADATA_FILE_NAME = 'operations.json';\n\n/**\n * Generates an operations metadata JSON file from an OpenAPI specification,\n * read by the vended Terraform modules to define one integration per operation.\n */\nexport const openApiJsonMetadataGenerator = async (\n tree: Tree,\n options: OpenApiJsonMetadataGeneratorSchema,\n) => {\n const data = await buildOpenApiCodeGenerationData(\n tree,\n options.openApiSpecPath,\n );\n\n generateOpenApiJsonMetadata(tree, data, options.outputPath);\n};\n\n/**\n * Write the operations metadata for the given spec to the target directory,\n * sorted by operation name so the file is stable across runs and does not churn\n * the Terraform plan.\n */\nexport const generateOpenApiJsonMetadata = (\n tree: Tree,\n data: CodeGenData,\n outputPath: string,\n) => {\n const operations = Object.fromEntries(\n data.allOperations\n .map((op) => ({\n name: op.dotNotationName ?? op.name,\n path: op.path,\n method: op.method.toUpperCase(),\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n .map(({ name, path, method }) => [name, { path, method }]),\n );\n\n tree.write(\n joinPathFragments(outputPath, OPERATIONS_METADATA_FILE_NAME),\n `${JSON.stringify(operations, null, 2)}\\n`,\n );\n};\n\nexport default openApiJsonMetadataGenerator;\n"],"names":["joinPathFragments","buildOpenApiCodeGenerationData","OPERATIONS_METADATA_FILE_NAME","openApiJsonMetadataGenerator","tree","options","data","openApiSpecPath","generateOpenApiJsonMetadata","outputPath","operations","Object","fromEntries","allOperations","map","op","name","dotNotationName","path","method","toUpperCase","sort","a","b","localeCompare","write","JSON","stringify"],"mappings":"AAAA;;;CAGC,GACD,SAASA,iBAAiB,QAAmB,aAAa;AAC1D,SAASC,8BAA8B,QAAQ,4BAA4B;AAI3E;;;CAGC,GACD,OAAO,MAAMC,gCAAgC,kBAAkB;AAE/D;;;CAGC,GACD,OAAO,MAAMC,+BAA+B,OAC1CC,MACAC;IAEA,MAAMC,OAAO,MAAML,+BACjBG,MACAC,QAAQE,eAAe;IAGzBC,4BAA4BJ,MAAME,MAAMD,QAAQI,UAAU;AAC5D,EAAE;AAEF;;;;CAIC,GACD,OAAO,MAAMD,8BAA8B,CACzCJ,MACAE,MACAG;IAEA,MAAMC,aAAaC,OAAOC,WAAW,CACnCN,KAAKO,aAAa,CACfC,GAAG,CAAC,CAACC,KAAQ,CAAA;YACZC,MAAMD,GAAGE,eAAe,IAAIF,GAAGC,IAAI;YACnCE,MAAMH,GAAGG,IAAI;YACbC,QAAQJ,GAAGI,MAAM,CAACC,WAAW;QAC/B,CAAA,GACCC,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEN,IAAI,CAACQ,aAAa,CAACD,EAAEP,IAAI,GAC1CF,GAAG,CAAC,CAAC,EAAEE,IAAI,EAAEE,IAAI,EAAEC,MAAM,EAAE,GAAK;YAACH;YAAM;gBAAEE;gBAAMC;YAAO;SAAE;IAG7Df,KAAKqB,KAAK,CACRzB,kBAAkBS,YAAYP,gCAC9B,GAAGwB,KAAKC,SAAS,CAACjB,YAAY,MAAM,GAAG,EAAE,CAAC;AAE9C,EAAE;AAEF,eAAeP,6BAA6B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../packages/nx-plugin/src/open-api/json-metadata/schema.d.ts"],"sourcesContent":["/**\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nexport interface OpenApiJsonMetadataGeneratorSchema {\n openApiSpecPath: string;\n outputPath: string;\n}\n"],"names":[],"mappings":"AAAA;;;CAGC,GAED,WAGC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/schema",
|
|
3
|
+
"$id": "OpenApiJsonMetadata",
|
|
4
|
+
"title": "OpenAPI JSON Metadata",
|
|
5
|
+
"description": "Generate a JSON metadata file describing an OpenAPI specification's operations",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"openApiSpecPath": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Path to the OpenAPI specification relative to the monorepo root",
|
|
11
|
+
"x-priority": "important"
|
|
12
|
+
},
|
|
13
|
+
"outputPath": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Path to the directory in which to generate the metadata relative to the monorepo root",
|
|
16
|
+
"x-priority": "important"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"required": ["openApiSpecPath", "outputPath"]
|
|
20
|
+
}
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
exports[`py#agent generator > should create DynamoDB checkpoint table and S3 offload bucket infrastructure when session is dynamodb-s3 (Terraform) > terraform-agent.tf (dynamodb-s3) 1`] = `
|
|
4
4
|
"terraform {
|
|
5
5
|
required_providers {
|
|
6
|
+
aws = {
|
|
7
|
+
source = "hashicorp/aws"
|
|
8
|
+
version = "6.61.0"
|
|
9
|
+
}
|
|
6
10
|
random = {
|
|
7
11
|
source = "hashicorp/random"
|
|
8
12
|
version = "3.9.0"
|
|
@@ -363,6 +367,10 @@ output "security_group_id" {
|
|
|
363
367
|
exports[`py#agent generator > should match snapshot for Terraform generated files > terraform-agent.tf 1`] = `
|
|
364
368
|
"terraform {
|
|
365
369
|
required_providers {
|
|
370
|
+
aws = {
|
|
371
|
+
source = "hashicorp/aws"
|
|
372
|
+
version = "6.61.0"
|
|
373
|
+
}
|
|
366
374
|
random = {
|
|
367
375
|
source = "hashicorp/random"
|
|
368
376
|
version = "3.9.0"
|
|
@@ -674,6 +682,10 @@ exports[`py#agent generator > should match snapshot for Terraform generated file
|
|
|
674
682
|
source = "hashicorp/aws"
|
|
675
683
|
version = "6.61.0"
|
|
676
684
|
}
|
|
685
|
+
external = {
|
|
686
|
+
source = "hashicorp/external"
|
|
687
|
+
version = "2.4.1"
|
|
688
|
+
}
|
|
677
689
|
null = {
|
|
678
690
|
source = "hashicorp/null"
|
|
679
691
|
version = "3.3.1"
|