@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
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -3139,6 +3139,62 @@ var TemplateParser = class {
|
|
|
3139
3139
|
|
|
3140
3140
|
// src/analyzer/dag-builder.ts
|
|
3141
3141
|
import graphlib from "graphlib";
|
|
3142
|
+
|
|
3143
|
+
// src/analyzer/lambda-vpc-deps.ts
|
|
3144
|
+
function extractLambdaVpcDeleteDeps(resources) {
|
|
3145
|
+
const edges = [];
|
|
3146
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3147
|
+
for (const [lambdaId, resource] of Object.entries(resources)) {
|
|
3148
|
+
if (resource.Type !== "AWS::Lambda::Function")
|
|
3149
|
+
continue;
|
|
3150
|
+
const vpcConfig = (resource.Properties ?? {})["VpcConfig"];
|
|
3151
|
+
if (!isObject(vpcConfig))
|
|
3152
|
+
continue;
|
|
3153
|
+
const targets = /* @__PURE__ */ new Set();
|
|
3154
|
+
collectRefIds(vpcConfig["SubnetIds"], targets);
|
|
3155
|
+
collectRefIds(vpcConfig["SecurityGroupIds"], targets);
|
|
3156
|
+
for (const targetId of targets) {
|
|
3157
|
+
if (targetId === lambdaId)
|
|
3158
|
+
continue;
|
|
3159
|
+
if (!(targetId in resources))
|
|
3160
|
+
continue;
|
|
3161
|
+
const key = `${lambdaId}\0${targetId}`;
|
|
3162
|
+
if (seen.has(key))
|
|
3163
|
+
continue;
|
|
3164
|
+
seen.add(key);
|
|
3165
|
+
edges.push({ before: lambdaId, after: targetId });
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
return edges;
|
|
3169
|
+
}
|
|
3170
|
+
function isObject(v) {
|
|
3171
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3172
|
+
}
|
|
3173
|
+
function collectRefIds(value, out) {
|
|
3174
|
+
if (value === null || value === void 0)
|
|
3175
|
+
return;
|
|
3176
|
+
if (Array.isArray(value)) {
|
|
3177
|
+
for (const item of value)
|
|
3178
|
+
collectRefIds(item, out);
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
3181
|
+
if (!isObject(value))
|
|
3182
|
+
return;
|
|
3183
|
+
if (typeof value["Ref"] === "string") {
|
|
3184
|
+
const ref = value["Ref"];
|
|
3185
|
+
if (!ref.startsWith("AWS::"))
|
|
3186
|
+
out.add(ref);
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
if (Array.isArray(value["Fn::GetAtt"])) {
|
|
3190
|
+
const arr = value["Fn::GetAtt"];
|
|
3191
|
+
if (typeof arr[0] === "string")
|
|
3192
|
+
out.add(arr[0]);
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
// src/analyzer/dag-builder.ts
|
|
3142
3198
|
var { Graph, alg } = graphlib;
|
|
3143
3199
|
var IAM_ROLE_POLICY_TYPES = /* @__PURE__ */ new Set([
|
|
3144
3200
|
"AWS::IAM::Policy",
|
|
@@ -3186,6 +3242,7 @@ var DagBuilder = class {
|
|
|
3186
3242
|
}
|
|
3187
3243
|
this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);
|
|
3188
3244
|
edgeCount += this.addCustomResourcePolicyEdges(graph, template);
|
|
3245
|
+
edgeCount += this.addLambdaVpcEdges(graph, template);
|
|
3189
3246
|
if (!alg.isAcyclic(graph)) {
|
|
3190
3247
|
const cycles = this.findCycles(graph);
|
|
3191
3248
|
throw new DependencyError(
|
|
@@ -3378,6 +3435,41 @@ var DagBuilder = class {
|
|
|
3378
3435
|
}
|
|
3379
3436
|
return added;
|
|
3380
3437
|
}
|
|
3438
|
+
/**
|
|
3439
|
+
* Add edges from Subnets / SecurityGroups referenced by an
|
|
3440
|
+
* AWS::Lambda::Function VpcConfig to the Lambda itself.
|
|
3441
|
+
*
|
|
3442
|
+
* Same direction as a normal `Ref`-derived edge (Subnet -> Lambda), so for
|
|
3443
|
+
* deploy this just duplicates what extractDependencies already produced.
|
|
3444
|
+
* The point is robustness: if a future template massages the VpcConfig
|
|
3445
|
+
* shape in a way the recursive extractor doesn't anticipate, this pass
|
|
3446
|
+
* still ties the Lambda to its networking resources so that the
|
|
3447
|
+
* deletion-time reverse traversal continues to delete Lambda before
|
|
3448
|
+
* Subnet / SecurityGroup.
|
|
3449
|
+
*
|
|
3450
|
+
* Returns the number of NEW edges added (existing edges are skipped).
|
|
3451
|
+
*/
|
|
3452
|
+
addLambdaVpcEdges(graph, template) {
|
|
3453
|
+
const edges = extractLambdaVpcDeleteDeps(template.Resources);
|
|
3454
|
+
if (edges.length === 0)
|
|
3455
|
+
return 0;
|
|
3456
|
+
let added = 0;
|
|
3457
|
+
for (const edge of edges) {
|
|
3458
|
+
const depId = edge.after;
|
|
3459
|
+
const dependentId = edge.before;
|
|
3460
|
+
if (!graph.hasNode(depId) || !graph.hasNode(dependentId))
|
|
3461
|
+
continue;
|
|
3462
|
+
if (graph.hasEdge(depId, dependentId))
|
|
3463
|
+
continue;
|
|
3464
|
+
graph.setEdge(depId, dependentId);
|
|
3465
|
+
added++;
|
|
3466
|
+
this.logger.debug(`Added implicit edge (lambda vpc): ${depId} -> ${dependentId}`);
|
|
3467
|
+
}
|
|
3468
|
+
if (added > 0) {
|
|
3469
|
+
this.logger.debug(`Added ${added} implicit edges for Lambda VpcConfig`);
|
|
3470
|
+
}
|
|
3471
|
+
return added;
|
|
3472
|
+
}
|
|
3381
3473
|
isCustomResourceType(type) {
|
|
3382
3474
|
return type === "AWS::CloudFormation::CustomResource" || type.startsWith("Custom::");
|
|
3383
3475
|
}
|
|
@@ -7082,6 +7174,44 @@ var DagExecutor = class {
|
|
|
7082
7174
|
}
|
|
7083
7175
|
};
|
|
7084
7176
|
|
|
7177
|
+
// src/analyzer/implicit-delete-deps.ts
|
|
7178
|
+
var IMPLICIT_DELETE_DEPENDENCIES = {
|
|
7179
|
+
// IGW must be deleted AFTER VPCGatewayAttachment
|
|
7180
|
+
"AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
|
|
7181
|
+
// EventBus must be deleted AFTER Rules on that bus
|
|
7182
|
+
"AWS::Events::EventBus": ["AWS::Events::Rule"],
|
|
7183
|
+
// Athena workgroup must be deleted AFTER its named queries
|
|
7184
|
+
"AWS::Athena::WorkGroup": ["AWS::Athena::NamedQuery"],
|
|
7185
|
+
// CloudFront managed-policy-style resources must be deleted AFTER
|
|
7186
|
+
// any Distribution that references them
|
|
7187
|
+
"AWS::CloudFront::ResponseHeadersPolicy": ["AWS::CloudFront::Distribution"],
|
|
7188
|
+
"AWS::CloudFront::CachePolicy": ["AWS::CloudFront::Distribution"],
|
|
7189
|
+
"AWS::CloudFront::OriginAccessControl": ["AWS::CloudFront::Distribution"],
|
|
7190
|
+
// VPC must be deleted AFTER all VPC-dependent resources
|
|
7191
|
+
"AWS::EC2::VPC": [
|
|
7192
|
+
"AWS::EC2::Subnet",
|
|
7193
|
+
"AWS::EC2::SecurityGroup",
|
|
7194
|
+
"AWS::EC2::InternetGateway",
|
|
7195
|
+
"AWS::EC2::EgressOnlyInternetGateway",
|
|
7196
|
+
"AWS::EC2::VPCGatewayAttachment",
|
|
7197
|
+
"AWS::EC2::RouteTable"
|
|
7198
|
+
],
|
|
7199
|
+
// Subnet must be deleted AFTER any Lambda that may still hold an ENI
|
|
7200
|
+
// in it. Lambda DELETE returns immediately but the ENI is detached
|
|
7201
|
+
// asynchronously by AWS, so deleting the Subnet first races the detach
|
|
7202
|
+
// and yields "DependencyViolation".
|
|
7203
|
+
"AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation", "AWS::Lambda::Function"],
|
|
7204
|
+
// RouteTable must be deleted AFTER Route and Association
|
|
7205
|
+
"AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
|
|
7206
|
+
// SecurityGroup must be deleted AFTER any Lambda whose ENI is bound
|
|
7207
|
+
// to it (same ENI-detach race as Subnet above).
|
|
7208
|
+
"AWS::EC2::SecurityGroup": [
|
|
7209
|
+
"AWS::EC2::SecurityGroupIngress",
|
|
7210
|
+
"AWS::EC2::SecurityGroupEgress",
|
|
7211
|
+
"AWS::Lambda::Function"
|
|
7212
|
+
]
|
|
7213
|
+
};
|
|
7214
|
+
|
|
7085
7215
|
// src/deployment/deploy-engine.ts
|
|
7086
7216
|
var InterruptedError = class extends Error {
|
|
7087
7217
|
constructor() {
|
|
@@ -7089,7 +7219,7 @@ var InterruptedError = class extends Error {
|
|
|
7089
7219
|
this.name = "InterruptedError";
|
|
7090
7220
|
}
|
|
7091
7221
|
};
|
|
7092
|
-
var DeployEngine = class
|
|
7222
|
+
var DeployEngine = class {
|
|
7093
7223
|
constructor(stateBackend, lockManager, dagBuilder, diffCalculator, providerRegistry, options = {}, stackRegion) {
|
|
7094
7224
|
this.stateBackend = stateBackend;
|
|
7095
7225
|
this.lockManager = lockManager;
|
|
@@ -7926,36 +8056,9 @@ var DeployEngine = class _DeployEngine {
|
|
|
7926
8056
|
const deps = parser.extractDependencies(resource);
|
|
7927
8057
|
return deps.size > 0 ? [...deps] : void 0;
|
|
7928
8058
|
}
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
* Key = resource type that must be deleted AFTER all value types are deleted.
|
|
7933
|
-
* Value = resource types that must be deleted BEFORE the key type.
|
|
7934
|
-
*
|
|
7935
|
-
* Example: InternetGateway depends on VPCGatewayAttachment being deleted first,
|
|
7936
|
-
* because AWS won't let you delete an IGW while it's still attached to a VPC.
|
|
7937
|
-
*/
|
|
7938
|
-
static IMPLICIT_DELETE_DEPENDENCIES = {
|
|
7939
|
-
// IGW must be deleted AFTER VPCGatewayAttachment
|
|
7940
|
-
"AWS::EC2::InternetGateway": ["AWS::EC2::VPCGatewayAttachment"],
|
|
7941
|
-
// EventBus must be deleted AFTER Rules on that bus
|
|
7942
|
-
"AWS::Events::EventBus": ["AWS::Events::Rule"],
|
|
7943
|
-
// VPC must be deleted AFTER all VPC-dependent resources
|
|
7944
|
-
"AWS::EC2::VPC": [
|
|
7945
|
-
"AWS::EC2::Subnet",
|
|
7946
|
-
"AWS::EC2::SecurityGroup",
|
|
7947
|
-
"AWS::EC2::InternetGateway",
|
|
7948
|
-
"AWS::EC2::EgressOnlyInternetGateway",
|
|
7949
|
-
"AWS::EC2::VPCGatewayAttachment",
|
|
7950
|
-
"AWS::EC2::RouteTable"
|
|
7951
|
-
],
|
|
7952
|
-
// Subnet must be deleted AFTER RouteTableAssociation
|
|
7953
|
-
"AWS::EC2::Subnet": ["AWS::EC2::SubnetRouteTableAssociation"],
|
|
7954
|
-
// RouteTable must be deleted AFTER Route and Association
|
|
7955
|
-
"AWS::EC2::RouteTable": ["AWS::EC2::Route", "AWS::EC2::SubnetRouteTableAssociation"],
|
|
7956
|
-
// SecurityGroup must be deleted AFTER SecurityGroupIngress/Egress
|
|
7957
|
-
"AWS::EC2::SecurityGroup": ["AWS::EC2::SecurityGroupIngress", "AWS::EC2::SecurityGroupEgress"]
|
|
7958
|
-
};
|
|
8059
|
+
// Type-based implicit deletion ordering rules are defined in
|
|
8060
|
+
// src/analyzer/implicit-delete-deps.ts so the deploy DELETE phase and the
|
|
8061
|
+
// standalone destroy command apply the same rules.
|
|
7959
8062
|
/**
|
|
7960
8063
|
* Build a per-resource map of "must be deleted before me" dependencies for
|
|
7961
8064
|
* the DELETE phase, derived from state-recorded dependencies plus implicit
|
|
@@ -8021,7 +8124,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
8021
8124
|
const resource = state.resources[id];
|
|
8022
8125
|
if (!resource)
|
|
8023
8126
|
continue;
|
|
8024
|
-
const mustDeleteAfter =
|
|
8127
|
+
const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];
|
|
8025
8128
|
if (!mustDeleteAfter)
|
|
8026
8129
|
continue;
|
|
8027
8130
|
for (const depType of mustDeleteAfter) {
|