@go-to-k/cdkd 0.158.0 → 0.159.0
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/README.md +159 -400
- package/dist/cli.js +190 -60
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-UmoqjtWH.js → deploy-engine-BzrECC3i.js} +44 -40
- package/dist/deploy-engine-BzrECC3i.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-UmoqjtWH.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { _ as withSkipPrefix, a as runDockerStreaming, c as getLogger, d as getLiveRenderer, f as PATTERN_B_NAME_PROPERTIES, g as generateResourceNameWithFallback, h as generateResourceName, i as runDockerForeground, n as formatDockerLoginError, p as PATTERN_B_RESOURCE_TYPES, r as getDockerCmd, u as runStackBuffered, v as withStackName } from "./docker-cmd-iDMcWcre.js";
|
|
3
|
-
import { $ as CdkdError, A as shouldRetainResource, B as resolveSkipPrefix, C as IntrinsicFunctionResolver, D as TemplateParser, E as DagBuilder, F as Synthesizer, G as CFN_TEMPLATE_URL_LIMIT, H as resolveStateBucketWithDefaultAndSource, I as getDefaultStateBucketName, J as uploadCfnTemplate, K as MIGRATE_TMP_PREFIX, L as getLegacyStateBucketName, M as stringifyValue, N as WorkGraph, O as LockManager, P as buildDockerImage, R as resolveApp, S as assertRegionMatch, T as DiffCalculator, U as warnDeprecatedNoPrefixCliFlag, V as resolveStateBucketWithDefault, W as CFN_TEMPLATE_BODY_LIMIT, Y as AssemblyReader, Z as resolveBucketRegion, _ as matchesCdkPath, a as withRetry, b as ProviderRegistry, bt as withErrorHandling, c as bold, ct as PartialFailureError, d as green, dt as ResourceUpdateNotSupportedError, f as red, ft as RouteDiscoveryError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, i as withResourceDeadline, it as LocalStartServiceError, j as AssetPublisher, k as S3StateBackend, l as cyan, lt as ProvisioningError, m as IAMRoleProvider, mt as StackTerminationProtectionError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as LocalInvokeBuildError, o as IMPLICIT_DELETE_DEPENDENCIES, ot as MissingCdkCliError, p as yellow, pt as StackHasActiveImportsError, q as findLargeInlineResources, r as DeployEngine, rt as LocalMigrateError, s as formatResourceLine, st as NestedStackChildDirectDestroyError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ResourceTimeoutError, v as normalizeAwsTagsToCfn, w as applyRoleArnIfSet, x as CloudControlProvider, y as resolveExplicitPhysicalId, yt as normalizeAwsError, z as resolveCaptureObservedState } from "./deploy-engine-
|
|
3
|
+
import { $ as CdkdError, A as shouldRetainResource, B as resolveSkipPrefix, C as IntrinsicFunctionResolver, D as TemplateParser, E as DagBuilder, F as Synthesizer, G as CFN_TEMPLATE_URL_LIMIT, H as resolveStateBucketWithDefaultAndSource, I as getDefaultStateBucketName, J as uploadCfnTemplate, K as MIGRATE_TMP_PREFIX, L as getLegacyStateBucketName, M as stringifyValue, N as WorkGraph, O as LockManager, P as buildDockerImage, R as resolveApp, S as assertRegionMatch, T as DiffCalculator, U as warnDeprecatedNoPrefixCliFlag, V as resolveStateBucketWithDefault, W as CFN_TEMPLATE_BODY_LIMIT, Y as AssemblyReader, Z as resolveBucketRegion, _ as matchesCdkPath, a as withRetry, b as ProviderRegistry, bt as withErrorHandling, c as bold, ct as PartialFailureError, d as green, dt as ResourceUpdateNotSupportedError, f as red, ft as RouteDiscoveryError, g as CDK_PATH_TAG, h as collectInlinePolicyNamesManagedBySiblings, i as withResourceDeadline, it as LocalStartServiceError, j as AssetPublisher, k as S3StateBackend, l as cyan, lt as ProvisioningError, m as IAMRoleProvider, mt as StackTerminationProtectionError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as LocalInvokeBuildError, o as IMPLICIT_DELETE_DEPENDENCIES, ot as MissingCdkCliError, p as yellow, pt as StackHasActiveImportsError, q as findLargeInlineResources, r as DeployEngine, rt as LocalMigrateError, s as formatResourceLine, st as NestedStackChildDirectDestroyError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as gray, ut as ResourceTimeoutError, v as normalizeAwsTagsToCfn, w as applyRoleArnIfSet, x as CloudControlProvider, y as resolveExplicitPhysicalId, yt as normalizeAwsError, z as resolveCaptureObservedState } from "./deploy-engine-BzrECC3i.js";
|
|
4
4
|
import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-B15NAPbL.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, createHmac, createPublicKey, createVerify, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -3225,6 +3225,7 @@ var S3BucketProvider = class {
|
|
|
3225
3225
|
"ObjectLockConfiguration",
|
|
3226
3226
|
"ObjectLockEnabled"
|
|
3227
3227
|
])]]);
|
|
3228
|
+
unhandledByDesign = new Map([["AWS::S3::Bucket", new Map([["AccessControl", "Legacy canned ACL; AWS disables ACLs by default since 2023-04 — use BucketOwnershipControls + BucketPolicy / PublicAccessBlockConfiguration instead"]])]]);
|
|
3228
3229
|
constructor() {
|
|
3229
3230
|
const awsClients = getAwsClients();
|
|
3230
3231
|
this.s3Client = awsClients.s3;
|
|
@@ -5997,6 +5998,7 @@ var SNSSubscriptionProvider = class {
|
|
|
5997
5998
|
"Endpoint",
|
|
5998
5999
|
"FilterPolicy"
|
|
5999
6000
|
])]]);
|
|
6001
|
+
unhandledByDesign = new Map([["AWS::SNS::Subscription", new Map([["Region", "CFn-only cross-region subscription hint; cdkd uses the SDK client region directly and has no per-resource region override"]])]]);
|
|
6000
6002
|
constructor() {
|
|
6001
6003
|
const awsClients = getAwsClients();
|
|
6002
6004
|
this.snsClient = awsClients.sns;
|
|
@@ -11700,9 +11702,11 @@ var EC2Provider = class {
|
|
|
11700
11702
|
"FromPort",
|
|
11701
11703
|
"ToPort",
|
|
11702
11704
|
"CidrIp",
|
|
11705
|
+
"CidrIpv6",
|
|
11703
11706
|
"Description",
|
|
11704
11707
|
"SourceSecurityGroupId",
|
|
11705
|
-
"SourceSecurityGroupOwnerId"
|
|
11708
|
+
"SourceSecurityGroupOwnerId",
|
|
11709
|
+
"SourcePrefixListId"
|
|
11706
11710
|
])],
|
|
11707
11711
|
["AWS::EC2::Instance", new Set([
|
|
11708
11712
|
"ImageId",
|
|
@@ -11726,10 +11730,15 @@ var EC2Provider = class {
|
|
|
11726
11730
|
"CidrBlock",
|
|
11727
11731
|
"Ipv6CidrBlock",
|
|
11728
11732
|
"PortRange",
|
|
11729
|
-
"
|
|
11733
|
+
"Icmp"
|
|
11730
11734
|
])],
|
|
11731
11735
|
["AWS::EC2::SubnetNetworkAclAssociation", new Set(["SubnetId", "NetworkAclId"])]
|
|
11732
11736
|
]);
|
|
11737
|
+
unhandledByDesign = new Map([
|
|
11738
|
+
["AWS::EC2::Instance", new Map([["ElasticGpuSpecifications", "AWS Elastic GPU end-of-life (announced 2023-11); no replacement API"], ["ElasticInferenceAccelerators", "AWS Elastic Inference end-of-life 2024-04; use AWS Inferentia / Trainium accelerator instance families instead"]])],
|
|
11739
|
+
["AWS::EC2::SecurityGroupIngress", new Map([["GroupName", "EC2-Classic-only — use GroupId for VPC security groups (EC2-Classic retired 2022-08-15)"], ["SourceSecurityGroupName", "EC2-Classic-only — use SourceSecurityGroupId for VPC peer security groups (EC2-Classic retired 2022-08-15)"]])],
|
|
11740
|
+
["AWS::EC2::NatGateway", new Map([["VpcId", "AWS derives the VPC from SubnetId; the ec2:CreateNatGateway API has no VpcId parameter"]])]
|
|
11741
|
+
]);
|
|
11733
11742
|
constructor() {
|
|
11734
11743
|
const awsClients = getAwsClients();
|
|
11735
11744
|
this.ec2Client = awsClients.ec2;
|
|
@@ -12864,7 +12873,7 @@ var EC2Provider = class {
|
|
|
12864
12873
|
const cidrBlock = properties["CidrBlock"];
|
|
12865
12874
|
const ipv6CidrBlock = properties["Ipv6CidrBlock"];
|
|
12866
12875
|
const portRange = properties["PortRange"];
|
|
12867
|
-
const icmpTypeCode = properties["IcmpTypeCode"];
|
|
12876
|
+
const icmpTypeCode = properties["Icmp"] ?? properties["IcmpTypeCode"];
|
|
12868
12877
|
await this.ec2Client.send(new CreateNetworkAclEntryCommand({
|
|
12869
12878
|
NetworkAclId: networkAclId,
|
|
12870
12879
|
RuleNumber: ruleNumber,
|
|
@@ -13614,7 +13623,10 @@ var EC2Provider = class {
|
|
|
13614
13623
|
const icmp = {};
|
|
13615
13624
|
if (entry.IcmpTypeCode.Type !== void 0) icmp["Type"] = entry.IcmpTypeCode.Type;
|
|
13616
13625
|
if (entry.IcmpTypeCode.Code !== void 0) icmp["Code"] = entry.IcmpTypeCode.Code;
|
|
13617
|
-
if (Object.keys(icmp).length > 0)
|
|
13626
|
+
if (Object.keys(icmp).length > 0) {
|
|
13627
|
+
result["Icmp"] = icmp;
|
|
13628
|
+
result["IcmpTypeCode"] = icmp;
|
|
13629
|
+
}
|
|
13618
13630
|
}
|
|
13619
13631
|
return result;
|
|
13620
13632
|
}
|
|
@@ -13861,6 +13873,7 @@ var ApiGatewayProvider = class ApiGatewayProvider {
|
|
|
13861
13873
|
"MethodResponses"
|
|
13862
13874
|
])]
|
|
13863
13875
|
]);
|
|
13876
|
+
unhandledByDesign = new Map([["AWS::ApiGateway::Deployment", new Map([["StageName", "CFn-only convenience for inline-creating a Stage from a Deployment; declare AWS::ApiGateway::Stage explicitly to attach to this Deployment"], ["StageDescription", "CFn-only convenience for inline-creating a Stage; declare AWS::ApiGateway::Stage with the Description property instead"]])]]);
|
|
13864
13877
|
/** Maximum number of retries for IAM propagation delays */
|
|
13865
13878
|
static MAX_IAM_RETRIES = 3;
|
|
13866
13879
|
/** Delay between IAM propagation retries (ms) - exponential backoff */
|
|
@@ -17003,7 +17016,7 @@ var ECSProvider = class {
|
|
|
17003
17016
|
"CapacityProviderStrategy",
|
|
17004
17017
|
"DeploymentConfiguration",
|
|
17005
17018
|
"PlacementConstraints",
|
|
17006
|
-
"
|
|
17019
|
+
"PlacementStrategies",
|
|
17007
17020
|
"PlatformVersion",
|
|
17008
17021
|
"HealthCheckGracePeriodSeconds",
|
|
17009
17022
|
"SchedulingStrategy",
|
|
@@ -17014,6 +17027,7 @@ var ECSProvider = class {
|
|
|
17014
17027
|
"Tags"
|
|
17015
17028
|
])]
|
|
17016
17029
|
]);
|
|
17030
|
+
unhandledByDesign = new Map([["AWS::ECS::Service", new Map([["Role", "Legacy classic-ELB service-linked-role override; AWS uses the AWSServiceRoleForECS service-linked role automatically since 2017"]])], ["AWS::ECS::TaskDefinition", new Map([["InferenceAccelerators", "AWS Elastic Inference end-of-life 2024-04; use AWS Inferentia / Trainium accelerator instance families instead"]])]]);
|
|
17017
17031
|
getClient() {
|
|
17018
17032
|
if (!this.ecsClient) this.ecsClient = new ECSClient(this.providerRegion ? { region: this.providerRegion } : {});
|
|
17019
17033
|
return this.ecsClient;
|
|
@@ -17215,7 +17229,7 @@ var ECSProvider = class {
|
|
|
17215
17229
|
capacityProviderStrategy: properties["CapacityProviderStrategy"],
|
|
17216
17230
|
deploymentConfiguration: properties["DeploymentConfiguration"],
|
|
17217
17231
|
placementConstraints: properties["PlacementConstraints"],
|
|
17218
|
-
placementStrategy: properties["PlacementStrategy"],
|
|
17232
|
+
placementStrategy: properties["PlacementStrategies"] ?? properties["PlacementStrategy"],
|
|
17219
17233
|
platformVersion: properties["PlatformVersion"],
|
|
17220
17234
|
healthCheckGracePeriodSeconds: properties["HealthCheckGracePeriodSeconds"],
|
|
17221
17235
|
schedulingStrategy: properties["SchedulingStrategy"],
|
|
@@ -17255,7 +17269,7 @@ var ECSProvider = class {
|
|
|
17255
17269
|
capacityProviderStrategy: properties["CapacityProviderStrategy"],
|
|
17256
17270
|
deploymentConfiguration: properties["DeploymentConfiguration"],
|
|
17257
17271
|
placementConstraints: properties["PlacementConstraints"],
|
|
17258
|
-
placementStrategy: properties["PlacementStrategy"],
|
|
17272
|
+
placementStrategy: properties["PlacementStrategies"] ?? properties["PlacementStrategy"],
|
|
17259
17273
|
platformVersion: properties["PlatformVersion"],
|
|
17260
17274
|
healthCheckGracePeriodSeconds: properties["HealthCheckGracePeriodSeconds"],
|
|
17261
17275
|
enableExecuteCommand: properties["EnableExecuteCommand"]
|
|
@@ -17664,8 +17678,14 @@ var ECSProvider = class {
|
|
|
17664
17678
|
else if (!s.launchType) result["CapacityProviderStrategy"] = [];
|
|
17665
17679
|
if (s.deploymentConfiguration) result["DeploymentConfiguration"] = s.deploymentConfiguration;
|
|
17666
17680
|
result["PlacementConstraints"] = s.placementConstraints ?? [];
|
|
17667
|
-
if (s.launchType === "EC2" || s.launchType === "EXTERNAL")
|
|
17668
|
-
|
|
17681
|
+
if (s.launchType === "EC2" || s.launchType === "EXTERNAL") {
|
|
17682
|
+
const strategy = s.placementStrategy ?? [];
|
|
17683
|
+
result["PlacementStrategy"] = strategy;
|
|
17684
|
+
result["PlacementStrategies"] = strategy;
|
|
17685
|
+
} else if (s.placementStrategy && s.placementStrategy.length > 0) {
|
|
17686
|
+
result["PlacementStrategy"] = s.placementStrategy;
|
|
17687
|
+
result["PlacementStrategies"] = s.placementStrategy;
|
|
17688
|
+
}
|
|
17669
17689
|
result["ServiceRegistries"] = s.serviceRegistries ?? [];
|
|
17670
17690
|
result["Tags"] = normalizeAwsTagsToCfn(s.tags);
|
|
17671
17691
|
return result;
|
|
@@ -18530,6 +18550,11 @@ var RDSProvider = class {
|
|
|
18530
18550
|
"Tags"
|
|
18531
18551
|
])]
|
|
18532
18552
|
]);
|
|
18553
|
+
unhandledByDesign = new Map([["AWS::RDS::DBCluster", new Map([["DeleteAutomatedBackups", "cdkd hardcodes SkipFinalSnapshot=true on destroy; this CFn lifecycle flag has no equivalent on the runtime path"]])], ["AWS::RDS::DBInstance", new Map([
|
|
18554
|
+
["DBSecurityGroups", "EC2-Classic-only feature retired by AWS (2022-08-15); new accounts cannot use this — use VPCSecurityGroups instead"],
|
|
18555
|
+
["ApplyImmediately", "cdkd always applies modifications immediately (rds:ModifyDBInstance.ApplyImmediately=true is hardcoded); users wanting maintenance-window deferral should run aws rds modify-db-instance directly"],
|
|
18556
|
+
["DeleteAutomatedBackups", "cdkd hardcodes SkipFinalSnapshot=true on destroy; this CFn lifecycle flag has no equivalent on the runtime path"]
|
|
18557
|
+
])]]);
|
|
18533
18558
|
getClient() {
|
|
18534
18559
|
if (!this.rdsClient) this.rdsClient = new RDSClient(this.providerRegion ? { region: this.providerRegion } : {});
|
|
18535
18560
|
return this.rdsClient;
|
|
@@ -20868,6 +20893,7 @@ var NeptuneProvider = class {
|
|
|
20868
20893
|
["AWS::Neptune::DBCluster", new Set([
|
|
20869
20894
|
"DBClusterIdentifier",
|
|
20870
20895
|
"EngineVersion",
|
|
20896
|
+
"DBPort",
|
|
20871
20897
|
"Port",
|
|
20872
20898
|
"VpcSecurityGroupIds",
|
|
20873
20899
|
"DBSubnetGroupName",
|
|
@@ -20997,7 +21023,7 @@ var NeptuneProvider = class {
|
|
|
20997
21023
|
DBClusterIdentifier: dbClusterIdentifier,
|
|
20998
21024
|
Engine: "neptune",
|
|
20999
21025
|
EngineVersion: properties["EngineVersion"],
|
|
21000
|
-
Port: properties["Port"] != null ? Number(properties["Port"]) : void 0,
|
|
21026
|
+
Port: properties["DBPort"] != null ? Number(properties["DBPort"]) : properties["Port"] != null ? Number(properties["Port"]) : void 0,
|
|
21001
21027
|
VpcSecurityGroupIds: properties["VpcSecurityGroupIds"],
|
|
21002
21028
|
DBSubnetGroupName: properties["DBSubnetGroupName"],
|
|
21003
21029
|
StorageEncrypted: properties["StorageEncrypted"],
|
|
@@ -21043,7 +21069,7 @@ var NeptuneProvider = class {
|
|
|
21043
21069
|
DBClusterParameterGroupName: properties["DBClusterParameterGroupName"],
|
|
21044
21070
|
EnableIAMDatabaseAuthentication: properties["IamAuthEnabled"],
|
|
21045
21071
|
...sendVpcSgIds && { VpcSecurityGroupIds: vpcSgIds },
|
|
21046
|
-
Port: properties["Port"] != null ? Number(properties["Port"]) : void 0,
|
|
21072
|
+
Port: properties["DBPort"] != null ? Number(properties["DBPort"]) : properties["Port"] != null ? Number(properties["Port"]) : void 0,
|
|
21047
21073
|
ApplyImmediately: true
|
|
21048
21074
|
}));
|
|
21049
21075
|
this.logger.debug(`Successfully updated Neptune DBCluster ${logicalId}`);
|
|
@@ -21370,7 +21396,10 @@ var NeptuneProvider = class {
|
|
|
21370
21396
|
const result = {};
|
|
21371
21397
|
if (cluster.DBClusterIdentifier !== void 0) result["DBClusterIdentifier"] = cluster.DBClusterIdentifier;
|
|
21372
21398
|
if (cluster.EngineVersion !== void 0) result["EngineVersion"] = cluster.EngineVersion;
|
|
21373
|
-
if (cluster.Port !== void 0)
|
|
21399
|
+
if (cluster.Port !== void 0) {
|
|
21400
|
+
result["Port"] = cluster.Port;
|
|
21401
|
+
result["DBPort"] = cluster.Port;
|
|
21402
|
+
}
|
|
21374
21403
|
result["VpcSecurityGroupIds"] = (cluster.VpcSecurityGroups ?? []).map((sg) => sg.VpcSecurityGroupId).filter((id) => !!id);
|
|
21375
21404
|
if (cluster.DBSubnetGroup !== void 0) result["DBSubnetGroupName"] = cluster.DBSubnetGroup;
|
|
21376
21405
|
if (cluster.StorageEncrypted !== void 0) result["StorageEncrypted"] = cluster.StorageEncrypted;
|
|
@@ -22810,6 +22839,7 @@ var ElastiCacheProvider = class {
|
|
|
22810
22839
|
handledProperties = new Map([["AWS::ElastiCache::SubnetGroup", new Set([
|
|
22811
22840
|
"CacheSubnetGroupName",
|
|
22812
22841
|
"CacheSubnetGroupDescription",
|
|
22842
|
+
"Description",
|
|
22813
22843
|
"SubnetIds",
|
|
22814
22844
|
"Tags"
|
|
22815
22845
|
])], ["AWS::ElastiCache::CacheCluster", new Set([
|
|
@@ -22837,6 +22867,7 @@ var ElastiCacheProvider = class {
|
|
|
22837
22867
|
"IpDiscovery",
|
|
22838
22868
|
"TransitEncryptionEnabled"
|
|
22839
22869
|
])]]);
|
|
22870
|
+
unhandledByDesign = new Map([["AWS::ElastiCache::CacheCluster", new Map([["CacheSecurityGroupNames", "EC2-Classic-only — use VpcSecurityGroupIds for VPC-deployed clusters (EC2-Classic retired 2022-08-15)"]])]]);
|
|
22840
22871
|
getClient() {
|
|
22841
22872
|
if (!this.client) this.client = new ElastiCacheClient(this.providerRegion ? { region: this.providerRegion } : {});
|
|
22842
22873
|
return this.client;
|
|
@@ -22871,7 +22902,7 @@ var ElastiCacheProvider = class {
|
|
|
22871
22902
|
try {
|
|
22872
22903
|
await this.getClient().send(new CreateCacheSubnetGroupCommand({
|
|
22873
22904
|
CacheSubnetGroupName: cacheSubnetGroupName,
|
|
22874
|
-
CacheSubnetGroupDescription: properties["CacheSubnetGroupDescription"]
|
|
22905
|
+
CacheSubnetGroupDescription: properties["Description"] ?? properties["CacheSubnetGroupDescription"] ?? `Subnet group for ${logicalId}`,
|
|
22875
22906
|
SubnetIds: properties["SubnetIds"]
|
|
22876
22907
|
}));
|
|
22877
22908
|
this.logger.debug(`Successfully created CacheSubnetGroup ${logicalId}: ${cacheSubnetGroupName}`);
|
|
@@ -22889,7 +22920,7 @@ var ElastiCacheProvider = class {
|
|
|
22889
22920
|
try {
|
|
22890
22921
|
await this.getClient().send(new ModifyCacheSubnetGroupCommand({
|
|
22891
22922
|
CacheSubnetGroupName: physicalId,
|
|
22892
|
-
CacheSubnetGroupDescription: properties["CacheSubnetGroupDescription"],
|
|
22923
|
+
CacheSubnetGroupDescription: properties["Description"] ?? properties["CacheSubnetGroupDescription"],
|
|
22893
22924
|
SubnetIds: properties["SubnetIds"]
|
|
22894
22925
|
}));
|
|
22895
22926
|
this.logger.debug(`Successfully updated CacheSubnetGroup ${logicalId}`);
|
|
@@ -23199,7 +23230,10 @@ var ElastiCacheProvider = class {
|
|
|
23199
23230
|
if (!group) return void 0;
|
|
23200
23231
|
const result = {};
|
|
23201
23232
|
if (group.CacheSubnetGroupName !== void 0) result["CacheSubnetGroupName"] = group.CacheSubnetGroupName;
|
|
23202
|
-
if (group.CacheSubnetGroupDescription !== void 0)
|
|
23233
|
+
if (group.CacheSubnetGroupDescription !== void 0) {
|
|
23234
|
+
result["CacheSubnetGroupDescription"] = group.CacheSubnetGroupDescription;
|
|
23235
|
+
result["Description"] = group.CacheSubnetGroupDescription;
|
|
23236
|
+
}
|
|
23203
23237
|
result["SubnetIds"] = (group.Subnets ?? []).map((s) => s.SubnetIdentifier).filter((id) => !!id);
|
|
23204
23238
|
if (group.ARN) await this.attachTags(result, group.ARN);
|
|
23205
23239
|
return result;
|
|
@@ -23853,6 +23887,7 @@ var AppSyncProvider = class {
|
|
|
23853
23887
|
"Expires"
|
|
23854
23888
|
])]
|
|
23855
23889
|
]);
|
|
23890
|
+
unhandledByDesign = new Map([["AWS::AppSync::DataSource", new Map([["ElasticsearchConfig", "Legacy Elasticsearch alias; use OpenSearchServiceConfig (AppSync deprecated the Elasticsearch DataSource type in favor of OpenSearch)"]])]]);
|
|
23856
23891
|
getClient() {
|
|
23857
23892
|
if (!this.client) this.client = new AppSyncClient(this.providerRegion ? { region: this.providerRegion } : {});
|
|
23858
23893
|
return this.client;
|
|
@@ -24718,9 +24753,14 @@ var GlueProvider = class {
|
|
|
24718
24753
|
cachedAccountId;
|
|
24719
24754
|
providerRegion = process.env["AWS_REGION"];
|
|
24720
24755
|
logger = getLogger().child("GlueProvider");
|
|
24721
|
-
handledProperties = new Map([["AWS::Glue::Database", new Set([
|
|
24756
|
+
handledProperties = new Map([["AWS::Glue::Database", new Set([
|
|
24757
|
+
"DatabaseInput",
|
|
24758
|
+
"DatabaseName",
|
|
24759
|
+
"CatalogId"
|
|
24760
|
+
])], ["AWS::Glue::Table", new Set([
|
|
24722
24761
|
"DatabaseName",
|
|
24723
24762
|
"TableInput",
|
|
24763
|
+
"Name",
|
|
24724
24764
|
"CatalogId"
|
|
24725
24765
|
])]]);
|
|
24726
24766
|
getClient() {
|
|
@@ -24752,8 +24792,8 @@ var GlueProvider = class {
|
|
|
24752
24792
|
this.logger.debug(`Creating Glue Database ${logicalId}`);
|
|
24753
24793
|
const databaseInput = properties["DatabaseInput"];
|
|
24754
24794
|
if (!databaseInput) throw new ProvisioningError(`DatabaseInput is required for Glue Database ${logicalId}`, resourceType, logicalId);
|
|
24755
|
-
const databaseName = databaseInput["Name"];
|
|
24756
|
-
if (!databaseName) throw new ProvisioningError(`DatabaseInput.Name is required for Glue Database ${logicalId}`, resourceType, logicalId);
|
|
24795
|
+
const databaseName = databaseInput["Name"] ?? properties["DatabaseName"];
|
|
24796
|
+
if (!databaseName) throw new ProvisioningError(`DatabaseInput.Name or top-level DatabaseName is required for Glue Database ${logicalId}`, resourceType, logicalId);
|
|
24757
24797
|
const catalogId = properties["CatalogId"];
|
|
24758
24798
|
try {
|
|
24759
24799
|
await this.getClient().send(new CreateDatabaseCommand({
|
|
@@ -24816,14 +24856,14 @@ var GlueProvider = class {
|
|
|
24816
24856
|
if (!databaseName) throw new ProvisioningError(`DatabaseName is required for Glue Table ${logicalId}`, resourceType, logicalId);
|
|
24817
24857
|
const tableInput = properties["TableInput"];
|
|
24818
24858
|
if (!tableInput) throw new ProvisioningError(`TableInput is required for Glue Table ${logicalId}`, resourceType, logicalId);
|
|
24819
|
-
const tableName = tableInput["Name"];
|
|
24820
|
-
if (!tableName) throw new ProvisioningError(`TableInput.Name is required for Glue Table ${logicalId}`, resourceType, logicalId);
|
|
24859
|
+
const tableName = tableInput["Name"] ?? properties["Name"];
|
|
24860
|
+
if (!tableName) throw new ProvisioningError(`TableInput.Name or top-level Name is required for Glue Table ${logicalId}`, resourceType, logicalId);
|
|
24821
24861
|
const catalogId = properties["CatalogId"];
|
|
24822
24862
|
try {
|
|
24823
24863
|
await this.getClient().send(new CreateTableCommand$1({
|
|
24824
24864
|
CatalogId: catalogId,
|
|
24825
24865
|
DatabaseName: databaseName,
|
|
24826
|
-
TableInput: this.buildTableInput(tableInput)
|
|
24866
|
+
TableInput: this.buildTableInput(tableInput, tableName)
|
|
24827
24867
|
}));
|
|
24828
24868
|
const physicalId = `${databaseName}|${tableName}`;
|
|
24829
24869
|
this.logger.debug(`Successfully created Glue Table ${logicalId}: ${physicalId}`);
|
|
@@ -24838,8 +24878,8 @@ var GlueProvider = class {
|
|
|
24838
24878
|
}
|
|
24839
24879
|
async updateTable(logicalId, physicalId, resourceType, properties) {
|
|
24840
24880
|
this.logger.debug(`Updating Glue Table ${logicalId}: ${physicalId}`);
|
|
24841
|
-
const [databaseName] = physicalId.split("|");
|
|
24842
|
-
if (!databaseName) throw new ProvisioningError(`Invalid Glue Table physical ID format: ${physicalId}`, resourceType, logicalId, physicalId);
|
|
24881
|
+
const [databaseName, tableName] = physicalId.split("|");
|
|
24882
|
+
if (!databaseName || !tableName) throw new ProvisioningError(`Invalid Glue Table physical ID format: ${physicalId}`, resourceType, logicalId, physicalId);
|
|
24843
24883
|
const tableInput = properties["TableInput"];
|
|
24844
24884
|
if (!tableInput) throw new ProvisioningError(`TableInput is required for Glue Table update ${logicalId}`, resourceType, logicalId, physicalId);
|
|
24845
24885
|
const catalogId = properties["CatalogId"];
|
|
@@ -24847,7 +24887,7 @@ var GlueProvider = class {
|
|
|
24847
24887
|
await this.getClient().send(new UpdateTableCommand$1({
|
|
24848
24888
|
CatalogId: catalogId,
|
|
24849
24889
|
DatabaseName: databaseName,
|
|
24850
|
-
TableInput: this.buildTableInput(tableInput)
|
|
24890
|
+
TableInput: this.buildTableInput(tableInput, tableName)
|
|
24851
24891
|
}));
|
|
24852
24892
|
this.logger.debug(`Successfully updated Glue Table ${logicalId}`);
|
|
24853
24893
|
return {
|
|
@@ -24902,8 +24942,8 @@ var GlueProvider = class {
|
|
|
24902
24942
|
/**
|
|
24903
24943
|
* Build TableInput for Glue API from CFn template properties
|
|
24904
24944
|
*/
|
|
24905
|
-
buildTableInput(tableInput) {
|
|
24906
|
-
const result = { Name: tableInput["Name"] };
|
|
24945
|
+
buildTableInput(tableInput, fallbackName) {
|
|
24946
|
+
const result = { Name: tableInput["Name"] ?? fallbackName };
|
|
24907
24947
|
if (tableInput["Description"] !== void 0) result.Description = tableInput["Description"];
|
|
24908
24948
|
if (tableInput["TableType"] !== void 0) result.TableType = tableInput["TableType"];
|
|
24909
24949
|
if (tableInput["Parameters"] !== void 0) {
|
|
@@ -25009,7 +25049,9 @@ var GlueProvider = class {
|
|
|
25009
25049
|
dbInput["Description"] = db.Description ?? "";
|
|
25010
25050
|
if (db.LocationUri !== void 0) dbInput["LocationUri"] = db.LocationUri;
|
|
25011
25051
|
dbInput["Parameters"] = db.Parameters ?? {};
|
|
25012
|
-
|
|
25052
|
+
const result = { DatabaseInput: dbInput };
|
|
25053
|
+
if (db.Name !== void 0) result["DatabaseName"] = db.Name;
|
|
25054
|
+
return result;
|
|
25013
25055
|
}
|
|
25014
25056
|
async readTable(physicalId) {
|
|
25015
25057
|
const [databaseName, tableName] = physicalId.split("|");
|
|
@@ -25037,10 +25079,12 @@ var GlueProvider = class {
|
|
|
25037
25079
|
if (table.ViewOriginalText !== void 0) tableInput["ViewOriginalText"] = table.ViewOriginalText;
|
|
25038
25080
|
if (table.ViewExpandedText !== void 0) tableInput["ViewExpandedText"] = table.ViewExpandedText;
|
|
25039
25081
|
if (table.TargetTable) tableInput["TargetTable"] = table.TargetTable;
|
|
25040
|
-
|
|
25082
|
+
const result = {
|
|
25041
25083
|
DatabaseName: databaseName,
|
|
25042
25084
|
TableInput: tableInput
|
|
25043
25085
|
};
|
|
25086
|
+
if (table.Name !== void 0) result["Name"] = table.Name;
|
|
25087
|
+
return result;
|
|
25044
25088
|
}
|
|
25045
25089
|
async import(input) {
|
|
25046
25090
|
switch (input.resourceType) {
|
|
@@ -27455,6 +27499,7 @@ var EFSProvider = class {
|
|
|
27455
27499
|
"AccessPointTags"
|
|
27456
27500
|
])]
|
|
27457
27501
|
]);
|
|
27502
|
+
unhandledByDesign = new Map([["AWS::EFS::AccessPoint", new Map([["ClientToken", "AWS SDK manages this idempotency token internally on CreateAccessPoint; no user-supplied value is honored"]])]]);
|
|
27458
27503
|
getClient() {
|
|
27459
27504
|
if (!this.client) this.client = new EFSClient(this.providerRegion ? { region: this.providerRegion } : {});
|
|
27460
27505
|
return this.client;
|
|
@@ -30466,6 +30511,7 @@ var S3TablesProvider = class {
|
|
|
30466
30511
|
["AWS::S3Tables::Table", new Set([
|
|
30467
30512
|
"TableBucketARN",
|
|
30468
30513
|
"Namespace",
|
|
30514
|
+
"TableName",
|
|
30469
30515
|
"Name",
|
|
30470
30516
|
"Format"
|
|
30471
30517
|
])]
|
|
@@ -30628,8 +30674,8 @@ var S3TablesProvider = class {
|
|
|
30628
30674
|
if (!tableBucketARN) throw new ProvisioningError(`TableBucketARN is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
|
|
30629
30675
|
const namespace = properties["Namespace"];
|
|
30630
30676
|
if (!namespace) throw new ProvisioningError(`Namespace is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
|
|
30631
|
-
const name = properties["Name"];
|
|
30632
|
-
if (!name) throw new ProvisioningError(`
|
|
30677
|
+
const name = properties["TableName"] ?? properties["Name"];
|
|
30678
|
+
if (!name) throw new ProvisioningError(`TableName is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
|
|
30633
30679
|
const format = properties["Format"];
|
|
30634
30680
|
if (!format) throw new ProvisioningError(`Format is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
|
|
30635
30681
|
try {
|
|
@@ -30714,10 +30760,12 @@ var S3TablesProvider = class {
|
|
|
30714
30760
|
if (err instanceof NotFoundException$5) return void 0;
|
|
30715
30761
|
throw err;
|
|
30716
30762
|
}
|
|
30763
|
+
const tableNameValue = resp.name ?? name;
|
|
30717
30764
|
const result = {
|
|
30718
30765
|
TableBucketARN: tableBucketARN,
|
|
30719
30766
|
Namespace: namespace,
|
|
30720
|
-
Name:
|
|
30767
|
+
Name: tableNameValue,
|
|
30768
|
+
TableName: tableNameValue
|
|
30721
30769
|
};
|
|
30722
30770
|
if (resp.format !== void 0) result["Format"] = resp.format;
|
|
30723
30771
|
return result;
|
|
@@ -31258,6 +31306,7 @@ var ASGProvider = class ASGProvider {
|
|
|
31258
31306
|
"InstanceMaintenancePolicy",
|
|
31259
31307
|
"DeletionProtection"
|
|
31260
31308
|
])]]);
|
|
31309
|
+
unhandledByDesign = new Map([["AWS::AutoScaling::AutoScalingGroup", new Map([["LaunchConfigurationName", "AWS Launch Configurations end-of-life 2024-10; use LaunchTemplate instead"], ["NotificationConfiguration", "Legacy singular form; use NotificationConfigurations (plural) which cdkd already wires"]])]]);
|
|
31261
31310
|
getClient() {
|
|
31262
31311
|
if (!this.asgClient) this.asgClient = new AutoScalingClient(this.providerRegion ? { region: this.providerRegion } : {});
|
|
31263
31312
|
return this.asgClient;
|
|
@@ -41608,6 +41657,7 @@ var CfnLocalStateProvider = class {
|
|
|
41608
41657
|
region;
|
|
41609
41658
|
client;
|
|
41610
41659
|
clientOptions;
|
|
41660
|
+
disposed = false;
|
|
41611
41661
|
constructor(opts) {
|
|
41612
41662
|
this.cfnStackName = opts.cfnStackName;
|
|
41613
41663
|
this.region = opts.region;
|
|
@@ -41615,6 +41665,7 @@ var CfnLocalStateProvider = class {
|
|
|
41615
41665
|
if (opts.profile !== void 0) this.clientOptions.profile = opts.profile;
|
|
41616
41666
|
}
|
|
41617
41667
|
getClient() {
|
|
41668
|
+
if (this.disposed) throw new Error("CfnLocalStateProvider used after dispose()");
|
|
41618
41669
|
if (!this.client) this.client = new CloudFormationClient({ region: this.region });
|
|
41619
41670
|
return this.client;
|
|
41620
41671
|
}
|
|
@@ -41631,13 +41682,14 @@ var CfnLocalStateProvider = class {
|
|
|
41631
41682
|
* behavior on every intrinsic-valued env var.
|
|
41632
41683
|
*/
|
|
41633
41684
|
async load(_stackName, _synthRegion) {
|
|
41685
|
+
if (this.disposed) throw new Error("CfnLocalStateProvider used after dispose()");
|
|
41634
41686
|
const logger = getLogger();
|
|
41635
41687
|
const client = this.getClient();
|
|
41636
41688
|
let resourceMap;
|
|
41637
41689
|
try {
|
|
41638
41690
|
resourceMap = buildResourceStateMap((await client.send(new DescribeStackResourcesCommand({ StackName: this.cfnStackName }))).StackResources ?? []);
|
|
41639
41691
|
} catch (err) {
|
|
41640
|
-
logger.warn(`${this.label}: DescribeStackResources(${this.cfnStackName}) failed: ${
|
|
41692
|
+
logger.warn(`${this.label}: DescribeStackResources(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err)}. Was the stack deployed in region '${this.region}'? Falling back.`);
|
|
41641
41693
|
return;
|
|
41642
41694
|
}
|
|
41643
41695
|
let outputs;
|
|
@@ -41648,7 +41700,7 @@ var CfnLocalStateProvider = class {
|
|
|
41648
41700
|
outputs = {};
|
|
41649
41701
|
} else outputs = buildOutputsMap(stack.Outputs ?? []);
|
|
41650
41702
|
} catch (err) {
|
|
41651
|
-
logger.warn(`${this.label}: DescribeStacks(${this.cfnStackName}) failed: ${
|
|
41703
|
+
logger.warn(`${this.label}: DescribeStacks(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err)}. Outputs will be empty (Fn::GetStackOutput cannot resolve).`);
|
|
41652
41704
|
outputs = {};
|
|
41653
41705
|
}
|
|
41654
41706
|
return {
|
|
@@ -41672,18 +41724,18 @@ var CfnLocalStateProvider = class {
|
|
|
41672
41724
|
* partition-aware region list.
|
|
41673
41725
|
*/
|
|
41674
41726
|
async buildCrossStackResolver(_consumerRegion) {
|
|
41727
|
+
if (this.disposed) throw new Error("CfnLocalStateProvider used after dispose()");
|
|
41675
41728
|
const logger = getLogger();
|
|
41676
41729
|
const client = this.getClient();
|
|
41677
41730
|
const label = this.label;
|
|
41678
41731
|
const region = this.region;
|
|
41679
|
-
let
|
|
41680
|
-
const ensureExports =
|
|
41681
|
-
if (
|
|
41682
|
-
|
|
41683
|
-
logger.warn(`${label}: ListExports (${region}) failed: ${
|
|
41732
|
+
let exportsPromise;
|
|
41733
|
+
const ensureExports = () => {
|
|
41734
|
+
if (exportsPromise) return exportsPromise;
|
|
41735
|
+
exportsPromise = fetchAllExports(client).catch((err) => {
|
|
41736
|
+
logger.warn(`${label}: ListExports (${region}) failed: ${formatAwsErrorForWarn(err)}. Fn::ImportValue intrinsics will warn-and-drop.`);
|
|
41684
41737
|
});
|
|
41685
|
-
|
|
41686
|
-
return result;
|
|
41738
|
+
return exportsPromise;
|
|
41687
41739
|
};
|
|
41688
41740
|
return {
|
|
41689
41741
|
async resolveImport(exportName) {
|
|
@@ -41697,6 +41749,7 @@ var CfnLocalStateProvider = class {
|
|
|
41697
41749
|
};
|
|
41698
41750
|
}
|
|
41699
41751
|
dispose() {
|
|
41752
|
+
this.disposed = true;
|
|
41700
41753
|
if (this.client) {
|
|
41701
41754
|
this.client.destroy();
|
|
41702
41755
|
this.client = void 0;
|
|
@@ -41764,9 +41817,32 @@ async function fetchAllExports(client) {
|
|
|
41764
41817
|
nextToken = resp.NextToken;
|
|
41765
41818
|
pages += 1;
|
|
41766
41819
|
if (pages > 50) throw new Error("ListExports pagination exceeded 50 pages — likely a malformed NextToken loop.");
|
|
41767
|
-
} while (nextToken !== void 0);
|
|
41820
|
+
} while (nextToken !== void 0 && nextToken !== "");
|
|
41768
41821
|
return out;
|
|
41769
41822
|
}
|
|
41823
|
+
/**
|
|
41824
|
+
* Format an AWS SDK error as `<name> (HTTP <status>): <message>` so the
|
|
41825
|
+
* surfaced warn name the error class (e.g. `ThrottlingException`,
|
|
41826
|
+
* `AccessDeniedException`, `ValidationError`) and HTTP status alongside
|
|
41827
|
+
* the human-readable message. Falls back to the bare message for
|
|
41828
|
+
* non-SDK errors (the existing pre-issue-#611 behavior) so non-AWS
|
|
41829
|
+
* thrown values still surface meaningfully. Exported for unit testing.
|
|
41830
|
+
*
|
|
41831
|
+
* Issue #611 NIT 4 — `normalizeAwsError` in `utils/error-handler.ts` is
|
|
41832
|
+
* S3-bucket-specific (it rewrites the synthetic `Unknown`/`UnknownError`
|
|
41833
|
+
* with bucket / region context), so the CFn provider extracts the
|
|
41834
|
+
* pieces directly here.
|
|
41835
|
+
*/
|
|
41836
|
+
function formatAwsErrorForWarn(err) {
|
|
41837
|
+
if (!(err instanceof Error)) return String(err);
|
|
41838
|
+
const name = err.name && err.name !== "Error" ? err.name : void 0;
|
|
41839
|
+
const status = err.$metadata?.httpStatusCode;
|
|
41840
|
+
const prefixParts = [];
|
|
41841
|
+
if (name !== void 0) prefixParts.push(name);
|
|
41842
|
+
if (status !== void 0) prefixParts.push(`HTTP ${status}`);
|
|
41843
|
+
if (prefixParts.length === 0) return err.message;
|
|
41844
|
+
return `${prefixParts.join(" ")}: ${err.message}`;
|
|
41845
|
+
}
|
|
41770
41846
|
|
|
41771
41847
|
//#endregion
|
|
41772
41848
|
//#region src/cli/commands/local-state-source.ts
|
|
@@ -41811,6 +41887,22 @@ function resolveCfnStackName(fromCfnStack, cdkdStackName) {
|
|
|
41811
41887
|
return cdkdStackName;
|
|
41812
41888
|
}
|
|
41813
41889
|
/**
|
|
41890
|
+
* Single source of truth for "is the user asking for `--from-cfn-stack`?".
|
|
41891
|
+
* Commander maps the flag to one of `undefined` (absent) / `true` (bare) /
|
|
41892
|
+
* `'<name>'` (explicit). Everything except `undefined` / `false` means the
|
|
41893
|
+
* flag is present. Extracted so the `local-start-api` "state source
|
|
41894
|
+
* active" check (the parent call site that decides whether to load any
|
|
41895
|
+
* state at all) and `createLocalStateProvider`'s own branch logic stay
|
|
41896
|
+
* in lock-step — a previous duplication of this predicate motivated the
|
|
41897
|
+
* extraction (issue #611 NIT 5).
|
|
41898
|
+
*
|
|
41899
|
+
* Exported for use by `local-start-api` and unit testing.
|
|
41900
|
+
*/
|
|
41901
|
+
function isCfnFlagPresent(opts) {
|
|
41902
|
+
const v = opts.fromCfnStack;
|
|
41903
|
+
return v !== void 0 && v !== false;
|
|
41904
|
+
}
|
|
41905
|
+
/**
|
|
41814
41906
|
* Resolve the region used for the CFn client. The CFn provider is
|
|
41815
41907
|
* region-bound at construction time; we apply the precedence chain
|
|
41816
41908
|
* `--stack-region` > `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION`
|
|
@@ -41881,8 +41973,9 @@ function rejectExplicitCfnStackWithMultipleStacks(options, routedStackCount) {
|
|
|
41881
41973
|
*/
|
|
41882
41974
|
function createLocalStateProvider(options, cdkdStackName, synthRegion) {
|
|
41883
41975
|
const cfnStackOpt = options.fromCfnStack;
|
|
41884
|
-
const cfnFlagPresent =
|
|
41976
|
+
const cfnFlagPresent = isCfnFlagPresent(options);
|
|
41885
41977
|
if (options.fromState && cfnFlagPresent) throw new LocalStateSourceError("--from-state and --from-cfn-stack are mutually exclusive. Use --from-state for stacks deployed via `cdkd deploy`; use --from-cfn-stack for stacks deployed via `cdk deploy` (CloudFormation).");
|
|
41978
|
+
if (cfnStackOpt === "") throw new LocalStateSourceError("--from-cfn-stack requires a non-empty stack name when an explicit value is provided. Drop the value to use the cdkd stack name, or pass --from-cfn-stack <name>.");
|
|
41886
41979
|
if (options.fromState) return new S3LocalStateProvider({
|
|
41887
41980
|
statePrefix: options.statePrefix,
|
|
41888
41981
|
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
@@ -46661,9 +46754,14 @@ function classifyServiceIntegrationRoute(baseRoute, integrationProps, stackName,
|
|
|
46661
46754
|
* JSON prelude — `{statusCode, headers, cookies?}` — followed by 8
|
|
46662
46755
|
* NULL bytes and then the raw body chunks). The HTTP server pipes
|
|
46663
46756
|
* the chunks to the client with `Transfer-Encoding: chunked` (#467).
|
|
46664
|
-
* - `AuthType
|
|
46665
|
-
*
|
|
46666
|
-
*
|
|
46757
|
+
* - `AuthType === 'AWS_IAM'` → normal route. The `IamAuthorizer` is
|
|
46758
|
+
* attached at the authorizer-resolver pass (`detectAuthorizer` in
|
|
46759
|
+
* `authorizer-resolver.ts`) so the HTTP server runs the same SigV4
|
|
46760
|
+
* verification it ships for REST v1 `AuthorizationType: 'AWS_IAM'`
|
|
46761
|
+
* (PR #447). Signature verification only — no IAM policy emulation.
|
|
46762
|
+
* - `AuthType` other than `'NONE'` / `'AWS_IAM'` (defensive — AWS docs
|
|
46763
|
+
* only define those two values) → deferred-error unsupported. Boot
|
|
46764
|
+
* proceeds; HTTP 501 + `reason` at request time.
|
|
46667
46765
|
*
|
|
46668
46766
|
* The Lambda Arn intrinsic resolution still **hard-errors** when it
|
|
46669
46767
|
* cannot pin down a same-template Lambda — Function URLs have no other
|
|
@@ -46688,10 +46786,10 @@ function discoverFunctionUrl(logicalId, resource, template, stackName) {
|
|
|
46688
46786
|
declaredAt: `${stackName}/${logicalId}`
|
|
46689
46787
|
};
|
|
46690
46788
|
const authType = props["AuthType"];
|
|
46691
|
-
if (authType !== "NONE") return [{
|
|
46789
|
+
if (authType !== "NONE" && authType !== "AWS_IAM") return [{
|
|
46692
46790
|
...baseRoute,
|
|
46693
46791
|
lambdaLogicalId,
|
|
46694
|
-
unsupported: { reason: `${stackName}/${logicalId}: AuthType
|
|
46792
|
+
unsupported: { reason: `${stackName}/${logicalId}: AuthType ${shortJson$1(authType)} is not a recognized Function URL auth type (expected 'NONE' or 'AWS_IAM').` }
|
|
46695
46793
|
}];
|
|
46696
46794
|
const invokeModeRaw = props["InvokeMode"];
|
|
46697
46795
|
let invokeMode = "BUFFERED";
|
|
@@ -50994,6 +51092,28 @@ function detectAuthorizer(route, stack) {
|
|
|
50994
51092
|
if (!resource) return void 0;
|
|
50995
51093
|
if (resource.Type === "AWS::ApiGateway::Method") return detectRestV1Authorizer(resource, logicalId, stack);
|
|
50996
51094
|
if (resource.Type === "AWS::ApiGatewayV2::Route") return detectHttpApiAuthorizer(resource, logicalId, stack);
|
|
51095
|
+
if (resource.Type === "AWS::Lambda::Url") return detectFunctionUrlAuthorizer(resource, logicalId, stack);
|
|
51096
|
+
}
|
|
51097
|
+
/**
|
|
51098
|
+
* Function URL (`AWS::Lambda::Url`) authorizer detection (issue #621).
|
|
51099
|
+
*
|
|
51100
|
+
* `AuthType: 'AWS_IAM'` uses the same SigV4 mechanism REST v1 ships
|
|
51101
|
+
* (PR #447), so we route through the same `IamAuthorizer` descriptor and
|
|
51102
|
+
* let the HTTP server's existing `if (authorizer.kind === 'iam')`
|
|
51103
|
+
* request-time branch run `verifySigV4`. Like REST v1 AWS_IAM, no IAM
|
|
51104
|
+
* policy emulation — signature verification only.
|
|
51105
|
+
*
|
|
51106
|
+
* `AuthType: 'NONE'` (and any non-AWS_IAM AuthType that
|
|
51107
|
+
* `route-discovery.ts` already flipped to `unsupported`) returns
|
|
51108
|
+
* `undefined` so the route runs without an authorizer pass.
|
|
51109
|
+
*/
|
|
51110
|
+
function detectFunctionUrlAuthorizer(urlResource, urlLogicalId, stack) {
|
|
51111
|
+
if ((urlResource.Properties ?? {})["AuthType"] !== "AWS_IAM") return void 0;
|
|
51112
|
+
return {
|
|
51113
|
+
kind: "iam",
|
|
51114
|
+
logicalId: "AWS_IAM",
|
|
51115
|
+
declaredAt: `${stack.stackName}/${urlLogicalId}`
|
|
51116
|
+
};
|
|
50997
51117
|
}
|
|
50998
51118
|
function detectRestV1Authorizer(methodResource, methodLogicalId, stack) {
|
|
50999
51119
|
const props = methodResource.Properties ?? {};
|
|
@@ -52455,15 +52575,15 @@ async function handleRequest(req, res, state, opts) {
|
|
|
52455
52575
|
outcome = await runAuthorizerPass(authorizer, snapshot, matchCtx, state, opts, baseEvent["requestContext"]);
|
|
52456
52576
|
} catch (err) {
|
|
52457
52577
|
logger.error(`Authorizer ${authorizer.logicalId} threw for ${match.route.declaredAt}: ${err instanceof Error ? err.message : String(err)}`);
|
|
52458
|
-
writeAuthRejection(res, match.route.apiVersion, "policy-deny");
|
|
52578
|
+
writeAuthRejection(res, match.route.apiVersion, "policy-deny", authorizer.kind);
|
|
52459
52579
|
return;
|
|
52460
52580
|
}
|
|
52461
52581
|
if (!outcome.result.allow) {
|
|
52462
|
-
writeAuthRejection(res, match.route.apiVersion, outcome.denyKind ?? "policy-deny");
|
|
52582
|
+
writeAuthRejection(res, match.route.apiVersion, outcome.denyKind ?? "policy-deny", authorizer.kind);
|
|
52463
52583
|
return;
|
|
52464
52584
|
}
|
|
52465
52585
|
authResult = outcome.result;
|
|
52466
|
-
const overlay = buildOverlay(authorizer, authResult);
|
|
52586
|
+
const overlay = buildOverlay(authorizer, authResult, match.route.apiVersion);
|
|
52467
52587
|
if (overlay) baseEvent = applyAuthorizerOverlay(baseEvent, overlay);
|
|
52468
52588
|
}
|
|
52469
52589
|
if (match.route.serviceIntegration) {
|
|
@@ -52874,7 +52994,7 @@ function pickSourceIp(apiVersion, requestContext, snapshot) {
|
|
|
52874
52994
|
}
|
|
52875
52995
|
return snapshot.sourceIp ?? "127.0.0.1";
|
|
52876
52996
|
}
|
|
52877
|
-
function buildOverlay(authorizer, result) {
|
|
52997
|
+
function buildOverlay(authorizer, result, routeApiVersion) {
|
|
52878
52998
|
if (authorizer.kind === "lambda-token" || authorizer.kind === "lambda-request") return authorizer.kind === "lambda-request" && authorizer.apiVersion === "v2" ? {
|
|
52879
52999
|
kind: "lambda-http-v2",
|
|
52880
53000
|
...result.principalId !== void 0 && { principalId: result.principalId },
|
|
@@ -52888,10 +53008,13 @@ function buildOverlay(authorizer, result) {
|
|
|
52888
53008
|
kind: "cognito-rest-v1",
|
|
52889
53009
|
claims: result.context ?? {}
|
|
52890
53010
|
};
|
|
52891
|
-
if (authorizer.kind === "iam")
|
|
52892
|
-
|
|
52893
|
-
|
|
52894
|
-
|
|
53011
|
+
if (authorizer.kind === "iam") {
|
|
53012
|
+
if (routeApiVersion === "v2") return void 0;
|
|
53013
|
+
return {
|
|
53014
|
+
kind: "lambda-rest-v1",
|
|
53015
|
+
...result.principalId !== void 0 && { principalId: result.principalId }
|
|
53016
|
+
};
|
|
53017
|
+
}
|
|
52895
53018
|
return {
|
|
52896
53019
|
kind: "jwt-http-v2",
|
|
52897
53020
|
claims: result.context ?? {}
|
|
@@ -52906,8 +53029,15 @@ function buildOverlay(authorizer, result) {
|
|
|
52906
53029
|
* authorizer ran and denied; status mirrors AWS API Gateway).
|
|
52907
53030
|
* - HTTP v2, both kinds → 401 `{"message":"Unauthorized"}` (HTTP API
|
|
52908
53031
|
* collapses both into the same response).
|
|
53032
|
+
* - Function URL with AWS_IAM (issue #621) → 403 `{"Message":"Forbidden"}`
|
|
53033
|
+
* for both deny kinds (matches Lambda's deployed Function URL IAM
|
|
53034
|
+
* behavior — the AWS SigV4 layer rejects with 403, not 401).
|
|
52909
53035
|
*/
|
|
52910
|
-
function writeAuthRejection(res, apiVersion, denyKind) {
|
|
53036
|
+
function writeAuthRejection(res, apiVersion, denyKind, authorizerKind) {
|
|
53037
|
+
if (apiVersion === "v2" && authorizerKind === "iam") {
|
|
53038
|
+
writeError(res, 403, "{\"Message\":\"Forbidden\"}");
|
|
53039
|
+
return;
|
|
53040
|
+
}
|
|
52911
53041
|
if (apiVersion === "v2") {
|
|
52912
53042
|
writeError(res, 401, "{\"message\":\"Unauthorized\"}");
|
|
52913
53043
|
return;
|
|
@@ -53827,7 +53957,7 @@ async function localStartApiCommand(target, options) {
|
|
|
53827
53957
|
const m = buildCorsConfigByApiId(stack.template);
|
|
53828
53958
|
for (const [k, v] of m) corsConfigByApiId.set(k, v);
|
|
53829
53959
|
}
|
|
53830
|
-
const stateByStack = options.fromState || options
|
|
53960
|
+
const stateByStack = options.fromState || isCfnFlagPresent(options) ? await loadStateForRoutedStacks(targetStacks, routes, routesWithAuth, options) : /* @__PURE__ */ new Map();
|
|
53831
53961
|
const lambdaIds = uniqueLambdaIds(routes, routesWithAuth, webSocketApis);
|
|
53832
53962
|
const specs = /* @__PURE__ */ new Map();
|
|
53833
53963
|
for (let i = 0; i < lambdaIds.length; i++) {
|
|
@@ -55004,7 +55134,7 @@ function resolveMtlsConfig(options) {
|
|
|
55004
55134
|
* Builder for the `start-api` subcommand. Wired up by `local.ts`.
|
|
55005
55135
|
*/
|
|
55006
55136
|
function createLocalStartApiCommand() {
|
|
55007
|
-
const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and REST v1 AWS_IAM
|
|
55137
|
+
const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and AWS_IAM auth (REST v1 `AuthorizationType: AWS_IAM` and Function URL `AuthType: AWS_IAM` — SigV4 signature verification only; IAM policy evaluation is NOT emulated; see docs/local-emulation.md). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", "Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors `cdkd local invoke` / `cdkd local run-task` target syntax.").addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when cdk.out/ or asset directories change. Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <target> argument instead. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-state", "Read cdkd S3 state for every routed stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::Join (and AWS pseudo parameters) in Lambda env vars with the deployed physical IDs / attributes. Off by default — pre-PR warn-and-drop semantics are preserved. Turn on for stacks already deployed via cdkd deploy. Mirrors `cdkd local invoke --from-state` / `cdkd local run-task --from-state`. Re-runs against fresh state on every hot-reload firing (--watch).").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in Lambda env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the cdkd stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. Mutually exclusive with --from-state. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-state when the same stack name has state in multiple regions, and with --from-cfn-stack as the CFn client region (cdkd does not have a separate --cfn-stack-region flag).")).addOption(new Option("--mtls-truststore <path>", "PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj \"/CN=cdkd-local-ca\" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj \"/CN=localhost\"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj \"/CN=client\"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...")).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(localStartApiCommand));
|
|
55008
55138
|
[
|
|
55009
55139
|
...commonOptions,
|
|
55010
55140
|
...appOptions,
|
|
@@ -59343,7 +59473,7 @@ function reorderArgs(argv) {
|
|
|
59343
59473
|
*/
|
|
59344
59474
|
async function main() {
|
|
59345
59475
|
const program = new Command();
|
|
59346
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
59476
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.159.0");
|
|
59347
59477
|
program.addCommand(createBootstrapCommand());
|
|
59348
59478
|
program.addCommand(createSynthCommand());
|
|
59349
59479
|
program.addCommand(createListCommand());
|