@go-to-k/cdkd 0.2.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/dist/cli.js +515 -80
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.3.0.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,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
|
}
|
|
@@ -25142,6 +25587,44 @@ var DagExecutor = class {
|
|
|
25142
25587
|
}
|
|
25143
25588
|
};
|
|
25144
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
|
+
|
|
25145
25628
|
// src/deployment/deploy-engine.ts
|
|
25146
25629
|
var InterruptedError = class extends Error {
|
|
25147
25630
|
constructor() {
|
|
@@ -25149,7 +25632,7 @@ var InterruptedError = class extends Error {
|
|
|
25149
25632
|
this.name = "InterruptedError";
|
|
25150
25633
|
}
|
|
25151
25634
|
};
|
|
25152
|
-
var DeployEngine = class
|
|
25635
|
+
var DeployEngine = class {
|
|
25153
25636
|
constructor(stateBackend, lockManager, dagBuilder, diffCalculator, providerRegistry, options = {}, stackRegion) {
|
|
25154
25637
|
this.stateBackend = stateBackend;
|
|
25155
25638
|
this.lockManager = lockManager;
|
|
@@ -25986,36 +26469,9 @@ var DeployEngine = class _DeployEngine {
|
|
|
25986
26469
|
const deps = parser.extractDependencies(resource);
|
|
25987
26470
|
return deps.size > 0 ? [...deps] : void 0;
|
|
25988
26471
|
}
|
|
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
|
-
};
|
|
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.
|
|
26019
26475
|
/**
|
|
26020
26476
|
* Build a per-resource map of "must be deleted before me" dependencies for
|
|
26021
26477
|
* the DELETE phase, derived from state-recorded dependencies plus implicit
|
|
@@ -26081,7 +26537,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
26081
26537
|
const resource = state.resources[id];
|
|
26082
26538
|
if (!resource)
|
|
26083
26539
|
continue;
|
|
26084
|
-
const mustDeleteAfter =
|
|
26540
|
+
const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];
|
|
26085
26541
|
if (!mustDeleteAfter)
|
|
26086
26542
|
continue;
|
|
26087
26543
|
for (const depType of mustDeleteAfter) {
|
|
@@ -26867,27 +27323,6 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
26867
27323
|
}
|
|
26868
27324
|
};
|
|
26869
27325
|
}
|
|
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
27326
|
const typeToLogicalIds = /* @__PURE__ */ new Map();
|
|
26892
27327
|
for (const [logicalId, resource] of Object.entries(currentState.resources)) {
|
|
26893
27328
|
const ids = typeToLogicalIds.get(resource.resourceType) ?? [];
|
|
@@ -26895,7 +27330,7 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
26895
27330
|
typeToLogicalIds.set(resource.resourceType, ids);
|
|
26896
27331
|
}
|
|
26897
27332
|
for (const [logicalId, resource] of Object.entries(currentState.resources)) {
|
|
26898
|
-
const mustDeleteAfter =
|
|
27333
|
+
const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];
|
|
26899
27334
|
if (!mustDeleteAfter)
|
|
26900
27335
|
continue;
|
|
26901
27336
|
for (const depType of mustDeleteAfter) {
|
|
@@ -27119,7 +27554,7 @@ function reorderArgs(argv) {
|
|
|
27119
27554
|
}
|
|
27120
27555
|
async function main() {
|
|
27121
27556
|
const program = new Command8();
|
|
27122
|
-
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");
|
|
27123
27558
|
program.addCommand(createBootstrapCommand());
|
|
27124
27559
|
program.addCommand(createSynthCommand());
|
|
27125
27560
|
program.addCommand(createDeployCommand());
|