@go-to-k/cdkd 0.157.0 → 0.158.1
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 +63 -160
- package/dist/cli.js +640 -159
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-UmoqjtWH.js → deploy-engine-CGmdz5WP.js} +38 -36
- package/dist/{deploy-engine-UmoqjtWH.js.map → deploy-engine-CGmdz5WP.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -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-CGmdz5WP.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";
|
|
@@ -12,7 +12,7 @@ import { AddPermissionCommand, CreateEventSourceMappingCommand, CreateFunctionCo
|
|
|
12
12
|
import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
13
13
|
import { AssociateRouteTableCommand, AttachInternetGatewayCommand, AuthorizeSecurityGroupEgressCommand, AuthorizeSecurityGroupIngressCommand, CreateInternetGatewayCommand, CreateNatGatewayCommand, CreateNetworkAclCommand, CreateNetworkAclEntryCommand, CreateRouteCommand, CreateRouteTableCommand, CreateSecurityGroupCommand, CreateSubnetCommand, CreateTagsCommand, CreateVpcCommand, DeleteInternetGatewayCommand, DeleteNatGatewayCommand, DeleteNetworkAclCommand, DeleteNetworkAclEntryCommand, DeleteNetworkInterfaceCommand, DeleteRouteCommand, DeleteRouteTableCommand, DeleteSecurityGroupCommand, DeleteSubnetCommand, DeleteTagsCommand, DeleteVpcCommand, DescribeAvailabilityZonesCommand, DescribeInstanceAttributeCommand, DescribeInstancesCommand, DescribeInternetGatewaysCommand, DescribeNatGatewaysCommand, DescribeNetworkAclsCommand, DescribeNetworkInterfacesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVolumesCommand, DescribeVpcAttributeCommand, DescribeVpcsCommand, DetachInternetGatewayCommand, DisassociateRouteTableCommand, EC2Client, ModifyInstanceAttributeCommand, ModifySubnetAttributeCommand, ModifyVpcAttributeCommand, ReplaceNetworkAclAssociationCommand, RevokeSecurityGroupEgressCommand, RevokeSecurityGroupIngressCommand, RunInstancesCommand, TerminateInstancesCommand, waitUntilInstanceRunning, waitUntilInstanceTerminated, waitUntilNatGatewayAvailable, waitUntilNatGatewayDeleted } from "@aws-sdk/client-ec2";
|
|
14
14
|
import { CreateTableCommand, DeleteTableCommand, DescribeContinuousBackupsCommand, DescribeContributorInsightsCommand, DescribeKinesisStreamingDestinationCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DynamoDBClient, ListTablesCommand, ListTagsOfResourceCommand, ResourceNotFoundException as ResourceNotFoundException$1, TagResourceCommand as TagResourceCommand$2, UntagResourceCommand as UntagResourceCommand$2, UpdateTableCommand, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
|
|
15
|
-
import { CloudFormationClient, CreateChangeSetCommand, DeleteChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, DescribeStackEventsCommand, DescribeStackResourcesCommand, DescribeStacksCommand, DescribeTypeCommand, ExecuteChangeSetCommand, GetTemplateCommand, UpdateStackCommand, waitUntilChangeSetCreateComplete, waitUntilStackDeleteComplete, waitUntilStackImportComplete, waitUntilStackUpdateComplete } from "@aws-sdk/client-cloudformation";
|
|
15
|
+
import { CloudFormationClient, CreateChangeSetCommand, DeleteChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, DescribeStackEventsCommand, DescribeStackResourcesCommand, DescribeStacksCommand, DescribeTypeCommand, ExecuteChangeSetCommand, GetTemplateCommand, ListExportsCommand, UpdateStackCommand, waitUntilChangeSetCreateComplete, waitUntilStackDeleteComplete, waitUntilStackImportComplete, waitUntilStackUpdateComplete } from "@aws-sdk/client-cloudformation";
|
|
16
16
|
import { APIGatewayClient, CreateAuthorizerCommand, CreateDeploymentCommand, CreateResourceCommand, CreateStageCommand, DeleteAuthorizerCommand, DeleteDeploymentCommand, DeleteMethodCommand, DeleteResourceCommand, DeleteStageCommand, GetAccountCommand, GetAuthorizerCommand, GetDeploymentCommand, GetMethodCommand, GetResourceCommand, GetStageCommand, NotFoundException as NotFoundException$1, PutIntegrationCommand, PutIntegrationResponseCommand, PutMethodCommand, PutMethodResponseCommand, TagResourceCommand as TagResourceCommand$3, UntagResourceCommand as UntagResourceCommand$3, UpdateAccountCommand, UpdateAuthorizerCommand, UpdateMethodCommand, UpdateStageCommand } from "@aws-sdk/client-api-gateway";
|
|
17
17
|
import { CreateEventBusCommand, DeleteEventBusCommand, DeleteRuleCommand, DescribeEventBusCommand, DescribeRuleCommand, EventBridgeClient, ListEventBusesCommand, ListRulesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$1, ListTargetsByRuleCommand, PutRuleCommand, PutTargetsCommand, RemoveTargetsCommand, ResourceNotFoundException as ResourceNotFoundException$2, TagResourceCommand as TagResourceCommand$4, UntagResourceCommand as UntagResourceCommand$4, UpdateEventBusCommand } from "@aws-sdk/client-eventbridge";
|
|
18
18
|
import { CreateSecretCommand, DeleteSecretCommand, DescribeSecretCommand, GetSecretValueCommand, ListSecretsCommand, RemoveRegionsFromReplicationCommand, ReplicateSecretToRegionsCommand, ResourceNotFoundException as ResourceNotFoundException$3, SecretsManagerClient, TagResourceCommand as TagResourceCommand$5, UntagResourceCommand as UntagResourceCommand$5, UpdateSecretCommand } from "@aws-sdk/client-secrets-manager";
|
|
@@ -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;
|
|
@@ -41485,6 +41534,462 @@ async function buildCrossStackResolver(consumerRegion, opts) {
|
|
|
41485
41534
|
};
|
|
41486
41535
|
}
|
|
41487
41536
|
|
|
41537
|
+
//#endregion
|
|
41538
|
+
//#region src/local/s3-local-state-provider.ts
|
|
41539
|
+
/**
|
|
41540
|
+
* `S3LocalStateProvider` — implementation of {@link LocalStateProvider}
|
|
41541
|
+
* backed by cdkd's S3 state. Wraps the existing
|
|
41542
|
+
* `loadStateForStack` + `buildCrossStackResolver` helpers in
|
|
41543
|
+
* `src/cli/commands/local-state-loader.ts` so the four `cdkd local *`
|
|
41544
|
+
* commands can route both `--from-state` and `--from-cfn-stack`
|
|
41545
|
+
* through the same provider-shaped interface (issue #606).
|
|
41546
|
+
*
|
|
41547
|
+
* Behavior is identical to the pre-issue-#606 code path — this file
|
|
41548
|
+
* exists ONLY to give the CLI layer a single interface against which
|
|
41549
|
+
* to wire both flags, so adding a third state source (a future
|
|
41550
|
+
* `--from-tf-state`? out of scope for now) doesn't require touching
|
|
41551
|
+
* the four `local-*.ts` command files again.
|
|
41552
|
+
*/
|
|
41553
|
+
var S3LocalStateProvider = class {
|
|
41554
|
+
label = "--from-state";
|
|
41555
|
+
opts;
|
|
41556
|
+
disposers = [];
|
|
41557
|
+
constructor(opts) {
|
|
41558
|
+
this.opts = opts;
|
|
41559
|
+
}
|
|
41560
|
+
async load(stackName, synthRegion) {
|
|
41561
|
+
const loaded = await loadStateForStack(stackName, synthRegion, {
|
|
41562
|
+
statePrefix: this.opts.statePrefix,
|
|
41563
|
+
...this.opts.stackRegion !== void 0 && { stackRegion: this.opts.stackRegion },
|
|
41564
|
+
...this.opts.stateBucket !== void 0 && { stateBucket: this.opts.stateBucket },
|
|
41565
|
+
...this.opts.region !== void 0 && { region: this.opts.region },
|
|
41566
|
+
...this.opts.profile !== void 0 && { profile: this.opts.profile }
|
|
41567
|
+
});
|
|
41568
|
+
if (!loaded) return void 0;
|
|
41569
|
+
const outputs = {};
|
|
41570
|
+
for (const [k, v] of Object.entries(loaded.state.outputs ?? {})) if (typeof v === "string") outputs[k] = v;
|
|
41571
|
+
else if (typeof v === "number" || typeof v === "boolean") outputs[k] = String(v);
|
|
41572
|
+
else outputs[k] = JSON.stringify(v);
|
|
41573
|
+
return {
|
|
41574
|
+
resources: loaded.state.resources,
|
|
41575
|
+
outputs,
|
|
41576
|
+
region: loaded.region
|
|
41577
|
+
};
|
|
41578
|
+
}
|
|
41579
|
+
async buildCrossStackResolver(consumerRegion) {
|
|
41580
|
+
const built = await buildCrossStackResolver(consumerRegion, {
|
|
41581
|
+
statePrefix: this.opts.statePrefix,
|
|
41582
|
+
...this.opts.stateBucket !== void 0 && { stateBucket: this.opts.stateBucket },
|
|
41583
|
+
...this.opts.region !== void 0 && { region: this.opts.region },
|
|
41584
|
+
...this.opts.profile !== void 0 && { profile: this.opts.profile }
|
|
41585
|
+
});
|
|
41586
|
+
if (!built) return void 0;
|
|
41587
|
+
this.disposers.push(built.dispose);
|
|
41588
|
+
return built.resolver;
|
|
41589
|
+
}
|
|
41590
|
+
dispose() {
|
|
41591
|
+
while (this.disposers.length > 0) {
|
|
41592
|
+
const fn = this.disposers.pop();
|
|
41593
|
+
if (fn) try {
|
|
41594
|
+
fn();
|
|
41595
|
+
} catch {}
|
|
41596
|
+
}
|
|
41597
|
+
}
|
|
41598
|
+
};
|
|
41599
|
+
|
|
41600
|
+
//#endregion
|
|
41601
|
+
//#region src/local/cfn-local-state-provider.ts
|
|
41602
|
+
/**
|
|
41603
|
+
* `CfnLocalStateProvider` — implementation of {@link LocalStateProvider}
|
|
41604
|
+
* backed by a deployed CloudFormation stack. Powers `cdkd local *
|
|
41605
|
+
* --from-cfn-stack` (issue #606).
|
|
41606
|
+
*
|
|
41607
|
+
* The shape mirrors the SAM CLI's `sam local invoke --stack-name X`
|
|
41608
|
+
* behavior: reach into a deployed CFn stack via `DescribeStackResources`
|
|
41609
|
+
* to look up physical IDs of every same-stack resource, then make those
|
|
41610
|
+
* IDs available to the existing `state-resolver.ts` substitution engine.
|
|
41611
|
+
* This lets `cdkd local *` substitute env vars / secrets / images that
|
|
41612
|
+
* reference deployed resources in a CDK app deployed via the upstream
|
|
41613
|
+
* CDK CLI (`cdk deploy` → CloudFormation) WITHOUT first migrating the
|
|
41614
|
+
* stack to cdkd.
|
|
41615
|
+
*
|
|
41616
|
+
* Wire-format mapping:
|
|
41617
|
+
*
|
|
41618
|
+
* - `Ref: <LogicalId>` → resolved via the synthetic `ResourceState`
|
|
41619
|
+
* map built from `DescribeStackResources.StackResources[]` (one
|
|
41620
|
+
* entry per `(LogicalResourceId, PhysicalResourceId, ResourceType)`
|
|
41621
|
+
* tuple).
|
|
41622
|
+
* - `Fn::GetAtt: [<LogicalId>, <Attr>]` → **warn-and-drop**. CFn's
|
|
41623
|
+
* `DescribeStackResources` does NOT return per-attribute values
|
|
41624
|
+
* and the v1 policy (issue #606 recommendation (a)) is to surface
|
|
41625
|
+
* a per-key warn instead of pulling in the full provisioning layer
|
|
41626
|
+
* to call provider-specific describe APIs (e.g. `GetQueueAttributes`
|
|
41627
|
+
* for SQS, `GetFunction` for Lambda). Users override the affected
|
|
41628
|
+
* env var via `--env-vars` if the value is critical.
|
|
41629
|
+
* - `Fn::ImportValue: <exportName>` → resolved via `ListExports`
|
|
41630
|
+
* (paginated). Same-region only — CFn exports are region-scoped.
|
|
41631
|
+
* - `Fn::GetStackOutput` → rejected with a clear pointer that the
|
|
41632
|
+
* intrinsic is cdkd-specific (CFn has no equivalent — exports +
|
|
41633
|
+
* outputs are the only cross-stack vocabulary CFn understands).
|
|
41634
|
+
* - Stack outputs (consumed by both `Fn::GetStackOutput` and the
|
|
41635
|
+
* cross-stack-resolver's index-miss fallback) → sourced from
|
|
41636
|
+
* `DescribeStacks.Outputs[]`.
|
|
41637
|
+
*
|
|
41638
|
+
* Region handling: the provider takes a single region at construction
|
|
41639
|
+
* time (the `cdkd local *` commands resolve this from
|
|
41640
|
+
* `--stack-region` > `--region` > `AWS_REGION` > the synth-derived
|
|
41641
|
+
* region per the existing `--from-state` precedence). Cross-region
|
|
41642
|
+
* `Fn::ImportValue` is out of scope for v1 (CFn's `ListExports` is
|
|
41643
|
+
* region-scoped; a future PR can add a multi-region scan if real
|
|
41644
|
+
* usage justifies it).
|
|
41645
|
+
*
|
|
41646
|
+
* AWS API contract notes:
|
|
41647
|
+
*
|
|
41648
|
+
* - `DescribeStackResources` is unpaginated up to 500 resources (CFn's
|
|
41649
|
+
* hard stack cap). One call suffices for the entire stack.
|
|
41650
|
+
* - `DescribeStacks` is unpaginated when called with `StackName`.
|
|
41651
|
+
* - `ListExports` is paginated; the provider walks `NextToken` until
|
|
41652
|
+
* the page set is exhausted.
|
|
41653
|
+
*/
|
|
41654
|
+
var CfnLocalStateProvider = class {
|
|
41655
|
+
label = "--from-cfn-stack";
|
|
41656
|
+
cfnStackName;
|
|
41657
|
+
region;
|
|
41658
|
+
client;
|
|
41659
|
+
clientOptions;
|
|
41660
|
+
disposed = false;
|
|
41661
|
+
constructor(opts) {
|
|
41662
|
+
this.cfnStackName = opts.cfnStackName;
|
|
41663
|
+
this.region = opts.region;
|
|
41664
|
+
this.clientOptions = { region: opts.region };
|
|
41665
|
+
if (opts.profile !== void 0) this.clientOptions.profile = opts.profile;
|
|
41666
|
+
}
|
|
41667
|
+
getClient() {
|
|
41668
|
+
if (this.disposed) throw new Error("CfnLocalStateProvider used after dispose()");
|
|
41669
|
+
if (!this.client) this.client = new CloudFormationClient({ region: this.region });
|
|
41670
|
+
return this.client;
|
|
41671
|
+
}
|
|
41672
|
+
/**
|
|
41673
|
+
* Load the deployed CFn stack's resources + outputs and return them
|
|
41674
|
+
* as a synthetic `LocalStateRecord` (matching the shape the existing
|
|
41675
|
+
* S3-state-driven path produces). `synthRegion` is accepted for
|
|
41676
|
+
* interface parity with the S3 provider but ignored here — the
|
|
41677
|
+
* provider is region-bound at construction time.
|
|
41678
|
+
*
|
|
41679
|
+
* Best-effort: on any CFn API failure (stack not found, access
|
|
41680
|
+
* denied, throttling) the provider logs a warn and returns
|
|
41681
|
+
* `undefined`. The caller then falls back to the PR 1 warn-and-drop
|
|
41682
|
+
* behavior on every intrinsic-valued env var.
|
|
41683
|
+
*/
|
|
41684
|
+
async load(_stackName, _synthRegion) {
|
|
41685
|
+
if (this.disposed) throw new Error("CfnLocalStateProvider used after dispose()");
|
|
41686
|
+
const logger = getLogger();
|
|
41687
|
+
const client = this.getClient();
|
|
41688
|
+
let resourceMap;
|
|
41689
|
+
try {
|
|
41690
|
+
resourceMap = buildResourceStateMap((await client.send(new DescribeStackResourcesCommand({ StackName: this.cfnStackName }))).StackResources ?? []);
|
|
41691
|
+
} catch (err) {
|
|
41692
|
+
logger.warn(`${this.label}: DescribeStackResources(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err)}. Was the stack deployed in region '${this.region}'? Falling back.`);
|
|
41693
|
+
return;
|
|
41694
|
+
}
|
|
41695
|
+
let outputs;
|
|
41696
|
+
try {
|
|
41697
|
+
const stack = (await client.send(new DescribeStacksCommand({ StackName: this.cfnStackName }))).Stacks?.[0];
|
|
41698
|
+
if (!stack) {
|
|
41699
|
+
logger.warn(`${this.label}: DescribeStacks(${this.cfnStackName}) returned no stack; outputs will be empty.`);
|
|
41700
|
+
outputs = {};
|
|
41701
|
+
} else outputs = buildOutputsMap(stack.Outputs ?? []);
|
|
41702
|
+
} catch (err) {
|
|
41703
|
+
logger.warn(`${this.label}: DescribeStacks(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err)}. Outputs will be empty (Fn::GetStackOutput cannot resolve).`);
|
|
41704
|
+
outputs = {};
|
|
41705
|
+
}
|
|
41706
|
+
return {
|
|
41707
|
+
resources: resourceMap,
|
|
41708
|
+
outputs,
|
|
41709
|
+
region: this.region
|
|
41710
|
+
};
|
|
41711
|
+
}
|
|
41712
|
+
/**
|
|
41713
|
+
* Build a `CrossStackResolver` that resolves `Fn::ImportValue` via
|
|
41714
|
+
* `cloudformation:ListExports`. `Fn::GetStackOutput` is rejected here
|
|
41715
|
+
* — it's a cdkd-specific intrinsic with no CFn-side equivalent, and
|
|
41716
|
+
* the user-visible error message names the right intrinsic
|
|
41717
|
+
* (`Fn::ImportValue`) for that use case.
|
|
41718
|
+
*
|
|
41719
|
+
* `consumerRegion` is accepted for interface parity with the S3
|
|
41720
|
+
* provider but the `CfnLocalStateProvider` only resolves exports in
|
|
41721
|
+
* the region the stack lives in (which is the same region the
|
|
41722
|
+
* consumer Lambda runs in for the common single-region use case).
|
|
41723
|
+
* A future PR can extend this to multi-region by walking the SDK's
|
|
41724
|
+
* partition-aware region list.
|
|
41725
|
+
*/
|
|
41726
|
+
async buildCrossStackResolver(_consumerRegion) {
|
|
41727
|
+
if (this.disposed) throw new Error("CfnLocalStateProvider used after dispose()");
|
|
41728
|
+
const logger = getLogger();
|
|
41729
|
+
const client = this.getClient();
|
|
41730
|
+
const label = this.label;
|
|
41731
|
+
const region = this.region;
|
|
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.`);
|
|
41737
|
+
});
|
|
41738
|
+
return exportsPromise;
|
|
41739
|
+
};
|
|
41740
|
+
return {
|
|
41741
|
+
async resolveImport(exportName) {
|
|
41742
|
+
const map = await ensureExports();
|
|
41743
|
+
if (!map) return void 0;
|
|
41744
|
+
return map.get(exportName);
|
|
41745
|
+
},
|
|
41746
|
+
async resolveGetStackOutput(producerStack, producerRegion, outputName) {
|
|
41747
|
+
logger.warn(`${label}: Fn::GetStackOutput '${producerStack}.${outputName}' (${producerRegion}) is a cdkd-specific intrinsic with no CloudFormation equivalent. Use Fn::ImportValue against an exported output instead, or deploy the producer stack via cdkd deploy and use --from-state.`);
|
|
41748
|
+
}
|
|
41749
|
+
};
|
|
41750
|
+
}
|
|
41751
|
+
dispose() {
|
|
41752
|
+
this.disposed = true;
|
|
41753
|
+
if (this.client) {
|
|
41754
|
+
this.client.destroy();
|
|
41755
|
+
this.client = void 0;
|
|
41756
|
+
}
|
|
41757
|
+
}
|
|
41758
|
+
};
|
|
41759
|
+
/**
|
|
41760
|
+
* Build the synthetic per-logical-id resource map from
|
|
41761
|
+
* `DescribeStackResources` output. Each `ResourceState` carries the
|
|
41762
|
+
* physical id (covers `Ref`) and the resource type; `attributes` is
|
|
41763
|
+
* left empty per issue #606's (a) recommendation — the warn-and-drop
|
|
41764
|
+
* policy on unresolvable `Fn::GetAtt` is the v1 contract. The other
|
|
41765
|
+
* `ResourceState` fields (`properties`, `dependencies`, etc.) are
|
|
41766
|
+
* also left empty since the substituter doesn't read them.
|
|
41767
|
+
*
|
|
41768
|
+
* Exported for unit testing.
|
|
41769
|
+
*/
|
|
41770
|
+
function buildResourceStateMap(stackResources) {
|
|
41771
|
+
const out = {};
|
|
41772
|
+
for (const r of stackResources) {
|
|
41773
|
+
if (!r.LogicalResourceId || !r.PhysicalResourceId || !r.ResourceType) continue;
|
|
41774
|
+
out[r.LogicalResourceId] = {
|
|
41775
|
+
physicalId: r.PhysicalResourceId,
|
|
41776
|
+
resourceType: r.ResourceType,
|
|
41777
|
+
properties: {},
|
|
41778
|
+
attributes: {},
|
|
41779
|
+
dependencies: []
|
|
41780
|
+
};
|
|
41781
|
+
}
|
|
41782
|
+
return out;
|
|
41783
|
+
}
|
|
41784
|
+
/**
|
|
41785
|
+
* Build the outputs map from `DescribeStacks.Outputs[]`. CFn outputs
|
|
41786
|
+
* are stringly typed at the wire level (key + value, with the value
|
|
41787
|
+
* always a string), so the cast is safe.
|
|
41788
|
+
*
|
|
41789
|
+
* Exported for unit testing.
|
|
41790
|
+
*/
|
|
41791
|
+
function buildOutputsMap(outputs) {
|
|
41792
|
+
const out = {};
|
|
41793
|
+
for (const o of outputs) {
|
|
41794
|
+
if (o.OutputKey === void 0 || o.OutputValue === void 0) continue;
|
|
41795
|
+
out[o.OutputKey] = o.OutputValue;
|
|
41796
|
+
}
|
|
41797
|
+
return out;
|
|
41798
|
+
}
|
|
41799
|
+
/**
|
|
41800
|
+
* Walk `ListExports` until every page is consumed and return the
|
|
41801
|
+
* `Name -> Value` map. Same-region only (CFn exports are
|
|
41802
|
+
* region-scoped); the caller picks the region at provider
|
|
41803
|
+
* construction time.
|
|
41804
|
+
*
|
|
41805
|
+
* Exported for unit testing.
|
|
41806
|
+
*/
|
|
41807
|
+
async function fetchAllExports(client) {
|
|
41808
|
+
const out = /* @__PURE__ */ new Map();
|
|
41809
|
+
let nextToken;
|
|
41810
|
+
let pages = 0;
|
|
41811
|
+
do {
|
|
41812
|
+
const resp = await client.send(new ListExportsCommand({ ...nextToken !== void 0 && { NextToken: nextToken } }));
|
|
41813
|
+
for (const exp of resp.Exports ?? []) {
|
|
41814
|
+
if (exp.Name === void 0 || exp.Value === void 0) continue;
|
|
41815
|
+
out.set(exp.Name, exp.Value);
|
|
41816
|
+
}
|
|
41817
|
+
nextToken = resp.NextToken;
|
|
41818
|
+
pages += 1;
|
|
41819
|
+
if (pages > 50) throw new Error("ListExports pagination exceeded 50 pages — likely a malformed NextToken loop.");
|
|
41820
|
+
} while (nextToken !== void 0 && nextToken !== "");
|
|
41821
|
+
return out;
|
|
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
|
+
}
|
|
41846
|
+
|
|
41847
|
+
//#endregion
|
|
41848
|
+
//#region src/cli/commands/local-state-source.ts
|
|
41849
|
+
/**
|
|
41850
|
+
* Single-source-of-truth helper that picks a {@link LocalStateProvider}
|
|
41851
|
+
* for the `cdkd local *` family from CLI flags (issue #606).
|
|
41852
|
+
*
|
|
41853
|
+
* The four `cdkd local *` commands all support two mutually-exclusive
|
|
41854
|
+
* state-source flags:
|
|
41855
|
+
*
|
|
41856
|
+
* - `--from-state` (S3-backed; reads cdkd's state for a stack
|
|
41857
|
+
* deployed via `cdkd deploy`).
|
|
41858
|
+
* - `--from-cfn-stack [<cfn-stack-name>]` (CFn-backed; reads a
|
|
41859
|
+
* deployed CloudFormation stack via `DescribeStackResources`).
|
|
41860
|
+
*
|
|
41861
|
+
* This module centralizes:
|
|
41862
|
+
*
|
|
41863
|
+
* - The mutual-exclusion check (rejected at the CLI layer before any
|
|
41864
|
+
* synth / AWS call fires).
|
|
41865
|
+
* - The bare-vs-explicit `--from-cfn-stack` resolution: bare flag uses
|
|
41866
|
+
* the cdkd stack name; explicit value overrides. Matches the
|
|
41867
|
+
* `cdkd import --migrate-from-cloudformation` precedent.
|
|
41868
|
+
* - Region resolution for the CFn client: reuses the existing
|
|
41869
|
+
* `--stack-region` flag (no separate `--cfn-stack-region`) per
|
|
41870
|
+
* issue #606 recommendation.
|
|
41871
|
+
*
|
|
41872
|
+
* Returns `undefined` when neither flag is set — the caller skips the
|
|
41873
|
+
* substitution pass entirely (which is the pre-issue-#606 behavior
|
|
41874
|
+
* when `--from-state` was absent).
|
|
41875
|
+
*/
|
|
41876
|
+
/**
|
|
41877
|
+
* Default cdkd stack name → CFn stack name. Matches the
|
|
41878
|
+
* `cdkd import --migrate-from-cloudformation` bare-form precedent:
|
|
41879
|
+
* bare `--from-cfn-stack` uses the cdkd stack name verbatim as the CFn
|
|
41880
|
+
* stack name (typical for CDK apps where the names are the same).
|
|
41881
|
+
* Override by passing `--from-cfn-stack <explicit-name>`.
|
|
41882
|
+
*
|
|
41883
|
+
* Exported for unit testing.
|
|
41884
|
+
*/
|
|
41885
|
+
function resolveCfnStackName(fromCfnStack, cdkdStackName) {
|
|
41886
|
+
if (typeof fromCfnStack === "string") return fromCfnStack;
|
|
41887
|
+
return cdkdStackName;
|
|
41888
|
+
}
|
|
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
|
+
/**
|
|
41906
|
+
* Resolve the region used for the CFn client. The CFn provider is
|
|
41907
|
+
* region-bound at construction time; we apply the precedence chain
|
|
41908
|
+
* `--stack-region` > `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION`
|
|
41909
|
+
* > the synth-derived stack region. Throws `LocalStateSourceError`
|
|
41910
|
+
* when none of these signals is set — the CFn API call needs a
|
|
41911
|
+
* concrete region and silently picking `us-east-1` would query the
|
|
41912
|
+
* wrong stack environment (worst case: succeed against the wrong
|
|
41913
|
+
* stack and return wrong physical IDs). Distinct from
|
|
41914
|
+
* `loadStateForStack`'s behavior: the S3 state bucket name is
|
|
41915
|
+
* account-scoped (not region-scoped) and the bucket's region is
|
|
41916
|
+
* auto-discovered via `GetBucketLocation`, so the S3 provider can
|
|
41917
|
+
* tolerate a missing region. The CFn provider cannot.
|
|
41918
|
+
*
|
|
41919
|
+
* Exported for unit testing.
|
|
41920
|
+
*/
|
|
41921
|
+
function resolveCfnRegion(options, synthRegion) {
|
|
41922
|
+
const region = options.stackRegion ?? options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion;
|
|
41923
|
+
if (region === void 0) throw new LocalStateSourceError("--from-cfn-stack requires a region to query CloudFormation. Set one of: --stack-region <region>, --region <region>, AWS_REGION env var, AWS_DEFAULT_REGION env var, or an env.region on the target CDK stack.");
|
|
41924
|
+
return region;
|
|
41925
|
+
}
|
|
41926
|
+
/**
|
|
41927
|
+
* Common error class for the mutual-exclusion check so the CLI layer
|
|
41928
|
+
* can surface a consistent error message from all four commands.
|
|
41929
|
+
*/
|
|
41930
|
+
var LocalStateSourceError = class extends Error {
|
|
41931
|
+
constructor(message) {
|
|
41932
|
+
super(message);
|
|
41933
|
+
this.name = "LocalStateSourceError";
|
|
41934
|
+
}
|
|
41935
|
+
};
|
|
41936
|
+
/**
|
|
41937
|
+
* Pre-flight check for `--from-cfn-stack <explicit-name>` when the
|
|
41938
|
+
* caller will construct one provider per routed stack (`local
|
|
41939
|
+
* start-api` / `local start-service`). An explicit value applies to
|
|
41940
|
+
* the SINGLE CFn stack named — when multiple cdkd stacks are routed,
|
|
41941
|
+
* every one of them would query the same CFn stack, yielding silent
|
|
41942
|
+
* wrong-physical-id substitutions for any logical id that happens to
|
|
41943
|
+
* collide between the user's stacks. Reject at the CLI layer instead.
|
|
41944
|
+
*
|
|
41945
|
+
* Bare `--from-cfn-stack` (the cdkdStackName-default) is fine for
|
|
41946
|
+
* multi-stack: each routed stack reads its own CFn counterpart.
|
|
41947
|
+
* `--from-state` is also fine: cdkd's state is per-(stack, region).
|
|
41948
|
+
*
|
|
41949
|
+
* Call this from `start-api` / `start-service` BEFORE the per-stack
|
|
41950
|
+
* `createLocalStateProvider` loop when `routedStackCount > 1`.
|
|
41951
|
+
*/
|
|
41952
|
+
function rejectExplicitCfnStackWithMultipleStacks(options, routedStackCount) {
|
|
41953
|
+
if (routedStackCount <= 1) return;
|
|
41954
|
+
if (typeof options.fromCfnStack !== "string") return;
|
|
41955
|
+
throw new LocalStateSourceError(`--from-cfn-stack <name> cannot be used with multiple routed stacks (got ${routedStackCount}). An explicit CFn stack name applies to one stack only and would silently mismap logical IDs across siblings. Use bare --from-cfn-stack (each cdkd stack uses its own name as the CFn stack name) or run one cdkd local invocation per stack.`);
|
|
41956
|
+
}
|
|
41957
|
+
/**
|
|
41958
|
+
* Pick and construct the right `LocalStateProvider` for the supplied
|
|
41959
|
+
* flag set. Returns `undefined` when neither flag is set (caller skips
|
|
41960
|
+
* the substitution pass). Throws `LocalStateSourceError` when both
|
|
41961
|
+
* flags are set (mutually exclusive — different state sources, asking
|
|
41962
|
+
* for both is ambiguous about precedence).
|
|
41963
|
+
*
|
|
41964
|
+
* `cdkdStackName` is the cdkd-side stack name the local command
|
|
41965
|
+
* resolved to its target — needed to apply the bare-`--from-cfn-stack`
|
|
41966
|
+
* default. `synthRegion` is the synth-derived stack region (`env.region`
|
|
41967
|
+
* on the CDK stack) — fallback for the CFn client when no explicit
|
|
41968
|
+
* region override is set.
|
|
41969
|
+
*
|
|
41970
|
+
* For multi-stack callers (`local start-api` / `local start-service`)
|
|
41971
|
+
* also invoke `rejectExplicitCfnStackWithMultipleStacks` BEFORE the
|
|
41972
|
+
* per-stack loop — see that helper's docstring for the rationale.
|
|
41973
|
+
*/
|
|
41974
|
+
function createLocalStateProvider(options, cdkdStackName, synthRegion) {
|
|
41975
|
+
const cfnStackOpt = options.fromCfnStack;
|
|
41976
|
+
const cfnFlagPresent = isCfnFlagPresent(options);
|
|
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>.");
|
|
41979
|
+
if (options.fromState) return new S3LocalStateProvider({
|
|
41980
|
+
statePrefix: options.statePrefix,
|
|
41981
|
+
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
41982
|
+
...options.region !== void 0 && { region: options.region },
|
|
41983
|
+
...options.profile !== void 0 && { profile: options.profile },
|
|
41984
|
+
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion }
|
|
41985
|
+
});
|
|
41986
|
+
if (cfnFlagPresent) return new CfnLocalStateProvider({
|
|
41987
|
+
cfnStackName: resolveCfnStackName(cfnStackOpt, cdkdStackName),
|
|
41988
|
+
region: resolveCfnRegion(options, synthRegion),
|
|
41989
|
+
...options.profile !== void 0 && { profile: options.profile }
|
|
41990
|
+
});
|
|
41991
|
+
}
|
|
41992
|
+
|
|
41488
41993
|
//#endregion
|
|
41489
41994
|
//#region src/local/intrinsic-image.ts
|
|
41490
41995
|
/**
|
|
@@ -53415,7 +53920,7 @@ async function localStartApiCommand(target, options) {
|
|
|
53415
53920
|
const m = buildCorsConfigByApiId(stack.template);
|
|
53416
53921
|
for (const [k, v] of m) corsConfigByApiId.set(k, v);
|
|
53417
53922
|
}
|
|
53418
|
-
const stateByStack = options.fromState ? await loadStateForRoutedStacks(targetStacks, routes, routesWithAuth, options) : /* @__PURE__ */ new Map();
|
|
53923
|
+
const stateByStack = options.fromState || isCfnFlagPresent(options) ? await loadStateForRoutedStacks(targetStacks, routes, routesWithAuth, options) : /* @__PURE__ */ new Map();
|
|
53419
53924
|
const lambdaIds = uniqueLambdaIds(routes, routesWithAuth, webSocketApis);
|
|
53420
53925
|
const specs = /* @__PURE__ */ new Map();
|
|
53421
53926
|
for (let i = 0; i < lambdaIds.length; i++) {
|
|
@@ -54455,13 +54960,14 @@ function envHasIntrinsicValue$1(templateEnv) {
|
|
|
54455
54960
|
return false;
|
|
54456
54961
|
}
|
|
54457
54962
|
/**
|
|
54458
|
-
* Load
|
|
54963
|
+
* Load deployed state for every stack that owns a routed Lambda. Once
|
|
54459
54964
|
* per `synthesizeAndBuild` pass (initial boot + every reload), so a
|
|
54460
54965
|
* Lambda's per-spec build does not pay one round-trip per Lambda. Per-
|
|
54461
54966
|
* stack failures (no state, ambiguous region, bucket resolution error)
|
|
54462
|
-
* degrade to warn-and-fall-back via
|
|
54463
|
-
* affected stack's reachable Lambdas behave as if `--from-state`
|
|
54464
|
-
* not set, while sibling stacks with loadable
|
|
54967
|
+
* degrade to warn-and-fall-back via the active `LocalStateProvider` —
|
|
54968
|
+
* the affected stack's reachable Lambdas behave as if `--from-state` /
|
|
54969
|
+
* `--from-cfn-stack` were not set, while sibling stacks with loadable
|
|
54970
|
+
* state still substitute.
|
|
54465
54971
|
*
|
|
54466
54972
|
* Pseudo parameters are resolved per stack and only when at least one
|
|
54467
54973
|
* reachable Lambda in that stack has an intrinsic-valued env entry
|
|
@@ -54490,24 +54996,31 @@ async function loadStateForRoutedStacks(stacks, routes, routesWithAuth, options)
|
|
|
54490
54996
|
}
|
|
54491
54997
|
return false;
|
|
54492
54998
|
};
|
|
54999
|
+
rejectExplicitCfnStackWithMultipleStacks(options, reachableStackNames.size);
|
|
54493
55000
|
for (const stackName of reachableStackNames) {
|
|
54494
55001
|
const stack = stacks.find((s) => s.stackName === stackName);
|
|
54495
55002
|
if (!stack) continue;
|
|
54496
|
-
const
|
|
54497
|
-
|
|
54498
|
-
|
|
54499
|
-
|
|
54500
|
-
|
|
54501
|
-
|
|
54502
|
-
|
|
54503
|
-
|
|
54504
|
-
|
|
54505
|
-
|
|
54506
|
-
|
|
54507
|
-
|
|
55003
|
+
const provider = createLocalStateProvider(options, stack.stackName, stack.region);
|
|
55004
|
+
if (!provider) continue;
|
|
55005
|
+
try {
|
|
55006
|
+
const loaded = await provider.load(stack.stackName, stack.region);
|
|
55007
|
+
if (!loaded) continue;
|
|
55008
|
+
const bundle = { state: {
|
|
55009
|
+
version: 1,
|
|
55010
|
+
stackName: stack.stackName,
|
|
55011
|
+
resources: loaded.resources,
|
|
55012
|
+
outputs: loaded.outputs,
|
|
55013
|
+
lastModified: 0
|
|
55014
|
+
} };
|
|
55015
|
+
if (stackHasIntrinsicEnv(stackName)) {
|
|
55016
|
+
const pseudo = await resolvePseudoParametersForStartApi(loaded.region, options);
|
|
55017
|
+
if (pseudo) bundle.pseudoParameters = pseudo;
|
|
55018
|
+
}
|
|
55019
|
+
out.set(stackName, bundle);
|
|
55020
|
+
logger.debug(`${provider.label}: loaded state for ${stackName} (${loaded.region})`);
|
|
55021
|
+
} finally {
|
|
55022
|
+
provider.dispose();
|
|
54508
55023
|
}
|
|
54509
|
-
out.set(stackName, bundle);
|
|
54510
|
-
logger.debug(`--from-state: loaded state for ${stackName} (${loaded.region})`);
|
|
54511
55024
|
}
|
|
54512
55025
|
return out;
|
|
54513
55026
|
}
|
|
@@ -54520,7 +55033,7 @@ async function loadStateForRoutedStacks(stacks, routes, routesWithAuth, options)
|
|
|
54520
55033
|
* region takes priority).
|
|
54521
55034
|
*
|
|
54522
55035
|
* Region precedence: `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION` >
|
|
54523
|
-
* the state record's region (returned by `
|
|
55036
|
+
* the state record's region (returned by the active `LocalStateProvider`).
|
|
54524
55037
|
*/
|
|
54525
55038
|
async function resolvePseudoParametersForStartApi(stateRegion, options) {
|
|
54526
55039
|
const logger = getLogger();
|
|
@@ -54584,7 +55097,7 @@ function resolveMtlsConfig(options) {
|
|
|
54584
55097
|
* Builder for the `start-api` subcommand. Wired up by `local.ts`.
|
|
54585
55098
|
*/
|
|
54586
55099
|
function createLocalStartApiCommand() {
|
|
54587
|
-
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 (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("--stack-region <region>", "Region of the
|
|
55100
|
+
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 (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));
|
|
54588
55101
|
[
|
|
54589
55102
|
...commonOptions,
|
|
54590
55103
|
...appOptions,
|
|
@@ -55478,6 +55991,7 @@ async function localRunTaskCommand(target, options) {
|
|
|
55478
55991
|
const state = createEcsRunState();
|
|
55479
55992
|
let sigintHandler;
|
|
55480
55993
|
let sigintCount = 0;
|
|
55994
|
+
let stateProvider;
|
|
55481
55995
|
let cleanupPromise;
|
|
55482
55996
|
const cleanup = async () => {
|
|
55483
55997
|
if (!cleanupPromise) cleanupPromise = (async () => {
|
|
@@ -55510,34 +56024,22 @@ async function localRunTaskCommand(target, options) {
|
|
|
55510
56024
|
...options.profile && { macroExpandS3ClientOpts: { profile: options.profile } }
|
|
55511
56025
|
};
|
|
55512
56026
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
55513
|
-
const
|
|
56027
|
+
const candidate = pickCandidateStack$1(parseEcsTarget(target).stackPattern, stacks);
|
|
56028
|
+
stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", candidate?.region);
|
|
56029
|
+
const imageContext = await buildEcsImageResolutionContext$1(candidate, stateProvider, options);
|
|
55514
56030
|
const task = resolveEcsTaskTarget(target, stacks, imageContext);
|
|
55515
56031
|
logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
|
|
55516
56032
|
const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
|
|
55517
|
-
|
|
55518
|
-
if (options.fromState && taskNeeds.needsCrossStackResolver) {
|
|
56033
|
+
if (stateProvider && taskNeeds.needsCrossStackResolver) {
|
|
55519
56034
|
const consumerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? task.stack.region ?? "us-east-1";
|
|
55520
|
-
const
|
|
55521
|
-
|
|
55522
|
-
|
|
55523
|
-
...
|
|
55524
|
-
|
|
56035
|
+
const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
|
|
56036
|
+
if (resolver) await applyCrossStackResolverToTask(task, {
|
|
56037
|
+
resources: imageContext?.stateResources ?? {},
|
|
56038
|
+
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
56039
|
+
consumerRegion,
|
|
56040
|
+
crossStackResolver: resolver
|
|
55525
56041
|
});
|
|
55526
|
-
|
|
55527
|
-
taskCrossStackDispose = built.dispose;
|
|
55528
|
-
try {
|
|
55529
|
-
await applyCrossStackResolverToTask(task, {
|
|
55530
|
-
resources: imageContext?.stateResources ?? {},
|
|
55531
|
-
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
55532
|
-
consumerRegion,
|
|
55533
|
-
crossStackResolver: built.resolver
|
|
55534
|
-
});
|
|
55535
|
-
} finally {
|
|
55536
|
-
taskCrossStackDispose();
|
|
55537
|
-
taskCrossStackDispose = void 0;
|
|
55538
|
-
}
|
|
55539
|
-
}
|
|
55540
|
-
} else if (!options.fromState && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass --from-state to substitute them against deployed cdkd state.");
|
|
56042
|
+
} else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute them against deployed state.");
|
|
55541
56043
|
sigintHandler = () => {
|
|
55542
56044
|
sigintCount += 1;
|
|
55543
56045
|
if (sigintCount >= 2) {
|
|
@@ -55583,6 +56085,7 @@ async function localRunTaskCommand(target, options) {
|
|
|
55583
56085
|
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
55584
56086
|
} finally {
|
|
55585
56087
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
56088
|
+
if (stateProvider) stateProvider.dispose();
|
|
55586
56089
|
if (!options.detach) await cleanup();
|
|
55587
56090
|
}
|
|
55588
56091
|
}
|
|
@@ -55635,19 +56138,18 @@ async function assumeTaskRole$1(roleArn, region) {
|
|
|
55635
56138
|
*
|
|
55636
56139
|
* Tier 1 (pseudo parameters) fires `sts:GetCallerIdentity` once for
|
|
55637
56140
|
* `${AWS::AccountId}`; region / partition / URL suffix come from the CLI
|
|
55638
|
-
* (`--region` → env vars → synth-derived stack region). Tier 2
|
|
55639
|
-
*
|
|
55640
|
-
*
|
|
55641
|
-
*
|
|
56141
|
+
* (`--region` → env vars → synth-derived stack region). Tier 2 (state
|
|
56142
|
+
* load) routes through the active {@link LocalStateProvider} so both
|
|
56143
|
+
* `--from-state` and `--from-cfn-stack` produce the same downstream
|
|
56144
|
+
* context shape (issue #606).
|
|
55642
56145
|
*/
|
|
55643
|
-
async function buildEcsImageResolutionContext$1(
|
|
56146
|
+
async function buildEcsImageResolutionContext$1(candidate, stateProvider, options) {
|
|
55644
56147
|
const logger = getLogger();
|
|
55645
|
-
const candidate = pickCandidateStack$1(parseEcsTarget(target).stackPattern, stacks);
|
|
55646
56148
|
if (!candidate) return void 0;
|
|
55647
56149
|
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
55648
56150
|
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) return;
|
|
55649
56151
|
const ctx = {};
|
|
55650
|
-
const wantsPseudoForEnvOrSecret =
|
|
56152
|
+
const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
|
|
55651
56153
|
if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
|
|
55652
56154
|
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
|
|
55653
56155
|
if (!region) logger.warn("Resolver references ${AWS::Region} but cdkd could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.");
|
|
@@ -55668,17 +56170,11 @@ async function buildEcsImageResolutionContext$1(target, stacks, options) {
|
|
|
55668
56170
|
};
|
|
55669
56171
|
}
|
|
55670
56172
|
const wantsState = needs.needsStateResources || needs.needsEnvOrSecretSubstitution;
|
|
55671
|
-
if (
|
|
55672
|
-
const loaded = await
|
|
55673
|
-
|
|
55674
|
-
|
|
55675
|
-
|
|
55676
|
-
...options.region !== void 0 && { region: options.region },
|
|
55677
|
-
...options.profile !== void 0 && { profile: options.profile }
|
|
55678
|
-
});
|
|
55679
|
-
if (loaded) ctx.stateResources = loaded.state.resources;
|
|
55680
|
-
} else if (!options.fromState && needs.needsStateResources) logger.warn("Container Image references a same-stack AWS::ECR::Repository. Pass --from-state to substitute the deployed repository URI (requires the stack to have been deployed via cdkd deploy). Otherwise the resolver will surface its existing error.");
|
|
55681
|
-
else if (!options.fromState && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics (Ref / Fn::GetAtt / Fn::Sub / Fn::Join). Pass --from-state to substitute them against the deployed cdkd state. Without --from-state these entries are dropped (per-key warnings will follow).");
|
|
56173
|
+
if (stateProvider && wantsState) {
|
|
56174
|
+
const loaded = await stateProvider.load(candidate.stackName, candidate.region);
|
|
56175
|
+
if (loaded) ctx.stateResources = loaded.resources;
|
|
56176
|
+
} else if (!stateProvider && needs.needsStateResources) logger.warn("Container Image references a same-stack AWS::ECR::Repository. Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute the deployed repository URI. Otherwise the resolver will surface its existing error.");
|
|
56177
|
+
else if (!stateProvider && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics (Ref / Fn::GetAtt / Fn::Sub / Fn::Join). Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute them against deployed state. Without a state source these entries are dropped (per-key warnings will follow).");
|
|
55682
56178
|
return ctx;
|
|
55683
56179
|
}
|
|
55684
56180
|
function pickCandidateStack$1(stackPattern, stacks) {
|
|
@@ -55721,7 +56217,7 @@ function readEnvOverridesFile$2(filePath) {
|
|
|
55721
56217
|
return parsed;
|
|
55722
56218
|
}
|
|
55723
56219
|
function createLocalRunTaskCommand() {
|
|
55724
|
-
const cmd = new Command("run-task").description("Run an AWS::ECS::TaskDefinition locally — pulls/builds images, sets up a per-task docker network with the AWS-published metadata-endpoints sidecar, and starts every container in dependsOn order. Target accepts a CDK display path (MyStack/MyService/TaskDef) or stack-qualified logical ID (MyStack:MyServiceTaskDefXYZ1234). Single-stack apps may omit the stack prefix.").argument("<target>", "CDK display path or stack-qualified logical ID of the AWS::ECS::TaskDefinition to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkd-local")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed function role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries (#455). Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--keep-running", "Don't docker rm -f the user containers on task exit (network + sidecar are still torn down). Use when you want to docker exec into a stopped container for post-mortems.").default(false)).addOption(new Option("--detach", "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle.").default(false)).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt references to same-stack AWS::ECR::Repository resources with the deployed URI. Off by default — only the AWS pseudo-parameter tier (${AWS::AccountId} / ${AWS::Region}) is resolved without this flag.").default(false)).addOption(new Option("--stack-region <region>", "Region of the
|
|
56220
|
+
const cmd = new Command("run-task").description("Run an AWS::ECS::TaskDefinition locally — pulls/builds images, sets up a per-task docker network with the AWS-published metadata-endpoints sidecar, and starts every container in dependsOn order. Target accepts a CDK display path (MyStack/MyService/TaskDef) or stack-qualified logical ID (MyStack:MyServiceTaskDefXYZ1234). Single-stack apps may omit the stack prefix.").argument("<target>", "CDK display path or stack-qualified logical ID of the AWS::ECS::TaskDefinition to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkd-local")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed function role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries (#455). Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--keep-running", "Don't docker rm -f the user containers on task exit (network + sidecar are still torn down). Use when you want to docker exec into a stopped container for post-mortems.").default(false)).addOption(new Option("--detach", "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle.").default(false)).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt references to same-stack AWS::ECR::Repository resources with the deployed URI. Off by default — only the AWS pseudo-parameter tier (${AWS::AccountId} / ${AWS::Region}) is resolved without this flag.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs 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; pass an explicit value when the CFn stack name differs. 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).")).action(withErrorHandling(localRunTaskCommand));
|
|
55725
56221
|
[
|
|
55726
56222
|
...commonOptions,
|
|
55727
56223
|
...appOptions,
|
|
@@ -56797,6 +57293,7 @@ async function localStartServiceCommand(targets, options) {
|
|
|
56797
57293
|
warnIfDeprecatedRegion(options);
|
|
56798
57294
|
const skipPull = options.pull === false;
|
|
56799
57295
|
if (!targets || targets.length === 0) throw new LocalStartServiceError("cdkd local start-service requires at least one <target>. Pass one or more service paths like 'Stack/Orders' 'Stack/Frontend'.");
|
|
57296
|
+
rejectExplicitCfnStackWithMultipleStacks(options, targets.length);
|
|
56800
57297
|
const perTarget = targets.map((t) => ({
|
|
56801
57298
|
target: t,
|
|
56802
57299
|
runState: createServiceRunState()
|
|
@@ -56893,34 +57390,36 @@ async function localStartServiceCommand(targets, options) {
|
|
|
56893
57390
|
* outer code to wait + tear down.
|
|
56894
57391
|
*/
|
|
56895
57392
|
async function bootOneTarget(target, runState, stacks, options, discovery, skipPull) {
|
|
57393
|
+
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
57394
|
+
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", candidate?.region);
|
|
57395
|
+
try {
|
|
57396
|
+
return await runOneTarget(target, runState, stacks, options, discovery, skipPull, stateProvider);
|
|
57397
|
+
} finally {
|
|
57398
|
+
if (stateProvider) stateProvider.dispose();
|
|
57399
|
+
}
|
|
57400
|
+
}
|
|
57401
|
+
async function runOneTarget(target, runState, stacks, options, discovery, skipPull, stateProvider) {
|
|
56896
57402
|
const logger = getLogger();
|
|
56897
|
-
const imageContext = await buildEcsImageResolutionContext(target, stacks, options);
|
|
57403
|
+
const imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
|
|
56898
57404
|
const service = resolveEcsServiceTarget(target, stacks, imageContext);
|
|
56899
57405
|
logger.info(`Target: ${service.stack.stackName}/${service.serviceLogicalId} (service=${service.serviceName}, desiredCount=${service.desiredCount}, task=${service.task.taskDefinitionLogicalId})`);
|
|
56900
57406
|
for (const w of service.warnings) logger.warn(w);
|
|
56901
57407
|
if (service.serviceConnect) logger.info(`Service Connect: namespace='${service.serviceConnect.namespaceName}', ${service.serviceConnect.services.length} service(s) registered for peer discovery.`);
|
|
56902
57408
|
if (service.serviceRegistries.length > 0) logger.info(`Cloud Map: ${service.serviceRegistries.length} ServiceRegistry binding(s).`);
|
|
56903
57409
|
const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === service.stack.stackName) ?? service.stack);
|
|
56904
|
-
if (
|
|
57410
|
+
if (stateProvider && taskNeeds.needsCrossStackResolver) {
|
|
56905
57411
|
const consumerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? service.stack.region ?? "us-east-1";
|
|
56906
|
-
const
|
|
56907
|
-
|
|
56908
|
-
statePrefix: options.statePrefix,
|
|
56909
|
-
...options.region !== void 0 && { region: options.region },
|
|
56910
|
-
...options.profile !== void 0 && { profile: options.profile }
|
|
56911
|
-
});
|
|
56912
|
-
if (built) try {
|
|
57412
|
+
const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
|
|
57413
|
+
if (resolver) {
|
|
56913
57414
|
const subContext = {
|
|
56914
57415
|
resources: imageContext?.stateResources ?? {},
|
|
56915
57416
|
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
56916
57417
|
consumerRegion,
|
|
56917
|
-
crossStackResolver:
|
|
57418
|
+
crossStackResolver: resolver
|
|
56918
57419
|
};
|
|
56919
57420
|
await applyCrossStackResolverToTask(service.task, subContext);
|
|
56920
|
-
} finally {
|
|
56921
|
-
built.dispose();
|
|
56922
57421
|
}
|
|
56923
|
-
} else if (!
|
|
57422
|
+
} else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute them against deployed state.");
|
|
56924
57423
|
let assumedCredentials;
|
|
56925
57424
|
let resolvedRoleArn;
|
|
56926
57425
|
if (options.assumeTaskRole === true) {
|
|
@@ -56989,14 +57488,14 @@ async function assumeTaskRole(roleArn, region) {
|
|
|
56989
57488
|
* the candidate stack picker differs because services and tasks share
|
|
56990
57489
|
* the same stack-pattern grammar.
|
|
56991
57490
|
*/
|
|
56992
|
-
async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
57491
|
+
async function buildEcsImageResolutionContext(target, stacks, options, stateProvider) {
|
|
56993
57492
|
const logger = getLogger();
|
|
56994
57493
|
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
56995
57494
|
if (!candidate) return void 0;
|
|
56996
57495
|
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
56997
57496
|
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) return;
|
|
56998
57497
|
const ctx = {};
|
|
56999
|
-
const wantsPseudoForEnvOrSecret =
|
|
57498
|
+
const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
|
|
57000
57499
|
if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
|
|
57001
57500
|
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
|
|
57002
57501
|
if (!region) logger.warn("Resolver references ${AWS::Region} but cdkd could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.");
|
|
@@ -57017,17 +57516,11 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
57017
57516
|
};
|
|
57018
57517
|
}
|
|
57019
57518
|
const wantsState = needs.needsStateResources || needs.needsEnvOrSecretSubstitution;
|
|
57020
|
-
if (
|
|
57021
|
-
const loaded = await
|
|
57022
|
-
|
|
57023
|
-
|
|
57024
|
-
|
|
57025
|
-
...options.region !== void 0 && { region: options.region },
|
|
57026
|
-
...options.profile !== void 0 && { profile: options.profile }
|
|
57027
|
-
});
|
|
57028
|
-
if (loaded) ctx.stateResources = loaded.state.resources;
|
|
57029
|
-
} else if (!options.fromState && needs.needsStateResources) logger.warn("Container Image references a same-stack AWS::ECR::Repository. Pass --from-state to substitute the deployed repository URI.");
|
|
57030
|
-
else if (!options.fromState && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics. Pass --from-state to substitute them against the deployed cdkd state.");
|
|
57519
|
+
if (stateProvider && wantsState) {
|
|
57520
|
+
const loaded = await stateProvider.load(candidate.stackName, candidate.region);
|
|
57521
|
+
if (loaded) ctx.stateResources = loaded.resources;
|
|
57522
|
+
} else if (!stateProvider && needs.needsStateResources) logger.warn("Container Image references a same-stack AWS::ECR::Repository. Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute the deployed repository URI.");
|
|
57523
|
+
else if (!stateProvider && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics. Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute them against the deployed cdkd state.");
|
|
57031
57524
|
return ctx;
|
|
57032
57525
|
}
|
|
57033
57526
|
function pickCandidateStack(stackPattern, stacks) {
|
|
@@ -57098,7 +57591,7 @@ function parseRestartPolicy(raw) {
|
|
|
57098
57591
|
throw new LocalStartServiceError(`--restart-policy must be one of 'on-failure', 'always', or 'none' (got '${raw}').`);
|
|
57099
57592
|
}
|
|
57100
57593
|
function createLocalStartServiceCommand() {
|
|
57101
|
-
const cmd = new Command("start-service").description("Run one or more AWS::ECS::Service resources locally as a long-running emulator. Spins up DesiredCount task replicas per service (clamped by --max-tasks) using the same per-task docker network + metadata sidecar pattern as `cdkd local run-task`, then keeps each replica running and restarts it on exit per --restart-policy. ^C tears every replica + sidecar + network down. Each <target> accepts a CDK display path (MyStack/MyService) or stack-qualified logical ID (MyStack:MyServiceXYZ); single-stack apps may omit the stack prefix. When two or more <target>s are supplied, every service is booted into a shared Cloud Map / Service Connect registry so peer services discover each other via docker --add-host overlay (Issue #460).").argument("<targets...>", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ECS::Service resources to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkd-local")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--max-tasks <n>", `Hard cap on local replica count. Caps the template DesiredCount so local dev machines don't run an unbounded number of containers. Cannot exceed ${83} due to the per-replica link-local /24 subnet allocator's range.`).default(3).argParser(parseMaxTasks)).addOption(new Option("--restart-policy <policy>", "How to react when an essential container exits. 'on-failure' (default) restarts only on non-zero exit; 'always' restarts on every exit; 'none' shuts the replica down and runs the service degraded.").default("on-failure").argParser(parseRestartPolicy)).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes.").default(false)).addOption(new Option("--stack-region <region>", "Region of the
|
|
57594
|
+
const cmd = new Command("start-service").description("Run one or more AWS::ECS::Service resources locally as a long-running emulator. Spins up DesiredCount task replicas per service (clamped by --max-tasks) using the same per-task docker network + metadata sidecar pattern as `cdkd local run-task`, then keeps each replica running and restarts it on exit per --restart-policy. ^C tears every replica + sidecar + network down. Each <target> accepts a CDK display path (MyStack/MyService) or stack-qualified logical ID (MyStack:MyServiceXYZ); single-stack apps may omit the stack prefix. When two or more <target>s are supplied, every service is booted into a shared Cloud Map / Service Connect registry so peer services discover each other via docker --add-host overlay (Issue #460).").argument("<targets...>", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ECS::Service resources to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkd-local")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--max-tasks <n>", `Hard cap on local replica count. Caps the template DesiredCount so local dev machines don't run an unbounded number of containers. Cannot exceed ${83} due to the per-replica link-local /24 subnet allocator's range.`).default(3).argParser(parseMaxTasks)).addOption(new Option("--restart-policy <policy>", "How to react when an essential container exits. 'on-failure' (default) restarts only on non-zero exit; 'always' restarts on every exit; 'none' shuts the replica down and runs the service degraded.").default("on-failure").argParser(parseRestartPolicy)).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs 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; pass an explicit value when the CFn stack name differs. 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).")).action(withErrorHandling(localStartServiceCommand));
|
|
57102
57595
|
[
|
|
57103
57596
|
...commonOptions,
|
|
57104
57597
|
...appOptions,
|
|
@@ -57212,19 +57705,19 @@ async function localInvokeCommand(target, options) {
|
|
|
57212
57705
|
let stateAudit;
|
|
57213
57706
|
let templateEnv = getTemplateEnv(lambda.resource);
|
|
57214
57707
|
let stateForRoleHint;
|
|
57215
|
-
|
|
57216
|
-
if (
|
|
57217
|
-
const loaded = await
|
|
57218
|
-
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
57219
|
-
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
57220
|
-
statePrefix: options.statePrefix,
|
|
57221
|
-
...options.region !== void 0 && { region: options.region },
|
|
57222
|
-
...options.profile !== void 0 && { profile: options.profile }
|
|
57223
|
-
});
|
|
57708
|
+
const stateProvider = createLocalStateProvider(options, lambda.stack.stackName, lambda.stack.region);
|
|
57709
|
+
if (stateProvider) try {
|
|
57710
|
+
const loaded = await stateProvider.load(lambda.stack.stackName, lambda.stack.region);
|
|
57224
57711
|
if (loaded) {
|
|
57225
|
-
stateForRoleHint =
|
|
57712
|
+
stateForRoleHint = {
|
|
57713
|
+
version: 1,
|
|
57714
|
+
stackName: lambda.stack.stackName,
|
|
57715
|
+
resources: loaded.resources,
|
|
57716
|
+
outputs: loaded.outputs,
|
|
57717
|
+
lastModified: 0
|
|
57718
|
+
};
|
|
57226
57719
|
const subContext = {
|
|
57227
|
-
resources: loaded.
|
|
57720
|
+
resources: loaded.resources,
|
|
57228
57721
|
consumerRegion: loaded.region
|
|
57229
57722
|
};
|
|
57230
57723
|
if (envHasIntrinsicValue(templateEnv)) {
|
|
@@ -57232,36 +57725,24 @@ async function localInvokeCommand(target, options) {
|
|
|
57232
57725
|
if (pseudo) subContext.pseudoParameters = pseudo;
|
|
57233
57726
|
}
|
|
57234
57727
|
if (envHasCrossStackIntrinsic(templateEnv)) {
|
|
57235
|
-
const
|
|
57236
|
-
|
|
57237
|
-
statePrefix: options.statePrefix,
|
|
57238
|
-
...options.region !== void 0 && { region: options.region },
|
|
57239
|
-
...options.profile !== void 0 && { profile: options.profile }
|
|
57240
|
-
});
|
|
57241
|
-
if (built) {
|
|
57242
|
-
subContext.crossStackResolver = built.resolver;
|
|
57243
|
-
crossStackDispose = built.dispose;
|
|
57244
|
-
}
|
|
57245
|
-
}
|
|
57246
|
-
try {
|
|
57247
|
-
const { env, audit } = await substituteEnvVarsFromStateAsync(templateEnv, subContext);
|
|
57248
|
-
templateEnv = env;
|
|
57249
|
-
stateAudit = audit;
|
|
57250
|
-
for (const key of audit.resolvedKeys) logger.debug(`--from-state: substituted env var ${key} from cdkd state`);
|
|
57251
|
-
for (const { key, reason } of audit.unresolved) logger.warn(`--from-state: could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`);
|
|
57252
|
-
} finally {
|
|
57253
|
-
if (crossStackDispose) {
|
|
57254
|
-
crossStackDispose();
|
|
57255
|
-
crossStackDispose = void 0;
|
|
57256
|
-
}
|
|
57728
|
+
const resolver = await stateProvider.buildCrossStackResolver(loaded.region);
|
|
57729
|
+
if (resolver) subContext.crossStackResolver = resolver;
|
|
57257
57730
|
}
|
|
57731
|
+
const { env, audit } = await substituteEnvVarsFromStateAsync(templateEnv, subContext);
|
|
57732
|
+
templateEnv = env;
|
|
57733
|
+
stateAudit = audit;
|
|
57734
|
+
const label = stateProvider.label;
|
|
57735
|
+
for (const key of audit.resolvedKeys) logger.debug(`${label}: substituted env var ${key}`);
|
|
57736
|
+
for (const { key, reason } of audit.unresolved) logger.warn(`${label}: could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`);
|
|
57258
57737
|
}
|
|
57738
|
+
} finally {
|
|
57739
|
+
stateProvider.dispose();
|
|
57259
57740
|
}
|
|
57260
57741
|
const overrides = readEnvOverridesFile(options.envVars);
|
|
57261
57742
|
const envResult = resolveEnvVars(lambda.logicalId, templateEnv, overrides);
|
|
57262
57743
|
for (const key of envResult.unresolved) {
|
|
57263
57744
|
if (stateAudit && stateAudit.unresolved.some((u) => u.key === key)) continue;
|
|
57264
|
-
logger.warn(`Environment variable ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${lambda.logicalId}":{"${key}":"<literal>"}}) or pass --from-state to recover deployed values.`);
|
|
57745
|
+
logger.warn(`Environment variable ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${lambda.logicalId}":{"${key}":"<literal>"}}), or pass --from-state (cdkd-deployed) / --from-cfn-stack (cdk-deployed) to recover deployed values.`);
|
|
57265
57746
|
}
|
|
57266
57747
|
let resolvedAssumeRoleArn;
|
|
57267
57748
|
if (typeof options.assumeRole === "string") resolvedAssumeRoleArn = options.assumeRole;
|
|
@@ -57831,7 +58312,7 @@ function pickReferencedLogicalId(intrinsic) {
|
|
|
57831
58312
|
*/
|
|
57832
58313
|
function createLocalCommand() {
|
|
57833
58314
|
const local = new Command("local").description("Local execution of Lambda functions (RIE) and ECS task definitions (Docker required)");
|
|
57834
|
-
const invoke = new Command("invoke").description("Run a Lambda function locally in a Docker container (RIE-backed). Target accepts a CDK display path (MyStack/MyApi/Handler) or stack-qualified logical ID (MyStack:MyApiHandler1234ABCD). Single-stack apps may omit the stack prefix.").argument("<target>", "CDK display path or stack-qualified logical ID of the Lambda to invoke").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for IMAGE local-build path; `docker build` does not pull base layers by default")).addOption(new Option("--no-build", "Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull.")).addOption(new Option("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(new Option("--container-host <host>", "Host to bind the RIE port to").default("127.0.0.1")).addOption(new Option("--assume-role [arn]", "Assume the Lambda's deployed execution role and forward STS-issued temp credentials to the container so the handler runs with the deployed function's narrow permissions (closes the \"developer admin / function narrow\" skew). Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) auto-resolves the function's execution role ARN from cdkd state (requires --from-state); (3) `--no-assume-role` explicitly opts out (forces dev creds even with --from-state). Off by default — when omitted, the developer's shell credentials are forwarded unchanged (SAM-compatible default). STS failures degrade to a warn + dev-creds fallback.")).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("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries (#455). Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub in env vars with the deployed physical IDs / attributes. Off by default — keep PR 1 warn-and-drop semantics; turn on for stacks already deployed via cdkd deploy.").default(false)).addOption(new Option("--stack-region <region>", "Region of the
|
|
58315
|
+
const invoke = new Command("invoke").description("Run a Lambda function locally in a Docker container (RIE-backed). Target accepts a CDK display path (MyStack/MyApi/Handler) or stack-qualified logical ID (MyStack:MyApiHandler1234ABCD). Single-stack apps may omit the stack prefix.").argument("<target>", "CDK display path or stack-qualified logical ID of the Lambda to invoke").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for IMAGE local-build path; `docker build` does not pull base layers by default")).addOption(new Option("--no-build", "Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull.")).addOption(new Option("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(new Option("--container-host <host>", "Host to bind the RIE port to").default("127.0.0.1")).addOption(new Option("--assume-role [arn]", "Assume the Lambda's deployed execution role and forward STS-issued temp credentials to the container so the handler runs with the deployed function's narrow permissions (closes the \"developer admin / function narrow\" skew). Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) auto-resolves the function's execution role ARN from cdkd state (requires --from-state); (3) `--no-assume-role` explicitly opts out (forces dev creds even with --from-state). Off by default — when omitted, the developer's shell credentials are forwarded unchanged (SAM-compatible default). STS failures degrade to a warn + dev-creds fallback.")).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("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries (#455). Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub in env vars with the deployed physical IDs / attributes. Off by default — keep PR 1 warn-and-drop semantics; turn on for stacks already deployed via cdkd deploy.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in 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; pass an explicit value when CFn stack name differs. 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).")).action(withErrorHandling(localInvokeCommand));
|
|
57835
58316
|
[
|
|
57836
58317
|
...commonOptions,
|
|
57837
58318
|
...appOptions,
|
|
@@ -58955,7 +59436,7 @@ function reorderArgs(argv) {
|
|
|
58955
59436
|
*/
|
|
58956
59437
|
async function main() {
|
|
58957
59438
|
const program = new Command();
|
|
58958
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
59439
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.158.1");
|
|
58959
59440
|
program.addCommand(createBootstrapCommand());
|
|
58960
59441
|
program.addCommand(createSynthCommand());
|
|
58961
59442
|
program.addCommand(createListCommand());
|