@go-to-k/cdkd 0.3.2 → 0.3.4
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 +200 -46
- package/dist/cli.js.map +3 -3
- package/dist/go-to-k-cdkd-0.3.4.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.3.2.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -11023,9 +11023,21 @@ 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; budget gives that time to land.
|
|
11037
|
+
// - retry interval: polling cadence inside the per-ENI loop.
|
|
11038
|
+
eniInitialSleepMs = 1e4;
|
|
11039
|
+
eniDeleteRetryBudgetMs = 9e4;
|
|
11040
|
+
eniDeleteRetryIntervalMs = 5e3;
|
|
11029
11041
|
constructor() {
|
|
11030
11042
|
const awsClients = getAwsClients();
|
|
11031
11043
|
this.lambdaClient = awsClients.lambda;
|
|
@@ -11216,6 +11228,7 @@ var LambdaFunctionProvider = class {
|
|
|
11216
11228
|
`Pre-delete VPC detach failed for ${physicalId}: ${error instanceof Error ? error.message : String(error)} \u2014 continuing with delete`
|
|
11217
11229
|
);
|
|
11218
11230
|
}
|
|
11231
|
+
await this.waitForLambdaUpdateCompleted(physicalId);
|
|
11219
11232
|
}
|
|
11220
11233
|
try {
|
|
11221
11234
|
await this.lambdaClient.send(new DeleteFunctionCommand({ FunctionName: physicalId }));
|
|
@@ -11323,51 +11336,47 @@ var LambdaFunctionProvider = class {
|
|
|
11323
11336
|
* Timeout is a soft warning — downstream Subnet/SG deletion has its own
|
|
11324
11337
|
* retries.
|
|
11325
11338
|
*/
|
|
11326
|
-
|
|
11339
|
+
/**
|
|
11340
|
+
* Poll GetFunction until LastUpdateStatus is no longer `InProgress`.
|
|
11341
|
+
*
|
|
11342
|
+
* After UpdateFunctionConfiguration the Lambda service processes the
|
|
11343
|
+
* change (including VPC detach + hyperplane ENI release) asynchronously.
|
|
11344
|
+
* Returning early — i.e. calling DeleteFunction while the update is still
|
|
11345
|
+
* `InProgress` — aborts the detach, leaving ENIs attached and blocking
|
|
11346
|
+
* downstream Subnet / SG deletion.
|
|
11347
|
+
*
|
|
11348
|
+
* Bounded by eniWaitTimeoutMs (10min) and treated as a soft warning on
|
|
11349
|
+
* timeout: the subsequent ENI cleanup loop and downstream retries cover
|
|
11350
|
+
* the residual edge case.
|
|
11351
|
+
*/
|
|
11352
|
+
async waitForLambdaUpdateCompleted(functionName) {
|
|
11327
11353
|
const start = Date.now();
|
|
11328
11354
|
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
11355
|
for (; ; ) {
|
|
11334
|
-
|
|
11335
|
-
let enis = [];
|
|
11336
|
-
let listFailed = false;
|
|
11356
|
+
let status;
|
|
11337
11357
|
try {
|
|
11338
|
-
|
|
11358
|
+
const resp = await this.lambdaClient.send(
|
|
11359
|
+
new GetFunctionCommand({ FunctionName: functionName })
|
|
11360
|
+
);
|
|
11361
|
+
status = resp.Configuration?.LastUpdateStatus;
|
|
11339
11362
|
} catch (error) {
|
|
11340
|
-
|
|
11341
|
-
|
|
11363
|
+
if (error instanceof ResourceNotFoundException) {
|
|
11364
|
+
return;
|
|
11365
|
+
}
|
|
11366
|
+
this.logger.debug(
|
|
11367
|
+
`GetFunction failed while waiting for ${functionName} update: ${error instanceof Error ? error.message : String(error)}`
|
|
11342
11368
|
);
|
|
11343
|
-
listFailed = true;
|
|
11344
11369
|
}
|
|
11345
|
-
if (
|
|
11370
|
+
if (status && status !== "InProgress") {
|
|
11346
11371
|
this.logger.debug(
|
|
11347
|
-
`Lambda
|
|
11372
|
+
`Lambda ${functionName} update completed (LastUpdateStatus=${status}) after ${Date.now() - start}ms`
|
|
11348
11373
|
);
|
|
11349
11374
|
return;
|
|
11350
11375
|
}
|
|
11351
|
-
if (enis.length > 0) {
|
|
11352
|
-
await Promise.all(
|
|
11353
|
-
enis.map(async (eni) => {
|
|
11354
|
-
try {
|
|
11355
|
-
await this.ec2Client.send(
|
|
11356
|
-
new DeleteNetworkInterfaceCommand({ NetworkInterfaceId: eni.id })
|
|
11357
|
-
);
|
|
11358
|
-
this.logger.debug(`Deleted Lambda ENI ${eni.id} for ${functionName}`);
|
|
11359
|
-
} catch (error) {
|
|
11360
|
-
this.logger.debug(
|
|
11361
|
-
`ENI ${eni.id} (status=${eni.status}) not yet deletable: ${error instanceof Error ? error.message : String(error)}`
|
|
11362
|
-
);
|
|
11363
|
-
}
|
|
11364
|
-
})
|
|
11365
|
-
);
|
|
11366
|
-
}
|
|
11367
11376
|
const elapsed = Date.now() - start;
|
|
11368
11377
|
if (elapsed >= this.eniWaitTimeoutMs) {
|
|
11369
11378
|
this.logger.warn(
|
|
11370
|
-
`Timeout (${this.eniWaitTimeoutMs}ms)
|
|
11379
|
+
`Timeout (${this.eniWaitTimeoutMs}ms) waiting for Lambda ${functionName} update to complete; proceeding with delete`
|
|
11371
11380
|
);
|
|
11372
11381
|
return;
|
|
11373
11382
|
}
|
|
@@ -11377,6 +11386,47 @@ var LambdaFunctionProvider = class {
|
|
|
11377
11386
|
delay = Math.min(delay * 2, this.eniWaitMaxDelayMs);
|
|
11378
11387
|
}
|
|
11379
11388
|
}
|
|
11389
|
+
async cleanupLambdaEnis(functionName) {
|
|
11390
|
+
this.logger.debug(`Cleaning up Lambda VPC ENIs for function ${functionName}`);
|
|
11391
|
+
await this.sleep(this.eniInitialSleepMs);
|
|
11392
|
+
let enis = [];
|
|
11393
|
+
try {
|
|
11394
|
+
enis = await this.listLambdaEnis(functionName);
|
|
11395
|
+
} catch (error) {
|
|
11396
|
+
this.logger.warn(
|
|
11397
|
+
`DescribeNetworkInterfaces failed for ${functionName}: ${error instanceof Error ? error.message : String(error)} \u2014 downstream Subnet/SG deletion will fall back to its own ENI cleanup`
|
|
11398
|
+
);
|
|
11399
|
+
return;
|
|
11400
|
+
}
|
|
11401
|
+
if (enis.length === 0) {
|
|
11402
|
+
this.logger.debug(`No Lambda ENIs found for ${functionName} after initial sleep`);
|
|
11403
|
+
return;
|
|
11404
|
+
}
|
|
11405
|
+
await Promise.all(enis.map((eni) => this.deleteEniWithRetry(eni.id, functionName)));
|
|
11406
|
+
}
|
|
11407
|
+
async deleteEniWithRetry(eniId, functionName) {
|
|
11408
|
+
const start = Date.now();
|
|
11409
|
+
for (; ; ) {
|
|
11410
|
+
try {
|
|
11411
|
+
await this.ec2Client.send(new DeleteNetworkInterfaceCommand({ NetworkInterfaceId: eniId }));
|
|
11412
|
+
this.logger.debug(`Deleted Lambda ENI ${eniId} for ${functionName}`);
|
|
11413
|
+
return;
|
|
11414
|
+
} catch (error) {
|
|
11415
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
11416
|
+
if (msg.includes("InvalidNetworkInterfaceID.NotFound") || msg.includes("does not exist")) {
|
|
11417
|
+
return;
|
|
11418
|
+
}
|
|
11419
|
+
const elapsed = Date.now() - start;
|
|
11420
|
+
if (elapsed >= this.eniDeleteRetryBudgetMs) {
|
|
11421
|
+
this.logger.warn(
|
|
11422
|
+
`Gave up deleting ENI ${eniId} for ${functionName} after ${elapsed}ms: ${msg} \u2014 downstream Subnet/SG deletion will retry`
|
|
11423
|
+
);
|
|
11424
|
+
return;
|
|
11425
|
+
}
|
|
11426
|
+
await this.sleep(this.eniDeleteRetryIntervalMs);
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
}
|
|
11380
11430
|
/**
|
|
11381
11431
|
* List Lambda-managed ENIs for the given function, paginating through
|
|
11382
11432
|
* DescribeNetworkInterfaces and filtering on Description substring.
|
|
@@ -13839,7 +13889,9 @@ import {
|
|
|
13839
13889
|
CreateNetworkAclEntryCommand,
|
|
13840
13890
|
DeleteNetworkAclEntryCommand,
|
|
13841
13891
|
ReplaceNetworkAclAssociationCommand,
|
|
13842
|
-
DescribeNetworkAclsCommand
|
|
13892
|
+
DescribeNetworkAclsCommand,
|
|
13893
|
+
DescribeNetworkInterfacesCommand as DescribeNetworkInterfacesCommand2,
|
|
13894
|
+
DeleteNetworkInterfaceCommand as DeleteNetworkInterfaceCommand2
|
|
13843
13895
|
} from "@aws-sdk/client-ec2";
|
|
13844
13896
|
init_aws_clients();
|
|
13845
13897
|
var EC2Provider = class {
|
|
@@ -14305,23 +14357,82 @@ var EC2Provider = class {
|
|
|
14305
14357
|
}
|
|
14306
14358
|
async deleteSubnet(logicalId, physicalId, resourceType) {
|
|
14307
14359
|
this.logger.debug(`Deleting Subnet ${logicalId}: ${physicalId}`);
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
this.logger.debug(`Subnet ${physicalId} does not exist, skipping deletion`);
|
|
14360
|
+
const maxAttempts = 10;
|
|
14361
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
14362
|
+
try {
|
|
14363
|
+
await this.ec2Client.send(new DeleteSubnetCommand({ SubnetId: physicalId }));
|
|
14364
|
+
this.logger.debug(`Successfully deleted Subnet ${logicalId}`);
|
|
14314
14365
|
return;
|
|
14366
|
+
} catch (error) {
|
|
14367
|
+
if (this.isNotFoundError(error)) {
|
|
14368
|
+
this.logger.debug(`Subnet ${physicalId} does not exist, skipping deletion`);
|
|
14369
|
+
return;
|
|
14370
|
+
}
|
|
14371
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
14372
|
+
const isDependencyError = msg.includes("has dependencies") || msg.includes("DependencyViolation");
|
|
14373
|
+
if (isDependencyError && attempt < maxAttempts) {
|
|
14374
|
+
await this.cleanupSubnetLambdaEnis(physicalId);
|
|
14375
|
+
this.logger.debug(
|
|
14376
|
+
`Subnet ${physicalId} has dependencies (attempt ${attempt}/${maxAttempts}), retrying in ${attempt * 5}s...`
|
|
14377
|
+
);
|
|
14378
|
+
await new Promise((resolve4) => setTimeout(resolve4, attempt * 5e3));
|
|
14379
|
+
continue;
|
|
14380
|
+
}
|
|
14381
|
+
const cause = error instanceof Error ? error : void 0;
|
|
14382
|
+
throw new ProvisioningError(
|
|
14383
|
+
`Failed to delete Subnet ${logicalId}: ${msg}`,
|
|
14384
|
+
resourceType,
|
|
14385
|
+
logicalId,
|
|
14386
|
+
physicalId,
|
|
14387
|
+
cause
|
|
14388
|
+
);
|
|
14315
14389
|
}
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
14390
|
+
}
|
|
14391
|
+
}
|
|
14392
|
+
/**
|
|
14393
|
+
* Best-effort: list Lambda-managed ENIs in the given subnet and try to
|
|
14394
|
+
* delete each one. Used as a side-channel cleanup when DeleteSubnet
|
|
14395
|
+
* fails with "has dependencies" — the Lambda provider's own ENI cleanup
|
|
14396
|
+
* may have run out of budget before AWS finished detaching, so a second
|
|
14397
|
+
* attempt from the subnet side typically succeeds a few seconds later
|
|
14398
|
+
* once the ENIs flip from `in-use` to `available`.
|
|
14399
|
+
*/
|
|
14400
|
+
async cleanupSubnetLambdaEnis(subnetId) {
|
|
14401
|
+
let enis;
|
|
14402
|
+
try {
|
|
14403
|
+
const resp = await this.ec2Client.send(
|
|
14404
|
+
new DescribeNetworkInterfacesCommand2({
|
|
14405
|
+
Filters: [
|
|
14406
|
+
{ Name: "subnet-id", Values: [subnetId] },
|
|
14407
|
+
{ Name: "requester-id", Values: ["*:awslambda_*"] }
|
|
14408
|
+
]
|
|
14409
|
+
})
|
|
14323
14410
|
);
|
|
14411
|
+
enis = (resp.NetworkInterfaces ?? []).filter((ni) => ni.NetworkInterfaceId).map((ni) => ({ id: ni.NetworkInterfaceId, status: ni.Status ?? "unknown" }));
|
|
14412
|
+
} catch (err) {
|
|
14413
|
+
this.logger.debug(
|
|
14414
|
+
`cleanupSubnetLambdaEnis: DescribeNetworkInterfaces failed for ${subnetId}: ${err instanceof Error ? err.message : String(err)}`
|
|
14415
|
+
);
|
|
14416
|
+
return;
|
|
14324
14417
|
}
|
|
14418
|
+
if (enis.length === 0)
|
|
14419
|
+
return;
|
|
14420
|
+
await Promise.all(
|
|
14421
|
+
enis.map(async (eni) => {
|
|
14422
|
+
try {
|
|
14423
|
+
await this.ec2Client.send(
|
|
14424
|
+
new DeleteNetworkInterfaceCommand2({ NetworkInterfaceId: eni.id })
|
|
14425
|
+
);
|
|
14426
|
+
this.logger.debug(
|
|
14427
|
+
`cleanupSubnetLambdaEnis: deleted Lambda ENI ${eni.id} in subnet ${subnetId}`
|
|
14428
|
+
);
|
|
14429
|
+
} catch (err) {
|
|
14430
|
+
this.logger.debug(
|
|
14431
|
+
`cleanupSubnetLambdaEnis: ENI ${eni.id} (status=${eni.status}) not yet deletable: ${err instanceof Error ? err.message : String(err)}`
|
|
14432
|
+
);
|
|
14433
|
+
}
|
|
14434
|
+
})
|
|
14435
|
+
);
|
|
14325
14436
|
}
|
|
14326
14437
|
async getSubnetAttribute(physicalId, attributeName) {
|
|
14327
14438
|
if (attributeName === "SubnetId")
|
|
@@ -14821,6 +14932,7 @@ var EC2Provider = class {
|
|
|
14821
14932
|
}
|
|
14822
14933
|
const msg = error instanceof Error ? error.message : String(error);
|
|
14823
14934
|
if (msg.includes("dependent object") && attempt < maxAttempts) {
|
|
14935
|
+
await this.cleanupSecurityGroupLambdaEnis(physicalId);
|
|
14824
14936
|
this.logger.debug(
|
|
14825
14937
|
`SecurityGroup ${physicalId} has dependent objects (attempt ${attempt}/${maxAttempts}), retrying in ${attempt * 5}s...`
|
|
14826
14938
|
);
|
|
@@ -14838,6 +14950,48 @@ var EC2Provider = class {
|
|
|
14838
14950
|
}
|
|
14839
14951
|
}
|
|
14840
14952
|
}
|
|
14953
|
+
/**
|
|
14954
|
+
* Best-effort: list Lambda-managed ENIs that reference the given security
|
|
14955
|
+
* group and try to delete each one. Mirror of cleanupSubnetLambdaEnis but
|
|
14956
|
+
* filtered by `group-id`.
|
|
14957
|
+
*/
|
|
14958
|
+
async cleanupSecurityGroupLambdaEnis(groupId) {
|
|
14959
|
+
let enis;
|
|
14960
|
+
try {
|
|
14961
|
+
const resp = await this.ec2Client.send(
|
|
14962
|
+
new DescribeNetworkInterfacesCommand2({
|
|
14963
|
+
Filters: [
|
|
14964
|
+
{ Name: "group-id", Values: [groupId] },
|
|
14965
|
+
{ Name: "requester-id", Values: ["*:awslambda_*"] }
|
|
14966
|
+
]
|
|
14967
|
+
})
|
|
14968
|
+
);
|
|
14969
|
+
enis = (resp.NetworkInterfaces ?? []).filter((ni) => ni.NetworkInterfaceId).map((ni) => ({ id: ni.NetworkInterfaceId, status: ni.Status ?? "unknown" }));
|
|
14970
|
+
} catch (err) {
|
|
14971
|
+
this.logger.debug(
|
|
14972
|
+
`cleanupSecurityGroupLambdaEnis: DescribeNetworkInterfaces failed for ${groupId}: ${err instanceof Error ? err.message : String(err)}`
|
|
14973
|
+
);
|
|
14974
|
+
return;
|
|
14975
|
+
}
|
|
14976
|
+
if (enis.length === 0)
|
|
14977
|
+
return;
|
|
14978
|
+
await Promise.all(
|
|
14979
|
+
enis.map(async (eni) => {
|
|
14980
|
+
try {
|
|
14981
|
+
await this.ec2Client.send(
|
|
14982
|
+
new DeleteNetworkInterfaceCommand2({ NetworkInterfaceId: eni.id })
|
|
14983
|
+
);
|
|
14984
|
+
this.logger.debug(
|
|
14985
|
+
`cleanupSecurityGroupLambdaEnis: deleted Lambda ENI ${eni.id} for SG ${groupId}`
|
|
14986
|
+
);
|
|
14987
|
+
} catch (err) {
|
|
14988
|
+
this.logger.debug(
|
|
14989
|
+
`cleanupSecurityGroupLambdaEnis: ENI ${eni.id} (status=${eni.status}) not yet deletable: ${err instanceof Error ? err.message : String(err)}`
|
|
14990
|
+
);
|
|
14991
|
+
}
|
|
14992
|
+
})
|
|
14993
|
+
);
|
|
14994
|
+
}
|
|
14841
14995
|
async getSecurityGroupAttribute(physicalId, attributeName) {
|
|
14842
14996
|
if (attributeName === "GroupId")
|
|
14843
14997
|
return physicalId;
|
|
@@ -27605,7 +27759,7 @@ function reorderArgs(argv) {
|
|
|
27605
27759
|
}
|
|
27606
27760
|
async function main() {
|
|
27607
27761
|
const program = new Command8();
|
|
27608
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.3.
|
|
27762
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.3.4");
|
|
27609
27763
|
program.addCommand(createBootstrapCommand());
|
|
27610
27764
|
program.addCommand(createSynthCommand());
|
|
27611
27765
|
program.addCommand(createDeployCommand());
|