@go-to-k/cdkd 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +566 -80
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.3.1.tgz +0 -0
- package/dist/index.js +135 -32
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.2.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,14 @@ import {
|
|
|
10897
10987
|
GetFunctionCommand,
|
|
10898
10988
|
ResourceNotFoundException
|
|
10899
10989
|
} from "@aws-sdk/client-lambda";
|
|
10990
|
+
import {
|
|
10991
|
+
DescribeNetworkInterfacesCommand,
|
|
10992
|
+
DeleteNetworkInterfaceCommand
|
|
10993
|
+
} from "@aws-sdk/client-ec2";
|
|
10900
10994
|
init_aws_clients();
|
|
10901
10995
|
var LambdaFunctionProvider = class {
|
|
10902
10996
|
lambdaClient;
|
|
10997
|
+
ec2Client;
|
|
10903
10998
|
logger = getLogger().child("LambdaFunctionProvider");
|
|
10904
10999
|
handledProperties = /* @__PURE__ */ new Map([
|
|
10905
11000
|
[
|
|
@@ -10919,13 +11014,22 @@ var LambdaFunctionProvider = class {
|
|
|
10919
11014
|
"Architectures",
|
|
10920
11015
|
"PackageType",
|
|
10921
11016
|
"TracingConfig",
|
|
10922
|
-
"EphemeralStorage"
|
|
11017
|
+
"EphemeralStorage",
|
|
11018
|
+
"VpcConfig"
|
|
10923
11019
|
])
|
|
10924
11020
|
]
|
|
10925
11021
|
]);
|
|
11022
|
+
// ENI detach polling configuration (overridable for tests).
|
|
11023
|
+
// Lambda VPC ENI detach is async and can take 20-40 minutes in the worst case;
|
|
11024
|
+
// we poll up to 10 minutes and then warn-and-continue, since downstream Subnet/SG
|
|
11025
|
+
// deletion has its own retry logic that handles a small remaining window.
|
|
11026
|
+
eniWaitTimeoutMs = 10 * 60 * 1e3;
|
|
11027
|
+
eniWaitInitialDelayMs = 1e4;
|
|
11028
|
+
eniWaitMaxDelayMs = 3e4;
|
|
10926
11029
|
constructor() {
|
|
10927
11030
|
const awsClients = getAwsClients();
|
|
10928
11031
|
this.lambdaClient = awsClients.lambda;
|
|
11032
|
+
this.ec2Client = awsClients.ec2;
|
|
10929
11033
|
}
|
|
10930
11034
|
/**
|
|
10931
11035
|
* Create a Lambda function
|
|
@@ -10973,6 +11077,7 @@ var LambdaFunctionProvider = class {
|
|
|
10973
11077
|
PackageType: properties["PackageType"],
|
|
10974
11078
|
TracingConfig: properties["TracingConfig"],
|
|
10975
11079
|
EphemeralStorage: properties["EphemeralStorage"],
|
|
11080
|
+
VpcConfig: this.buildVpcConfig(properties["VpcConfig"]),
|
|
10976
11081
|
Tags: tags
|
|
10977
11082
|
};
|
|
10978
11083
|
const response = await this.lambdaClient.send(new CreateFunctionCommand(createParams));
|
|
@@ -11011,7 +11116,8 @@ var LambdaFunctionProvider = class {
|
|
|
11011
11116
|
"Environment",
|
|
11012
11117
|
"Layers",
|
|
11013
11118
|
"TracingConfig",
|
|
11014
|
-
"EphemeralStorage"
|
|
11119
|
+
"EphemeralStorage",
|
|
11120
|
+
"VpcConfig"
|
|
11015
11121
|
];
|
|
11016
11122
|
let hasConfigChanges = false;
|
|
11017
11123
|
for (const field of configFields) {
|
|
@@ -11032,7 +11138,11 @@ var LambdaFunctionProvider = class {
|
|
|
11032
11138
|
Environment: properties["Environment"],
|
|
11033
11139
|
Layers: properties["Layers"],
|
|
11034
11140
|
TracingConfig: properties["TracingConfig"],
|
|
11035
|
-
EphemeralStorage: properties["EphemeralStorage"]
|
|
11141
|
+
EphemeralStorage: properties["EphemeralStorage"],
|
|
11142
|
+
VpcConfig: this.buildVpcConfigForUpdate(
|
|
11143
|
+
properties["VpcConfig"],
|
|
11144
|
+
previousProperties["VpcConfig"]
|
|
11145
|
+
)
|
|
11036
11146
|
};
|
|
11037
11147
|
await this.lambdaClient.send(new UpdateFunctionConfigurationCommand(configParams));
|
|
11038
11148
|
this.logger.debug(`Updated configuration for Lambda function ${physicalId}`);
|
|
@@ -11076,9 +11186,37 @@ var LambdaFunctionProvider = class {
|
|
|
11076
11186
|
}
|
|
11077
11187
|
/**
|
|
11078
11188
|
* Delete a Lambda function
|
|
11189
|
+
*
|
|
11190
|
+
* For VPC-enabled Lambda functions, AWS detaches the hyperplane ENIs
|
|
11191
|
+
* asynchronously after DeleteFunction returns. If we let downstream
|
|
11192
|
+
* resource deletion (Subnet / SecurityGroup) proceed immediately, those
|
|
11193
|
+
* deletions fail with "has dependencies" / "has a dependent object".
|
|
11194
|
+
*
|
|
11195
|
+
* To smooth this out, when properties carry a VpcConfig with subnets or
|
|
11196
|
+
* security groups, we poll DescribeNetworkInterfaces for the function's
|
|
11197
|
+
* managed ENIs and only return once they are gone (or the timeout elapses).
|
|
11079
11198
|
*/
|
|
11080
|
-
async delete(logicalId, physicalId, resourceType,
|
|
11199
|
+
async delete(logicalId, physicalId, resourceType, properties) {
|
|
11081
11200
|
this.logger.debug(`Deleting Lambda function ${logicalId}: ${physicalId}`);
|
|
11201
|
+
const hasVpcConfig = this.hasVpcConfig(properties?.["VpcConfig"]);
|
|
11202
|
+
if (hasVpcConfig) {
|
|
11203
|
+
try {
|
|
11204
|
+
await this.lambdaClient.send(
|
|
11205
|
+
new UpdateFunctionConfigurationCommand({
|
|
11206
|
+
FunctionName: physicalId,
|
|
11207
|
+
VpcConfig: { SubnetIds: [], SecurityGroupIds: [] }
|
|
11208
|
+
})
|
|
11209
|
+
);
|
|
11210
|
+
this.logger.debug(`Detached VPC config from Lambda ${physicalId} before deletion`);
|
|
11211
|
+
} catch (error) {
|
|
11212
|
+
if (error instanceof ResourceNotFoundException) {
|
|
11213
|
+
return;
|
|
11214
|
+
}
|
|
11215
|
+
this.logger.warn(
|
|
11216
|
+
`Pre-delete VPC detach failed for ${physicalId}: ${error instanceof Error ? error.message : String(error)} \u2014 continuing with delete`
|
|
11217
|
+
);
|
|
11218
|
+
}
|
|
11219
|
+
}
|
|
11082
11220
|
try {
|
|
11083
11221
|
await this.lambdaClient.send(new DeleteFunctionCommand({ FunctionName: physicalId }));
|
|
11084
11222
|
this.logger.debug(`Successfully deleted Lambda function ${logicalId}`);
|
|
@@ -11096,6 +11234,183 @@ var LambdaFunctionProvider = class {
|
|
|
11096
11234
|
cause
|
|
11097
11235
|
);
|
|
11098
11236
|
}
|
|
11237
|
+
if (hasVpcConfig) {
|
|
11238
|
+
await this.cleanupLambdaEnis(physicalId);
|
|
11239
|
+
}
|
|
11240
|
+
}
|
|
11241
|
+
/**
|
|
11242
|
+
* Build Lambda VpcConfig parameter from CDK properties.
|
|
11243
|
+
*
|
|
11244
|
+
* Returns undefined when VpcConfig is unset, so the SDK leaves the function
|
|
11245
|
+
* outside any VPC. Returns an empty config (no subnets, no SGs) when caller
|
|
11246
|
+
* explicitly clears it on update — that detaches the function from its VPC.
|
|
11247
|
+
*/
|
|
11248
|
+
buildVpcConfig(raw) {
|
|
11249
|
+
if (raw === void 0 || raw === null) {
|
|
11250
|
+
return void 0;
|
|
11251
|
+
}
|
|
11252
|
+
if (typeof raw !== "object") {
|
|
11253
|
+
return void 0;
|
|
11254
|
+
}
|
|
11255
|
+
const vpc = raw;
|
|
11256
|
+
const result = {};
|
|
11257
|
+
if (Array.isArray(vpc["SubnetIds"])) {
|
|
11258
|
+
result.SubnetIds = vpc["SubnetIds"];
|
|
11259
|
+
}
|
|
11260
|
+
if (Array.isArray(vpc["SecurityGroupIds"])) {
|
|
11261
|
+
result.SecurityGroupIds = vpc["SecurityGroupIds"];
|
|
11262
|
+
}
|
|
11263
|
+
if (typeof vpc["Ipv6AllowedForDualStack"] === "boolean") {
|
|
11264
|
+
result.Ipv6AllowedForDualStack = vpc["Ipv6AllowedForDualStack"];
|
|
11265
|
+
}
|
|
11266
|
+
return result;
|
|
11267
|
+
}
|
|
11268
|
+
/**
|
|
11269
|
+
* Build VpcConfig for an update call, accounting for VPC detach.
|
|
11270
|
+
*
|
|
11271
|
+
* UpdateFunctionConfiguration treats an absent VpcConfig as "no change",
|
|
11272
|
+
* so omitting it cannot move a function out of its existing VPC. To
|
|
11273
|
+
* detach we must explicitly send empty SubnetIds / SecurityGroupIds.
|
|
11274
|
+
*/
|
|
11275
|
+
buildVpcConfigForUpdate(newRaw, previousRaw) {
|
|
11276
|
+
const next = this.buildVpcConfig(newRaw);
|
|
11277
|
+
if (next) {
|
|
11278
|
+
return next;
|
|
11279
|
+
}
|
|
11280
|
+
if (this.hasVpcConfig(previousRaw)) {
|
|
11281
|
+
return { SubnetIds: [], SecurityGroupIds: [] };
|
|
11282
|
+
}
|
|
11283
|
+
return void 0;
|
|
11284
|
+
}
|
|
11285
|
+
/**
|
|
11286
|
+
* Determine whether the function actually attaches to a VPC, i.e. has at
|
|
11287
|
+
* least one Subnet ID. A bare VpcConfig with empty arrays does not create
|
|
11288
|
+
* any ENIs, so we skip the wait in that case.
|
|
11289
|
+
*/
|
|
11290
|
+
hasVpcConfig(raw) {
|
|
11291
|
+
if (raw === void 0 || raw === null || typeof raw !== "object") {
|
|
11292
|
+
return false;
|
|
11293
|
+
}
|
|
11294
|
+
const vpc = raw;
|
|
11295
|
+
const subnets = vpc["SubnetIds"];
|
|
11296
|
+
return Array.isArray(subnets) && subnets.length > 0;
|
|
11297
|
+
}
|
|
11298
|
+
/**
|
|
11299
|
+
* Clean up Lambda-managed ENIs for the given function: list, then attempt
|
|
11300
|
+
* DeleteNetworkInterface on each. Repeat until no matching ENIs remain
|
|
11301
|
+
* or the configured timeout elapses.
|
|
11302
|
+
*
|
|
11303
|
+
* Why direct delete (not just wait): an `available` ENI still counts as a
|
|
11304
|
+
* Subnet / SecurityGroup dependency, so DeleteSubnet / DeleteSecurityGroup
|
|
11305
|
+
* fail until the ENI itself is gone. AWS's eventual cleanup of unused
|
|
11306
|
+
* Lambda hyperplane ENIs can take well over an hour, which is far longer
|
|
11307
|
+
* than any reasonable destroy budget. Calling DeleteNetworkInterface
|
|
11308
|
+
* ourselves (best-effort) clears `available` ENIs in seconds.
|
|
11309
|
+
*
|
|
11310
|
+
* In-use ENIs (e.g. immediately after the pre-delete VPC detach) cannot
|
|
11311
|
+
* be deleted yet — we swallow that error and retry on the next iteration
|
|
11312
|
+
* once they transition to `available`.
|
|
11313
|
+
*
|
|
11314
|
+
* Lambda VPC ENI Descriptions follow the pattern
|
|
11315
|
+
* "AWS Lambda VPC ENI-<functionName>"
|
|
11316
|
+
* (and historically "AWS Lambda VPC ENI-<functionName>-<uuid>"). We
|
|
11317
|
+
* narrow the query with a `requester-id` filter and then match the
|
|
11318
|
+
* function name as a hyphen-bounded token to avoid false positives like
|
|
11319
|
+
* "myfn" matching for function "fn".
|
|
11320
|
+
*
|
|
11321
|
+
* Polling: starts at eniWaitInitialDelayMs (10s), exponential backoff up
|
|
11322
|
+
* to eniWaitMaxDelayMs (30s), bounded by eniWaitTimeoutMs (10min).
|
|
11323
|
+
* Timeout is a soft warning — downstream Subnet/SG deletion has its own
|
|
11324
|
+
* retries.
|
|
11325
|
+
*/
|
|
11326
|
+
async cleanupLambdaEnis(functionName) {
|
|
11327
|
+
const start = Date.now();
|
|
11328
|
+
let delay = this.eniWaitInitialDelayMs;
|
|
11329
|
+
let attempt = 0;
|
|
11330
|
+
this.logger.debug(
|
|
11331
|
+
`Cleaning up Lambda VPC ENIs for function ${functionName} (timeout ${this.eniWaitTimeoutMs}ms)`
|
|
11332
|
+
);
|
|
11333
|
+
const descriptionNeedle = `AWS Lambda VPC ENI`;
|
|
11334
|
+
const functionNamePattern = new RegExp(`(^|-)${escapeRegExp(functionName)}(-|$)`);
|
|
11335
|
+
for (; ; ) {
|
|
11336
|
+
attempt++;
|
|
11337
|
+
let enis = [];
|
|
11338
|
+
let listFailed = false;
|
|
11339
|
+
try {
|
|
11340
|
+
enis = await this.listLambdaEnis(descriptionNeedle, functionNamePattern);
|
|
11341
|
+
} catch (error) {
|
|
11342
|
+
this.logger.warn(
|
|
11343
|
+
`DescribeNetworkInterfaces failed while cleaning up Lambda ENIs of ${functionName}: ${error instanceof Error ? error.message : String(error)}`
|
|
11344
|
+
);
|
|
11345
|
+
listFailed = true;
|
|
11346
|
+
}
|
|
11347
|
+
if (!listFailed && enis.length === 0) {
|
|
11348
|
+
this.logger.debug(
|
|
11349
|
+
`Lambda ENIs for ${functionName} fully cleaned up after ${attempt} attempt(s) / ${Date.now() - start}ms`
|
|
11350
|
+
);
|
|
11351
|
+
return;
|
|
11352
|
+
}
|
|
11353
|
+
if (enis.length > 0) {
|
|
11354
|
+
await Promise.all(
|
|
11355
|
+
enis.map(async (eni) => {
|
|
11356
|
+
try {
|
|
11357
|
+
await this.ec2Client.send(
|
|
11358
|
+
new DeleteNetworkInterfaceCommand({ NetworkInterfaceId: eni.id })
|
|
11359
|
+
);
|
|
11360
|
+
this.logger.debug(`Deleted Lambda ENI ${eni.id} for ${functionName}`);
|
|
11361
|
+
} catch (error) {
|
|
11362
|
+
this.logger.debug(
|
|
11363
|
+
`ENI ${eni.id} (status=${eni.status}) not yet deletable: ${error instanceof Error ? error.message : String(error)}`
|
|
11364
|
+
);
|
|
11365
|
+
}
|
|
11366
|
+
})
|
|
11367
|
+
);
|
|
11368
|
+
}
|
|
11369
|
+
const elapsed = Date.now() - start;
|
|
11370
|
+
if (elapsed >= this.eniWaitTimeoutMs) {
|
|
11371
|
+
this.logger.warn(
|
|
11372
|
+
`Timeout (${this.eniWaitTimeoutMs}ms) cleaning up Lambda VPC ENIs of ${functionName} (${enis.length} remaining). Continuing \u2014 downstream Subnet/SG deletion will retry as needed.`
|
|
11373
|
+
);
|
|
11374
|
+
return;
|
|
11375
|
+
}
|
|
11376
|
+
const remaining = this.eniWaitTimeoutMs - elapsed;
|
|
11377
|
+
const sleepMs = Math.min(delay, remaining);
|
|
11378
|
+
await this.sleep(sleepMs);
|
|
11379
|
+
delay = Math.min(delay * 2, this.eniWaitMaxDelayMs);
|
|
11380
|
+
}
|
|
11381
|
+
}
|
|
11382
|
+
/**
|
|
11383
|
+
* List Lambda-managed ENIs for the given function, paginating through
|
|
11384
|
+
* DescribeNetworkInterfaces and filtering on Description substring.
|
|
11385
|
+
*
|
|
11386
|
+
* Server-side filter (`description`) does not support wildcards on this
|
|
11387
|
+
* API, so we narrow with `requester-id` + match Description client-side.
|
|
11388
|
+
*/
|
|
11389
|
+
async listLambdaEnis(descriptionNeedle, functionNamePattern) {
|
|
11390
|
+
const enis = [];
|
|
11391
|
+
let nextToken;
|
|
11392
|
+
do {
|
|
11393
|
+
const resp = await this.ec2Client.send(
|
|
11394
|
+
new DescribeNetworkInterfacesCommand({
|
|
11395
|
+
Filters: [
|
|
11396
|
+
// Lambda hyperplane ENIs are owned by the Lambda service principal.
|
|
11397
|
+
{ Name: "requester-id", Values: ["*:awslambda_*"] }
|
|
11398
|
+
],
|
|
11399
|
+
NextToken: nextToken
|
|
11400
|
+
})
|
|
11401
|
+
);
|
|
11402
|
+
for (const ni of resp.NetworkInterfaces ?? []) {
|
|
11403
|
+
const desc = ni.Description ?? "";
|
|
11404
|
+
if (ni.NetworkInterfaceId && desc.includes(descriptionNeedle) && functionNamePattern.test(desc)) {
|
|
11405
|
+
enis.push({ id: ni.NetworkInterfaceId, status: ni.Status ?? "unknown" });
|
|
11406
|
+
}
|
|
11407
|
+
}
|
|
11408
|
+
nextToken = resp.NextToken;
|
|
11409
|
+
} while (nextToken);
|
|
11410
|
+
return enis;
|
|
11411
|
+
}
|
|
11412
|
+
sleep(ms) {
|
|
11413
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
11099
11414
|
}
|
|
11100
11415
|
/**
|
|
11101
11416
|
* Build Lambda Code parameter from CDK properties
|
|
@@ -11182,6 +11497,9 @@ var LambdaFunctionProvider = class {
|
|
|
11182
11497
|
return (crc ^ 4294967295) >>> 0;
|
|
11183
11498
|
}
|
|
11184
11499
|
};
|
|
11500
|
+
function escapeRegExp(input) {
|
|
11501
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11502
|
+
}
|
|
11185
11503
|
|
|
11186
11504
|
// src/provisioning/providers/lambda-permission-provider.ts
|
|
11187
11505
|
import {
|
|
@@ -13505,6 +13823,8 @@ import {
|
|
|
13505
13823
|
DeleteSecurityGroupCommand,
|
|
13506
13824
|
AuthorizeSecurityGroupIngressCommand,
|
|
13507
13825
|
RevokeSecurityGroupIngressCommand,
|
|
13826
|
+
AuthorizeSecurityGroupEgressCommand,
|
|
13827
|
+
RevokeSecurityGroupEgressCommand,
|
|
13508
13828
|
CreateTagsCommand,
|
|
13509
13829
|
DescribeSubnetsCommand as DescribeSubnetsCommand2,
|
|
13510
13830
|
DescribeSecurityGroupsCommand as DescribeSecurityGroupsCommand2,
|
|
@@ -13554,7 +13874,14 @@ var EC2Provider = class {
|
|
|
13554
13874
|
["AWS::EC2::SubnetRouteTableAssociation", /* @__PURE__ */ new Set(["SubnetId", "RouteTableId"])],
|
|
13555
13875
|
[
|
|
13556
13876
|
"AWS::EC2::SecurityGroup",
|
|
13557
|
-
/* @__PURE__ */ new Set([
|
|
13877
|
+
/* @__PURE__ */ new Set([
|
|
13878
|
+
"GroupDescription",
|
|
13879
|
+
"GroupName",
|
|
13880
|
+
"VpcId",
|
|
13881
|
+
"SecurityGroupIngress",
|
|
13882
|
+
"SecurityGroupEgress",
|
|
13883
|
+
"Tags"
|
|
13884
|
+
])
|
|
13558
13885
|
],
|
|
13559
13886
|
[
|
|
13560
13887
|
"AWS::EC2::SecurityGroupIngress",
|
|
@@ -13565,7 +13892,8 @@ var EC2Provider = class {
|
|
|
13565
13892
|
"ToPort",
|
|
13566
13893
|
"CidrIp",
|
|
13567
13894
|
"Description",
|
|
13568
|
-
"SourceSecurityGroupId"
|
|
13895
|
+
"SourceSecurityGroupId",
|
|
13896
|
+
"SourceSecurityGroupOwnerId"
|
|
13569
13897
|
])
|
|
13570
13898
|
],
|
|
13571
13899
|
[
|
|
@@ -13664,7 +13992,13 @@ var EC2Provider = class {
|
|
|
13664
13992
|
case "AWS::EC2::SubnetRouteTableAssociation":
|
|
13665
13993
|
return this.updateSubnetRouteTableAssociation(logicalId, physicalId);
|
|
13666
13994
|
case "AWS::EC2::SecurityGroup":
|
|
13667
|
-
return this.updateSecurityGroup(
|
|
13995
|
+
return this.updateSecurityGroup(
|
|
13996
|
+
logicalId,
|
|
13997
|
+
physicalId,
|
|
13998
|
+
resourceType,
|
|
13999
|
+
properties,
|
|
14000
|
+
previousProperties
|
|
14001
|
+
);
|
|
13668
14002
|
case "AWS::EC2::SecurityGroupIngress":
|
|
13669
14003
|
return this.updateSecurityGroupIngress(
|
|
13670
14004
|
logicalId,
|
|
@@ -14389,6 +14723,34 @@ var EC2Provider = class {
|
|
|
14389
14723
|
);
|
|
14390
14724
|
}
|
|
14391
14725
|
}
|
|
14726
|
+
const egressRules = properties["SecurityGroupEgress"];
|
|
14727
|
+
if (egressRules && Array.isArray(egressRules)) {
|
|
14728
|
+
try {
|
|
14729
|
+
await this.ec2Client.send(
|
|
14730
|
+
new RevokeSecurityGroupEgressCommand({
|
|
14731
|
+
GroupId: groupId,
|
|
14732
|
+
IpPermissions: [
|
|
14733
|
+
{
|
|
14734
|
+
IpProtocol: "-1",
|
|
14735
|
+
IpRanges: [{ CidrIp: "0.0.0.0/0" }]
|
|
14736
|
+
}
|
|
14737
|
+
]
|
|
14738
|
+
})
|
|
14739
|
+
);
|
|
14740
|
+
} catch (error) {
|
|
14741
|
+
if (!this.isNotFoundError(error)) {
|
|
14742
|
+
throw error;
|
|
14743
|
+
}
|
|
14744
|
+
}
|
|
14745
|
+
for (const rule of egressRules) {
|
|
14746
|
+
await this.ec2Client.send(
|
|
14747
|
+
new AuthorizeSecurityGroupEgressCommand({
|
|
14748
|
+
GroupId: groupId,
|
|
14749
|
+
IpPermissions: [this.buildIpPermission(rule, "egress")]
|
|
14750
|
+
})
|
|
14751
|
+
);
|
|
14752
|
+
}
|
|
14753
|
+
}
|
|
14392
14754
|
this.logger.debug(`Successfully created SecurityGroup ${logicalId}: ${groupId}`);
|
|
14393
14755
|
return {
|
|
14394
14756
|
physicalId: groupId,
|
|
@@ -14408,10 +14770,22 @@ var EC2Provider = class {
|
|
|
14408
14770
|
);
|
|
14409
14771
|
}
|
|
14410
14772
|
}
|
|
14411
|
-
async updateSecurityGroup(logicalId, physicalId, resourceType, properties) {
|
|
14773
|
+
async updateSecurityGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
14412
14774
|
this.logger.debug(`Updating SecurityGroup ${logicalId}: ${physicalId}`);
|
|
14413
14775
|
try {
|
|
14414
14776
|
await this.applyTags(physicalId, properties, logicalId);
|
|
14777
|
+
await this.applySecurityGroupRuleDiff(
|
|
14778
|
+
physicalId,
|
|
14779
|
+
previousProperties["SecurityGroupIngress"] ?? [],
|
|
14780
|
+
properties["SecurityGroupIngress"] ?? [],
|
|
14781
|
+
"ingress"
|
|
14782
|
+
);
|
|
14783
|
+
await this.applySecurityGroupRuleDiff(
|
|
14784
|
+
physicalId,
|
|
14785
|
+
previousProperties["SecurityGroupEgress"] ?? [],
|
|
14786
|
+
properties["SecurityGroupEgress"] ?? [],
|
|
14787
|
+
"egress"
|
|
14788
|
+
);
|
|
14415
14789
|
this.logger.debug(`Successfully updated SecurityGroup ${logicalId}`);
|
|
14416
14790
|
return {
|
|
14417
14791
|
physicalId,
|
|
@@ -14769,9 +15143,13 @@ var EC2Provider = class {
|
|
|
14769
15143
|
}
|
|
14770
15144
|
// ─── Helpers ──────────────────────────────────────────────────────
|
|
14771
15145
|
/**
|
|
14772
|
-
* Build an IpPermission object from CloudFormation-style properties
|
|
15146
|
+
* Build an IpPermission object from CloudFormation-style properties.
|
|
15147
|
+
*
|
|
15148
|
+
* The EC2 IpPermission shape is identical for ingress and egress; only the
|
|
15149
|
+
* CFn property names that point to the "other" security group differ
|
|
15150
|
+
* (SourceSecurityGroupId vs DestinationSecurityGroupId).
|
|
14773
15151
|
*/
|
|
14774
|
-
buildIpPermission(properties) {
|
|
15152
|
+
buildIpPermission(properties, direction = "ingress") {
|
|
14775
15153
|
const ipProtocol = properties["IpProtocol"] ?? "-1";
|
|
14776
15154
|
const fromPort = properties["FromPort"];
|
|
14777
15155
|
const toPort = properties["ToPort"];
|
|
@@ -14781,6 +15159,7 @@ var EC2Provider = class {
|
|
|
14781
15159
|
if (toPort !== void 0)
|
|
14782
15160
|
permission.ToPort = toPort;
|
|
14783
15161
|
const cidrIp = properties["CidrIp"];
|
|
15162
|
+
const cidrIpv6 = properties["CidrIpv6"];
|
|
14784
15163
|
const description = properties["Description"];
|
|
14785
15164
|
if (cidrIp) {
|
|
14786
15165
|
const ipRange = { CidrIp: cidrIp };
|
|
@@ -14788,17 +15167,124 @@ var EC2Provider = class {
|
|
|
14788
15167
|
ipRange.Description = description;
|
|
14789
15168
|
permission.IpRanges = [ipRange];
|
|
14790
15169
|
}
|
|
14791
|
-
|
|
14792
|
-
|
|
15170
|
+
if (cidrIpv6) {
|
|
15171
|
+
const ipv6Range = { CidrIpv6: cidrIpv6 };
|
|
15172
|
+
if (description)
|
|
15173
|
+
ipv6Range.Description = description;
|
|
15174
|
+
permission.Ipv6Ranges = [ipv6Range];
|
|
15175
|
+
}
|
|
15176
|
+
const peerGroupId = direction === "egress" ? properties["DestinationSecurityGroupId"] : properties["SourceSecurityGroupId"];
|
|
15177
|
+
if (peerGroupId) {
|
|
14793
15178
|
const groupPair = {
|
|
14794
|
-
GroupId:
|
|
15179
|
+
GroupId: peerGroupId
|
|
14795
15180
|
};
|
|
15181
|
+
if (direction === "ingress") {
|
|
15182
|
+
const peerOwnerId = properties["SourceSecurityGroupOwnerId"];
|
|
15183
|
+
if (peerOwnerId)
|
|
15184
|
+
groupPair.UserId = peerOwnerId;
|
|
15185
|
+
}
|
|
14796
15186
|
if (description)
|
|
14797
15187
|
groupPair.Description = description;
|
|
14798
15188
|
permission.UserIdGroupPairs = [groupPair];
|
|
14799
15189
|
}
|
|
15190
|
+
const prefixListId = direction === "egress" ? properties["DestinationPrefixListId"] : properties["SourcePrefixListId"];
|
|
15191
|
+
if (prefixListId) {
|
|
15192
|
+
const prefixEntry = {
|
|
15193
|
+
PrefixListId: prefixListId
|
|
15194
|
+
};
|
|
15195
|
+
if (description)
|
|
15196
|
+
prefixEntry.Description = description;
|
|
15197
|
+
permission.PrefixListIds = [prefixEntry];
|
|
15198
|
+
}
|
|
14800
15199
|
return permission;
|
|
14801
15200
|
}
|
|
15201
|
+
/**
|
|
15202
|
+
* Compute the diff between two sets of SecurityGroup rule definitions
|
|
15203
|
+
* (ingress or egress) and apply the resulting authorize/revoke calls.
|
|
15204
|
+
*
|
|
15205
|
+
* Rules are identified by a deterministic key derived from their full
|
|
15206
|
+
* shape — protocol, ports, CIDR, peer group, prefix list, description —
|
|
15207
|
+
* so updating any of those fields counts as a replacement (revoke + authorize).
|
|
15208
|
+
*/
|
|
15209
|
+
async applySecurityGroupRuleDiff(groupId, previousRules, nextRules, direction) {
|
|
15210
|
+
const ruleKey = (rule) => {
|
|
15211
|
+
const peerKey = direction === "egress" ? rule["DestinationSecurityGroupId"] : rule["SourceSecurityGroupId"];
|
|
15212
|
+
const prefixKey = direction === "egress" ? rule["DestinationPrefixListId"] : rule["SourcePrefixListId"];
|
|
15213
|
+
const peerOwner = direction === "ingress" ? rule["SourceSecurityGroupOwnerId"] : void 0;
|
|
15214
|
+
return JSON.stringify({
|
|
15215
|
+
p: rule["IpProtocol"] ?? "-1",
|
|
15216
|
+
f: rule["FromPort"] ?? null,
|
|
15217
|
+
t: rule["ToPort"] ?? null,
|
|
15218
|
+
c4: rule["CidrIp"] ?? null,
|
|
15219
|
+
c6: rule["CidrIpv6"] ?? null,
|
|
15220
|
+
peer: peerKey ?? null,
|
|
15221
|
+
peerOwner: peerOwner ?? null,
|
|
15222
|
+
pl: prefixKey ?? null,
|
|
15223
|
+
d: rule["Description"] ?? null
|
|
15224
|
+
});
|
|
15225
|
+
};
|
|
15226
|
+
const prevByKey = /* @__PURE__ */ new Map();
|
|
15227
|
+
for (const rule of previousRules)
|
|
15228
|
+
prevByKey.set(ruleKey(rule), rule);
|
|
15229
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
15230
|
+
for (const rule of nextRules)
|
|
15231
|
+
nextByKey.set(ruleKey(rule), rule);
|
|
15232
|
+
const toRevoke = [];
|
|
15233
|
+
for (const [key, rule] of prevByKey) {
|
|
15234
|
+
if (!nextByKey.has(key))
|
|
15235
|
+
toRevoke.push(rule);
|
|
15236
|
+
}
|
|
15237
|
+
const toAuthorize = [];
|
|
15238
|
+
for (const [key, rule] of nextByKey) {
|
|
15239
|
+
if (!prevByKey.has(key))
|
|
15240
|
+
toAuthorize.push(rule);
|
|
15241
|
+
}
|
|
15242
|
+
for (const rule of toRevoke) {
|
|
15243
|
+
try {
|
|
15244
|
+
if (direction === "egress") {
|
|
15245
|
+
await this.ec2Client.send(
|
|
15246
|
+
new RevokeSecurityGroupEgressCommand({
|
|
15247
|
+
GroupId: groupId,
|
|
15248
|
+
IpPermissions: [this.buildIpPermission(rule, "egress")]
|
|
15249
|
+
})
|
|
15250
|
+
);
|
|
15251
|
+
} else {
|
|
15252
|
+
await this.ec2Client.send(
|
|
15253
|
+
new RevokeSecurityGroupIngressCommand({
|
|
15254
|
+
GroupId: groupId,
|
|
15255
|
+
IpPermissions: [this.buildIpPermission(rule, "ingress")]
|
|
15256
|
+
})
|
|
15257
|
+
);
|
|
15258
|
+
}
|
|
15259
|
+
} catch (error) {
|
|
15260
|
+
if (!this.isNotFoundError(error))
|
|
15261
|
+
throw error;
|
|
15262
|
+
}
|
|
15263
|
+
}
|
|
15264
|
+
for (const rule of toAuthorize) {
|
|
15265
|
+
try {
|
|
15266
|
+
if (direction === "egress") {
|
|
15267
|
+
await this.ec2Client.send(
|
|
15268
|
+
new AuthorizeSecurityGroupEgressCommand({
|
|
15269
|
+
GroupId: groupId,
|
|
15270
|
+
IpPermissions: [this.buildIpPermission(rule, "egress")]
|
|
15271
|
+
})
|
|
15272
|
+
);
|
|
15273
|
+
} else {
|
|
15274
|
+
await this.ec2Client.send(
|
|
15275
|
+
new AuthorizeSecurityGroupIngressCommand({
|
|
15276
|
+
GroupId: groupId,
|
|
15277
|
+
IpPermissions: [this.buildIpPermission(rule, "ingress")]
|
|
15278
|
+
})
|
|
15279
|
+
);
|
|
15280
|
+
}
|
|
15281
|
+
} catch (error) {
|
|
15282
|
+
if (!(error instanceof Error && error.message.includes("already exists"))) {
|
|
15283
|
+
throw error;
|
|
15284
|
+
}
|
|
15285
|
+
}
|
|
15286
|
+
}
|
|
15287
|
+
}
|
|
14802
15288
|
// ─── AWS::EC2::NetworkAcl ────────────────────────────────────────
|
|
14803
15289
|
async createNetworkAcl(logicalId, resourceType, properties) {
|
|
14804
15290
|
this.logger.debug(`Creating NetworkAcl ${logicalId}`);
|
|
@@ -16719,7 +17205,7 @@ var CloudFrontDistributionProvider = class {
|
|
|
16719
17205
|
this.logger.debug(`Created CloudFront Distribution: ${distributionId} (${domainName})`);
|
|
16720
17206
|
if (process.env["CDKD_NO_WAIT"] !== "true") {
|
|
16721
17207
|
this.logger.debug(`Waiting for Distribution ${distributionId} to reach Deployed status...`);
|
|
16722
|
-
await this.
|
|
17208
|
+
await this.waitForDistributionStable(distributionId);
|
|
16723
17209
|
}
|
|
16724
17210
|
return {
|
|
16725
17211
|
physicalId: distributionId,
|
|
@@ -16829,7 +17315,7 @@ var CloudFrontDistributionProvider = class {
|
|
|
16829
17315
|
})
|
|
16830
17316
|
);
|
|
16831
17317
|
etag = updateResponse.ETag;
|
|
16832
|
-
await this.
|
|
17318
|
+
await this.waitForDistributionStable(physicalId, false);
|
|
16833
17319
|
const refreshResponse = await this.cloudFrontClient.send(
|
|
16834
17320
|
new GetDistributionConfigCommand({ Id: physicalId })
|
|
16835
17321
|
);
|
|
@@ -16873,10 +17359,16 @@ var CloudFrontDistributionProvider = class {
|
|
|
16873
17359
|
throw new Error(`Unsupported attribute: ${attributeName} for AWS::CloudFront::Distribution`);
|
|
16874
17360
|
}
|
|
16875
17361
|
/**
|
|
16876
|
-
* Wait for a distribution to reach
|
|
17362
|
+
* Wait for a distribution to reach a stable state.
|
|
17363
|
+
*
|
|
17364
|
+
* "Stable" means Status === 'Deployed'. When `expectedEnabled` is provided,
|
|
17365
|
+
* we additionally require DistributionConfig.Enabled === expectedEnabled —
|
|
17366
|
+
* this guards against CloudFront's eventually-consistent reads that can
|
|
17367
|
+
* briefly return the pre-update snapshot after UpdateDistribution returns.
|
|
17368
|
+
*
|
|
16877
17369
|
* Uses exponential backoff polling.
|
|
16878
17370
|
*/
|
|
16879
|
-
async
|
|
17371
|
+
async waitForDistributionStable(distributionId, expectedEnabled) {
|
|
16880
17372
|
const maxAttempts = 60;
|
|
16881
17373
|
let delay = 5e3;
|
|
16882
17374
|
const maxDelay = 3e4;
|
|
@@ -16897,12 +17389,16 @@ var CloudFrontDistributionProvider = class {
|
|
|
16897
17389
|
new GetDistributionCommand({ Id: distributionId })
|
|
16898
17390
|
);
|
|
16899
17391
|
const status = response.Distribution?.Status;
|
|
16900
|
-
|
|
16901
|
-
|
|
17392
|
+
const enabled = response.Distribution?.DistributionConfig?.Enabled;
|
|
17393
|
+
const enabledMatches = expectedEnabled === void 0 || enabled === expectedEnabled;
|
|
17394
|
+
if (status === "Deployed" && enabledMatches) {
|
|
17395
|
+
this.logger.debug(
|
|
17396
|
+
`Distribution ${distributionId} is stable (Status=Deployed, Enabled=${enabled})`
|
|
17397
|
+
);
|
|
16902
17398
|
return;
|
|
16903
17399
|
}
|
|
16904
17400
|
this.logger.debug(
|
|
16905
|
-
`Distribution ${distributionId} status: ${status} (attempt ${attempt}/${maxAttempts})`
|
|
17401
|
+
`Distribution ${distributionId} status: ${status}, enabled: ${enabled}` + (expectedEnabled === void 0 ? "" : ` (waiting for Enabled=${expectedEnabled})`) + ` (attempt ${attempt}/${maxAttempts})`
|
|
16906
17402
|
);
|
|
16907
17403
|
const sleepEnd = Date.now() + delay;
|
|
16908
17404
|
while (Date.now() < sleepEnd && !interrupted) {
|
|
@@ -16911,7 +17407,7 @@ var CloudFrontDistributionProvider = class {
|
|
|
16911
17407
|
delay = Math.min(delay * 1.5, maxDelay);
|
|
16912
17408
|
}
|
|
16913
17409
|
this.logger.debug(
|
|
16914
|
-
`Distribution ${distributionId} did not reach
|
|
17410
|
+
`Distribution ${distributionId} did not reach stable state within timeout, proceeding with next step`
|
|
16915
17411
|
);
|
|
16916
17412
|
} finally {
|
|
16917
17413
|
process.removeListener("SIGINT", sigintHandler);
|
|
@@ -24137,7 +24633,7 @@ import {
|
|
|
24137
24633
|
DeleteObjectsCommand as DeleteObjectsCommand2
|
|
24138
24634
|
} from "@aws-sdk/client-s3";
|
|
24139
24635
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand7 } from "@aws-sdk/client-sts";
|
|
24140
|
-
import { EC2Client as
|
|
24636
|
+
import { EC2Client as EC2Client8, DescribeAvailabilityZonesCommand as DescribeAvailabilityZonesCommand3 } from "@aws-sdk/client-ec2";
|
|
24141
24637
|
init_aws_clients();
|
|
24142
24638
|
var S3DirectoryBucketProvider = class {
|
|
24143
24639
|
s3Client;
|
|
@@ -24155,7 +24651,7 @@ var S3DirectoryBucketProvider = class {
|
|
|
24155
24651
|
}
|
|
24156
24652
|
getEc2Client() {
|
|
24157
24653
|
if (!this.ec2Client) {
|
|
24158
|
-
this.ec2Client = new
|
|
24654
|
+
this.ec2Client = new EC2Client8(this.providerRegion ? { region: this.providerRegion } : {});
|
|
24159
24655
|
}
|
|
24160
24656
|
return this.ec2Client;
|
|
24161
24657
|
}
|
|
@@ -25142,6 +25638,44 @@ var DagExecutor = class {
|
|
|
25142
25638
|
}
|
|
25143
25639
|
};
|
|
25144
25640
|
|
|
25641
|
+
// src/analyzer/implicit-delete-deps.ts
|
|
25642
|
+
var IMPLICIT_DELETE_DEPENDENCIES = {
|
|
25643
|
+
// IGW must be deleted AFTER VPCGatewayAttachment
|
|
25644
|
+
"AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
|
|
25645
|
+
// EventBus must be deleted AFTER Rules on that bus
|
|
25646
|
+
"AWS::Events::EventBus": ["AWS::Events::Rule"],
|
|
25647
|
+
// Athena workgroup must be deleted AFTER its named queries
|
|
25648
|
+
"AWS::Athena::WorkGroup": ["AWS::Athena::NamedQuery"],
|
|
25649
|
+
// CloudFront managed-policy-style resources must be deleted AFTER
|
|
25650
|
+
// any Distribution that references them
|
|
25651
|
+
"AWS::CloudFront::ResponseHeadersPolicy": ["AWS::CloudFront::Distribution"],
|
|
25652
|
+
"AWS::CloudFront::CachePolicy": ["AWS::CloudFront::Distribution"],
|
|
25653
|
+
"AWS::CloudFront::OriginAccessControl": ["AWS::CloudFront::Distribution"],
|
|
25654
|
+
// VPC must be deleted AFTER all VPC-dependent resources
|
|
25655
|
+
"AWS::EC2::VPC": [
|
|
25656
|
+
"AWS::EC2::Subnet",
|
|
25657
|
+
"AWS::EC2::SecurityGroup",
|
|
25658
|
+
"AWS::EC2::InternetGateway",
|
|
25659
|
+
"AWS::EC2::EgressOnlyInternetGateway",
|
|
25660
|
+
"AWS::EC2::VPCGatewayAttachment",
|
|
25661
|
+
"AWS::EC2::RouteTable"
|
|
25662
|
+
],
|
|
25663
|
+
// Subnet must be deleted AFTER any Lambda that may still hold an ENI
|
|
25664
|
+
// in it. Lambda DELETE returns immediately but the ENI is detached
|
|
25665
|
+
// asynchronously by AWS, so deleting the Subnet first races the detach
|
|
25666
|
+
// and yields "DependencyViolation".
|
|
25667
|
+
"AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation", "AWS::Lambda::Function"],
|
|
25668
|
+
// RouteTable must be deleted AFTER Route and Association
|
|
25669
|
+
"AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
|
|
25670
|
+
// SecurityGroup must be deleted AFTER any Lambda whose ENI is bound
|
|
25671
|
+
// to it (same ENI-detach race as Subnet above).
|
|
25672
|
+
"AWS::EC2::SecurityGroup": [
|
|
25673
|
+
"AWS::EC2::SecurityGroupIngress",
|
|
25674
|
+
"AWS::EC2::SecurityGroupEgress",
|
|
25675
|
+
"AWS::Lambda::Function"
|
|
25676
|
+
]
|
|
25677
|
+
};
|
|
25678
|
+
|
|
25145
25679
|
// src/deployment/deploy-engine.ts
|
|
25146
25680
|
var InterruptedError = class extends Error {
|
|
25147
25681
|
constructor() {
|
|
@@ -25149,7 +25683,7 @@ var InterruptedError = class extends Error {
|
|
|
25149
25683
|
this.name = "InterruptedError";
|
|
25150
25684
|
}
|
|
25151
25685
|
};
|
|
25152
|
-
var DeployEngine = class
|
|
25686
|
+
var DeployEngine = class {
|
|
25153
25687
|
constructor(stateBackend, lockManager, dagBuilder, diffCalculator, providerRegistry, options = {}, stackRegion) {
|
|
25154
25688
|
this.stateBackend = stateBackend;
|
|
25155
25689
|
this.lockManager = lockManager;
|
|
@@ -25986,36 +26520,9 @@ var DeployEngine = class _DeployEngine {
|
|
|
25986
26520
|
const deps = parser.extractDependencies(resource);
|
|
25987
26521
|
return deps.size > 0 ? [...deps] : void 0;
|
|
25988
26522
|
}
|
|
25989
|
-
|
|
25990
|
-
|
|
25991
|
-
|
|
25992
|
-
* Key = resource type that must be deleted AFTER all value types are deleted.
|
|
25993
|
-
* Value = resource types that must be deleted BEFORE the key type.
|
|
25994
|
-
*
|
|
25995
|
-
* Example: InternetGateway depends on VPCGatewayAttachment being deleted first,
|
|
25996
|
-
* because AWS won't let you delete an IGW while it's still attached to a VPC.
|
|
25997
|
-
*/
|
|
25998
|
-
static IMPLICIT_DELETE_DEPENDENCIES = {
|
|
25999
|
-
// IGW must be deleted AFTER VPCGatewayAttachment
|
|
26000
|
-
"AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
|
|
26001
|
-
// EventBus must be deleted AFTER Rules on that bus
|
|
26002
|
-
"AWS::Events::EventBus": ["AWS::Events::Rule"],
|
|
26003
|
-
// VPC must be deleted AFTER all VPC-dependent resources
|
|
26004
|
-
"AWS::EC2::VPC": [
|
|
26005
|
-
"AWS::EC2::Subnet",
|
|
26006
|
-
"AWS::EC2::SecurityGroup",
|
|
26007
|
-
"AWS::EC2::InternetGateway",
|
|
26008
|
-
"AWS::EC2::EgressOnlyInternetGateway",
|
|
26009
|
-
"AWS::EC2::VPCGatewayAttachment",
|
|
26010
|
-
"AWS::EC2::RouteTable"
|
|
26011
|
-
],
|
|
26012
|
-
// Subnet must be deleted AFTER RouteTableAssociation
|
|
26013
|
-
"AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation"],
|
|
26014
|
-
// RouteTable must be deleted AFTER Route and Association
|
|
26015
|
-
"AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
|
|
26016
|
-
// SecurityGroup must be deleted AFTER SecurityGroupIngress/Egress
|
|
26017
|
-
"AWS::EC2::SecurityGroup": ["AWS::EC2::SecurityGroupIngress", "AWS::EC2::SecurityGroupEgress"]
|
|
26018
|
-
};
|
|
26523
|
+
// Type-based implicit deletion ordering rules are defined in
|
|
26524
|
+
// src/analyzer/implicit-delete-deps.ts so the deploy DELETE phase and the
|
|
26525
|
+
// standalone destroy command apply the same rules.
|
|
26019
26526
|
/**
|
|
26020
26527
|
* Build a per-resource map of "must be deleted before me" dependencies for
|
|
26021
26528
|
* the DELETE phase, derived from state-recorded dependencies plus implicit
|
|
@@ -26081,7 +26588,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
26081
26588
|
const resource = state.resources[id];
|
|
26082
26589
|
if (!resource)
|
|
26083
26590
|
continue;
|
|
26084
|
-
const mustDeleteAfter =
|
|
26591
|
+
const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];
|
|
26085
26592
|
if (!mustDeleteAfter)
|
|
26086
26593
|
continue;
|
|
26087
26594
|
for (const depType of mustDeleteAfter) {
|
|
@@ -26867,27 +27374,6 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
26867
27374
|
}
|
|
26868
27375
|
};
|
|
26869
27376
|
}
|
|
26870
|
-
const implicitDeleteDeps = {
|
|
26871
|
-
"AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
|
|
26872
|
-
"AWS::Events::EventBus": ["AWS::Events::Rule"],
|
|
26873
|
-
"AWS::Athena::WorkGroup": ["AWS::Athena::NamedQuery"],
|
|
26874
|
-
"AWS::CloudFront::ResponseHeadersPolicy": ["AWS::CloudFront::Distribution"],
|
|
26875
|
-
"AWS::CloudFront::CachePolicy": ["AWS::CloudFront::Distribution"],
|
|
26876
|
-
"AWS::CloudFront::OriginAccessControl": ["AWS::CloudFront::Distribution"],
|
|
26877
|
-
"AWS::EC2::VPC": [
|
|
26878
|
-
"AWS::EC2::Subnet",
|
|
26879
|
-
"AWS::EC2::SecurityGroup",
|
|
26880
|
-
"AWS::EC2::InternetGateway",
|
|
26881
|
-
"AWS::EC2::VPCGatewayAttachment",
|
|
26882
|
-
"AWS::EC2::RouteTable"
|
|
26883
|
-
],
|
|
26884
|
-
"AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation"],
|
|
26885
|
-
"AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
|
|
26886
|
-
"AWS::EC2::SecurityGroup": [
|
|
26887
|
-
"AWS::EC2::SecurityGroupIngress",
|
|
26888
|
-
"AWS::EC2::SecurityGroupEgress"
|
|
26889
|
-
]
|
|
26890
|
-
};
|
|
26891
27377
|
const typeToLogicalIds = /* @__PURE__ */ new Map();
|
|
26892
27378
|
for (const [logicalId, resource] of Object.entries(currentState.resources)) {
|
|
26893
27379
|
const ids = typeToLogicalIds.get(resource.resourceType) ?? [];
|
|
@@ -26895,7 +27381,7 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
26895
27381
|
typeToLogicalIds.set(resource.resourceType, ids);
|
|
26896
27382
|
}
|
|
26897
27383
|
for (const [logicalId, resource] of Object.entries(currentState.resources)) {
|
|
26898
|
-
const mustDeleteAfter =
|
|
27384
|
+
const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];
|
|
26899
27385
|
if (!mustDeleteAfter)
|
|
26900
27386
|
continue;
|
|
26901
27387
|
for (const depType of mustDeleteAfter) {
|
|
@@ -27119,7 +27605,7 @@ function reorderArgs(argv) {
|
|
|
27119
27605
|
}
|
|
27120
27606
|
async function main() {
|
|
27121
27607
|
const program = new Command8();
|
|
27122
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
27608
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.3.1");
|
|
27123
27609
|
program.addCommand(createBootstrapCommand());
|
|
27124
27610
|
program.addCommand(createSynthCommand());
|
|
27125
27611
|
program.addCommand(createDeployCommand());
|