@go-to-k/cdkd 0.3.3 → 0.3.5
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 +167 -61
- package/dist/cli.js.map +3 -3
- package/dist/go-to-k-cdkd-0.3.5.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.3.3.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -11023,9 +11023,24 @@ var LambdaFunctionProvider = class {
|
|
|
11023
11023
|
// Lambda VPC ENI detach is async and can take 20-40 minutes in the worst case;
|
|
11024
11024
|
// we poll up to 10 minutes and then warn-and-continue, since downstream Subnet/SG
|
|
11025
11025
|
// deletion has its own retry logic that handles a small remaining window.
|
|
11026
|
+
// Budget for waiting on UpdateFunctionConfiguration to fully apply
|
|
11027
|
+
// (LastUpdateStatus -> Successful) after pre-delete VPC detach.
|
|
11026
11028
|
eniWaitTimeoutMs = 10 * 60 * 1e3;
|
|
11027
11029
|
eniWaitInitialDelayMs = 1e4;
|
|
11028
11030
|
eniWaitMaxDelayMs = 3e4;
|
|
11031
|
+
// delstack-style ENI cleanup tunables.
|
|
11032
|
+
// - initial sleep: gives AWS time to publish post-detach ENI state via
|
|
11033
|
+
// DescribeNetworkInterfaces (right after the update, the API can return
|
|
11034
|
+
// an empty list even though ENIs still exist).
|
|
11035
|
+
// - per-ENI retry budget: an in-use ENI cannot be deleted until AWS
|
|
11036
|
+
// finishes the asynchronous detach. AWS's hyperplane ENI release is
|
|
11037
|
+
// eventually-consistent and can take 5-30 minutes in practice — the
|
|
11038
|
+
// budget here must cover that worst case so downstream Subnet/SG
|
|
11039
|
+
// deletes don't race ahead and fail with "has dependencies".
|
|
11040
|
+
// - retry interval: polling cadence inside the per-ENI loop.
|
|
11041
|
+
eniInitialSleepMs = 1e4;
|
|
11042
|
+
eniDeleteRetryBudgetMs = 30 * 60 * 1e3;
|
|
11043
|
+
eniDeleteRetryIntervalMs = 15e3;
|
|
11029
11044
|
constructor() {
|
|
11030
11045
|
const awsClients = getAwsClients();
|
|
11031
11046
|
this.lambdaClient = awsClients.lambda;
|
|
@@ -11375,57 +11390,44 @@ var LambdaFunctionProvider = class {
|
|
|
11375
11390
|
}
|
|
11376
11391
|
}
|
|
11377
11392
|
async cleanupLambdaEnis(functionName) {
|
|
11393
|
+
this.logger.debug(`Cleaning up Lambda VPC ENIs for function ${functionName}`);
|
|
11394
|
+
await this.sleep(this.eniInitialSleepMs);
|
|
11395
|
+
let enis = [];
|
|
11396
|
+
try {
|
|
11397
|
+
enis = await this.listLambdaEnis(functionName);
|
|
11398
|
+
} catch (error) {
|
|
11399
|
+
this.logger.warn(
|
|
11400
|
+
`DescribeNetworkInterfaces failed for ${functionName}: ${error instanceof Error ? error.message : String(error)} \u2014 downstream Subnet/SG deletion will fall back to its own ENI cleanup`
|
|
11401
|
+
);
|
|
11402
|
+
return;
|
|
11403
|
+
}
|
|
11404
|
+
if (enis.length === 0) {
|
|
11405
|
+
this.logger.debug(`No Lambda ENIs found for ${functionName} after initial sleep`);
|
|
11406
|
+
return;
|
|
11407
|
+
}
|
|
11408
|
+
await Promise.all(enis.map((eni) => this.deleteEniWithRetry(eni.id, functionName)));
|
|
11409
|
+
}
|
|
11410
|
+
async deleteEniWithRetry(eniId, functionName) {
|
|
11378
11411
|
const start = Date.now();
|
|
11379
|
-
let delay = this.eniWaitInitialDelayMs;
|
|
11380
|
-
let attempt = 0;
|
|
11381
|
-
this.logger.debug(
|
|
11382
|
-
`Cleaning up Lambda VPC ENIs for function ${functionName} (timeout ${this.eniWaitTimeoutMs}ms)`
|
|
11383
|
-
);
|
|
11384
11412
|
for (; ; ) {
|
|
11385
|
-
attempt++;
|
|
11386
|
-
let enis = [];
|
|
11387
|
-
let listFailed = false;
|
|
11388
11413
|
try {
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
this.logger.warn(
|
|
11392
|
-
`DescribeNetworkInterfaces failed while cleaning up Lambda ENIs of ${functionName}: ${error instanceof Error ? error.message : String(error)}`
|
|
11393
|
-
);
|
|
11394
|
-
listFailed = true;
|
|
11395
|
-
}
|
|
11396
|
-
if (!listFailed && enis.length === 0) {
|
|
11397
|
-
this.logger.debug(
|
|
11398
|
-
`Lambda ENIs for ${functionName} fully cleaned up after ${attempt} attempt(s) / ${Date.now() - start}ms`
|
|
11399
|
-
);
|
|
11400
|
-
return;
|
|
11401
|
-
}
|
|
11402
|
-
if (enis.length > 0) {
|
|
11403
|
-
await Promise.all(
|
|
11404
|
-
enis.map(async (eni) => {
|
|
11405
|
-
try {
|
|
11406
|
-
await this.ec2Client.send(
|
|
11407
|
-
new DeleteNetworkInterfaceCommand({ NetworkInterfaceId: eni.id })
|
|
11408
|
-
);
|
|
11409
|
-
this.logger.debug(`Deleted Lambda ENI ${eni.id} for ${functionName}`);
|
|
11410
|
-
} catch (error) {
|
|
11411
|
-
this.logger.debug(
|
|
11412
|
-
`ENI ${eni.id} (status=${eni.status}) not yet deletable: ${error instanceof Error ? error.message : String(error)}`
|
|
11413
|
-
);
|
|
11414
|
-
}
|
|
11415
|
-
})
|
|
11416
|
-
);
|
|
11417
|
-
}
|
|
11418
|
-
const elapsed = Date.now() - start;
|
|
11419
|
-
if (elapsed >= this.eniWaitTimeoutMs) {
|
|
11420
|
-
this.logger.warn(
|
|
11421
|
-
`Timeout (${this.eniWaitTimeoutMs}ms) cleaning up Lambda VPC ENIs of ${functionName} (${enis.length} remaining). Continuing \u2014 downstream Subnet/SG deletion will retry as needed.`
|
|
11422
|
-
);
|
|
11414
|
+
await this.ec2Client.send(new DeleteNetworkInterfaceCommand({ NetworkInterfaceId: eniId }));
|
|
11415
|
+
this.logger.debug(`Deleted Lambda ENI ${eniId} for ${functionName}`);
|
|
11423
11416
|
return;
|
|
11417
|
+
} catch (error) {
|
|
11418
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11419
|
+
if (msg.includes("InvalidNetworkInterfaceID.NotFound") || msg.includes("does not exist")) {
|
|
11420
|
+
return;
|
|
11421
|
+
}
|
|
11422
|
+
const elapsed = Date.now() - start;
|
|
11423
|
+
if (elapsed >= this.eniDeleteRetryBudgetMs) {
|
|
11424
|
+
this.logger.warn(
|
|
11425
|
+
`Gave up deleting ENI ${eniId} for ${functionName} after ${elapsed}ms: ${msg} \u2014 downstream Subnet/SG deletion will retry`
|
|
11426
|
+
);
|
|
11427
|
+
return;
|
|
11428
|
+
}
|
|
11429
|
+
await this.sleep(this.eniDeleteRetryIntervalMs);
|
|
11424
11430
|
}
|
|
11425
|
-
const remaining = this.eniWaitTimeoutMs - elapsed;
|
|
11426
|
-
const sleepMs = Math.min(delay, remaining);
|
|
11427
|
-
await this.sleep(sleepMs);
|
|
11428
|
-
delay = Math.min(delay * 2, this.eniWaitMaxDelayMs);
|
|
11429
11431
|
}
|
|
11430
11432
|
}
|
|
11431
11433
|
/**
|
|
@@ -13890,7 +13892,9 @@ import {
|
|
|
13890
13892
|
CreateNetworkAclEntryCommand,
|
|
13891
13893
|
DeleteNetworkAclEntryCommand,
|
|
13892
13894
|
ReplaceNetworkAclAssociationCommand,
|
|
13893
|
-
DescribeNetworkAclsCommand
|
|
13895
|
+
DescribeNetworkAclsCommand,
|
|
13896
|
+
DescribeNetworkInterfacesCommand as DescribeNetworkInterfacesCommand2,
|
|
13897
|
+
DeleteNetworkInterfaceCommand as DeleteNetworkInterfaceCommand2
|
|
13894
13898
|
} from "@aws-sdk/client-ec2";
|
|
13895
13899
|
init_aws_clients();
|
|
13896
13900
|
var EC2Provider = class {
|
|
@@ -14356,23 +14360,82 @@ var EC2Provider = class {
|
|
|
14356
14360
|
}
|
|
14357
14361
|
async deleteSubnet(logicalId, physicalId, resourceType) {
|
|
14358
14362
|
this.logger.debug(`Deleting Subnet ${logicalId}: ${physicalId}`);
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
|
|
14363
|
-
|
|
14364
|
-
this.logger.debug(`Subnet ${physicalId} does not exist, skipping deletion`);
|
|
14363
|
+
const maxAttempts = 10;
|
|
14364
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
14365
|
+
try {
|
|
14366
|
+
await this.ec2Client.send(new DeleteSubnetCommand({ SubnetId: physicalId }));
|
|
14367
|
+
this.logger.debug(`Successfully deleted Subnet ${logicalId}`);
|
|
14365
14368
|
return;
|
|
14369
|
+
} catch (error) {
|
|
14370
|
+
if (this.isNotFoundError(error)) {
|
|
14371
|
+
this.logger.debug(`Subnet ${physicalId} does not exist, skipping deletion`);
|
|
14372
|
+
return;
|
|
14373
|
+
}
|
|
14374
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
14375
|
+
const isDependencyError = msg.includes("has dependencies") || msg.includes("DependencyViolation");
|
|
14376
|
+
if (isDependencyError && attempt < maxAttempts) {
|
|
14377
|
+
await this.cleanupSubnetLambdaEnis(physicalId);
|
|
14378
|
+
this.logger.debug(
|
|
14379
|
+
`Subnet ${physicalId} has dependencies (attempt ${attempt}/${maxAttempts}), retrying in ${attempt * 5}s...`
|
|
14380
|
+
);
|
|
14381
|
+
await new Promise((resolve4) => setTimeout(resolve4, attempt * 5e3));
|
|
14382
|
+
continue;
|
|
14383
|
+
}
|
|
14384
|
+
const cause = error instanceof Error ? error : void 0;
|
|
14385
|
+
throw new ProvisioningError(
|
|
14386
|
+
`Failed to delete Subnet ${logicalId}: ${msg}`,
|
|
14387
|
+
resourceType,
|
|
14388
|
+
logicalId,
|
|
14389
|
+
physicalId,
|
|
14390
|
+
cause
|
|
14391
|
+
);
|
|
14366
14392
|
}
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14393
|
+
}
|
|
14394
|
+
}
|
|
14395
|
+
/**
|
|
14396
|
+
* Best-effort: list Lambda-managed ENIs in the given subnet and try to
|
|
14397
|
+
* delete each one. Used as a side-channel cleanup when DeleteSubnet
|
|
14398
|
+
* fails with "has dependencies" — the Lambda provider's own ENI cleanup
|
|
14399
|
+
* may have run out of budget before AWS finished detaching, so a second
|
|
14400
|
+
* attempt from the subnet side typically succeeds a few seconds later
|
|
14401
|
+
* once the ENIs flip from `in-use` to `available`.
|
|
14402
|
+
*/
|
|
14403
|
+
async cleanupSubnetLambdaEnis(subnetId) {
|
|
14404
|
+
let enis;
|
|
14405
|
+
try {
|
|
14406
|
+
const resp = await this.ec2Client.send(
|
|
14407
|
+
new DescribeNetworkInterfacesCommand2({
|
|
14408
|
+
Filters: [
|
|
14409
|
+
{ Name: "subnet-id", Values: [subnetId] },
|
|
14410
|
+
{ Name: "requester-id", Values: ["*:awslambda_*"] }
|
|
14411
|
+
]
|
|
14412
|
+
})
|
|
14413
|
+
);
|
|
14414
|
+
enis = (resp.NetworkInterfaces ?? []).filter((ni) => ni.NetworkInterfaceId).map((ni) => ({ id: ni.NetworkInterfaceId, status: ni.Status ?? "unknown" }));
|
|
14415
|
+
} catch (err) {
|
|
14416
|
+
this.logger.debug(
|
|
14417
|
+
`cleanupSubnetLambdaEnis: DescribeNetworkInterfaces failed for ${subnetId}: ${err instanceof Error ? err.message : String(err)}`
|
|
14374
14418
|
);
|
|
14419
|
+
return;
|
|
14375
14420
|
}
|
|
14421
|
+
if (enis.length === 0)
|
|
14422
|
+
return;
|
|
14423
|
+
await Promise.all(
|
|
14424
|
+
enis.map(async (eni) => {
|
|
14425
|
+
try {
|
|
14426
|
+
await this.ec2Client.send(
|
|
14427
|
+
new DeleteNetworkInterfaceCommand2({ NetworkInterfaceId: eni.id })
|
|
14428
|
+
);
|
|
14429
|
+
this.logger.debug(
|
|
14430
|
+
`cleanupSubnetLambdaEnis: deleted Lambda ENI ${eni.id} in subnet ${subnetId}`
|
|
14431
|
+
);
|
|
14432
|
+
} catch (err) {
|
|
14433
|
+
this.logger.debug(
|
|
14434
|
+
`cleanupSubnetLambdaEnis: ENI ${eni.id} (status=${eni.status}) not yet deletable: ${err instanceof Error ? err.message : String(err)}`
|
|
14435
|
+
);
|
|
14436
|
+
}
|
|
14437
|
+
})
|
|
14438
|
+
);
|
|
14376
14439
|
}
|
|
14377
14440
|
async getSubnetAttribute(physicalId, attributeName) {
|
|
14378
14441
|
if (attributeName === "SubnetId")
|
|
@@ -14872,6 +14935,7 @@ var EC2Provider = class {
|
|
|
14872
14935
|
}
|
|
14873
14936
|
const msg = error instanceof Error ? error.message : String(error);
|
|
14874
14937
|
if (msg.includes("dependent object") && attempt < maxAttempts) {
|
|
14938
|
+
await this.cleanupSecurityGroupLambdaEnis(physicalId);
|
|
14875
14939
|
this.logger.debug(
|
|
14876
14940
|
`SecurityGroup ${physicalId} has dependent objects (attempt ${attempt}/${maxAttempts}), retrying in ${attempt * 5}s...`
|
|
14877
14941
|
);
|
|
@@ -14889,6 +14953,48 @@ var EC2Provider = class {
|
|
|
14889
14953
|
}
|
|
14890
14954
|
}
|
|
14891
14955
|
}
|
|
14956
|
+
/**
|
|
14957
|
+
* Best-effort: list Lambda-managed ENIs that reference the given security
|
|
14958
|
+
* group and try to delete each one. Mirror of cleanupSubnetLambdaEnis but
|
|
14959
|
+
* filtered by `group-id`.
|
|
14960
|
+
*/
|
|
14961
|
+
async cleanupSecurityGroupLambdaEnis(groupId) {
|
|
14962
|
+
let enis;
|
|
14963
|
+
try {
|
|
14964
|
+
const resp = await this.ec2Client.send(
|
|
14965
|
+
new DescribeNetworkInterfacesCommand2({
|
|
14966
|
+
Filters: [
|
|
14967
|
+
{ Name: "group-id", Values: [groupId] },
|
|
14968
|
+
{ Name: "requester-id", Values: ["*:awslambda_*"] }
|
|
14969
|
+
]
|
|
14970
|
+
})
|
|
14971
|
+
);
|
|
14972
|
+
enis = (resp.NetworkInterfaces ?? []).filter((ni) => ni.NetworkInterfaceId).map((ni) => ({ id: ni.NetworkInterfaceId, status: ni.Status ?? "unknown" }));
|
|
14973
|
+
} catch (err) {
|
|
14974
|
+
this.logger.debug(
|
|
14975
|
+
`cleanupSecurityGroupLambdaEnis: DescribeNetworkInterfaces failed for ${groupId}: ${err instanceof Error ? err.message : String(err)}`
|
|
14976
|
+
);
|
|
14977
|
+
return;
|
|
14978
|
+
}
|
|
14979
|
+
if (enis.length === 0)
|
|
14980
|
+
return;
|
|
14981
|
+
await Promise.all(
|
|
14982
|
+
enis.map(async (eni) => {
|
|
14983
|
+
try {
|
|
14984
|
+
await this.ec2Client.send(
|
|
14985
|
+
new DeleteNetworkInterfaceCommand2({ NetworkInterfaceId: eni.id })
|
|
14986
|
+
);
|
|
14987
|
+
this.logger.debug(
|
|
14988
|
+
`cleanupSecurityGroupLambdaEnis: deleted Lambda ENI ${eni.id} for SG ${groupId}`
|
|
14989
|
+
);
|
|
14990
|
+
} catch (err) {
|
|
14991
|
+
this.logger.debug(
|
|
14992
|
+
`cleanupSecurityGroupLambdaEnis: ENI ${eni.id} (status=${eni.status}) not yet deletable: ${err instanceof Error ? err.message : String(err)}`
|
|
14993
|
+
);
|
|
14994
|
+
}
|
|
14995
|
+
})
|
|
14996
|
+
);
|
|
14997
|
+
}
|
|
14892
14998
|
async getSecurityGroupAttribute(physicalId, attributeName) {
|
|
14893
14999
|
if (attributeName === "GroupId")
|
|
14894
15000
|
return physicalId;
|
|
@@ -27656,7 +27762,7 @@ function reorderArgs(argv) {
|
|
|
27656
27762
|
}
|
|
27657
27763
|
async function main() {
|
|
27658
27764
|
const program = new Command8();
|
|
27659
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.3.
|
|
27765
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.3.5");
|
|
27660
27766
|
program.addCommand(createBootstrapCommand());
|
|
27661
27767
|
program.addCommand(createSynthCommand());
|
|
27662
27768
|
program.addCommand(createDeployCommand());
|