@go-to-k/cdkd 0.1.0 → 0.3.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 +5 -4
- package/dist/cli.js +710 -185
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.3.0.tgz +0 -0
- package/dist/index.js +330 -137
- package/dist/index.js.map +4 -4
- package/package.json +3 -2
- package/dist/go-to-k-cdkd-0.1.0.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -3533,6 +3533,60 @@ var TemplateParser = class {
|
|
|
3533
3533
|
}
|
|
3534
3534
|
};
|
|
3535
3535
|
|
|
3536
|
+
// src/analyzer/lambda-vpc-deps.ts
|
|
3537
|
+
function extractLambdaVpcDeleteDeps(resources) {
|
|
3538
|
+
const edges = [];
|
|
3539
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3540
|
+
for (const [lambdaId, resource] of Object.entries(resources)) {
|
|
3541
|
+
if (resource.Type !== "AWS::Lambda::Function")
|
|
3542
|
+
continue;
|
|
3543
|
+
const vpcConfig = (resource.Properties ?? {})["VpcConfig"];
|
|
3544
|
+
if (!isObject(vpcConfig))
|
|
3545
|
+
continue;
|
|
3546
|
+
const targets = /* @__PURE__ */ new Set();
|
|
3547
|
+
collectRefIds(vpcConfig["SubnetIds"], targets);
|
|
3548
|
+
collectRefIds(vpcConfig["SecurityGroupIds"], targets);
|
|
3549
|
+
for (const targetId of targets) {
|
|
3550
|
+
if (targetId === lambdaId)
|
|
3551
|
+
continue;
|
|
3552
|
+
if (!(targetId in resources))
|
|
3553
|
+
continue;
|
|
3554
|
+
const key = `${lambdaId}\0${targetId}`;
|
|
3555
|
+
if (seen.has(key))
|
|
3556
|
+
continue;
|
|
3557
|
+
seen.add(key);
|
|
3558
|
+
edges.push({ before: lambdaId, after: targetId });
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
return edges;
|
|
3562
|
+
}
|
|
3563
|
+
function isObject(v) {
|
|
3564
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3565
|
+
}
|
|
3566
|
+
function collectRefIds(value, out) {
|
|
3567
|
+
if (value === null || value === void 0)
|
|
3568
|
+
return;
|
|
3569
|
+
if (Array.isArray(value)) {
|
|
3570
|
+
for (const item of value)
|
|
3571
|
+
collectRefIds(item, out);
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
if (!isObject(value))
|
|
3575
|
+
return;
|
|
3576
|
+
if (typeof value["Ref"] === "string") {
|
|
3577
|
+
const ref = value["Ref"];
|
|
3578
|
+
if (!ref.startsWith("AWS::"))
|
|
3579
|
+
out.add(ref);
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
if (Array.isArray(value["Fn::GetAtt"])) {
|
|
3583
|
+
const arr = value["Fn::GetAtt"];
|
|
3584
|
+
if (typeof arr[0] === "string")
|
|
3585
|
+
out.add(arr[0]);
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3536
3590
|
// src/analyzer/dag-builder.ts
|
|
3537
3591
|
var { Graph, alg } = graphlib;
|
|
3538
3592
|
var IAM_ROLE_POLICY_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -3581,6 +3635,7 @@ var DagBuilder = class {
|
|
|
3581
3635
|
}
|
|
3582
3636
|
this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);
|
|
3583
3637
|
edgeCount += this.addCustomResourcePolicyEdges(graph, template);
|
|
3638
|
+
edgeCount += this.addLambdaVpcEdges(graph, template);
|
|
3584
3639
|
if (!alg.isAcyclic(graph)) {
|
|
3585
3640
|
const cycles = this.findCycles(graph);
|
|
3586
3641
|
throw new DependencyError(
|
|
@@ -3773,6 +3828,41 @@ var DagBuilder = class {
|
|
|
3773
3828
|
}
|
|
3774
3829
|
return added;
|
|
3775
3830
|
}
|
|
3831
|
+
/**
|
|
3832
|
+
* Add edges from Subnets / SecurityGroups referenced by an
|
|
3833
|
+
* AWS::Lambda::Function VpcConfig to the Lambda itself.
|
|
3834
|
+
*
|
|
3835
|
+
* Same direction as a normal `Ref`-derived edge (Subnet -> Lambda), so for
|
|
3836
|
+
* deploy this just duplicates what extractDependencies already produced.
|
|
3837
|
+
* The point is robustness: if a future template massages the VpcConfig
|
|
3838
|
+
* shape in a way the recursive extractor doesn't anticipate, this pass
|
|
3839
|
+
* still ties the Lambda to its networking resources so that the
|
|
3840
|
+
* deletion-time reverse traversal continues to delete Lambda before
|
|
3841
|
+
* Subnet / SecurityGroup.
|
|
3842
|
+
*
|
|
3843
|
+
* Returns the number of NEW edges added (existing edges are skipped).
|
|
3844
|
+
*/
|
|
3845
|
+
addLambdaVpcEdges(graph, template) {
|
|
3846
|
+
const edges = extractLambdaVpcDeleteDeps(template.Resources);
|
|
3847
|
+
if (edges.length === 0)
|
|
3848
|
+
return 0;
|
|
3849
|
+
let added = 0;
|
|
3850
|
+
for (const edge of edges) {
|
|
3851
|
+
const depId = edge.after;
|
|
3852
|
+
const dependentId = edge.before;
|
|
3853
|
+
if (!graph.hasNode(depId) || !graph.hasNode(dependentId))
|
|
3854
|
+
continue;
|
|
3855
|
+
if (graph.hasEdge(depId, dependentId))
|
|
3856
|
+
continue;
|
|
3857
|
+
graph.setEdge(depId, dependentId);
|
|
3858
|
+
added++;
|
|
3859
|
+
this.logger.debug(`Added implicit edge (lambda vpc): ${depId} -> ${dependentId}`);
|
|
3860
|
+
}
|
|
3861
|
+
if (added > 0) {
|
|
3862
|
+
this.logger.debug(`Added ${added} implicit edges for Lambda VpcConfig`);
|
|
3863
|
+
}
|
|
3864
|
+
return added;
|
|
3865
|
+
}
|
|
3776
3866
|
isCustomResourceType(type) {
|
|
3777
3867
|
return type === "AWS::CloudFormation::CustomResource" || type.startsWith("Custom::");
|
|
3778
3868
|
}
|
|
@@ -4688,8 +4778,8 @@ var IntrinsicFunctionResolver = class {
|
|
|
4688
4778
|
return resource.attributes?.["CidrBlock"] || resource.properties?.["CidrBlock"];
|
|
4689
4779
|
case "Ipv6CidrBlocks": {
|
|
4690
4780
|
try {
|
|
4691
|
-
const { EC2Client:
|
|
4692
|
-
const ec2 = new
|
|
4781
|
+
const { EC2Client: EC2Client9, DescribeVpcsCommand: DescribeVpcsCommand3 } = await import("@aws-sdk/client-ec2");
|
|
4782
|
+
const ec2 = new EC2Client9({ region: this.resolverRegion });
|
|
4693
4783
|
const maxAttempts = 15;
|
|
4694
4784
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
4695
4785
|
const resp = await ec2.send(new DescribeVpcsCommand3({ VpcIds: [physicalId] }));
|
|
@@ -10897,9 +10987,11 @@ import {
|
|
|
10897
10987
|
GetFunctionCommand,
|
|
10898
10988
|
ResourceNotFoundException
|
|
10899
10989
|
} from "@aws-sdk/client-lambda";
|
|
10990
|
+
import { DescribeNetworkInterfacesCommand } from "@aws-sdk/client-ec2";
|
|
10900
10991
|
init_aws_clients();
|
|
10901
10992
|
var LambdaFunctionProvider = class {
|
|
10902
10993
|
lambdaClient;
|
|
10994
|
+
ec2Client;
|
|
10903
10995
|
logger = getLogger().child("LambdaFunctionProvider");
|
|
10904
10996
|
handledProperties = /* @__PURE__ */ new Map([
|
|
10905
10997
|
[
|
|
@@ -10919,13 +11011,22 @@ var LambdaFunctionProvider = class {
|
|
|
10919
11011
|
"Architectures",
|
|
10920
11012
|
"PackageType",
|
|
10921
11013
|
"TracingConfig",
|
|
10922
|
-
"EphemeralStorage"
|
|
11014
|
+
"EphemeralStorage",
|
|
11015
|
+
"VpcConfig"
|
|
10923
11016
|
])
|
|
10924
11017
|
]
|
|
10925
11018
|
]);
|
|
11019
|
+
// ENI detach polling configuration (overridable for tests).
|
|
11020
|
+
// Lambda VPC ENI detach is async and can take 20-40 minutes in the worst case;
|
|
11021
|
+
// we poll up to 10 minutes and then warn-and-continue, since downstream Subnet/SG
|
|
11022
|
+
// deletion has its own retry logic that handles a small remaining window.
|
|
11023
|
+
eniWaitTimeoutMs = 10 * 60 * 1e3;
|
|
11024
|
+
eniWaitInitialDelayMs = 1e4;
|
|
11025
|
+
eniWaitMaxDelayMs = 3e4;
|
|
10926
11026
|
constructor() {
|
|
10927
11027
|
const awsClients = getAwsClients();
|
|
10928
11028
|
this.lambdaClient = awsClients.lambda;
|
|
11029
|
+
this.ec2Client = awsClients.ec2;
|
|
10929
11030
|
}
|
|
10930
11031
|
/**
|
|
10931
11032
|
* Create a Lambda function
|
|
@@ -10973,6 +11074,7 @@ var LambdaFunctionProvider = class {
|
|
|
10973
11074
|
PackageType: properties["PackageType"],
|
|
10974
11075
|
TracingConfig: properties["TracingConfig"],
|
|
10975
11076
|
EphemeralStorage: properties["EphemeralStorage"],
|
|
11077
|
+
VpcConfig: this.buildVpcConfig(properties["VpcConfig"]),
|
|
10976
11078
|
Tags: tags
|
|
10977
11079
|
};
|
|
10978
11080
|
const response = await this.lambdaClient.send(new CreateFunctionCommand(createParams));
|
|
@@ -11011,7 +11113,8 @@ var LambdaFunctionProvider = class {
|
|
|
11011
11113
|
"Environment",
|
|
11012
11114
|
"Layers",
|
|
11013
11115
|
"TracingConfig",
|
|
11014
|
-
"EphemeralStorage"
|
|
11116
|
+
"EphemeralStorage",
|
|
11117
|
+
"VpcConfig"
|
|
11015
11118
|
];
|
|
11016
11119
|
let hasConfigChanges = false;
|
|
11017
11120
|
for (const field of configFields) {
|
|
@@ -11032,7 +11135,11 @@ var LambdaFunctionProvider = class {
|
|
|
11032
11135
|
Environment: properties["Environment"],
|
|
11033
11136
|
Layers: properties["Layers"],
|
|
11034
11137
|
TracingConfig: properties["TracingConfig"],
|
|
11035
|
-
EphemeralStorage: properties["EphemeralStorage"]
|
|
11138
|
+
EphemeralStorage: properties["EphemeralStorage"],
|
|
11139
|
+
VpcConfig: this.buildVpcConfigForUpdate(
|
|
11140
|
+
properties["VpcConfig"],
|
|
11141
|
+
previousProperties["VpcConfig"]
|
|
11142
|
+
)
|
|
11036
11143
|
};
|
|
11037
11144
|
await this.lambdaClient.send(new UpdateFunctionConfigurationCommand(configParams));
|
|
11038
11145
|
this.logger.debug(`Updated configuration for Lambda function ${physicalId}`);
|
|
@@ -11076,9 +11183,19 @@ var LambdaFunctionProvider = class {
|
|
|
11076
11183
|
}
|
|
11077
11184
|
/**
|
|
11078
11185
|
* Delete a Lambda function
|
|
11186
|
+
*
|
|
11187
|
+
* For VPC-enabled Lambda functions, AWS detaches the hyperplane ENIs
|
|
11188
|
+
* asynchronously after DeleteFunction returns. If we let downstream
|
|
11189
|
+
* resource deletion (Subnet / SecurityGroup) proceed immediately, those
|
|
11190
|
+
* deletions fail with "has dependencies" / "has a dependent object".
|
|
11191
|
+
*
|
|
11192
|
+
* To smooth this out, when properties carry a VpcConfig with subnets or
|
|
11193
|
+
* security groups, we poll DescribeNetworkInterfaces for the function's
|
|
11194
|
+
* managed ENIs and only return once they are gone (or the timeout elapses).
|
|
11079
11195
|
*/
|
|
11080
|
-
async delete(logicalId, physicalId, resourceType,
|
|
11196
|
+
async delete(logicalId, physicalId, resourceType, properties) {
|
|
11081
11197
|
this.logger.debug(`Deleting Lambda function ${logicalId}: ${physicalId}`);
|
|
11198
|
+
const hasVpcConfig = this.hasVpcConfig(properties?.["VpcConfig"]);
|
|
11082
11199
|
try {
|
|
11083
11200
|
await this.lambdaClient.send(new DeleteFunctionCommand({ FunctionName: physicalId }));
|
|
11084
11201
|
this.logger.debug(`Successfully deleted Lambda function ${logicalId}`);
|
|
@@ -11096,6 +11213,153 @@ var LambdaFunctionProvider = class {
|
|
|
11096
11213
|
cause
|
|
11097
11214
|
);
|
|
11098
11215
|
}
|
|
11216
|
+
if (hasVpcConfig) {
|
|
11217
|
+
await this.waitForLambdaEnisDetached(physicalId);
|
|
11218
|
+
}
|
|
11219
|
+
}
|
|
11220
|
+
/**
|
|
11221
|
+
* Build Lambda VpcConfig parameter from CDK properties.
|
|
11222
|
+
*
|
|
11223
|
+
* Returns undefined when VpcConfig is unset, so the SDK leaves the function
|
|
11224
|
+
* outside any VPC. Returns an empty config (no subnets, no SGs) when caller
|
|
11225
|
+
* explicitly clears it on update — that detaches the function from its VPC.
|
|
11226
|
+
*/
|
|
11227
|
+
buildVpcConfig(raw) {
|
|
11228
|
+
if (raw === void 0 || raw === null) {
|
|
11229
|
+
return void 0;
|
|
11230
|
+
}
|
|
11231
|
+
if (typeof raw !== "object") {
|
|
11232
|
+
return void 0;
|
|
11233
|
+
}
|
|
11234
|
+
const vpc = raw;
|
|
11235
|
+
const result = {};
|
|
11236
|
+
if (Array.isArray(vpc["SubnetIds"])) {
|
|
11237
|
+
result.SubnetIds = vpc["SubnetIds"];
|
|
11238
|
+
}
|
|
11239
|
+
if (Array.isArray(vpc["SecurityGroupIds"])) {
|
|
11240
|
+
result.SecurityGroupIds = vpc["SecurityGroupIds"];
|
|
11241
|
+
}
|
|
11242
|
+
if (typeof vpc["Ipv6AllowedForDualStack"] === "boolean") {
|
|
11243
|
+
result.Ipv6AllowedForDualStack = vpc["Ipv6AllowedForDualStack"];
|
|
11244
|
+
}
|
|
11245
|
+
return result;
|
|
11246
|
+
}
|
|
11247
|
+
/**
|
|
11248
|
+
* Build VpcConfig for an update call, accounting for VPC detach.
|
|
11249
|
+
*
|
|
11250
|
+
* UpdateFunctionConfiguration treats an absent VpcConfig as "no change",
|
|
11251
|
+
* so omitting it cannot move a function out of its existing VPC. To
|
|
11252
|
+
* detach we must explicitly send empty SubnetIds / SecurityGroupIds.
|
|
11253
|
+
*/
|
|
11254
|
+
buildVpcConfigForUpdate(newRaw, previousRaw) {
|
|
11255
|
+
const next = this.buildVpcConfig(newRaw);
|
|
11256
|
+
if (next) {
|
|
11257
|
+
return next;
|
|
11258
|
+
}
|
|
11259
|
+
if (this.hasVpcConfig(previousRaw)) {
|
|
11260
|
+
return { SubnetIds: [], SecurityGroupIds: [] };
|
|
11261
|
+
}
|
|
11262
|
+
return void 0;
|
|
11263
|
+
}
|
|
11264
|
+
/**
|
|
11265
|
+
* Determine whether the function actually attaches to a VPC, i.e. has at
|
|
11266
|
+
* least one Subnet ID. A bare VpcConfig with empty arrays does not create
|
|
11267
|
+
* any ENIs, so we skip the wait in that case.
|
|
11268
|
+
*/
|
|
11269
|
+
hasVpcConfig(raw) {
|
|
11270
|
+
if (raw === void 0 || raw === null || typeof raw !== "object") {
|
|
11271
|
+
return false;
|
|
11272
|
+
}
|
|
11273
|
+
const vpc = raw;
|
|
11274
|
+
const subnets = vpc["SubnetIds"];
|
|
11275
|
+
return Array.isArray(subnets) && subnets.length > 0;
|
|
11276
|
+
}
|
|
11277
|
+
/**
|
|
11278
|
+
* Poll DescribeNetworkInterfaces until the Lambda-managed ENIs for the
|
|
11279
|
+
* given function are gone, or the configured timeout elapses.
|
|
11280
|
+
*
|
|
11281
|
+
* Lambda VPC ENIs carry a Description like:
|
|
11282
|
+
* "AWS Lambda VPC ENI-<functionName>-<uuid>"
|
|
11283
|
+
* We match on a substring to be tolerant of format drift.
|
|
11284
|
+
*
|
|
11285
|
+
* Polling: starts at eniWaitInitialDelayMs (10s), exponential backoff up
|
|
11286
|
+
* to eniWaitMaxDelayMs (30s), bounded by eniWaitTimeoutMs (10min).
|
|
11287
|
+
*
|
|
11288
|
+
* Timeout is treated as a soft warning: detach can legitimately take 20-40
|
|
11289
|
+
* minutes in degraded conditions, and downstream Subnet/SG deletion has
|
|
11290
|
+
* its own retries to handle the residual window.
|
|
11291
|
+
*/
|
|
11292
|
+
async waitForLambdaEnisDetached(functionName) {
|
|
11293
|
+
const start = Date.now();
|
|
11294
|
+
let delay = this.eniWaitInitialDelayMs;
|
|
11295
|
+
let attempt = 0;
|
|
11296
|
+
this.logger.debug(
|
|
11297
|
+
`Waiting for Lambda VPC ENIs to detach for function ${functionName} (timeout ${this.eniWaitTimeoutMs}ms)`
|
|
11298
|
+
);
|
|
11299
|
+
const descriptionNeedle = `AWS Lambda VPC ENI`;
|
|
11300
|
+
const functionNamePattern = new RegExp(`(^|-)${escapeRegExp(functionName)}(-|$)`);
|
|
11301
|
+
for (; ; ) {
|
|
11302
|
+
attempt++;
|
|
11303
|
+
let count;
|
|
11304
|
+
try {
|
|
11305
|
+
count = await this.countLambdaEnis(descriptionNeedle, functionNamePattern);
|
|
11306
|
+
} catch (error) {
|
|
11307
|
+
this.logger.warn(
|
|
11308
|
+
`DescribeNetworkInterfaces failed while waiting for Lambda ENIs of ${functionName}: ${error instanceof Error ? error.message : String(error)}`
|
|
11309
|
+
);
|
|
11310
|
+
count = -1;
|
|
11311
|
+
}
|
|
11312
|
+
if (count === 0) {
|
|
11313
|
+
this.logger.debug(
|
|
11314
|
+
`Lambda ENIs for ${functionName} fully detached after ${attempt} poll(s) / ${Date.now() - start}ms`
|
|
11315
|
+
);
|
|
11316
|
+
return;
|
|
11317
|
+
}
|
|
11318
|
+
const elapsed = Date.now() - start;
|
|
11319
|
+
if (elapsed >= this.eniWaitTimeoutMs) {
|
|
11320
|
+
this.logger.warn(
|
|
11321
|
+
`Timeout (${this.eniWaitTimeoutMs}ms) waiting for Lambda VPC ENIs of ${functionName} to detach (remaining: ${count >= 0 ? count : "unknown"}). Continuing \u2014 downstream Subnet/SG deletion will retry as needed.`
|
|
11322
|
+
);
|
|
11323
|
+
return;
|
|
11324
|
+
}
|
|
11325
|
+
const remaining = this.eniWaitTimeoutMs - elapsed;
|
|
11326
|
+
const sleepMs = Math.min(delay, remaining);
|
|
11327
|
+
await this.sleep(sleepMs);
|
|
11328
|
+
delay = Math.min(delay * 2, this.eniWaitMaxDelayMs);
|
|
11329
|
+
}
|
|
11330
|
+
}
|
|
11331
|
+
/**
|
|
11332
|
+
* Count remaining Lambda-managed ENIs for the given function, paginating
|
|
11333
|
+
* through DescribeNetworkInterfaces and filtering on Description substring.
|
|
11334
|
+
*
|
|
11335
|
+
* Server-side filter (`description`) does not support wildcards in EC2's API,
|
|
11336
|
+
* so we filter client-side after narrowing on `requester-id` + `status`.
|
|
11337
|
+
*/
|
|
11338
|
+
async countLambdaEnis(descriptionNeedle, functionNamePattern) {
|
|
11339
|
+
let nextToken;
|
|
11340
|
+
let count = 0;
|
|
11341
|
+
do {
|
|
11342
|
+
const resp = await this.ec2Client.send(
|
|
11343
|
+
new DescribeNetworkInterfacesCommand({
|
|
11344
|
+
Filters: [
|
|
11345
|
+
// Lambda hyperplane ENIs are owned by the Lambda service principal.
|
|
11346
|
+
{ Name: "requester-id", Values: ["*:awslambda_*"] }
|
|
11347
|
+
],
|
|
11348
|
+
NextToken: nextToken
|
|
11349
|
+
})
|
|
11350
|
+
);
|
|
11351
|
+
for (const ni of resp.NetworkInterfaces ?? []) {
|
|
11352
|
+
const desc = ni.Description ?? "";
|
|
11353
|
+
if (desc.includes(descriptionNeedle) && functionNamePattern.test(desc)) {
|
|
11354
|
+
count++;
|
|
11355
|
+
}
|
|
11356
|
+
}
|
|
11357
|
+
nextToken = resp.NextToken;
|
|
11358
|
+
} while (nextToken);
|
|
11359
|
+
return count;
|
|
11360
|
+
}
|
|
11361
|
+
sleep(ms) {
|
|
11362
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
11099
11363
|
}
|
|
11100
11364
|
/**
|
|
11101
11365
|
* Build Lambda Code parameter from CDK properties
|
|
@@ -11182,6 +11446,9 @@ var LambdaFunctionProvider = class {
|
|
|
11182
11446
|
return (crc ^ 4294967295) >>> 0;
|
|
11183
11447
|
}
|
|
11184
11448
|
};
|
|
11449
|
+
function escapeRegExp(input) {
|
|
11450
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11451
|
+
}
|
|
11185
11452
|
|
|
11186
11453
|
// src/provisioning/providers/lambda-permission-provider.ts
|
|
11187
11454
|
import {
|
|
@@ -13505,6 +13772,8 @@ import {
|
|
|
13505
13772
|
DeleteSecurityGroupCommand,
|
|
13506
13773
|
AuthorizeSecurityGroupIngressCommand,
|
|
13507
13774
|
RevokeSecurityGroupIngressCommand,
|
|
13775
|
+
AuthorizeSecurityGroupEgressCommand,
|
|
13776
|
+
RevokeSecurityGroupEgressCommand,
|
|
13508
13777
|
CreateTagsCommand,
|
|
13509
13778
|
DescribeSubnetsCommand as DescribeSubnetsCommand2,
|
|
13510
13779
|
DescribeSecurityGroupsCommand as DescribeSecurityGroupsCommand2,
|
|
@@ -13554,7 +13823,14 @@ var EC2Provider = class {
|
|
|
13554
13823
|
["AWS::EC2::SubnetRouteTableAssociation", /* @__PURE__ */ new Set(["SubnetId", "RouteTableId"])],
|
|
13555
13824
|
[
|
|
13556
13825
|
"AWS::EC2::SecurityGroup",
|
|
13557
|
-
/* @__PURE__ */ new Set([
|
|
13826
|
+
/* @__PURE__ */ new Set([
|
|
13827
|
+
"GroupDescription",
|
|
13828
|
+
"GroupName",
|
|
13829
|
+
"VpcId",
|
|
13830
|
+
"SecurityGroupIngress",
|
|
13831
|
+
"SecurityGroupEgress",
|
|
13832
|
+
"Tags"
|
|
13833
|
+
])
|
|
13558
13834
|
],
|
|
13559
13835
|
[
|
|
13560
13836
|
"AWS::EC2::SecurityGroupIngress",
|
|
@@ -13565,7 +13841,8 @@ var EC2Provider = class {
|
|
|
13565
13841
|
"ToPort",
|
|
13566
13842
|
"CidrIp",
|
|
13567
13843
|
"Description",
|
|
13568
|
-
"SourceSecurityGroupId"
|
|
13844
|
+
"SourceSecurityGroupId",
|
|
13845
|
+
"SourceSecurityGroupOwnerId"
|
|
13569
13846
|
])
|
|
13570
13847
|
],
|
|
13571
13848
|
[
|
|
@@ -13664,7 +13941,13 @@ var EC2Provider = class {
|
|
|
13664
13941
|
case "AWS::EC2::SubnetRouteTableAssociation":
|
|
13665
13942
|
return this.updateSubnetRouteTableAssociation(logicalId, physicalId);
|
|
13666
13943
|
case "AWS::EC2::SecurityGroup":
|
|
13667
|
-
return this.updateSecurityGroup(
|
|
13944
|
+
return this.updateSecurityGroup(
|
|
13945
|
+
logicalId,
|
|
13946
|
+
physicalId,
|
|
13947
|
+
resourceType,
|
|
13948
|
+
properties,
|
|
13949
|
+
previousProperties
|
|
13950
|
+
);
|
|
13668
13951
|
case "AWS::EC2::SecurityGroupIngress":
|
|
13669
13952
|
return this.updateSecurityGroupIngress(
|
|
13670
13953
|
logicalId,
|
|
@@ -14389,6 +14672,34 @@ var EC2Provider = class {
|
|
|
14389
14672
|
);
|
|
14390
14673
|
}
|
|
14391
14674
|
}
|
|
14675
|
+
const egressRules = properties["SecurityGroupEgress"];
|
|
14676
|
+
if (egressRules && Array.isArray(egressRules)) {
|
|
14677
|
+
try {
|
|
14678
|
+
await this.ec2Client.send(
|
|
14679
|
+
new RevokeSecurityGroupEgressCommand({
|
|
14680
|
+
GroupId: groupId,
|
|
14681
|
+
IpPermissions: [
|
|
14682
|
+
{
|
|
14683
|
+
IpProtocol: "-1",
|
|
14684
|
+
IpRanges: [{ CidrIp: "0.0.0.0/0" }]
|
|
14685
|
+
}
|
|
14686
|
+
]
|
|
14687
|
+
})
|
|
14688
|
+
);
|
|
14689
|
+
} catch (error) {
|
|
14690
|
+
if (!this.isNotFoundError(error)) {
|
|
14691
|
+
throw error;
|
|
14692
|
+
}
|
|
14693
|
+
}
|
|
14694
|
+
for (const rule of egressRules) {
|
|
14695
|
+
await this.ec2Client.send(
|
|
14696
|
+
new AuthorizeSecurityGroupEgressCommand({
|
|
14697
|
+
GroupId: groupId,
|
|
14698
|
+
IpPermissions: [this.buildIpPermission(rule, "egress")]
|
|
14699
|
+
})
|
|
14700
|
+
);
|
|
14701
|
+
}
|
|
14702
|
+
}
|
|
14392
14703
|
this.logger.debug(`Successfully created SecurityGroup ${logicalId}: ${groupId}`);
|
|
14393
14704
|
return {
|
|
14394
14705
|
physicalId: groupId,
|
|
@@ -14408,10 +14719,22 @@ var EC2Provider = class {
|
|
|
14408
14719
|
);
|
|
14409
14720
|
}
|
|
14410
14721
|
}
|
|
14411
|
-
async updateSecurityGroup(logicalId, physicalId, resourceType, properties) {
|
|
14722
|
+
async updateSecurityGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
14412
14723
|
this.logger.debug(`Updating SecurityGroup ${logicalId}: ${physicalId}`);
|
|
14413
14724
|
try {
|
|
14414
14725
|
await this.applyTags(physicalId, properties, logicalId);
|
|
14726
|
+
await this.applySecurityGroupRuleDiff(
|
|
14727
|
+
physicalId,
|
|
14728
|
+
previousProperties["SecurityGroupIngress"] ?? [],
|
|
14729
|
+
properties["SecurityGroupIngress"] ?? [],
|
|
14730
|
+
"ingress"
|
|
14731
|
+
);
|
|
14732
|
+
await this.applySecurityGroupRuleDiff(
|
|
14733
|
+
physicalId,
|
|
14734
|
+
previousProperties["SecurityGroupEgress"] ?? [],
|
|
14735
|
+
properties["SecurityGroupEgress"] ?? [],
|
|
14736
|
+
"egress"
|
|
14737
|
+
);
|
|
14415
14738
|
this.logger.debug(`Successfully updated SecurityGroup ${logicalId}`);
|
|
14416
14739
|
return {
|
|
14417
14740
|
physicalId,
|
|
@@ -14769,9 +15092,13 @@ var EC2Provider = class {
|
|
|
14769
15092
|
}
|
|
14770
15093
|
// ─── Helpers ──────────────────────────────────────────────────────
|
|
14771
15094
|
/**
|
|
14772
|
-
* Build an IpPermission object from CloudFormation-style properties
|
|
15095
|
+
* Build an IpPermission object from CloudFormation-style properties.
|
|
15096
|
+
*
|
|
15097
|
+
* The EC2 IpPermission shape is identical for ingress and egress; only the
|
|
15098
|
+
* CFn property names that point to the "other" security group differ
|
|
15099
|
+
* (SourceSecurityGroupId vs DestinationSecurityGroupId).
|
|
14773
15100
|
*/
|
|
14774
|
-
buildIpPermission(properties) {
|
|
15101
|
+
buildIpPermission(properties, direction = "ingress") {
|
|
14775
15102
|
const ipProtocol = properties["IpProtocol"] ?? "-1";
|
|
14776
15103
|
const fromPort = properties["FromPort"];
|
|
14777
15104
|
const toPort = properties["ToPort"];
|
|
@@ -14781,6 +15108,7 @@ var EC2Provider = class {
|
|
|
14781
15108
|
if (toPort !== void 0)
|
|
14782
15109
|
permission.ToPort = toPort;
|
|
14783
15110
|
const cidrIp = properties["CidrIp"];
|
|
15111
|
+
const cidrIpv6 = properties["CidrIpv6"];
|
|
14784
15112
|
const description = properties["Description"];
|
|
14785
15113
|
if (cidrIp) {
|
|
14786
15114
|
const ipRange = { CidrIp: cidrIp };
|
|
@@ -14788,17 +15116,124 @@ var EC2Provider = class {
|
|
|
14788
15116
|
ipRange.Description = description;
|
|
14789
15117
|
permission.IpRanges = [ipRange];
|
|
14790
15118
|
}
|
|
14791
|
-
|
|
14792
|
-
|
|
15119
|
+
if (cidrIpv6) {
|
|
15120
|
+
const ipv6Range = { CidrIpv6: cidrIpv6 };
|
|
15121
|
+
if (description)
|
|
15122
|
+
ipv6Range.Description = description;
|
|
15123
|
+
permission.Ipv6Ranges = [ipv6Range];
|
|
15124
|
+
}
|
|
15125
|
+
const peerGroupId = direction === "egress" ? properties["DestinationSecurityGroupId"] : properties["SourceSecurityGroupId"];
|
|
15126
|
+
if (peerGroupId) {
|
|
14793
15127
|
const groupPair = {
|
|
14794
|
-
GroupId:
|
|
15128
|
+
GroupId: peerGroupId
|
|
14795
15129
|
};
|
|
15130
|
+
if (direction === "ingress") {
|
|
15131
|
+
const peerOwnerId = properties["SourceSecurityGroupOwnerId"];
|
|
15132
|
+
if (peerOwnerId)
|
|
15133
|
+
groupPair.UserId = peerOwnerId;
|
|
15134
|
+
}
|
|
14796
15135
|
if (description)
|
|
14797
15136
|
groupPair.Description = description;
|
|
14798
15137
|
permission.UserIdGroupPairs = [groupPair];
|
|
14799
15138
|
}
|
|
15139
|
+
const prefixListId = direction === "egress" ? properties["DestinationPrefixListId"] : properties["SourcePrefixListId"];
|
|
15140
|
+
if (prefixListId) {
|
|
15141
|
+
const prefixEntry = {
|
|
15142
|
+
PrefixListId: prefixListId
|
|
15143
|
+
};
|
|
15144
|
+
if (description)
|
|
15145
|
+
prefixEntry.Description = description;
|
|
15146
|
+
permission.PrefixListIds = [prefixEntry];
|
|
15147
|
+
}
|
|
14800
15148
|
return permission;
|
|
14801
15149
|
}
|
|
15150
|
+
/**
|
|
15151
|
+
* Compute the diff between two sets of SecurityGroup rule definitions
|
|
15152
|
+
* (ingress or egress) and apply the resulting authorize/revoke calls.
|
|
15153
|
+
*
|
|
15154
|
+
* Rules are identified by a deterministic key derived from their full
|
|
15155
|
+
* shape — protocol, ports, CIDR, peer group, prefix list, description —
|
|
15156
|
+
* so updating any of those fields counts as a replacement (revoke + authorize).
|
|
15157
|
+
*/
|
|
15158
|
+
async applySecurityGroupRuleDiff(groupId, previousRules, nextRules, direction) {
|
|
15159
|
+
const ruleKey = (rule) => {
|
|
15160
|
+
const peerKey = direction === "egress" ? rule["DestinationSecurityGroupId"] : rule["SourceSecurityGroupId"];
|
|
15161
|
+
const prefixKey = direction === "egress" ? rule["DestinationPrefixListId"] : rule["SourcePrefixListId"];
|
|
15162
|
+
const peerOwner = direction === "ingress" ? rule["SourceSecurityGroupOwnerId"] : void 0;
|
|
15163
|
+
return JSON.stringify({
|
|
15164
|
+
p: rule["IpProtocol"] ?? "-1",
|
|
15165
|
+
f: rule["FromPort"] ?? null,
|
|
15166
|
+
t: rule["ToPort"] ?? null,
|
|
15167
|
+
c4: rule["CidrIp"] ?? null,
|
|
15168
|
+
c6: rule["CidrIpv6"] ?? null,
|
|
15169
|
+
peer: peerKey ?? null,
|
|
15170
|
+
peerOwner: peerOwner ?? null,
|
|
15171
|
+
pl: prefixKey ?? null,
|
|
15172
|
+
d: rule["Description"] ?? null
|
|
15173
|
+
});
|
|
15174
|
+
};
|
|
15175
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
15176
|
+
for (const rule of previousRules)
|
|
15177
|
+
prevByKey.set(ruleKey(rule), rule);
|
|
15178
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
15179
|
+
for (const rule of nextRules)
|
|
15180
|
+
nextByKey.set(ruleKey(rule), rule);
|
|
15181
|
+
const toRevoke = [];
|
|
15182
|
+
for (const [key, rule] of prevByKey) {
|
|
15183
|
+
if (!nextByKey.has(key))
|
|
15184
|
+
toRevoke.push(rule);
|
|
15185
|
+
}
|
|
15186
|
+
const toAuthorize = [];
|
|
15187
|
+
for (const [key, rule] of nextByKey) {
|
|
15188
|
+
if (!prevByKey.has(key))
|
|
15189
|
+
toAuthorize.push(rule);
|
|
15190
|
+
}
|
|
15191
|
+
for (const rule of toRevoke) {
|
|
15192
|
+
try {
|
|
15193
|
+
if (direction === "egress") {
|
|
15194
|
+
await this.ec2Client.send(
|
|
15195
|
+
new RevokeSecurityGroupEgressCommand({
|
|
15196
|
+
GroupId: groupId,
|
|
15197
|
+
IpPermissions: [this.buildIpPermission(rule, "egress")]
|
|
15198
|
+
})
|
|
15199
|
+
);
|
|
15200
|
+
} else {
|
|
15201
|
+
await this.ec2Client.send(
|
|
15202
|
+
new RevokeSecurityGroupIngressCommand({
|
|
15203
|
+
GroupId: groupId,
|
|
15204
|
+
IpPermissions: [this.buildIpPermission(rule, "ingress")]
|
|
15205
|
+
})
|
|
15206
|
+
);
|
|
15207
|
+
}
|
|
15208
|
+
} catch (error) {
|
|
15209
|
+
if (!this.isNotFoundError(error))
|
|
15210
|
+
throw error;
|
|
15211
|
+
}
|
|
15212
|
+
}
|
|
15213
|
+
for (const rule of toAuthorize) {
|
|
15214
|
+
try {
|
|
15215
|
+
if (direction === "egress") {
|
|
15216
|
+
await this.ec2Client.send(
|
|
15217
|
+
new AuthorizeSecurityGroupEgressCommand({
|
|
15218
|
+
GroupId: groupId,
|
|
15219
|
+
IpPermissions: [this.buildIpPermission(rule, "egress")]
|
|
15220
|
+
})
|
|
15221
|
+
);
|
|
15222
|
+
} else {
|
|
15223
|
+
await this.ec2Client.send(
|
|
15224
|
+
new AuthorizeSecurityGroupIngressCommand({
|
|
15225
|
+
GroupId: groupId,
|
|
15226
|
+
IpPermissions: [this.buildIpPermission(rule, "ingress")]
|
|
15227
|
+
})
|
|
15228
|
+
);
|
|
15229
|
+
}
|
|
15230
|
+
} catch (error) {
|
|
15231
|
+
if (!(error instanceof Error && error.message.includes("already exists"))) {
|
|
15232
|
+
throw error;
|
|
15233
|
+
}
|
|
15234
|
+
}
|
|
15235
|
+
}
|
|
15236
|
+
}
|
|
14802
15237
|
// ─── AWS::EC2::NetworkAcl ────────────────────────────────────────
|
|
14803
15238
|
async createNetworkAcl(logicalId, resourceType, properties) {
|
|
14804
15239
|
this.logger.debug(`Creating NetworkAcl ${logicalId}`);
|
|
@@ -16719,7 +17154,7 @@ var CloudFrontDistributionProvider = class {
|
|
|
16719
17154
|
this.logger.debug(`Created CloudFront Distribution: ${distributionId} (${domainName})`);
|
|
16720
17155
|
if (process.env["CDKD_NO_WAIT"] !== "true") {
|
|
16721
17156
|
this.logger.debug(`Waiting for Distribution ${distributionId} to reach Deployed status...`);
|
|
16722
|
-
await this.
|
|
17157
|
+
await this.waitForDistributionStable(distributionId);
|
|
16723
17158
|
}
|
|
16724
17159
|
return {
|
|
16725
17160
|
physicalId: distributionId,
|
|
@@ -16829,7 +17264,7 @@ var CloudFrontDistributionProvider = class {
|
|
|
16829
17264
|
})
|
|
16830
17265
|
);
|
|
16831
17266
|
etag = updateResponse.ETag;
|
|
16832
|
-
await this.
|
|
17267
|
+
await this.waitForDistributionStable(physicalId, false);
|
|
16833
17268
|
const refreshResponse = await this.cloudFrontClient.send(
|
|
16834
17269
|
new GetDistributionConfigCommand({ Id: physicalId })
|
|
16835
17270
|
);
|
|
@@ -16873,10 +17308,16 @@ var CloudFrontDistributionProvider = class {
|
|
|
16873
17308
|
throw new Error(`Unsupported attribute: ${attributeName} for AWS::CloudFront::Distribution`);
|
|
16874
17309
|
}
|
|
16875
17310
|
/**
|
|
16876
|
-
* Wait for a distribution to reach
|
|
17311
|
+
* Wait for a distribution to reach a stable state.
|
|
17312
|
+
*
|
|
17313
|
+
* "Stable" means Status === 'Deployed'. When `expectedEnabled` is provided,
|
|
17314
|
+
* we additionally require DistributionConfig.Enabled === expectedEnabled —
|
|
17315
|
+
* this guards against CloudFront's eventually-consistent reads that can
|
|
17316
|
+
* briefly return the pre-update snapshot after UpdateDistribution returns.
|
|
17317
|
+
*
|
|
16877
17318
|
* Uses exponential backoff polling.
|
|
16878
17319
|
*/
|
|
16879
|
-
async
|
|
17320
|
+
async waitForDistributionStable(distributionId, expectedEnabled) {
|
|
16880
17321
|
const maxAttempts = 60;
|
|
16881
17322
|
let delay = 5e3;
|
|
16882
17323
|
const maxDelay = 3e4;
|
|
@@ -16897,12 +17338,16 @@ var CloudFrontDistributionProvider = class {
|
|
|
16897
17338
|
new GetDistributionCommand({ Id: distributionId })
|
|
16898
17339
|
);
|
|
16899
17340
|
const status = response.Distribution?.Status;
|
|
16900
|
-
|
|
16901
|
-
|
|
17341
|
+
const enabled = response.Distribution?.DistributionConfig?.Enabled;
|
|
17342
|
+
const enabledMatches = expectedEnabled === void 0 || enabled === expectedEnabled;
|
|
17343
|
+
if (status === "Deployed" && enabledMatches) {
|
|
17344
|
+
this.logger.debug(
|
|
17345
|
+
`Distribution ${distributionId} is stable (Status=Deployed, Enabled=${enabled})`
|
|
17346
|
+
);
|
|
16902
17347
|
return;
|
|
16903
17348
|
}
|
|
16904
17349
|
this.logger.debug(
|
|
16905
|
-
`Distribution ${distributionId} status: ${status} (attempt ${attempt}/${maxAttempts})`
|
|
17350
|
+
`Distribution ${distributionId} status: ${status}, enabled: ${enabled}` + (expectedEnabled === void 0 ? "" : ` (waiting for Enabled=${expectedEnabled})`) + ` (attempt ${attempt}/${maxAttempts})`
|
|
16906
17351
|
);
|
|
16907
17352
|
const sleepEnd = Date.now() + delay;
|
|
16908
17353
|
while (Date.now() < sleepEnd && !interrupted) {
|
|
@@ -16911,7 +17356,7 @@ var CloudFrontDistributionProvider = class {
|
|
|
16911
17356
|
delay = Math.min(delay * 1.5, maxDelay);
|
|
16912
17357
|
}
|
|
16913
17358
|
this.logger.debug(
|
|
16914
|
-
`Distribution ${distributionId} did not reach
|
|
17359
|
+
`Distribution ${distributionId} did not reach stable state within timeout, proceeding with next step`
|
|
16915
17360
|
);
|
|
16916
17361
|
} finally {
|
|
16917
17362
|
process.removeListener("SIGINT", sigintHandler);
|
|
@@ -24137,7 +24582,7 @@ import {
|
|
|
24137
24582
|
DeleteObjectsCommand as DeleteObjectsCommand2
|
|
24138
24583
|
} from "@aws-sdk/client-s3";
|
|
24139
24584
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand7 } from "@aws-sdk/client-sts";
|
|
24140
|
-
import { EC2Client as
|
|
24585
|
+
import { EC2Client as EC2Client8, DescribeAvailabilityZonesCommand as DescribeAvailabilityZonesCommand3 } from "@aws-sdk/client-ec2";
|
|
24141
24586
|
init_aws_clients();
|
|
24142
24587
|
var S3DirectoryBucketProvider = class {
|
|
24143
24588
|
s3Client;
|
|
@@ -24155,7 +24600,7 @@ var S3DirectoryBucketProvider = class {
|
|
|
24155
24600
|
}
|
|
24156
24601
|
getEc2Client() {
|
|
24157
24602
|
if (!this.ec2Client) {
|
|
24158
|
-
this.ec2Client = new
|
|
24603
|
+
this.ec2Client = new EC2Client8(this.providerRegion ? { region: this.providerRegion } : {});
|
|
24159
24604
|
}
|
|
24160
24605
|
return this.ec2Client;
|
|
24161
24606
|
}
|
|
@@ -25053,15 +25498,141 @@ function registerAllProviders(registry) {
|
|
|
25053
25498
|
registry.register("AWS::S3Tables::Table", s3TablesProvider);
|
|
25054
25499
|
}
|
|
25055
25500
|
|
|
25501
|
+
// src/deployment/dag-executor.ts
|
|
25502
|
+
var DagExecutor = class {
|
|
25503
|
+
nodes = /* @__PURE__ */ new Map();
|
|
25504
|
+
logger = getLogger().child("DagExecutor");
|
|
25505
|
+
add(node) {
|
|
25506
|
+
this.nodes.set(node.id, node);
|
|
25507
|
+
}
|
|
25508
|
+
has(id) {
|
|
25509
|
+
return this.nodes.has(id);
|
|
25510
|
+
}
|
|
25511
|
+
size() {
|
|
25512
|
+
return this.nodes.size;
|
|
25513
|
+
}
|
|
25514
|
+
values() {
|
|
25515
|
+
return this.nodes.values();
|
|
25516
|
+
}
|
|
25517
|
+
async execute(concurrency, fn, cancelled = () => false) {
|
|
25518
|
+
let active = 0;
|
|
25519
|
+
const errors = [];
|
|
25520
|
+
return new Promise((resolve4, reject) => {
|
|
25521
|
+
const dispatch = () => {
|
|
25522
|
+
let changed = true;
|
|
25523
|
+
while (changed) {
|
|
25524
|
+
changed = false;
|
|
25525
|
+
for (const node of this.nodes.values()) {
|
|
25526
|
+
if (node.state !== "pending")
|
|
25527
|
+
continue;
|
|
25528
|
+
const hasFailedDep = [...node.dependencies].some((depId) => {
|
|
25529
|
+
const dep = this.nodes.get(depId);
|
|
25530
|
+
return dep && (dep.state === "failed" || dep.state === "skipped");
|
|
25531
|
+
});
|
|
25532
|
+
if (hasFailedDep) {
|
|
25533
|
+
node.state = "skipped";
|
|
25534
|
+
changed = true;
|
|
25535
|
+
this.logger.debug(`Skipped ${node.id}: dependency failed or was skipped`);
|
|
25536
|
+
}
|
|
25537
|
+
}
|
|
25538
|
+
}
|
|
25539
|
+
const ready = [];
|
|
25540
|
+
for (const node of this.nodes.values()) {
|
|
25541
|
+
if (node.state !== "pending")
|
|
25542
|
+
continue;
|
|
25543
|
+
const depsReady = [...node.dependencies].every((depId) => {
|
|
25544
|
+
const dep = this.nodes.get(depId);
|
|
25545
|
+
return !dep || dep.state === "completed";
|
|
25546
|
+
});
|
|
25547
|
+
if (depsReady)
|
|
25548
|
+
ready.push(node);
|
|
25549
|
+
}
|
|
25550
|
+
if (!cancelled()) {
|
|
25551
|
+
for (const node of ready) {
|
|
25552
|
+
if (active >= concurrency)
|
|
25553
|
+
break;
|
|
25554
|
+
node.state = "running";
|
|
25555
|
+
active++;
|
|
25556
|
+
fn(node).then(() => {
|
|
25557
|
+
node.state = "completed";
|
|
25558
|
+
}).catch((error) => {
|
|
25559
|
+
node.state = "failed";
|
|
25560
|
+
errors.push({ id: node.id, error });
|
|
25561
|
+
}).finally(() => {
|
|
25562
|
+
active--;
|
|
25563
|
+
dispatch();
|
|
25564
|
+
});
|
|
25565
|
+
}
|
|
25566
|
+
}
|
|
25567
|
+
if (active === 0) {
|
|
25568
|
+
if (errors.length > 0) {
|
|
25569
|
+
reject(errors[0].error);
|
|
25570
|
+
return;
|
|
25571
|
+
}
|
|
25572
|
+
const stillPending = [...this.nodes.values()].some((n) => n.state === "pending");
|
|
25573
|
+
if (stillPending && !cancelled()) {
|
|
25574
|
+
const pending = [...this.nodes.values()].filter((n) => n.state === "pending").map((n) => n.id);
|
|
25575
|
+
reject(
|
|
25576
|
+
new Error(
|
|
25577
|
+
`Deadlock detected: ${pending.length} node(s) stuck with unresolvable dependencies (${pending.join(", ")})`
|
|
25578
|
+
)
|
|
25579
|
+
);
|
|
25580
|
+
return;
|
|
25581
|
+
}
|
|
25582
|
+
resolve4();
|
|
25583
|
+
}
|
|
25584
|
+
};
|
|
25585
|
+
dispatch();
|
|
25586
|
+
});
|
|
25587
|
+
}
|
|
25588
|
+
};
|
|
25589
|
+
|
|
25590
|
+
// src/analyzer/implicit-delete-deps.ts
|
|
25591
|
+
var IMPLICIT_DELETE_DEPENDENCIES = {
|
|
25592
|
+
// IGW must be deleted AFTER VPCGatewayAttachment
|
|
25593
|
+
"AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
|
|
25594
|
+
// EventBus must be deleted AFTER Rules on that bus
|
|
25595
|
+
"AWS::Events::EventBus": ["AWS::Events::Rule"],
|
|
25596
|
+
// Athena workgroup must be deleted AFTER its named queries
|
|
25597
|
+
"AWS::Athena::WorkGroup": ["AWS::Athena::NamedQuery"],
|
|
25598
|
+
// CloudFront managed-policy-style resources must be deleted AFTER
|
|
25599
|
+
// any Distribution that references them
|
|
25600
|
+
"AWS::CloudFront::ResponseHeadersPolicy": ["AWS::CloudFront::Distribution"],
|
|
25601
|
+
"AWS::CloudFront::CachePolicy": ["AWS::CloudFront::Distribution"],
|
|
25602
|
+
"AWS::CloudFront::OriginAccessControl": ["AWS::CloudFront::Distribution"],
|
|
25603
|
+
// VPC must be deleted AFTER all VPC-dependent resources
|
|
25604
|
+
"AWS::EC2::VPC": [
|
|
25605
|
+
"AWS::EC2::Subnet",
|
|
25606
|
+
"AWS::EC2::SecurityGroup",
|
|
25607
|
+
"AWS::EC2::InternetGateway",
|
|
25608
|
+
"AWS::EC2::EgressOnlyInternetGateway",
|
|
25609
|
+
"AWS::EC2::VPCGatewayAttachment",
|
|
25610
|
+
"AWS::EC2::RouteTable"
|
|
25611
|
+
],
|
|
25612
|
+
// Subnet must be deleted AFTER any Lambda that may still hold an ENI
|
|
25613
|
+
// in it. Lambda DELETE returns immediately but the ENI is detached
|
|
25614
|
+
// asynchronously by AWS, so deleting the Subnet first races the detach
|
|
25615
|
+
// and yields "DependencyViolation".
|
|
25616
|
+
"AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation", "AWS::Lambda::Function"],
|
|
25617
|
+
// RouteTable must be deleted AFTER Route and Association
|
|
25618
|
+
"AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
|
|
25619
|
+
// SecurityGroup must be deleted AFTER any Lambda whose ENI is bound
|
|
25620
|
+
// to it (same ENI-detach race as Subnet above).
|
|
25621
|
+
"AWS::EC2::SecurityGroup": [
|
|
25622
|
+
"AWS::EC2::SecurityGroupIngress",
|
|
25623
|
+
"AWS::EC2::SecurityGroupEgress",
|
|
25624
|
+
"AWS::Lambda::Function"
|
|
25625
|
+
]
|
|
25626
|
+
};
|
|
25627
|
+
|
|
25056
25628
|
// src/deployment/deploy-engine.ts
|
|
25057
|
-
import pLimit from "p-limit";
|
|
25058
25629
|
var InterruptedError = class extends Error {
|
|
25059
25630
|
constructor() {
|
|
25060
25631
|
super("Deployment interrupted by user (Ctrl+C)");
|
|
25061
25632
|
this.name = "InterruptedError";
|
|
25062
25633
|
}
|
|
25063
25634
|
};
|
|
25064
|
-
var DeployEngine = class
|
|
25635
|
+
var DeployEngine = class {
|
|
25065
25636
|
constructor(stateBackend, lockManager, dagBuilder, diffCalculator, providerRegistry, options = {}, stackRegion) {
|
|
25066
25637
|
this.stateBackend = stateBackend;
|
|
25067
25638
|
this.lockManager = lockManager;
|
|
@@ -25187,6 +25758,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
25187
25758
|
template,
|
|
25188
25759
|
currentState,
|
|
25189
25760
|
changes,
|
|
25761
|
+
dag,
|
|
25190
25762
|
executionLevels,
|
|
25191
25763
|
stackName,
|
|
25192
25764
|
parameterValues,
|
|
@@ -25219,13 +25791,16 @@ var DeployEngine = class _DeployEngine {
|
|
|
25219
25791
|
}
|
|
25220
25792
|
}
|
|
25221
25793
|
/**
|
|
25222
|
-
* Execute deployment by processing resources
|
|
25794
|
+
* Execute deployment by processing resources via event-driven DAG dispatch.
|
|
25223
25795
|
*
|
|
25224
|
-
*
|
|
25225
|
-
*
|
|
25226
|
-
|
|
25227
|
-
|
|
25228
|
-
|
|
25796
|
+
* - CREATE/UPDATE follow forward dependency order (a node starts as soon as
|
|
25797
|
+
* ALL of its dependencies are completed — does not wait for unrelated
|
|
25798
|
+
* siblings in the same "level")
|
|
25799
|
+
* - DELETE follows reverse dependency order (a node starts as soon as all
|
|
25800
|
+
* resources that depend ON it have finished deleting)
|
|
25801
|
+
*/
|
|
25802
|
+
async executeDeployment(template, currentState, changes, dag, executionLevels, stackName, parameterValues, conditions, currentEtag, progress) {
|
|
25803
|
+
const concurrency = this.options.concurrency;
|
|
25229
25804
|
const newResources = { ...currentState.resources };
|
|
25230
25805
|
const actualCounts = { created: 0, updated: 0, deleted: 0, skipped: 0 };
|
|
25231
25806
|
const completedOperations = [];
|
|
@@ -25256,32 +25831,36 @@ var DeployEngine = class _DeployEngine {
|
|
|
25256
25831
|
Array.from(changes.entries()).filter(([_, change]) => change.changeType === "DELETE").map(([logicalId]) => logicalId)
|
|
25257
25832
|
);
|
|
25258
25833
|
try {
|
|
25259
|
-
|
|
25260
|
-
|
|
25261
|
-
|
|
25262
|
-
}
|
|
25263
|
-
const levelNodes = executionLevels[levelIndex];
|
|
25264
|
-
if (!levelNodes)
|
|
25834
|
+
const createUpdateIds = [];
|
|
25835
|
+
for (const [id, change] of changes.entries()) {
|
|
25836
|
+
if (deleteChanges.has(id))
|
|
25265
25837
|
continue;
|
|
25266
|
-
|
|
25267
|
-
if (deleteChanges.has(id))
|
|
25268
|
-
return false;
|
|
25269
|
-
const change = changes.get(id);
|
|
25270
|
-
return !!change && change.changeType !== "NO_CHANGE";
|
|
25271
|
-
});
|
|
25272
|
-
if (level.length === 0)
|
|
25838
|
+
if (change.changeType === "NO_CHANGE")
|
|
25273
25839
|
continue;
|
|
25840
|
+
createUpdateIds.push(id);
|
|
25841
|
+
}
|
|
25842
|
+
if (createUpdateIds.length > 0) {
|
|
25274
25843
|
this.logger.info(
|
|
25275
|
-
`
|
|
25844
|
+
`Deploying ${createUpdateIds.length} resource(s) (DAG: ${executionLevels.length} levels, max parallel: ${concurrency})`
|
|
25276
25845
|
);
|
|
25277
|
-
const
|
|
25278
|
-
|
|
25279
|
-
|
|
25280
|
-
|
|
25281
|
-
|
|
25282
|
-
|
|
25283
|
-
|
|
25284
|
-
|
|
25846
|
+
const createUpdateExecutor = new DagExecutor();
|
|
25847
|
+
const provisionable = new Set(createUpdateIds);
|
|
25848
|
+
for (const id of createUpdateIds) {
|
|
25849
|
+
const allDeps = this.dagBuilder.getDirectDependencies(dag, id);
|
|
25850
|
+
const deps = new Set(allDeps.filter((d) => provisionable.has(d)));
|
|
25851
|
+
createUpdateExecutor.add({
|
|
25852
|
+
id,
|
|
25853
|
+
dependencies: deps,
|
|
25854
|
+
state: "pending",
|
|
25855
|
+
data: changes.get(id)
|
|
25856
|
+
});
|
|
25857
|
+
}
|
|
25858
|
+
try {
|
|
25859
|
+
await createUpdateExecutor.execute(
|
|
25860
|
+
concurrency,
|
|
25861
|
+
async (node) => {
|
|
25862
|
+
const logicalId = node.id;
|
|
25863
|
+
const change = node.data;
|
|
25285
25864
|
const previousState = currentState.resources[logicalId] ? { ...currentState.resources[logicalId] } : void 0;
|
|
25286
25865
|
try {
|
|
25287
25866
|
await this.provisionResource(
|
|
@@ -25308,30 +25887,36 @@ var DeployEngine = class _DeployEngine {
|
|
|
25308
25887
|
properties: newResources[logicalId]?.properties
|
|
25309
25888
|
});
|
|
25310
25889
|
saveStateAfterResource(logicalId);
|
|
25311
|
-
}
|
|
25312
|
-
|
|
25313
|
-
|
|
25314
|
-
|
|
25315
|
-
|
|
25316
|
-
|
|
25317
|
-
|
|
25890
|
+
},
|
|
25891
|
+
() => this.interrupted
|
|
25892
|
+
);
|
|
25893
|
+
} finally {
|
|
25894
|
+
await saveChain;
|
|
25895
|
+
}
|
|
25896
|
+
if (this.interrupted && this.hasPending(createUpdateExecutor)) {
|
|
25897
|
+
throw new InterruptedError();
|
|
25318
25898
|
}
|
|
25319
25899
|
}
|
|
25320
25900
|
if (deleteChanges.size > 0) {
|
|
25321
25901
|
this.logger.info(`Deleting ${deleteChanges.size} resource(s)`);
|
|
25322
|
-
const
|
|
25323
|
-
|
|
25324
|
-
|
|
25325
|
-
|
|
25326
|
-
|
|
25327
|
-
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
|
|
25333
|
-
|
|
25334
|
-
|
|
25902
|
+
const deleteDeps = this.buildDeletionDependencies(deleteChanges, currentState);
|
|
25903
|
+
const deleteExecutor = new DagExecutor();
|
|
25904
|
+
for (const id of deleteChanges) {
|
|
25905
|
+
deleteExecutor.add({
|
|
25906
|
+
id,
|
|
25907
|
+
dependencies: deleteDeps.get(id) ?? /* @__PURE__ */ new Set(),
|
|
25908
|
+
state: "pending",
|
|
25909
|
+
data: changes.get(id)
|
|
25910
|
+
});
|
|
25911
|
+
}
|
|
25912
|
+
try {
|
|
25913
|
+
await deleteExecutor.execute(
|
|
25914
|
+
concurrency,
|
|
25915
|
+
async (node) => {
|
|
25916
|
+
const logicalId = node.id;
|
|
25917
|
+
const change = node.data;
|
|
25918
|
+
const previousState = currentState.resources[logicalId] ? { ...currentState.resources[logicalId] } : void 0;
|
|
25919
|
+
try {
|
|
25335
25920
|
await this.provisionResource(
|
|
25336
25921
|
logicalId,
|
|
25337
25922
|
change,
|
|
@@ -25343,23 +25928,25 @@ var DeployEngine = class _DeployEngine {
|
|
|
25343
25928
|
actualCounts,
|
|
25344
25929
|
progress
|
|
25345
25930
|
);
|
|
25346
|
-
|
|
25347
|
-
|
|
25348
|
-
|
|
25349
|
-
|
|
25350
|
-
|
|
25351
|
-
|
|
25352
|
-
|
|
25353
|
-
|
|
25354
|
-
|
|
25931
|
+
} catch (provisionError) {
|
|
25932
|
+
this.interrupted = true;
|
|
25933
|
+
throw provisionError;
|
|
25934
|
+
}
|
|
25935
|
+
completedOperations.push({
|
|
25936
|
+
logicalId,
|
|
25937
|
+
changeType: "DELETE",
|
|
25938
|
+
resourceType: change.resourceType,
|
|
25939
|
+
previousState
|
|
25940
|
+
});
|
|
25941
|
+
saveStateAfterResource(logicalId);
|
|
25942
|
+
},
|
|
25943
|
+
() => this.interrupted
|
|
25355
25944
|
);
|
|
25945
|
+
} finally {
|
|
25356
25946
|
await saveChain;
|
|
25357
|
-
|
|
25358
|
-
|
|
25359
|
-
);
|
|
25360
|
-
if (deleteFailures.length > 0) {
|
|
25361
|
-
throw deleteFailures[0].reason;
|
|
25362
|
-
}
|
|
25947
|
+
}
|
|
25948
|
+
if (this.interrupted && this.hasPending(deleteExecutor)) {
|
|
25949
|
+
throw new InterruptedError();
|
|
25363
25950
|
}
|
|
25364
25951
|
}
|
|
25365
25952
|
} catch (error) {
|
|
@@ -25453,12 +26040,12 @@ var DeployEngine = class _DeployEngine {
|
|
|
25453
26040
|
* - UPDATE → update back to previous properties
|
|
25454
26041
|
* - DELETE → cannot rollback (resource already deleted), log warning
|
|
25455
26042
|
*
|
|
25456
|
-
* Resources
|
|
25457
|
-
* (e.g., IAM Policy depends on IAM Role). When rolling back
|
|
25458
|
-
* dependent resources must be deleted before their
|
|
25459
|
-
* sorts CREATE rollback operations using dependency
|
|
25460
|
-
* then processes UPDATE/DELETE rollbacks, and finally
|
|
25461
|
-
* rollback deletions.
|
|
26043
|
+
* Resources completed concurrently in the dispatcher may have dependencies
|
|
26044
|
+
* between them (e.g., IAM Policy depends on IAM Role). When rolling back
|
|
26045
|
+
* CREATEs (deleting), dependent resources must be deleted before their
|
|
26046
|
+
* dependencies. This method sorts CREATE rollback operations using dependency
|
|
26047
|
+
* information from state, then processes UPDATE/DELETE rollbacks, and finally
|
|
26048
|
+
* processes sorted CREATE rollback deletions.
|
|
25462
26049
|
*/
|
|
25463
26050
|
async performRollback(completedOperations, stateResources, _stackName) {
|
|
25464
26051
|
if (completedOperations.length === 0) {
|
|
@@ -25491,7 +26078,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
25491
26078
|
* Sort CREATE rollback operations so that resources depending on others
|
|
25492
26079
|
* are deleted first (reverse dependency order).
|
|
25493
26080
|
*
|
|
25494
|
-
* Uses state dependencies to
|
|
26081
|
+
* Uses state dependencies to determine reverse-dependency order, similar to buildDeletionDependencies.
|
|
25495
26082
|
*/
|
|
25496
26083
|
sortRollbackCreates(createOps, stateResources) {
|
|
25497
26084
|
const opMap = /* @__PURE__ */ new Map();
|
|
@@ -25882,48 +26469,34 @@ var DeployEngine = class _DeployEngine {
|
|
|
25882
26469
|
const deps = parser.extractDependencies(resource);
|
|
25883
26470
|
return deps.size > 0 ? [...deps] : void 0;
|
|
25884
26471
|
}
|
|
26472
|
+
// Type-based implicit deletion ordering rules are defined in
|
|
26473
|
+
// src/analyzer/implicit-delete-deps.ts so the deploy DELETE phase and the
|
|
26474
|
+
// standalone destroy command apply the same rules.
|
|
25885
26475
|
/**
|
|
25886
|
-
*
|
|
26476
|
+
* Build a per-resource map of "must be deleted before me" dependencies for
|
|
26477
|
+
* the DELETE phase, derived from state-recorded dependencies plus implicit
|
|
26478
|
+
* type-based ordering rules.
|
|
25887
26479
|
*
|
|
25888
|
-
*
|
|
25889
|
-
*
|
|
25890
|
-
*
|
|
25891
|
-
|
|
25892
|
-
* because AWS won't let you delete an IGW while it's still attached to a VPC.
|
|
25893
|
-
*/
|
|
25894
|
-
static IMPLICIT_DELETE_DEPENDENCIES = {
|
|
25895
|
-
// IGW must be deleted AFTER VPCGatewayAttachment
|
|
25896
|
-
"AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
|
|
25897
|
-
// EventBus must be deleted AFTER Rules on that bus
|
|
25898
|
-
"AWS::Events::EventBus": ["AWS::Events::Rule"],
|
|
25899
|
-
// VPC must be deleted AFTER all VPC-dependent resources
|
|
25900
|
-
"AWS::EC2::VPC": [
|
|
25901
|
-
"AWS::EC2::Subnet",
|
|
25902
|
-
"AWS::EC2::SecurityGroup",
|
|
25903
|
-
"AWS::EC2::InternetGateway",
|
|
25904
|
-
"AWS::EC2::EgressOnlyInternetGateway",
|
|
25905
|
-
"AWS::EC2::VPCGatewayAttachment",
|
|
25906
|
-
"AWS::EC2::RouteTable"
|
|
25907
|
-
],
|
|
25908
|
-
// Subnet must be deleted AFTER RouteTableAssociation
|
|
25909
|
-
"AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation"],
|
|
25910
|
-
// RouteTable must be deleted AFTER Route and Association
|
|
25911
|
-
"AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
|
|
25912
|
-
// SecurityGroup must be deleted AFTER SecurityGroupIngress/Egress
|
|
25913
|
-
"AWS::EC2::SecurityGroup": ["AWS::EC2::SecurityGroupIngress", "AWS::EC2::SecurityGroupEgress"]
|
|
25914
|
-
};
|
|
26480
|
+
* For a resource X, the returned set contains every resource Y such that Y
|
|
26481
|
+
* must finish deleting before X starts — i.e., Y depends on X (or is otherwise
|
|
26482
|
+
* required to vanish first per implicit type rules).
|
|
26483
|
+
*/
|
|
25915
26484
|
/**
|
|
25916
|
-
*
|
|
25917
|
-
*
|
|
26485
|
+
* Returns true if the executor still has un-started pending nodes —
|
|
26486
|
+
* used to distinguish "SIGINT cancelled real work" from "SIGINT landed
|
|
26487
|
+
* after all nodes already completed" (the latter should not error).
|
|
25918
26488
|
*/
|
|
25919
|
-
|
|
26489
|
+
hasPending(executor) {
|
|
26490
|
+
for (const node of executor.values()) {
|
|
26491
|
+
if (node.state === "pending")
|
|
26492
|
+
return true;
|
|
26493
|
+
}
|
|
26494
|
+
return false;
|
|
26495
|
+
}
|
|
26496
|
+
buildDeletionDependencies(deleteIds, state) {
|
|
25920
26497
|
const dependedBy = /* @__PURE__ */ new Map();
|
|
25921
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
25922
26498
|
for (const id of deleteIds) {
|
|
25923
|
-
|
|
25924
|
-
dependedBy.set(id, /* @__PURE__ */ new Set());
|
|
25925
|
-
if (!inDegree.has(id))
|
|
25926
|
-
inDegree.set(id, 0);
|
|
26499
|
+
dependedBy.set(id, /* @__PURE__ */ new Set());
|
|
25927
26500
|
}
|
|
25928
26501
|
for (const id of deleteIds) {
|
|
25929
26502
|
const resource = state.resources[id];
|
|
@@ -25932,38 +26505,11 @@ var DeployEngine = class _DeployEngine {
|
|
|
25932
26505
|
for (const dep of resource.dependencies) {
|
|
25933
26506
|
if (!deleteIds.has(dep))
|
|
25934
26507
|
continue;
|
|
25935
|
-
if (!dependedBy.has(dep))
|
|
25936
|
-
dependedBy.set(dep, /* @__PURE__ */ new Set());
|
|
25937
26508
|
dependedBy.get(dep).add(id);
|
|
25938
|
-
inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
|
|
25939
26509
|
}
|
|
25940
26510
|
}
|
|
25941
26511
|
this.addImplicitDeleteDependencies(deleteIds, state, dependedBy);
|
|
25942
|
-
|
|
25943
|
-
let remaining = new Set(deleteIds);
|
|
25944
|
-
while (remaining.size > 0) {
|
|
25945
|
-
const level = [];
|
|
25946
|
-
for (const id of remaining) {
|
|
25947
|
-
const dependents = dependedBy.get(id);
|
|
25948
|
-
const hasPendingDependents = dependents ? [...dependents].some((d) => remaining.has(d)) : false;
|
|
25949
|
-
if (!hasPendingDependents) {
|
|
25950
|
-
level.push(id);
|
|
25951
|
-
}
|
|
25952
|
-
}
|
|
25953
|
-
if (level.length === 0) {
|
|
25954
|
-
this.logger.warn(
|
|
25955
|
-
`Circular dependency detected in delete order, deleting remaining ${remaining.size} resources`
|
|
25956
|
-
);
|
|
25957
|
-
levels.push([...remaining]);
|
|
25958
|
-
break;
|
|
25959
|
-
}
|
|
25960
|
-
levels.push(level);
|
|
25961
|
-
remaining = new Set([...remaining].filter((id) => !level.includes(id)));
|
|
25962
|
-
}
|
|
25963
|
-
this.logger.debug(
|
|
25964
|
-
`Delete order: ${levels.length} levels - ${levels.map((l, i) => `L${i + 1}(${l.length})`).join(", ")}`
|
|
25965
|
-
);
|
|
25966
|
-
return levels;
|
|
26512
|
+
return dependedBy;
|
|
25967
26513
|
}
|
|
25968
26514
|
/**
|
|
25969
26515
|
* Add implicit delete dependency edges based on resource type relationships.
|
|
@@ -25991,7 +26537,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
25991
26537
|
const resource = state.resources[id];
|
|
25992
26538
|
if (!resource)
|
|
25993
26539
|
continue;
|
|
25994
|
-
const mustDeleteAfter =
|
|
26540
|
+
const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];
|
|
25995
26541
|
if (!mustDeleteAfter)
|
|
25996
26542
|
continue;
|
|
25997
26543
|
for (const depType of mustDeleteAfter) {
|
|
@@ -26777,27 +27323,6 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
26777
27323
|
}
|
|
26778
27324
|
};
|
|
26779
27325
|
}
|
|
26780
|
-
const implicitDeleteDeps = {
|
|
26781
|
-
"AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
|
|
26782
|
-
"AWS::Events::EventBus": ["AWS::Events::Rule"],
|
|
26783
|
-
"AWS::Athena::WorkGroup": ["AWS::Athena::NamedQuery"],
|
|
26784
|
-
"AWS::CloudFront::ResponseHeadersPolicy": ["AWS::CloudFront::Distribution"],
|
|
26785
|
-
"AWS::CloudFront::CachePolicy": ["AWS::CloudFront::Distribution"],
|
|
26786
|
-
"AWS::CloudFront::OriginAccessControl": ["AWS::CloudFront::Distribution"],
|
|
26787
|
-
"AWS::EC2::VPC": [
|
|
26788
|
-
"AWS::EC2::Subnet",
|
|
26789
|
-
"AWS::EC2::SecurityGroup",
|
|
26790
|
-
"AWS::EC2::InternetGateway",
|
|
26791
|
-
"AWS::EC2::VPCGatewayAttachment",
|
|
26792
|
-
"AWS::EC2::RouteTable"
|
|
26793
|
-
],
|
|
26794
|
-
"AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation"],
|
|
26795
|
-
"AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
|
|
26796
|
-
"AWS::EC2::SecurityGroup": [
|
|
26797
|
-
"AWS::EC2::SecurityGroupIngress",
|
|
26798
|
-
"AWS::EC2::SecurityGroupEgress"
|
|
26799
|
-
]
|
|
26800
|
-
};
|
|
26801
27326
|
const typeToLogicalIds = /* @__PURE__ */ new Map();
|
|
26802
27327
|
for (const [logicalId, resource] of Object.entries(currentState.resources)) {
|
|
26803
27328
|
const ids = typeToLogicalIds.get(resource.resourceType) ?? [];
|
|
@@ -26805,7 +27330,7 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
26805
27330
|
typeToLogicalIds.set(resource.resourceType, ids);
|
|
26806
27331
|
}
|
|
26807
27332
|
for (const [logicalId, resource] of Object.entries(currentState.resources)) {
|
|
26808
|
-
const mustDeleteAfter =
|
|
27333
|
+
const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];
|
|
26809
27334
|
if (!mustDeleteAfter)
|
|
26810
27335
|
continue;
|
|
26811
27336
|
for (const depType of mustDeleteAfter) {
|
|
@@ -27029,7 +27554,7 @@ function reorderArgs(argv) {
|
|
|
27029
27554
|
}
|
|
27030
27555
|
async function main() {
|
|
27031
27556
|
const program = new Command8();
|
|
27032
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
27557
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.3.0");
|
|
27033
27558
|
program.addCommand(createBootstrapCommand());
|
|
27034
27559
|
program.addCommand(createSynthCommand());
|
|
27035
27560
|
program.addCommand(createDeployCommand());
|