@go-to-k/cdkd 0.31.2 → 0.32.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 +46 -0
- package/dist/cli.js +79 -2
- package/dist/cli.js.map +3 -3
- package/dist/go-to-k-cdkd-0.32.0.tgz +0 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.31.2.tgz +0 -0
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
- **S3-based state management**: No DynamoDB required, uses S3 conditional writes for locking
|
|
26
26
|
- **DAG-based parallelization**: Analyze `Ref`/`Fn::GetAtt` dependencies and execute in parallel
|
|
27
27
|
- **`--no-wait` for async resources**: Skip the multi-minute wait on CloudFront / RDS / ElastiCache / NAT Gateway and return as soon as the create call returns (CloudFormation always blocks)
|
|
28
|
+
- **`--aggressive-vpc-parallel`**: Drop CDK-injected defensive `DependsOn` edges from VPC Lambdas onto private-subnet routes so `CloudFront::Distribution` and `Lambda::Url` start their ~3-min propagation in parallel with NAT Gateway stabilization (~50% faster on VPC + Lambda + CloudFront stacks)
|
|
28
29
|
|
|
29
30
|
> **Note**: Resource types not covered by either SDK Providers or Cloud Control API cannot be deployed with cdkd. If you encounter an unsupported resource type, deployment will fail with a clear error message.
|
|
30
31
|
|
|
@@ -405,6 +406,51 @@ ElastiCache) don't apply to destroy either — their providers are
|
|
|
405
406
|
already non-blocking on delete because they're leaves in the destroy
|
|
406
407
|
DAG.
|
|
407
408
|
|
|
409
|
+
## `--aggressive-vpc-parallel`: relax CDK-defensive VPC route DependsOn
|
|
410
|
+
|
|
411
|
+
CDK synth eagerly injects `DependsOn` from VPC Lambdas (and adjacent
|
|
412
|
+
IAM Role / Policy / Lambda::Url / EventSourceMapping resources) onto
|
|
413
|
+
the private subnet's `DefaultRoute` / `RouteTableAssociation` so that
|
|
414
|
+
nothing tries to invoke the Lambda before its egress path to the
|
|
415
|
+
internet is up. The dependency is real at *runtime* (a Lambda code
|
|
416
|
+
call to a third-party API can't reach the internet without a NAT
|
|
417
|
+
route), but it is NOT required at *deploy time* — `CreateFunction` /
|
|
418
|
+
`CreateFunctionUrlConfig` / `AddPermission` /
|
|
419
|
+
`CreateEventSourceMapping` all accept a function in `Pending` state.
|
|
420
|
+
|
|
421
|
+
For VPC + Lambda + CloudFront stacks this turns into a serial
|
|
422
|
+
critical path:
|
|
423
|
+
|
|
424
|
+
```text
|
|
425
|
+
NAT GW (~2-3 min) → DefaultRoute → Lambda → Lambda::Url → Distribution propagation (~3 min)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Pass `--aggressive-vpc-parallel` to drop the route DependsOn so
|
|
429
|
+
Distribution + Lambda::Url dispatch right after IAM Role / Subnet are
|
|
430
|
+
ready and propagate in parallel with NAT stabilization:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
cdkd deploy --aggressive-vpc-parallel
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
| Mode | Critical path | Total |
|
|
437
|
+
| --- | --- | --- |
|
|
438
|
+
| Default | NAT → Lambda → CF (serial) | ~6 min |
|
|
439
|
+
| `--aggressive-vpc-parallel` | max(NAT, CF) | ~3 min |
|
|
440
|
+
|
|
441
|
+
Measured **−45.6%** on `tests/integration/bench-cdk-sample` (387s
|
|
442
|
+
baseline → 211s relaxed).
|
|
443
|
+
|
|
444
|
+
Off by default for v1: opt-in is the conservative play because
|
|
445
|
+
CloudFront `Create` / `Delete` are each ~5 min, so a Lambda-side
|
|
446
|
+
async failure incurs a high rollback cost. Deploy-only —
|
|
447
|
+
`cdkd destroy` doesn't accept it (the route DependsOn doesn't
|
|
448
|
+
constrain delete-time correctness; Lambda hyperplane ENI release
|
|
449
|
+
is the actual destroy bottleneck).
|
|
450
|
+
|
|
451
|
+
See [docs/cli-reference.md](docs/cli-reference.md) for the full
|
|
452
|
+
type-pair allowlist, implementation pointers, and trade-off notes.
|
|
453
|
+
|
|
408
454
|
## Other CLI flags
|
|
409
455
|
|
|
410
456
|
For concurrency knobs (`--concurrency`, `--stack-concurrency`,
|
package/dist/cli.js
CHANGED
|
@@ -610,6 +610,10 @@ var noWaitOption = new Option(
|
|
|
610
610
|
"--no-wait",
|
|
611
611
|
"Skip waiting for async resources to stabilize (CloudFront, RDS, ElastiCache, NAT Gateway)"
|
|
612
612
|
);
|
|
613
|
+
var aggressiveVpcParallelOption = new Option(
|
|
614
|
+
"--aggressive-vpc-parallel",
|
|
615
|
+
"Relax CDK-injected VPC route DependsOn to let CloudFront/Lambda::Url create in parallel with NAT GW stabilization"
|
|
616
|
+
).default(false);
|
|
613
617
|
var deployOptions = [
|
|
614
618
|
new Option("--concurrency <number>", "Maximum concurrent resource operations").default(10).argParser((value) => parseInt(value, 10)),
|
|
615
619
|
new Option("--stack-concurrency <number>", "Maximum concurrent stack deployments").default(4).argParser((value) => parseInt(value, 10)),
|
|
@@ -622,6 +626,7 @@ var deployOptions = [
|
|
|
622
626
|
new Option("--skip-assets", "Skip asset publishing").default(false),
|
|
623
627
|
new Option("--no-rollback", "Skip rollback on deployment failure"),
|
|
624
628
|
noWaitOption,
|
|
629
|
+
aggressiveVpcParallelOption,
|
|
625
630
|
new Option(
|
|
626
631
|
"-e, --exclusively",
|
|
627
632
|
"Only deploy requested stacks, do not include dependencies"
|
|
@@ -4699,6 +4704,58 @@ function collectRefIds(value, out) {
|
|
|
4699
4704
|
}
|
|
4700
4705
|
}
|
|
4701
4706
|
|
|
4707
|
+
// src/analyzer/cdk-defensive-deps.ts
|
|
4708
|
+
var DEFENSIVE_DEPENDS_ON_TYPE_PAIRS = [
|
|
4709
|
+
// VPC Lambda's execution Role (and its inline Policy) get DependsOn'd onto
|
|
4710
|
+
// the route only because CDK assumes the Lambda will run before the route
|
|
4711
|
+
// is up. The Role/Policy create call itself is VPC-agnostic.
|
|
4712
|
+
{ fromType: "AWS::IAM::Role", toType: "AWS::EC2::Route" },
|
|
4713
|
+
{ fromType: "AWS::IAM::Role", toType: "AWS::EC2::SubnetRouteTableAssociation" },
|
|
4714
|
+
{ fromType: "AWS::IAM::Policy", toType: "AWS::EC2::Route" },
|
|
4715
|
+
{ fromType: "AWS::IAM::Policy", toType: "AWS::EC2::SubnetRouteTableAssociation" },
|
|
4716
|
+
// VPC Lambda itself: CreateFunction returns synchronously while the
|
|
4717
|
+
// function is still in Pending; the route only matters once the function
|
|
4718
|
+
// is invoked at runtime.
|
|
4719
|
+
{ fromType: "AWS::Lambda::Function", toType: "AWS::EC2::Route" },
|
|
4720
|
+
{ fromType: "AWS::Lambda::Function", toType: "AWS::EC2::SubnetRouteTableAssociation" },
|
|
4721
|
+
// Lambda::Url is just a deterministic URL derivation off the function; it
|
|
4722
|
+
// doesn't need the function's runtime egress to exist.
|
|
4723
|
+
{ fromType: "AWS::Lambda::Url", toType: "AWS::EC2::Route" },
|
|
4724
|
+
{ fromType: "AWS::Lambda::Url", toType: "AWS::EC2::SubnetRouteTableAssociation" },
|
|
4725
|
+
// EventSourceMapping just registers the wire-up; AWS handles delivery
|
|
4726
|
+
// async and will retry once the function reaches Active.
|
|
4727
|
+
{ fromType: "AWS::Lambda::EventSourceMapping", toType: "AWS::EC2::Route" },
|
|
4728
|
+
{
|
|
4729
|
+
fromType: "AWS::Lambda::EventSourceMapping",
|
|
4730
|
+
toType: "AWS::EC2::SubnetRouteTableAssociation"
|
|
4731
|
+
}
|
|
4732
|
+
];
|
|
4733
|
+
function defensiveDependsOnToSkip(resource, template) {
|
|
4734
|
+
const skip = /* @__PURE__ */ new Set();
|
|
4735
|
+
if (!resource.DependsOn) {
|
|
4736
|
+
return skip;
|
|
4737
|
+
}
|
|
4738
|
+
const dependsOn = Array.isArray(resource.DependsOn) ? resource.DependsOn : [resource.DependsOn];
|
|
4739
|
+
for (const dep of dependsOn) {
|
|
4740
|
+
if (typeof dep !== "string")
|
|
4741
|
+
continue;
|
|
4742
|
+
const target = template.Resources[dep];
|
|
4743
|
+
if (!target)
|
|
4744
|
+
continue;
|
|
4745
|
+
const fromType = resource.Type;
|
|
4746
|
+
const toType = target.Type;
|
|
4747
|
+
if (!fromType || !toType)
|
|
4748
|
+
continue;
|
|
4749
|
+
const matched = DEFENSIVE_DEPENDS_ON_TYPE_PAIRS.some(
|
|
4750
|
+
(pair) => pair.fromType === fromType && pair.toType === toType
|
|
4751
|
+
);
|
|
4752
|
+
if (matched) {
|
|
4753
|
+
skip.add(dep);
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
return skip;
|
|
4757
|
+
}
|
|
4758
|
+
|
|
4702
4759
|
// src/analyzer/dag-builder.ts
|
|
4703
4760
|
var { Graph, alg } = graphlib;
|
|
4704
4761
|
var IAM_ROLE_POLICY_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -4709,6 +4766,10 @@ var IAM_ROLE_POLICY_TYPES = /* @__PURE__ */ new Set([
|
|
|
4709
4766
|
var DagBuilder = class {
|
|
4710
4767
|
logger = getLogger().child("DagBuilder");
|
|
4711
4768
|
parser = new TemplateParser();
|
|
4769
|
+
options;
|
|
4770
|
+
constructor(options = {}) {
|
|
4771
|
+
this.options = options;
|
|
4772
|
+
}
|
|
4712
4773
|
/**
|
|
4713
4774
|
* Build dependency graph from CloudFormation template
|
|
4714
4775
|
*
|
|
@@ -4727,13 +4788,22 @@ var DagBuilder = class {
|
|
|
4727
4788
|
});
|
|
4728
4789
|
this.logger.debug(`Total nodes: ${resourceIds.length}`);
|
|
4729
4790
|
let edgeCount = 0;
|
|
4791
|
+
let relaxedEdgeCount = 0;
|
|
4730
4792
|
for (const logicalId of resourceIds) {
|
|
4731
4793
|
const resource = this.parser.getResource(template, logicalId);
|
|
4732
4794
|
if (!resource) {
|
|
4733
4795
|
continue;
|
|
4734
4796
|
}
|
|
4735
4797
|
const dependencies = this.parser.extractDependencies(resource);
|
|
4798
|
+
const skip = this.options.relaxCdkVpcDefensiveDeps ? defensiveDependsOnToSkip(resource, template) : null;
|
|
4736
4799
|
for (const depId of dependencies) {
|
|
4800
|
+
if (skip?.has(depId)) {
|
|
4801
|
+
relaxedEdgeCount++;
|
|
4802
|
+
this.logger.debug(
|
|
4803
|
+
`Skipped CDK-defensive DependsOn edge: ${depId} -> ${logicalId} (--aggressive-vpc-parallel)`
|
|
4804
|
+
);
|
|
4805
|
+
continue;
|
|
4806
|
+
}
|
|
4737
4807
|
if (graph.hasNode(depId)) {
|
|
4738
4808
|
graph.setEdge(depId, logicalId);
|
|
4739
4809
|
edgeCount++;
|
|
@@ -4745,6 +4815,11 @@ var DagBuilder = class {
|
|
|
4745
4815
|
}
|
|
4746
4816
|
}
|
|
4747
4817
|
}
|
|
4818
|
+
if (relaxedEdgeCount > 0) {
|
|
4819
|
+
this.logger.info(
|
|
4820
|
+
`[DagBuilder] Relaxed ${relaxedEdgeCount} CDK-defensive DependsOn edge(s) (--aggressive-vpc-parallel)`
|
|
4821
|
+
);
|
|
4822
|
+
}
|
|
4748
4823
|
this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);
|
|
4749
4824
|
edgeCount += this.addCustomResourcePolicyEdges(graph, template);
|
|
4750
4825
|
edgeCount += this.addLambdaVpcEdges(graph, template);
|
|
@@ -32957,7 +33032,9 @@ async function deployCommand(stacks, options) {
|
|
|
32957
33032
|
bucket: stateBucket,
|
|
32958
33033
|
prefix: options.statePrefix
|
|
32959
33034
|
};
|
|
32960
|
-
const dagBuilder = new DagBuilder(
|
|
33035
|
+
const dagBuilder = new DagBuilder({
|
|
33036
|
+
relaxCdkVpcDefensiveDeps: !!options.aggressiveVpcParallel
|
|
33037
|
+
});
|
|
32961
33038
|
const diffCalculator = new DiffCalculator();
|
|
32962
33039
|
const baseRegion = options.region || process.env["AWS_REGION"] || "us-east-1";
|
|
32963
33040
|
const switchRegion = (region2) => {
|
|
@@ -36359,7 +36436,7 @@ function reorderArgs(argv) {
|
|
|
36359
36436
|
}
|
|
36360
36437
|
async function main() {
|
|
36361
36438
|
const program = new Command13();
|
|
36362
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
36439
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.32.0");
|
|
36363
36440
|
program.addCommand(createBootstrapCommand());
|
|
36364
36441
|
program.addCommand(createSynthCommand());
|
|
36365
36442
|
program.addCommand(createListCommand());
|