@go-to-k/cdkd 0.157.0 → 0.158.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/dist/cli.js +515 -127
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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";
|
|
@@ -41485,6 +41485,418 @@ async function buildCrossStackResolver(consumerRegion, opts) {
|
|
|
41485
41485
|
};
|
|
41486
41486
|
}
|
|
41487
41487
|
|
|
41488
|
+
//#endregion
|
|
41489
|
+
//#region src/local/s3-local-state-provider.ts
|
|
41490
|
+
/**
|
|
41491
|
+
* `S3LocalStateProvider` — implementation of {@link LocalStateProvider}
|
|
41492
|
+
* backed by cdkd's S3 state. Wraps the existing
|
|
41493
|
+
* `loadStateForStack` + `buildCrossStackResolver` helpers in
|
|
41494
|
+
* `src/cli/commands/local-state-loader.ts` so the four `cdkd local *`
|
|
41495
|
+
* commands can route both `--from-state` and `--from-cfn-stack`
|
|
41496
|
+
* through the same provider-shaped interface (issue #606).
|
|
41497
|
+
*
|
|
41498
|
+
* Behavior is identical to the pre-issue-#606 code path — this file
|
|
41499
|
+
* exists ONLY to give the CLI layer a single interface against which
|
|
41500
|
+
* to wire both flags, so adding a third state source (a future
|
|
41501
|
+
* `--from-tf-state`? out of scope for now) doesn't require touching
|
|
41502
|
+
* the four `local-*.ts` command files again.
|
|
41503
|
+
*/
|
|
41504
|
+
var S3LocalStateProvider = class {
|
|
41505
|
+
label = "--from-state";
|
|
41506
|
+
opts;
|
|
41507
|
+
disposers = [];
|
|
41508
|
+
constructor(opts) {
|
|
41509
|
+
this.opts = opts;
|
|
41510
|
+
}
|
|
41511
|
+
async load(stackName, synthRegion) {
|
|
41512
|
+
const loaded = await loadStateForStack(stackName, synthRegion, {
|
|
41513
|
+
statePrefix: this.opts.statePrefix,
|
|
41514
|
+
...this.opts.stackRegion !== void 0 && { stackRegion: this.opts.stackRegion },
|
|
41515
|
+
...this.opts.stateBucket !== void 0 && { stateBucket: this.opts.stateBucket },
|
|
41516
|
+
...this.opts.region !== void 0 && { region: this.opts.region },
|
|
41517
|
+
...this.opts.profile !== void 0 && { profile: this.opts.profile }
|
|
41518
|
+
});
|
|
41519
|
+
if (!loaded) return void 0;
|
|
41520
|
+
const outputs = {};
|
|
41521
|
+
for (const [k, v] of Object.entries(loaded.state.outputs ?? {})) if (typeof v === "string") outputs[k] = v;
|
|
41522
|
+
else if (typeof v === "number" || typeof v === "boolean") outputs[k] = String(v);
|
|
41523
|
+
else outputs[k] = JSON.stringify(v);
|
|
41524
|
+
return {
|
|
41525
|
+
resources: loaded.state.resources,
|
|
41526
|
+
outputs,
|
|
41527
|
+
region: loaded.region
|
|
41528
|
+
};
|
|
41529
|
+
}
|
|
41530
|
+
async buildCrossStackResolver(consumerRegion) {
|
|
41531
|
+
const built = await buildCrossStackResolver(consumerRegion, {
|
|
41532
|
+
statePrefix: this.opts.statePrefix,
|
|
41533
|
+
...this.opts.stateBucket !== void 0 && { stateBucket: this.opts.stateBucket },
|
|
41534
|
+
...this.opts.region !== void 0 && { region: this.opts.region },
|
|
41535
|
+
...this.opts.profile !== void 0 && { profile: this.opts.profile }
|
|
41536
|
+
});
|
|
41537
|
+
if (!built) return void 0;
|
|
41538
|
+
this.disposers.push(built.dispose);
|
|
41539
|
+
return built.resolver;
|
|
41540
|
+
}
|
|
41541
|
+
dispose() {
|
|
41542
|
+
while (this.disposers.length > 0) {
|
|
41543
|
+
const fn = this.disposers.pop();
|
|
41544
|
+
if (fn) try {
|
|
41545
|
+
fn();
|
|
41546
|
+
} catch {}
|
|
41547
|
+
}
|
|
41548
|
+
}
|
|
41549
|
+
};
|
|
41550
|
+
|
|
41551
|
+
//#endregion
|
|
41552
|
+
//#region src/local/cfn-local-state-provider.ts
|
|
41553
|
+
/**
|
|
41554
|
+
* `CfnLocalStateProvider` — implementation of {@link LocalStateProvider}
|
|
41555
|
+
* backed by a deployed CloudFormation stack. Powers `cdkd local *
|
|
41556
|
+
* --from-cfn-stack` (issue #606).
|
|
41557
|
+
*
|
|
41558
|
+
* The shape mirrors the SAM CLI's `sam local invoke --stack-name X`
|
|
41559
|
+
* behavior: reach into a deployed CFn stack via `DescribeStackResources`
|
|
41560
|
+
* to look up physical IDs of every same-stack resource, then make those
|
|
41561
|
+
* IDs available to the existing `state-resolver.ts` substitution engine.
|
|
41562
|
+
* This lets `cdkd local *` substitute env vars / secrets / images that
|
|
41563
|
+
* reference deployed resources in a CDK app deployed via the upstream
|
|
41564
|
+
* CDK CLI (`cdk deploy` → CloudFormation) WITHOUT first migrating the
|
|
41565
|
+
* stack to cdkd.
|
|
41566
|
+
*
|
|
41567
|
+
* Wire-format mapping:
|
|
41568
|
+
*
|
|
41569
|
+
* - `Ref: <LogicalId>` → resolved via the synthetic `ResourceState`
|
|
41570
|
+
* map built from `DescribeStackResources.StackResources[]` (one
|
|
41571
|
+
* entry per `(LogicalResourceId, PhysicalResourceId, ResourceType)`
|
|
41572
|
+
* tuple).
|
|
41573
|
+
* - `Fn::GetAtt: [<LogicalId>, <Attr>]` → **warn-and-drop**. CFn's
|
|
41574
|
+
* `DescribeStackResources` does NOT return per-attribute values
|
|
41575
|
+
* and the v1 policy (issue #606 recommendation (a)) is to surface
|
|
41576
|
+
* a per-key warn instead of pulling in the full provisioning layer
|
|
41577
|
+
* to call provider-specific describe APIs (e.g. `GetQueueAttributes`
|
|
41578
|
+
* for SQS, `GetFunction` for Lambda). Users override the affected
|
|
41579
|
+
* env var via `--env-vars` if the value is critical.
|
|
41580
|
+
* - `Fn::ImportValue: <exportName>` → resolved via `ListExports`
|
|
41581
|
+
* (paginated). Same-region only — CFn exports are region-scoped.
|
|
41582
|
+
* - `Fn::GetStackOutput` → rejected with a clear pointer that the
|
|
41583
|
+
* intrinsic is cdkd-specific (CFn has no equivalent — exports +
|
|
41584
|
+
* outputs are the only cross-stack vocabulary CFn understands).
|
|
41585
|
+
* - Stack outputs (consumed by both `Fn::GetStackOutput` and the
|
|
41586
|
+
* cross-stack-resolver's index-miss fallback) → sourced from
|
|
41587
|
+
* `DescribeStacks.Outputs[]`.
|
|
41588
|
+
*
|
|
41589
|
+
* Region handling: the provider takes a single region at construction
|
|
41590
|
+
* time (the `cdkd local *` commands resolve this from
|
|
41591
|
+
* `--stack-region` > `--region` > `AWS_REGION` > the synth-derived
|
|
41592
|
+
* region per the existing `--from-state` precedence). Cross-region
|
|
41593
|
+
* `Fn::ImportValue` is out of scope for v1 (CFn's `ListExports` is
|
|
41594
|
+
* region-scoped; a future PR can add a multi-region scan if real
|
|
41595
|
+
* usage justifies it).
|
|
41596
|
+
*
|
|
41597
|
+
* AWS API contract notes:
|
|
41598
|
+
*
|
|
41599
|
+
* - `DescribeStackResources` is unpaginated up to 500 resources (CFn's
|
|
41600
|
+
* hard stack cap). One call suffices for the entire stack.
|
|
41601
|
+
* - `DescribeStacks` is unpaginated when called with `StackName`.
|
|
41602
|
+
* - `ListExports` is paginated; the provider walks `NextToken` until
|
|
41603
|
+
* the page set is exhausted.
|
|
41604
|
+
*/
|
|
41605
|
+
var CfnLocalStateProvider = class {
|
|
41606
|
+
label = "--from-cfn-stack";
|
|
41607
|
+
cfnStackName;
|
|
41608
|
+
region;
|
|
41609
|
+
client;
|
|
41610
|
+
clientOptions;
|
|
41611
|
+
constructor(opts) {
|
|
41612
|
+
this.cfnStackName = opts.cfnStackName;
|
|
41613
|
+
this.region = opts.region;
|
|
41614
|
+
this.clientOptions = { region: opts.region };
|
|
41615
|
+
if (opts.profile !== void 0) this.clientOptions.profile = opts.profile;
|
|
41616
|
+
}
|
|
41617
|
+
getClient() {
|
|
41618
|
+
if (!this.client) this.client = new CloudFormationClient({ region: this.region });
|
|
41619
|
+
return this.client;
|
|
41620
|
+
}
|
|
41621
|
+
/**
|
|
41622
|
+
* Load the deployed CFn stack's resources + outputs and return them
|
|
41623
|
+
* as a synthetic `LocalStateRecord` (matching the shape the existing
|
|
41624
|
+
* S3-state-driven path produces). `synthRegion` is accepted for
|
|
41625
|
+
* interface parity with the S3 provider but ignored here — the
|
|
41626
|
+
* provider is region-bound at construction time.
|
|
41627
|
+
*
|
|
41628
|
+
* Best-effort: on any CFn API failure (stack not found, access
|
|
41629
|
+
* denied, throttling) the provider logs a warn and returns
|
|
41630
|
+
* `undefined`. The caller then falls back to the PR 1 warn-and-drop
|
|
41631
|
+
* behavior on every intrinsic-valued env var.
|
|
41632
|
+
*/
|
|
41633
|
+
async load(_stackName, _synthRegion) {
|
|
41634
|
+
const logger = getLogger();
|
|
41635
|
+
const client = this.getClient();
|
|
41636
|
+
let resourceMap;
|
|
41637
|
+
try {
|
|
41638
|
+
resourceMap = buildResourceStateMap((await client.send(new DescribeStackResourcesCommand({ StackName: this.cfnStackName }))).StackResources ?? []);
|
|
41639
|
+
} catch (err) {
|
|
41640
|
+
logger.warn(`${this.label}: DescribeStackResources(${this.cfnStackName}) failed: ${err instanceof Error ? err.message : String(err)}. Was the stack deployed in region '${this.region}'? Falling back.`);
|
|
41641
|
+
return;
|
|
41642
|
+
}
|
|
41643
|
+
let outputs;
|
|
41644
|
+
try {
|
|
41645
|
+
const stack = (await client.send(new DescribeStacksCommand({ StackName: this.cfnStackName }))).Stacks?.[0];
|
|
41646
|
+
if (!stack) {
|
|
41647
|
+
logger.warn(`${this.label}: DescribeStacks(${this.cfnStackName}) returned no stack; outputs will be empty.`);
|
|
41648
|
+
outputs = {};
|
|
41649
|
+
} else outputs = buildOutputsMap(stack.Outputs ?? []);
|
|
41650
|
+
} catch (err) {
|
|
41651
|
+
logger.warn(`${this.label}: DescribeStacks(${this.cfnStackName}) failed: ${err instanceof Error ? err.message : String(err)}. Outputs will be empty (Fn::GetStackOutput cannot resolve).`);
|
|
41652
|
+
outputs = {};
|
|
41653
|
+
}
|
|
41654
|
+
return {
|
|
41655
|
+
resources: resourceMap,
|
|
41656
|
+
outputs,
|
|
41657
|
+
region: this.region
|
|
41658
|
+
};
|
|
41659
|
+
}
|
|
41660
|
+
/**
|
|
41661
|
+
* Build a `CrossStackResolver` that resolves `Fn::ImportValue` via
|
|
41662
|
+
* `cloudformation:ListExports`. `Fn::GetStackOutput` is rejected here
|
|
41663
|
+
* — it's a cdkd-specific intrinsic with no CFn-side equivalent, and
|
|
41664
|
+
* the user-visible error message names the right intrinsic
|
|
41665
|
+
* (`Fn::ImportValue`) for that use case.
|
|
41666
|
+
*
|
|
41667
|
+
* `consumerRegion` is accepted for interface parity with the S3
|
|
41668
|
+
* provider but the `CfnLocalStateProvider` only resolves exports in
|
|
41669
|
+
* the region the stack lives in (which is the same region the
|
|
41670
|
+
* consumer Lambda runs in for the common single-region use case).
|
|
41671
|
+
* A future PR can extend this to multi-region by walking the SDK's
|
|
41672
|
+
* partition-aware region list.
|
|
41673
|
+
*/
|
|
41674
|
+
async buildCrossStackResolver(_consumerRegion) {
|
|
41675
|
+
const logger = getLogger();
|
|
41676
|
+
const client = this.getClient();
|
|
41677
|
+
const label = this.label;
|
|
41678
|
+
const region = this.region;
|
|
41679
|
+
let cachedExports;
|
|
41680
|
+
const ensureExports = async () => {
|
|
41681
|
+
if (cachedExports) return cachedExports;
|
|
41682
|
+
const result = await fetchAllExports(client).catch((err) => {
|
|
41683
|
+
logger.warn(`${label}: ListExports (${region}) failed: ${err instanceof Error ? err.message : String(err)}. Fn::ImportValue intrinsics will warn-and-drop.`);
|
|
41684
|
+
});
|
|
41685
|
+
if (result) cachedExports = result;
|
|
41686
|
+
return result;
|
|
41687
|
+
};
|
|
41688
|
+
return {
|
|
41689
|
+
async resolveImport(exportName) {
|
|
41690
|
+
const map = await ensureExports();
|
|
41691
|
+
if (!map) return void 0;
|
|
41692
|
+
return map.get(exportName);
|
|
41693
|
+
},
|
|
41694
|
+
async resolveGetStackOutput(producerStack, producerRegion, outputName) {
|
|
41695
|
+
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.`);
|
|
41696
|
+
}
|
|
41697
|
+
};
|
|
41698
|
+
}
|
|
41699
|
+
dispose() {
|
|
41700
|
+
if (this.client) {
|
|
41701
|
+
this.client.destroy();
|
|
41702
|
+
this.client = void 0;
|
|
41703
|
+
}
|
|
41704
|
+
}
|
|
41705
|
+
};
|
|
41706
|
+
/**
|
|
41707
|
+
* Build the synthetic per-logical-id resource map from
|
|
41708
|
+
* `DescribeStackResources` output. Each `ResourceState` carries the
|
|
41709
|
+
* physical id (covers `Ref`) and the resource type; `attributes` is
|
|
41710
|
+
* left empty per issue #606's (a) recommendation — the warn-and-drop
|
|
41711
|
+
* policy on unresolvable `Fn::GetAtt` is the v1 contract. The other
|
|
41712
|
+
* `ResourceState` fields (`properties`, `dependencies`, etc.) are
|
|
41713
|
+
* also left empty since the substituter doesn't read them.
|
|
41714
|
+
*
|
|
41715
|
+
* Exported for unit testing.
|
|
41716
|
+
*/
|
|
41717
|
+
function buildResourceStateMap(stackResources) {
|
|
41718
|
+
const out = {};
|
|
41719
|
+
for (const r of stackResources) {
|
|
41720
|
+
if (!r.LogicalResourceId || !r.PhysicalResourceId || !r.ResourceType) continue;
|
|
41721
|
+
out[r.LogicalResourceId] = {
|
|
41722
|
+
physicalId: r.PhysicalResourceId,
|
|
41723
|
+
resourceType: r.ResourceType,
|
|
41724
|
+
properties: {},
|
|
41725
|
+
attributes: {},
|
|
41726
|
+
dependencies: []
|
|
41727
|
+
};
|
|
41728
|
+
}
|
|
41729
|
+
return out;
|
|
41730
|
+
}
|
|
41731
|
+
/**
|
|
41732
|
+
* Build the outputs map from `DescribeStacks.Outputs[]`. CFn outputs
|
|
41733
|
+
* are stringly typed at the wire level (key + value, with the value
|
|
41734
|
+
* always a string), so the cast is safe.
|
|
41735
|
+
*
|
|
41736
|
+
* Exported for unit testing.
|
|
41737
|
+
*/
|
|
41738
|
+
function buildOutputsMap(outputs) {
|
|
41739
|
+
const out = {};
|
|
41740
|
+
for (const o of outputs) {
|
|
41741
|
+
if (o.OutputKey === void 0 || o.OutputValue === void 0) continue;
|
|
41742
|
+
out[o.OutputKey] = o.OutputValue;
|
|
41743
|
+
}
|
|
41744
|
+
return out;
|
|
41745
|
+
}
|
|
41746
|
+
/**
|
|
41747
|
+
* Walk `ListExports` until every page is consumed and return the
|
|
41748
|
+
* `Name -> Value` map. Same-region only (CFn exports are
|
|
41749
|
+
* region-scoped); the caller picks the region at provider
|
|
41750
|
+
* construction time.
|
|
41751
|
+
*
|
|
41752
|
+
* Exported for unit testing.
|
|
41753
|
+
*/
|
|
41754
|
+
async function fetchAllExports(client) {
|
|
41755
|
+
const out = /* @__PURE__ */ new Map();
|
|
41756
|
+
let nextToken;
|
|
41757
|
+
let pages = 0;
|
|
41758
|
+
do {
|
|
41759
|
+
const resp = await client.send(new ListExportsCommand({ ...nextToken !== void 0 && { NextToken: nextToken } }));
|
|
41760
|
+
for (const exp of resp.Exports ?? []) {
|
|
41761
|
+
if (exp.Name === void 0 || exp.Value === void 0) continue;
|
|
41762
|
+
out.set(exp.Name, exp.Value);
|
|
41763
|
+
}
|
|
41764
|
+
nextToken = resp.NextToken;
|
|
41765
|
+
pages += 1;
|
|
41766
|
+
if (pages > 50) throw new Error("ListExports pagination exceeded 50 pages — likely a malformed NextToken loop.");
|
|
41767
|
+
} while (nextToken !== void 0);
|
|
41768
|
+
return out;
|
|
41769
|
+
}
|
|
41770
|
+
|
|
41771
|
+
//#endregion
|
|
41772
|
+
//#region src/cli/commands/local-state-source.ts
|
|
41773
|
+
/**
|
|
41774
|
+
* Single-source-of-truth helper that picks a {@link LocalStateProvider}
|
|
41775
|
+
* for the `cdkd local *` family from CLI flags (issue #606).
|
|
41776
|
+
*
|
|
41777
|
+
* The four `cdkd local *` commands all support two mutually-exclusive
|
|
41778
|
+
* state-source flags:
|
|
41779
|
+
*
|
|
41780
|
+
* - `--from-state` (S3-backed; reads cdkd's state for a stack
|
|
41781
|
+
* deployed via `cdkd deploy`).
|
|
41782
|
+
* - `--from-cfn-stack [<cfn-stack-name>]` (CFn-backed; reads a
|
|
41783
|
+
* deployed CloudFormation stack via `DescribeStackResources`).
|
|
41784
|
+
*
|
|
41785
|
+
* This module centralizes:
|
|
41786
|
+
*
|
|
41787
|
+
* - The mutual-exclusion check (rejected at the CLI layer before any
|
|
41788
|
+
* synth / AWS call fires).
|
|
41789
|
+
* - The bare-vs-explicit `--from-cfn-stack` resolution: bare flag uses
|
|
41790
|
+
* the cdkd stack name; explicit value overrides. Matches the
|
|
41791
|
+
* `cdkd import --migrate-from-cloudformation` precedent.
|
|
41792
|
+
* - Region resolution for the CFn client: reuses the existing
|
|
41793
|
+
* `--stack-region` flag (no separate `--cfn-stack-region`) per
|
|
41794
|
+
* issue #606 recommendation.
|
|
41795
|
+
*
|
|
41796
|
+
* Returns `undefined` when neither flag is set — the caller skips the
|
|
41797
|
+
* substitution pass entirely (which is the pre-issue-#606 behavior
|
|
41798
|
+
* when `--from-state` was absent).
|
|
41799
|
+
*/
|
|
41800
|
+
/**
|
|
41801
|
+
* Default cdkd stack name → CFn stack name. Matches the
|
|
41802
|
+
* `cdkd import --migrate-from-cloudformation` bare-form precedent:
|
|
41803
|
+
* bare `--from-cfn-stack` uses the cdkd stack name verbatim as the CFn
|
|
41804
|
+
* stack name (typical for CDK apps where the names are the same).
|
|
41805
|
+
* Override by passing `--from-cfn-stack <explicit-name>`.
|
|
41806
|
+
*
|
|
41807
|
+
* Exported for unit testing.
|
|
41808
|
+
*/
|
|
41809
|
+
function resolveCfnStackName(fromCfnStack, cdkdStackName) {
|
|
41810
|
+
if (typeof fromCfnStack === "string") return fromCfnStack;
|
|
41811
|
+
return cdkdStackName;
|
|
41812
|
+
}
|
|
41813
|
+
/**
|
|
41814
|
+
* Resolve the region used for the CFn client. The CFn provider is
|
|
41815
|
+
* region-bound at construction time; we apply the precedence chain
|
|
41816
|
+
* `--stack-region` > `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION`
|
|
41817
|
+
* > the synth-derived stack region. Throws `LocalStateSourceError`
|
|
41818
|
+
* when none of these signals is set — the CFn API call needs a
|
|
41819
|
+
* concrete region and silently picking `us-east-1` would query the
|
|
41820
|
+
* wrong stack environment (worst case: succeed against the wrong
|
|
41821
|
+
* stack and return wrong physical IDs). Distinct from
|
|
41822
|
+
* `loadStateForStack`'s behavior: the S3 state bucket name is
|
|
41823
|
+
* account-scoped (not region-scoped) and the bucket's region is
|
|
41824
|
+
* auto-discovered via `GetBucketLocation`, so the S3 provider can
|
|
41825
|
+
* tolerate a missing region. The CFn provider cannot.
|
|
41826
|
+
*
|
|
41827
|
+
* Exported for unit testing.
|
|
41828
|
+
*/
|
|
41829
|
+
function resolveCfnRegion(options, synthRegion) {
|
|
41830
|
+
const region = options.stackRegion ?? options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? synthRegion;
|
|
41831
|
+
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.");
|
|
41832
|
+
return region;
|
|
41833
|
+
}
|
|
41834
|
+
/**
|
|
41835
|
+
* Common error class for the mutual-exclusion check so the CLI layer
|
|
41836
|
+
* can surface a consistent error message from all four commands.
|
|
41837
|
+
*/
|
|
41838
|
+
var LocalStateSourceError = class extends Error {
|
|
41839
|
+
constructor(message) {
|
|
41840
|
+
super(message);
|
|
41841
|
+
this.name = "LocalStateSourceError";
|
|
41842
|
+
}
|
|
41843
|
+
};
|
|
41844
|
+
/**
|
|
41845
|
+
* Pre-flight check for `--from-cfn-stack <explicit-name>` when the
|
|
41846
|
+
* caller will construct one provider per routed stack (`local
|
|
41847
|
+
* start-api` / `local start-service`). An explicit value applies to
|
|
41848
|
+
* the SINGLE CFn stack named — when multiple cdkd stacks are routed,
|
|
41849
|
+
* every one of them would query the same CFn stack, yielding silent
|
|
41850
|
+
* wrong-physical-id substitutions for any logical id that happens to
|
|
41851
|
+
* collide between the user's stacks. Reject at the CLI layer instead.
|
|
41852
|
+
*
|
|
41853
|
+
* Bare `--from-cfn-stack` (the cdkdStackName-default) is fine for
|
|
41854
|
+
* multi-stack: each routed stack reads its own CFn counterpart.
|
|
41855
|
+
* `--from-state` is also fine: cdkd's state is per-(stack, region).
|
|
41856
|
+
*
|
|
41857
|
+
* Call this from `start-api` / `start-service` BEFORE the per-stack
|
|
41858
|
+
* `createLocalStateProvider` loop when `routedStackCount > 1`.
|
|
41859
|
+
*/
|
|
41860
|
+
function rejectExplicitCfnStackWithMultipleStacks(options, routedStackCount) {
|
|
41861
|
+
if (routedStackCount <= 1) return;
|
|
41862
|
+
if (typeof options.fromCfnStack !== "string") return;
|
|
41863
|
+
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.`);
|
|
41864
|
+
}
|
|
41865
|
+
/**
|
|
41866
|
+
* Pick and construct the right `LocalStateProvider` for the supplied
|
|
41867
|
+
* flag set. Returns `undefined` when neither flag is set (caller skips
|
|
41868
|
+
* the substitution pass). Throws `LocalStateSourceError` when both
|
|
41869
|
+
* flags are set (mutually exclusive — different state sources, asking
|
|
41870
|
+
* for both is ambiguous about precedence).
|
|
41871
|
+
*
|
|
41872
|
+
* `cdkdStackName` is the cdkd-side stack name the local command
|
|
41873
|
+
* resolved to its target — needed to apply the bare-`--from-cfn-stack`
|
|
41874
|
+
* default. `synthRegion` is the synth-derived stack region (`env.region`
|
|
41875
|
+
* on the CDK stack) — fallback for the CFn client when no explicit
|
|
41876
|
+
* region override is set.
|
|
41877
|
+
*
|
|
41878
|
+
* For multi-stack callers (`local start-api` / `local start-service`)
|
|
41879
|
+
* also invoke `rejectExplicitCfnStackWithMultipleStacks` BEFORE the
|
|
41880
|
+
* per-stack loop — see that helper's docstring for the rationale.
|
|
41881
|
+
*/
|
|
41882
|
+
function createLocalStateProvider(options, cdkdStackName, synthRegion) {
|
|
41883
|
+
const cfnStackOpt = options.fromCfnStack;
|
|
41884
|
+
const cfnFlagPresent = cfnStackOpt !== void 0 && cfnStackOpt !== false;
|
|
41885
|
+
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).");
|
|
41886
|
+
if (options.fromState) return new S3LocalStateProvider({
|
|
41887
|
+
statePrefix: options.statePrefix,
|
|
41888
|
+
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
41889
|
+
...options.region !== void 0 && { region: options.region },
|
|
41890
|
+
...options.profile !== void 0 && { profile: options.profile },
|
|
41891
|
+
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion }
|
|
41892
|
+
});
|
|
41893
|
+
if (cfnFlagPresent) return new CfnLocalStateProvider({
|
|
41894
|
+
cfnStackName: resolveCfnStackName(cfnStackOpt, cdkdStackName),
|
|
41895
|
+
region: resolveCfnRegion(options, synthRegion),
|
|
41896
|
+
...options.profile !== void 0 && { profile: options.profile }
|
|
41897
|
+
});
|
|
41898
|
+
}
|
|
41899
|
+
|
|
41488
41900
|
//#endregion
|
|
41489
41901
|
//#region src/local/intrinsic-image.ts
|
|
41490
41902
|
/**
|
|
@@ -53415,7 +53827,7 @@ async function localStartApiCommand(target, options) {
|
|
|
53415
53827
|
const m = buildCorsConfigByApiId(stack.template);
|
|
53416
53828
|
for (const [k, v] of m) corsConfigByApiId.set(k, v);
|
|
53417
53829
|
}
|
|
53418
|
-
const stateByStack = options.fromState ? await loadStateForRoutedStacks(targetStacks, routes, routesWithAuth, options) : /* @__PURE__ */ new Map();
|
|
53830
|
+
const stateByStack = options.fromState || options.fromCfnStack !== void 0 && options.fromCfnStack !== false ? await loadStateForRoutedStacks(targetStacks, routes, routesWithAuth, options) : /* @__PURE__ */ new Map();
|
|
53419
53831
|
const lambdaIds = uniqueLambdaIds(routes, routesWithAuth, webSocketApis);
|
|
53420
53832
|
const specs = /* @__PURE__ */ new Map();
|
|
53421
53833
|
for (let i = 0; i < lambdaIds.length; i++) {
|
|
@@ -54455,13 +54867,14 @@ function envHasIntrinsicValue$1(templateEnv) {
|
|
|
54455
54867
|
return false;
|
|
54456
54868
|
}
|
|
54457
54869
|
/**
|
|
54458
|
-
* Load
|
|
54870
|
+
* Load deployed state for every stack that owns a routed Lambda. Once
|
|
54459
54871
|
* per `synthesizeAndBuild` pass (initial boot + every reload), so a
|
|
54460
54872
|
* Lambda's per-spec build does not pay one round-trip per Lambda. Per-
|
|
54461
54873
|
* 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
|
|
54874
|
+
* degrade to warn-and-fall-back via the active `LocalStateProvider` —
|
|
54875
|
+
* the affected stack's reachable Lambdas behave as if `--from-state` /
|
|
54876
|
+
* `--from-cfn-stack` were not set, while sibling stacks with loadable
|
|
54877
|
+
* state still substitute.
|
|
54465
54878
|
*
|
|
54466
54879
|
* Pseudo parameters are resolved per stack and only when at least one
|
|
54467
54880
|
* reachable Lambda in that stack has an intrinsic-valued env entry
|
|
@@ -54490,24 +54903,31 @@ async function loadStateForRoutedStacks(stacks, routes, routesWithAuth, options)
|
|
|
54490
54903
|
}
|
|
54491
54904
|
return false;
|
|
54492
54905
|
};
|
|
54906
|
+
rejectExplicitCfnStackWithMultipleStacks(options, reachableStackNames.size);
|
|
54493
54907
|
for (const stackName of reachableStackNames) {
|
|
54494
54908
|
const stack = stacks.find((s) => s.stackName === stackName);
|
|
54495
54909
|
if (!stack) continue;
|
|
54496
|
-
const
|
|
54497
|
-
|
|
54498
|
-
|
|
54499
|
-
|
|
54500
|
-
|
|
54501
|
-
|
|
54502
|
-
|
|
54503
|
-
|
|
54504
|
-
|
|
54505
|
-
|
|
54506
|
-
|
|
54507
|
-
|
|
54910
|
+
const provider = createLocalStateProvider(options, stack.stackName, stack.region);
|
|
54911
|
+
if (!provider) continue;
|
|
54912
|
+
try {
|
|
54913
|
+
const loaded = await provider.load(stack.stackName, stack.region);
|
|
54914
|
+
if (!loaded) continue;
|
|
54915
|
+
const bundle = { state: {
|
|
54916
|
+
version: 1,
|
|
54917
|
+
stackName: stack.stackName,
|
|
54918
|
+
resources: loaded.resources,
|
|
54919
|
+
outputs: loaded.outputs,
|
|
54920
|
+
lastModified: 0
|
|
54921
|
+
} };
|
|
54922
|
+
if (stackHasIntrinsicEnv(stackName)) {
|
|
54923
|
+
const pseudo = await resolvePseudoParametersForStartApi(loaded.region, options);
|
|
54924
|
+
if (pseudo) bundle.pseudoParameters = pseudo;
|
|
54925
|
+
}
|
|
54926
|
+
out.set(stackName, bundle);
|
|
54927
|
+
logger.debug(`${provider.label}: loaded state for ${stackName} (${loaded.region})`);
|
|
54928
|
+
} finally {
|
|
54929
|
+
provider.dispose();
|
|
54508
54930
|
}
|
|
54509
|
-
out.set(stackName, bundle);
|
|
54510
|
-
logger.debug(`--from-state: loaded state for ${stackName} (${loaded.region})`);
|
|
54511
54931
|
}
|
|
54512
54932
|
return out;
|
|
54513
54933
|
}
|
|
@@ -54520,7 +54940,7 @@ async function loadStateForRoutedStacks(stacks, routes, routesWithAuth, options)
|
|
|
54520
54940
|
* region takes priority).
|
|
54521
54941
|
*
|
|
54522
54942
|
* Region precedence: `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION` >
|
|
54523
|
-
* the state record's region (returned by `
|
|
54943
|
+
* the state record's region (returned by the active `LocalStateProvider`).
|
|
54524
54944
|
*/
|
|
54525
54945
|
async function resolvePseudoParametersForStartApi(stateRegion, options) {
|
|
54526
54946
|
const logger = getLogger();
|
|
@@ -54584,7 +55004,7 @@ function resolveMtlsConfig(options) {
|
|
|
54584
55004
|
* Builder for the `start-api` subcommand. Wired up by `local.ts`.
|
|
54585
55005
|
*/
|
|
54586
55006
|
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
|
|
55007
|
+
const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and REST v1 AWS_IAM (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
55008
|
[
|
|
54589
55009
|
...commonOptions,
|
|
54590
55010
|
...appOptions,
|
|
@@ -55478,6 +55898,7 @@ async function localRunTaskCommand(target, options) {
|
|
|
55478
55898
|
const state = createEcsRunState();
|
|
55479
55899
|
let sigintHandler;
|
|
55480
55900
|
let sigintCount = 0;
|
|
55901
|
+
let stateProvider;
|
|
55481
55902
|
let cleanupPromise;
|
|
55482
55903
|
const cleanup = async () => {
|
|
55483
55904
|
if (!cleanupPromise) cleanupPromise = (async () => {
|
|
@@ -55510,34 +55931,22 @@ async function localRunTaskCommand(target, options) {
|
|
|
55510
55931
|
...options.profile && { macroExpandS3ClientOpts: { profile: options.profile } }
|
|
55511
55932
|
};
|
|
55512
55933
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
55513
|
-
const
|
|
55934
|
+
const candidate = pickCandidateStack$1(parseEcsTarget(target).stackPattern, stacks);
|
|
55935
|
+
stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", candidate?.region);
|
|
55936
|
+
const imageContext = await buildEcsImageResolutionContext$1(candidate, stateProvider, options);
|
|
55514
55937
|
const task = resolveEcsTaskTarget(target, stacks, imageContext);
|
|
55515
55938
|
logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
|
|
55516
55939
|
const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
|
|
55517
|
-
|
|
55518
|
-
if (options.fromState && taskNeeds.needsCrossStackResolver) {
|
|
55940
|
+
if (stateProvider && taskNeeds.needsCrossStackResolver) {
|
|
55519
55941
|
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
|
-
|
|
55942
|
+
const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
|
|
55943
|
+
if (resolver) await applyCrossStackResolverToTask(task, {
|
|
55944
|
+
resources: imageContext?.stateResources ?? {},
|
|
55945
|
+
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
55946
|
+
consumerRegion,
|
|
55947
|
+
crossStackResolver: resolver
|
|
55525
55948
|
});
|
|
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.");
|
|
55949
|
+
} 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
55950
|
sigintHandler = () => {
|
|
55542
55951
|
sigintCount += 1;
|
|
55543
55952
|
if (sigintCount >= 2) {
|
|
@@ -55583,6 +55992,7 @@ async function localRunTaskCommand(target, options) {
|
|
|
55583
55992
|
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
55584
55993
|
} finally {
|
|
55585
55994
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
55995
|
+
if (stateProvider) stateProvider.dispose();
|
|
55586
55996
|
if (!options.detach) await cleanup();
|
|
55587
55997
|
}
|
|
55588
55998
|
}
|
|
@@ -55635,19 +56045,18 @@ async function assumeTaskRole$1(roleArn, region) {
|
|
|
55635
56045
|
*
|
|
55636
56046
|
* Tier 1 (pseudo parameters) fires `sts:GetCallerIdentity` once for
|
|
55637
56047
|
* `${AWS::AccountId}`; region / partition / URL suffix come from the CLI
|
|
55638
|
-
* (`--region` → env vars → synth-derived stack region). Tier 2
|
|
55639
|
-
*
|
|
55640
|
-
*
|
|
55641
|
-
*
|
|
56048
|
+
* (`--region` → env vars → synth-derived stack region). Tier 2 (state
|
|
56049
|
+
* load) routes through the active {@link LocalStateProvider} so both
|
|
56050
|
+
* `--from-state` and `--from-cfn-stack` produce the same downstream
|
|
56051
|
+
* context shape (issue #606).
|
|
55642
56052
|
*/
|
|
55643
|
-
async function buildEcsImageResolutionContext$1(
|
|
56053
|
+
async function buildEcsImageResolutionContext$1(candidate, stateProvider, options) {
|
|
55644
56054
|
const logger = getLogger();
|
|
55645
|
-
const candidate = pickCandidateStack$1(parseEcsTarget(target).stackPattern, stacks);
|
|
55646
56055
|
if (!candidate) return void 0;
|
|
55647
56056
|
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
55648
56057
|
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) return;
|
|
55649
56058
|
const ctx = {};
|
|
55650
|
-
const wantsPseudoForEnvOrSecret =
|
|
56059
|
+
const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
|
|
55651
56060
|
if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
|
|
55652
56061
|
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
|
|
55653
56062
|
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 +56077,11 @@ async function buildEcsImageResolutionContext$1(target, stacks, options) {
|
|
|
55668
56077
|
};
|
|
55669
56078
|
}
|
|
55670
56079
|
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).");
|
|
56080
|
+
if (stateProvider && wantsState) {
|
|
56081
|
+
const loaded = await stateProvider.load(candidate.stackName, candidate.region);
|
|
56082
|
+
if (loaded) ctx.stateResources = loaded.resources;
|
|
56083
|
+
} 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.");
|
|
56084
|
+
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
56085
|
return ctx;
|
|
55683
56086
|
}
|
|
55684
56087
|
function pickCandidateStack$1(stackPattern, stacks) {
|
|
@@ -55721,7 +56124,7 @@ function readEnvOverridesFile$2(filePath) {
|
|
|
55721
56124
|
return parsed;
|
|
55722
56125
|
}
|
|
55723
56126
|
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
|
|
56127
|
+
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
56128
|
[
|
|
55726
56129
|
...commonOptions,
|
|
55727
56130
|
...appOptions,
|
|
@@ -56797,6 +57200,7 @@ async function localStartServiceCommand(targets, options) {
|
|
|
56797
57200
|
warnIfDeprecatedRegion(options);
|
|
56798
57201
|
const skipPull = options.pull === false;
|
|
56799
57202
|
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'.");
|
|
57203
|
+
rejectExplicitCfnStackWithMultipleStacks(options, targets.length);
|
|
56800
57204
|
const perTarget = targets.map((t) => ({
|
|
56801
57205
|
target: t,
|
|
56802
57206
|
runState: createServiceRunState()
|
|
@@ -56893,34 +57297,36 @@ async function localStartServiceCommand(targets, options) {
|
|
|
56893
57297
|
* outer code to wait + tear down.
|
|
56894
57298
|
*/
|
|
56895
57299
|
async function bootOneTarget(target, runState, stacks, options, discovery, skipPull) {
|
|
57300
|
+
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
57301
|
+
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", candidate?.region);
|
|
57302
|
+
try {
|
|
57303
|
+
return await runOneTarget(target, runState, stacks, options, discovery, skipPull, stateProvider);
|
|
57304
|
+
} finally {
|
|
57305
|
+
if (stateProvider) stateProvider.dispose();
|
|
57306
|
+
}
|
|
57307
|
+
}
|
|
57308
|
+
async function runOneTarget(target, runState, stacks, options, discovery, skipPull, stateProvider) {
|
|
56896
57309
|
const logger = getLogger();
|
|
56897
|
-
const imageContext = await buildEcsImageResolutionContext(target, stacks, options);
|
|
57310
|
+
const imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
|
|
56898
57311
|
const service = resolveEcsServiceTarget(target, stacks, imageContext);
|
|
56899
57312
|
logger.info(`Target: ${service.stack.stackName}/${service.serviceLogicalId} (service=${service.serviceName}, desiredCount=${service.desiredCount}, task=${service.task.taskDefinitionLogicalId})`);
|
|
56900
57313
|
for (const w of service.warnings) logger.warn(w);
|
|
56901
57314
|
if (service.serviceConnect) logger.info(`Service Connect: namespace='${service.serviceConnect.namespaceName}', ${service.serviceConnect.services.length} service(s) registered for peer discovery.`);
|
|
56902
57315
|
if (service.serviceRegistries.length > 0) logger.info(`Cloud Map: ${service.serviceRegistries.length} ServiceRegistry binding(s).`);
|
|
56903
57316
|
const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === service.stack.stackName) ?? service.stack);
|
|
56904
|
-
if (
|
|
57317
|
+
if (stateProvider && taskNeeds.needsCrossStackResolver) {
|
|
56905
57318
|
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 {
|
|
57319
|
+
const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
|
|
57320
|
+
if (resolver) {
|
|
56913
57321
|
const subContext = {
|
|
56914
57322
|
resources: imageContext?.stateResources ?? {},
|
|
56915
57323
|
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
56916
57324
|
consumerRegion,
|
|
56917
|
-
crossStackResolver:
|
|
57325
|
+
crossStackResolver: resolver
|
|
56918
57326
|
};
|
|
56919
57327
|
await applyCrossStackResolverToTask(service.task, subContext);
|
|
56920
|
-
} finally {
|
|
56921
|
-
built.dispose();
|
|
56922
57328
|
}
|
|
56923
|
-
} else if (!
|
|
57329
|
+
} 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
57330
|
let assumedCredentials;
|
|
56925
57331
|
let resolvedRoleArn;
|
|
56926
57332
|
if (options.assumeTaskRole === true) {
|
|
@@ -56989,14 +57395,14 @@ async function assumeTaskRole(roleArn, region) {
|
|
|
56989
57395
|
* the candidate stack picker differs because services and tasks share
|
|
56990
57396
|
* the same stack-pattern grammar.
|
|
56991
57397
|
*/
|
|
56992
|
-
async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
57398
|
+
async function buildEcsImageResolutionContext(target, stacks, options, stateProvider) {
|
|
56993
57399
|
const logger = getLogger();
|
|
56994
57400
|
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
56995
57401
|
if (!candidate) return void 0;
|
|
56996
57402
|
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
56997
57403
|
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) return;
|
|
56998
57404
|
const ctx = {};
|
|
56999
|
-
const wantsPseudoForEnvOrSecret =
|
|
57405
|
+
const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
|
|
57000
57406
|
if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
|
|
57001
57407
|
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
|
|
57002
57408
|
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 +57423,11 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
57017
57423
|
};
|
|
57018
57424
|
}
|
|
57019
57425
|
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.");
|
|
57426
|
+
if (stateProvider && wantsState) {
|
|
57427
|
+
const loaded = await stateProvider.load(candidate.stackName, candidate.region);
|
|
57428
|
+
if (loaded) ctx.stateResources = loaded.resources;
|
|
57429
|
+
} 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.");
|
|
57430
|
+
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
57431
|
return ctx;
|
|
57032
57432
|
}
|
|
57033
57433
|
function pickCandidateStack(stackPattern, stacks) {
|
|
@@ -57098,7 +57498,7 @@ function parseRestartPolicy(raw) {
|
|
|
57098
57498
|
throw new LocalStartServiceError(`--restart-policy must be one of 'on-failure', 'always', or 'none' (got '${raw}').`);
|
|
57099
57499
|
}
|
|
57100
57500
|
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
|
|
57501
|
+
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
57502
|
[
|
|
57103
57503
|
...commonOptions,
|
|
57104
57504
|
...appOptions,
|
|
@@ -57212,19 +57612,19 @@ async function localInvokeCommand(target, options) {
|
|
|
57212
57612
|
let stateAudit;
|
|
57213
57613
|
let templateEnv = getTemplateEnv(lambda.resource);
|
|
57214
57614
|
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
|
-
});
|
|
57615
|
+
const stateProvider = createLocalStateProvider(options, lambda.stack.stackName, lambda.stack.region);
|
|
57616
|
+
if (stateProvider) try {
|
|
57617
|
+
const loaded = await stateProvider.load(lambda.stack.stackName, lambda.stack.region);
|
|
57224
57618
|
if (loaded) {
|
|
57225
|
-
stateForRoleHint =
|
|
57619
|
+
stateForRoleHint = {
|
|
57620
|
+
version: 1,
|
|
57621
|
+
stackName: lambda.stack.stackName,
|
|
57622
|
+
resources: loaded.resources,
|
|
57623
|
+
outputs: loaded.outputs,
|
|
57624
|
+
lastModified: 0
|
|
57625
|
+
};
|
|
57226
57626
|
const subContext = {
|
|
57227
|
-
resources: loaded.
|
|
57627
|
+
resources: loaded.resources,
|
|
57228
57628
|
consumerRegion: loaded.region
|
|
57229
57629
|
};
|
|
57230
57630
|
if (envHasIntrinsicValue(templateEnv)) {
|
|
@@ -57232,36 +57632,24 @@ async function localInvokeCommand(target, options) {
|
|
|
57232
57632
|
if (pseudo) subContext.pseudoParameters = pseudo;
|
|
57233
57633
|
}
|
|
57234
57634
|
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
|
-
}
|
|
57635
|
+
const resolver = await stateProvider.buildCrossStackResolver(loaded.region);
|
|
57636
|
+
if (resolver) subContext.crossStackResolver = resolver;
|
|
57257
57637
|
}
|
|
57638
|
+
const { env, audit } = await substituteEnvVarsFromStateAsync(templateEnv, subContext);
|
|
57639
|
+
templateEnv = env;
|
|
57640
|
+
stateAudit = audit;
|
|
57641
|
+
const label = stateProvider.label;
|
|
57642
|
+
for (const key of audit.resolvedKeys) logger.debug(`${label}: substituted env var ${key}`);
|
|
57643
|
+
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
57644
|
}
|
|
57645
|
+
} finally {
|
|
57646
|
+
stateProvider.dispose();
|
|
57259
57647
|
}
|
|
57260
57648
|
const overrides = readEnvOverridesFile(options.envVars);
|
|
57261
57649
|
const envResult = resolveEnvVars(lambda.logicalId, templateEnv, overrides);
|
|
57262
57650
|
for (const key of envResult.unresolved) {
|
|
57263
57651
|
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.`);
|
|
57652
|
+
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
57653
|
}
|
|
57266
57654
|
let resolvedAssumeRoleArn;
|
|
57267
57655
|
if (typeof options.assumeRole === "string") resolvedAssumeRoleArn = options.assumeRole;
|
|
@@ -57831,7 +58219,7 @@ function pickReferencedLogicalId(intrinsic) {
|
|
|
57831
58219
|
*/
|
|
57832
58220
|
function createLocalCommand() {
|
|
57833
58221
|
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
|
|
58222
|
+
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
58223
|
[
|
|
57836
58224
|
...commonOptions,
|
|
57837
58225
|
...appOptions,
|
|
@@ -58955,7 +59343,7 @@ function reorderArgs(argv) {
|
|
|
58955
59343
|
*/
|
|
58956
59344
|
async function main() {
|
|
58957
59345
|
const program = new Command();
|
|
58958
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
59346
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.158.0");
|
|
58959
59347
|
program.addCommand(createBootstrapCommand());
|
|
58960
59348
|
program.addCommand(createSynthCommand());
|
|
58961
59349
|
program.addCommand(createListCommand());
|