@go-to-k/cdkd 0.57.1 → 0.59.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/README.md +62 -0
- package/dist/cli.js +925 -72
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.59.0.tgz +0 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +2 -2
- package/package.json +3 -2
- package/dist/go-to-k-cdkd-0.57.1.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -650,7 +650,11 @@ var contextOptions = [
|
|
|
650
650
|
var destroyOptions = [
|
|
651
651
|
new Option("-f, --force", "Do not ask for confirmation before destroying the stacks").default(
|
|
652
652
|
false
|
|
653
|
-
)
|
|
653
|
+
),
|
|
654
|
+
new Option(
|
|
655
|
+
"--remove-protection",
|
|
656
|
+
"Bypass deletion protection on protected resources by flipping the per-resource protection flag off in-place before delete. Covers stack-level terminationProtection (CDK property) and resource-level protection on AWS::Logs::LogGroup, AWS::RDS::DBInstance, AWS::RDS::DBCluster, AWS::DynamoDB::Table, AWS::EC2::Instance, and AWS::ElasticLoadBalancingV2::LoadBalancer."
|
|
657
|
+
).default(false)
|
|
654
658
|
];
|
|
655
659
|
|
|
656
660
|
// src/provisioning/resource-name.ts
|
|
@@ -1207,6 +1211,18 @@ var ResourceUpdateNotSupportedError = class _ResourceUpdateNotSupportedError ext
|
|
|
1207
1211
|
}
|
|
1208
1212
|
exitCode = 2;
|
|
1209
1213
|
};
|
|
1214
|
+
var StackTerminationProtectionError = class _StackTerminationProtectionError extends CdkdError {
|
|
1215
|
+
constructor(stackName, cause) {
|
|
1216
|
+
super(
|
|
1217
|
+
`Stack '${stackName}' has terminationProtection: true and cannot be destroyed. Set terminationProtection: false in the CDK code, redeploy, then retry 'cdkd destroy ${stackName}'.`,
|
|
1218
|
+
"STACK_TERMINATION_PROTECTION",
|
|
1219
|
+
cause
|
|
1220
|
+
);
|
|
1221
|
+
this.stackName = stackName;
|
|
1222
|
+
this.name = "StackTerminationProtectionError";
|
|
1223
|
+
Object.setPrototypeOf(this, _StackTerminationProtectionError.prototype);
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1210
1226
|
function isCdkdError(error) {
|
|
1211
1227
|
return error instanceof CdkdError;
|
|
1212
1228
|
}
|
|
@@ -1915,7 +1931,10 @@ var AssemblyReader = class {
|
|
|
1915
1931
|
assetManifestPath,
|
|
1916
1932
|
dependencyNames,
|
|
1917
1933
|
region: env?.region !== "unknown-region" ? env?.region : void 0,
|
|
1918
|
-
account: env?.account !== "unknown-account" ? env?.account : void 0
|
|
1934
|
+
account: env?.account !== "unknown-account" ? env?.account : void 0,
|
|
1935
|
+
...props?.terminationProtection !== void 0 && {
|
|
1936
|
+
terminationProtection: props.terminationProtection
|
|
1937
|
+
}
|
|
1919
1938
|
};
|
|
1920
1939
|
}
|
|
1921
1940
|
/**
|
|
@@ -16669,6 +16688,7 @@ import {
|
|
|
16669
16688
|
ListTagsOfResourceCommand,
|
|
16670
16689
|
TagResourceCommand as TagResourceCommand4,
|
|
16671
16690
|
UntagResourceCommand as UntagResourceCommand4,
|
|
16691
|
+
UpdateTableCommand,
|
|
16672
16692
|
ResourceNotFoundException as ResourceNotFoundException6
|
|
16673
16693
|
} from "@aws-sdk/client-dynamodb";
|
|
16674
16694
|
init_aws_clients();
|
|
@@ -16834,6 +16854,32 @@ var DynamoDBTableProvider = class {
|
|
|
16834
16854
|
*/
|
|
16835
16855
|
async delete(logicalId, physicalId, resourceType, _properties, context) {
|
|
16836
16856
|
this.logger.debug(`Deleting DynamoDB table ${logicalId}: ${physicalId}`);
|
|
16857
|
+
if (context?.removeProtection === true) {
|
|
16858
|
+
try {
|
|
16859
|
+
await this.dynamoDBClient.send(
|
|
16860
|
+
new UpdateTableCommand({
|
|
16861
|
+
TableName: physicalId,
|
|
16862
|
+
DeletionProtectionEnabled: false
|
|
16863
|
+
})
|
|
16864
|
+
);
|
|
16865
|
+
this.logger.debug(
|
|
16866
|
+
`Disabled DeletionProtectionEnabled on DynamoDB table ${logicalId}, waiting for ACTIVE`
|
|
16867
|
+
);
|
|
16868
|
+
try {
|
|
16869
|
+
await this.waitForTableActiveAfterUpdate(physicalId);
|
|
16870
|
+
} catch (waitErr) {
|
|
16871
|
+
this.logger.debug(
|
|
16872
|
+
`Could not wait for table ${physicalId} ACTIVE after disabling protection: ${waitErr instanceof Error ? waitErr.message : String(waitErr)}`
|
|
16873
|
+
);
|
|
16874
|
+
}
|
|
16875
|
+
} catch (flipError) {
|
|
16876
|
+
if (!(flipError instanceof ResourceNotFoundException6)) {
|
|
16877
|
+
this.logger.debug(
|
|
16878
|
+
`Could not disable DeletionProtectionEnabled on ${physicalId}: ${flipError instanceof Error ? flipError.message : String(flipError)}`
|
|
16879
|
+
);
|
|
16880
|
+
}
|
|
16881
|
+
}
|
|
16882
|
+
}
|
|
16837
16883
|
try {
|
|
16838
16884
|
await this.dynamoDBClient.send(new DeleteTableCommand({ TableName: physicalId }));
|
|
16839
16885
|
this.logger.debug(`Successfully deleted DynamoDB table ${logicalId}`);
|
|
@@ -16926,6 +16972,28 @@ var DynamoDBTableProvider = class {
|
|
|
16926
16972
|
}
|
|
16927
16973
|
throw new Error(`Table ${tableName} did not reach ACTIVE status within ${maxAttempts} seconds`);
|
|
16928
16974
|
}
|
|
16975
|
+
/**
|
|
16976
|
+
* Poll DescribeTable until the table reaches ACTIVE after an UpdateTable
|
|
16977
|
+
* call. Distinct from `waitForTableActive` because `UpdateTable`
|
|
16978
|
+
* transitions the table to `UPDATING` (not `CREATING`); a status
|
|
16979
|
+
* mismatch should not throw — just keep polling — and the call may
|
|
16980
|
+
* also return immediately ACTIVE on the no-op path (already disabled).
|
|
16981
|
+
*/
|
|
16982
|
+
async waitForTableActiveAfterUpdate(tableName, maxAttempts = 60) {
|
|
16983
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
16984
|
+
const response = await this.dynamoDBClient.send(
|
|
16985
|
+
new DescribeTableCommand2({ TableName: tableName })
|
|
16986
|
+
);
|
|
16987
|
+
const status = response.Table?.TableStatus;
|
|
16988
|
+
if (status === "ACTIVE") {
|
|
16989
|
+
return;
|
|
16990
|
+
}
|
|
16991
|
+
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
16992
|
+
}
|
|
16993
|
+
throw new Error(
|
|
16994
|
+
`Table ${tableName} did not reach ACTIVE status within ${maxAttempts} seconds after UpdateTable`
|
|
16995
|
+
);
|
|
16996
|
+
}
|
|
16929
16997
|
/**
|
|
16930
16998
|
* Resolve a single `Fn::GetAtt` attribute for an existing DynamoDB table.
|
|
16931
16999
|
*
|
|
@@ -17405,6 +17473,23 @@ var LogsLogGroupProvider = class {
|
|
|
17405
17473
|
*/
|
|
17406
17474
|
async delete(logicalId, physicalId, resourceType, _properties, context) {
|
|
17407
17475
|
this.logger.debug(`Deleting log group ${logicalId}: ${physicalId}`);
|
|
17476
|
+
if (context?.removeProtection === true) {
|
|
17477
|
+
try {
|
|
17478
|
+
await this.logsClient.send(
|
|
17479
|
+
new PutLogGroupDeletionProtectionCommand({
|
|
17480
|
+
logGroupIdentifier: physicalId,
|
|
17481
|
+
deletionProtectionEnabled: false
|
|
17482
|
+
})
|
|
17483
|
+
);
|
|
17484
|
+
this.logger.debug(
|
|
17485
|
+
`Disabled DeletionProtectionEnabled on log group ${logicalId} before delete`
|
|
17486
|
+
);
|
|
17487
|
+
} catch (flipError) {
|
|
17488
|
+
this.logger.debug(
|
|
17489
|
+
`Could not disable DeletionProtectionEnabled on ${physicalId}: ${flipError instanceof Error ? flipError.message : String(flipError)}`
|
|
17490
|
+
);
|
|
17491
|
+
}
|
|
17492
|
+
}
|
|
17408
17493
|
try {
|
|
17409
17494
|
await this.logsClient.send(new DeleteLogGroupCommand({ logGroupName: physicalId }));
|
|
17410
17495
|
this.logger.debug(`Successfully deleted log group ${logicalId}`);
|
|
@@ -19660,7 +19745,8 @@ import {
|
|
|
19660
19745
|
DescribeNetworkInterfacesCommand as DescribeNetworkInterfacesCommand2,
|
|
19661
19746
|
DeleteNetworkInterfaceCommand as DeleteNetworkInterfaceCommand2,
|
|
19662
19747
|
DescribeVolumesCommand,
|
|
19663
|
-
DescribeInstanceAttributeCommand
|
|
19748
|
+
DescribeInstanceAttributeCommand,
|
|
19749
|
+
ModifyInstanceAttributeCommand
|
|
19664
19750
|
} from "@aws-sdk/client-ec2";
|
|
19665
19751
|
init_aws_clients();
|
|
19666
19752
|
var EC2Provider = class {
|
|
@@ -21308,6 +21394,25 @@ var EC2Provider = class {
|
|
|
21308
21394
|
}
|
|
21309
21395
|
async deleteInstance(logicalId, physicalId, resourceType, context) {
|
|
21310
21396
|
this.logger.debug(`Terminating EC2 Instance ${logicalId}: ${physicalId}`);
|
|
21397
|
+
if (context?.removeProtection === true) {
|
|
21398
|
+
try {
|
|
21399
|
+
await this.ec2Client.send(
|
|
21400
|
+
new ModifyInstanceAttributeCommand({
|
|
21401
|
+
InstanceId: physicalId,
|
|
21402
|
+
DisableApiTermination: { Value: false }
|
|
21403
|
+
})
|
|
21404
|
+
);
|
|
21405
|
+
this.logger.debug(
|
|
21406
|
+
`Disabled DisableApiTermination on EC2 Instance ${logicalId} before termination`
|
|
21407
|
+
);
|
|
21408
|
+
} catch (flipError) {
|
|
21409
|
+
if (!this.isNotFoundError(flipError)) {
|
|
21410
|
+
this.logger.debug(
|
|
21411
|
+
`Could not disable DisableApiTermination on ${physicalId}: ${flipError instanceof Error ? flipError.message : String(flipError)}`
|
|
21412
|
+
);
|
|
21413
|
+
}
|
|
21414
|
+
}
|
|
21415
|
+
}
|
|
21311
21416
|
try {
|
|
21312
21417
|
await this.ec2Client.send(new TerminateInstancesCommand({ InstanceIds: [physicalId] }));
|
|
21313
21418
|
this.logger.debug(`Terminate requested for EC2 Instance ${logicalId}, waiting...`);
|
|
@@ -28231,6 +28336,25 @@ var ELBv2Provider = class {
|
|
|
28231
28336
|
}
|
|
28232
28337
|
async deleteLoadBalancer(logicalId, physicalId, resourceType, context) {
|
|
28233
28338
|
this.logger.debug(`Deleting LoadBalancer ${logicalId}: ${physicalId}`);
|
|
28339
|
+
if (context?.removeProtection === true) {
|
|
28340
|
+
try {
|
|
28341
|
+
await this.getClient().send(
|
|
28342
|
+
new ModifyLoadBalancerAttributesCommand({
|
|
28343
|
+
LoadBalancerArn: physicalId,
|
|
28344
|
+
Attributes: [{ Key: "deletion_protection.enabled", Value: "false" }]
|
|
28345
|
+
})
|
|
28346
|
+
);
|
|
28347
|
+
this.logger.debug(
|
|
28348
|
+
`Disabled deletion_protection.enabled on LoadBalancer ${logicalId} before delete`
|
|
28349
|
+
);
|
|
28350
|
+
} catch (flipError) {
|
|
28351
|
+
if (!this.isNotFoundError(flipError)) {
|
|
28352
|
+
this.logger.debug(
|
|
28353
|
+
`Could not disable deletion_protection.enabled on ${physicalId}: ${flipError instanceof Error ? flipError.message : String(flipError)}`
|
|
28354
|
+
);
|
|
28355
|
+
}
|
|
28356
|
+
}
|
|
28357
|
+
}
|
|
28234
28358
|
try {
|
|
28235
28359
|
await this.getClient().send(new DeleteLoadBalancerCommand({ LoadBalancerArn: physicalId }));
|
|
28236
28360
|
this.logger.debug(`Successfully deleted LoadBalancer ${logicalId}`);
|
|
@@ -29242,18 +29366,22 @@ var RDSProvider = class {
|
|
|
29242
29366
|
async deleteDBCluster(logicalId, physicalId, resourceType, context) {
|
|
29243
29367
|
this.logger.debug(`Deleting DBCluster ${logicalId}: ${physicalId}`);
|
|
29244
29368
|
try {
|
|
29245
|
-
|
|
29246
|
-
|
|
29247
|
-
|
|
29248
|
-
|
|
29249
|
-
|
|
29250
|
-
|
|
29251
|
-
|
|
29252
|
-
|
|
29253
|
-
if (!this.isNotFoundError(disableError, "DBClusterNotFoundFault")) {
|
|
29254
|
-
this.logger.debug(
|
|
29255
|
-
`Could not disable deletion protection for ${physicalId}: ${disableError instanceof Error ? disableError.message : String(disableError)}`
|
|
29369
|
+
if (context?.removeProtection === true) {
|
|
29370
|
+
try {
|
|
29371
|
+
await this.getClient().send(
|
|
29372
|
+
new ModifyDBClusterCommand({
|
|
29373
|
+
DBClusterIdentifier: physicalId,
|
|
29374
|
+
DeletionProtection: false,
|
|
29375
|
+
ApplyImmediately: true
|
|
29376
|
+
})
|
|
29256
29377
|
);
|
|
29378
|
+
this.logger.debug(`Disabled DeletionProtection on DBCluster ${logicalId} before delete`);
|
|
29379
|
+
} catch (disableError) {
|
|
29380
|
+
if (!this.isNotFoundError(disableError, "DBClusterNotFoundFault")) {
|
|
29381
|
+
this.logger.debug(
|
|
29382
|
+
`Could not disable deletion protection for ${physicalId}: ${disableError instanceof Error ? disableError.message : String(disableError)}`
|
|
29383
|
+
);
|
|
29384
|
+
}
|
|
29257
29385
|
}
|
|
29258
29386
|
}
|
|
29259
29387
|
await this.getClient().send(
|
|
@@ -29377,19 +29505,22 @@ var RDSProvider = class {
|
|
|
29377
29505
|
async deleteDBInstance(logicalId, physicalId, resourceType, context) {
|
|
29378
29506
|
this.logger.debug(`Deleting DBInstance ${logicalId}: ${physicalId}`);
|
|
29379
29507
|
try {
|
|
29380
|
-
|
|
29381
|
-
|
|
29382
|
-
|
|
29383
|
-
|
|
29384
|
-
|
|
29385
|
-
|
|
29386
|
-
|
|
29387
|
-
|
|
29388
|
-
} catch (disableError) {
|
|
29389
|
-
if (!this.isNotFoundError(disableError, "DBInstanceNotFoundFault")) {
|
|
29390
|
-
this.logger.debug(
|
|
29391
|
-
`Could not disable deletion protection for ${physicalId}: ${disableError instanceof Error ? disableError.message : String(disableError)}`
|
|
29508
|
+
if (context?.removeProtection === true) {
|
|
29509
|
+
try {
|
|
29510
|
+
await this.getClient().send(
|
|
29511
|
+
new ModifyDBInstanceCommand({
|
|
29512
|
+
DBInstanceIdentifier: physicalId,
|
|
29513
|
+
DeletionProtection: false,
|
|
29514
|
+
ApplyImmediately: true
|
|
29515
|
+
})
|
|
29392
29516
|
);
|
|
29517
|
+
this.logger.debug(`Disabled DeletionProtection on DBInstance ${logicalId} before delete`);
|
|
29518
|
+
} catch (disableError) {
|
|
29519
|
+
if (!this.isNotFoundError(disableError, "DBInstanceNotFoundFault")) {
|
|
29520
|
+
this.logger.debug(
|
|
29521
|
+
`Could not disable deletion protection for ${physicalId}: ${disableError instanceof Error ? disableError.message : String(disableError)}`
|
|
29522
|
+
);
|
|
29523
|
+
}
|
|
29393
29524
|
}
|
|
29394
29525
|
}
|
|
29395
29526
|
await this.getClient().send(
|
|
@@ -31432,56 +31563,69 @@ var CognitoUserPoolProvider = class {
|
|
|
31432
31563
|
}
|
|
31433
31564
|
}
|
|
31434
31565
|
/**
|
|
31435
|
-
* Delete a Cognito User Pool
|
|
31566
|
+
* Delete a Cognito User Pool.
|
|
31436
31567
|
*
|
|
31437
|
-
*
|
|
31568
|
+
* When `context.removeProtection === true`, `DeletionProtection` is flipped
|
|
31569
|
+
* from `ACTIVE` to `INACTIVE` via `UpdateUserPool` before deletion. The
|
|
31570
|
+
* call is idempotent — AWS accepts the no-op already-disabled case
|
|
31571
|
+
* without error. Without `removeProtection`, AWS rejects the delete on a
|
|
31572
|
+
* protected pool with `InvalidParameterException` and the destroy fails;
|
|
31573
|
+
* the user is expected to set `--remove-protection` explicitly.
|
|
31574
|
+
*
|
|
31575
|
+
* Pre-PR behavior was an unconditional flip-off; that silent bypass has
|
|
31576
|
+
* been gated on `--remove-protection` to match the rest of the
|
|
31577
|
+
* deletion-protection-bearing types and CDK CLI's refuse-on-protected
|
|
31578
|
+
* semantics. See PR body for migration notes.
|
|
31438
31579
|
*/
|
|
31439
31580
|
async delete(logicalId, physicalId, resourceType, properties, context) {
|
|
31440
31581
|
this.logger.debug(`Deleting Cognito User Pool ${logicalId}: ${physicalId}`);
|
|
31441
31582
|
try {
|
|
31442
|
-
|
|
31443
|
-
|
|
31444
|
-
|
|
31445
|
-
|
|
31446
|
-
|
|
31447
|
-
|
|
31448
|
-
|
|
31449
|
-
|
|
31450
|
-
DeletionProtection
|
|
31451
|
-
})
|
|
31452
|
-
|
|
31453
|
-
|
|
31454
|
-
|
|
31455
|
-
|
|
31456
|
-
|
|
31457
|
-
|
|
31458
|
-
|
|
31583
|
+
if (context?.removeProtection === true) {
|
|
31584
|
+
const templatedActive = properties?.["DeletionProtection"] === "ACTIVE";
|
|
31585
|
+
let needsFlip = templatedActive;
|
|
31586
|
+
if (!templatedActive) {
|
|
31587
|
+
try {
|
|
31588
|
+
const describeResponse = await this.getClient().send(
|
|
31589
|
+
new DescribeUserPoolCommand({ UserPoolId: physicalId })
|
|
31590
|
+
);
|
|
31591
|
+
needsFlip = describeResponse.UserPool?.DeletionProtection === "ACTIVE";
|
|
31592
|
+
} catch (descError) {
|
|
31593
|
+
if (descError instanceof ResourceNotFoundException12) {
|
|
31594
|
+
const clientRegion = await this.getClient().config.region();
|
|
31595
|
+
assertRegionMatch(
|
|
31596
|
+
clientRegion,
|
|
31597
|
+
context?.expectedRegion,
|
|
31598
|
+
resourceType,
|
|
31599
|
+
logicalId,
|
|
31600
|
+
physicalId
|
|
31601
|
+
);
|
|
31602
|
+
this.logger.debug(
|
|
31603
|
+
`Cognito User Pool ${physicalId} does not exist, skipping deletion`
|
|
31604
|
+
);
|
|
31605
|
+
return;
|
|
31606
|
+
}
|
|
31459
31607
|
this.logger.debug(
|
|
31460
|
-
`
|
|
31608
|
+
`Failed to describe Cognito User Pool ${physicalId}, attempting flip-off anyway`
|
|
31461
31609
|
);
|
|
31610
|
+
needsFlip = true;
|
|
31611
|
+
}
|
|
31612
|
+
}
|
|
31613
|
+
if (needsFlip) {
|
|
31614
|
+
this.logger.debug(
|
|
31615
|
+
`Disabling DeletionProtection on Cognito User Pool ${physicalId} before deletion (--remove-protection)`
|
|
31616
|
+
);
|
|
31617
|
+
try {
|
|
31462
31618
|
await this.getClient().send(
|
|
31463
31619
|
new UpdateUserPoolCommand({
|
|
31464
31620
|
UserPoolId: physicalId,
|
|
31465
31621
|
DeletionProtection: "INACTIVE"
|
|
31466
31622
|
})
|
|
31467
31623
|
);
|
|
31468
|
-
}
|
|
31469
|
-
|
|
31470
|
-
|
|
31471
|
-
const clientRegion = await this.getClient().config.region();
|
|
31472
|
-
assertRegionMatch(
|
|
31473
|
-
clientRegion,
|
|
31474
|
-
context?.expectedRegion,
|
|
31475
|
-
resourceType,
|
|
31476
|
-
logicalId,
|
|
31477
|
-
physicalId
|
|
31624
|
+
} catch (flipError) {
|
|
31625
|
+
this.logger.debug(
|
|
31626
|
+
`Could not disable DeletionProtection for ${physicalId}: ${flipError instanceof Error ? flipError.message : String(flipError)}`
|
|
31478
31627
|
);
|
|
31479
|
-
this.logger.debug(`Cognito User Pool ${physicalId} does not exist, skipping deletion`);
|
|
31480
|
-
return;
|
|
31481
31628
|
}
|
|
31482
|
-
this.logger.debug(
|
|
31483
|
-
`Failed to describe Cognito User Pool ${physicalId}, proceeding with delete`
|
|
31484
|
-
);
|
|
31485
31629
|
}
|
|
31486
31630
|
}
|
|
31487
31631
|
await this.getClient().send(new DeleteUserPoolCommand({ UserPoolId: physicalId }));
|
|
@@ -33870,7 +34014,7 @@ import {
|
|
|
33870
34014
|
UpdateDatabaseCommand,
|
|
33871
34015
|
DeleteDatabaseCommand,
|
|
33872
34016
|
CreateTableCommand as CreateTableCommand2,
|
|
33873
|
-
UpdateTableCommand,
|
|
34017
|
+
UpdateTableCommand as UpdateTableCommand2,
|
|
33874
34018
|
DeleteTableCommand as DeleteTableCommand2,
|
|
33875
34019
|
GetDatabaseCommand,
|
|
33876
34020
|
GetDatabasesCommand,
|
|
@@ -34130,7 +34274,7 @@ var GlueProvider = class {
|
|
|
34130
34274
|
const catalogId = properties["CatalogId"];
|
|
34131
34275
|
try {
|
|
34132
34276
|
await this.getClient().send(
|
|
34133
|
-
new
|
|
34277
|
+
new UpdateTableCommand2({
|
|
34134
34278
|
CatalogId: catalogId,
|
|
34135
34279
|
DatabaseName: databaseName,
|
|
34136
34280
|
TableInput: this.buildTableInput(tableInput)
|
|
@@ -39869,6 +40013,645 @@ var ECRProvider = class {
|
|
|
39869
40013
|
}
|
|
39870
40014
|
};
|
|
39871
40015
|
|
|
40016
|
+
// src/provisioning/providers/asg-provider.ts
|
|
40017
|
+
import {
|
|
40018
|
+
AutoScalingClient,
|
|
40019
|
+
CreateAutoScalingGroupCommand,
|
|
40020
|
+
UpdateAutoScalingGroupCommand,
|
|
40021
|
+
DeleteAutoScalingGroupCommand,
|
|
40022
|
+
DescribeAutoScalingGroupsCommand
|
|
40023
|
+
} from "@aws-sdk/client-auto-scaling";
|
|
40024
|
+
var ASGProvider = class {
|
|
40025
|
+
asgClient;
|
|
40026
|
+
providerRegion = process.env["AWS_REGION"];
|
|
40027
|
+
logger = getLogger().child("ASGProvider");
|
|
40028
|
+
handledProperties = /* @__PURE__ */ new Map([
|
|
40029
|
+
[
|
|
40030
|
+
"AWS::AutoScaling::AutoScalingGroup",
|
|
40031
|
+
/* @__PURE__ */ new Set([
|
|
40032
|
+
"AutoScalingGroupName",
|
|
40033
|
+
"LaunchTemplate",
|
|
40034
|
+
"MinSize",
|
|
40035
|
+
"MaxSize",
|
|
40036
|
+
"DesiredCapacity",
|
|
40037
|
+
"VPCZoneIdentifier",
|
|
40038
|
+
"AvailabilityZones",
|
|
40039
|
+
"HealthCheckType",
|
|
40040
|
+
"HealthCheckGracePeriod",
|
|
40041
|
+
"Cooldown",
|
|
40042
|
+
"DefaultCooldown",
|
|
40043
|
+
"Tags",
|
|
40044
|
+
"TerminationPolicies",
|
|
40045
|
+
"NewInstancesProtectedFromScaleIn",
|
|
40046
|
+
"CapacityRebalance",
|
|
40047
|
+
"ServiceLinkedRoleARN",
|
|
40048
|
+
"MaxInstanceLifetime",
|
|
40049
|
+
"LoadBalancerNames",
|
|
40050
|
+
"TargetGroupARNs",
|
|
40051
|
+
"MetricsCollection",
|
|
40052
|
+
"LifecycleHookSpecificationList",
|
|
40053
|
+
"MixedInstancesPolicy",
|
|
40054
|
+
"Context",
|
|
40055
|
+
"DesiredCapacityType",
|
|
40056
|
+
"DefaultInstanceWarmup",
|
|
40057
|
+
"TrafficSources",
|
|
40058
|
+
"AvailabilityZoneDistribution",
|
|
40059
|
+
"AvailabilityZoneImpairmentPolicy",
|
|
40060
|
+
"SkipZonalShiftValidation",
|
|
40061
|
+
"CapacityReservationSpecification",
|
|
40062
|
+
"InstanceMaintenancePolicy",
|
|
40063
|
+
"DeletionProtection"
|
|
40064
|
+
])
|
|
40065
|
+
]
|
|
40066
|
+
]);
|
|
40067
|
+
getClient() {
|
|
40068
|
+
if (!this.asgClient) {
|
|
40069
|
+
this.asgClient = new AutoScalingClient(
|
|
40070
|
+
this.providerRegion ? { region: this.providerRegion } : {}
|
|
40071
|
+
);
|
|
40072
|
+
}
|
|
40073
|
+
return this.asgClient;
|
|
40074
|
+
}
|
|
40075
|
+
// ─── Dispatch ─────────────────────────────────────────────────────
|
|
40076
|
+
async create(logicalId, resourceType, properties) {
|
|
40077
|
+
if (resourceType !== "AWS::AutoScaling::AutoScalingGroup") {
|
|
40078
|
+
throw new ProvisioningError(
|
|
40079
|
+
`Unsupported resource type: ${resourceType}`,
|
|
40080
|
+
resourceType,
|
|
40081
|
+
logicalId
|
|
40082
|
+
);
|
|
40083
|
+
}
|
|
40084
|
+
const groupName = properties["AutoScalingGroupName"] || generateResourceName(logicalId, { maxLength: 255 });
|
|
40085
|
+
this.logger.debug(`Creating AutoScalingGroup ${logicalId}: ${groupName}`);
|
|
40086
|
+
try {
|
|
40087
|
+
const launchTemplate = this.buildLaunchTemplate(properties);
|
|
40088
|
+
const tags = this.buildTags(groupName, properties);
|
|
40089
|
+
const vpcZoneIdentifier = this.joinVpcZoneIdentifier(properties["VPCZoneIdentifier"]);
|
|
40090
|
+
const minSize = properties["MinSize"] != null ? Number(properties["MinSize"]) : 0;
|
|
40091
|
+
const maxSize = properties["MaxSize"] != null ? Number(properties["MaxSize"]) : minSize;
|
|
40092
|
+
await this.getClient().send(
|
|
40093
|
+
new CreateAutoScalingGroupCommand({
|
|
40094
|
+
AutoScalingGroupName: groupName,
|
|
40095
|
+
MinSize: minSize,
|
|
40096
|
+
MaxSize: maxSize,
|
|
40097
|
+
...properties["DesiredCapacity"] != null && {
|
|
40098
|
+
DesiredCapacity: Number(properties["DesiredCapacity"])
|
|
40099
|
+
},
|
|
40100
|
+
...launchTemplate && { LaunchTemplate: launchTemplate },
|
|
40101
|
+
...properties["MixedInstancesPolicy"] !== void 0 && {
|
|
40102
|
+
MixedInstancesPolicy: properties["MixedInstancesPolicy"]
|
|
40103
|
+
},
|
|
40104
|
+
...vpcZoneIdentifier !== void 0 && { VPCZoneIdentifier: vpcZoneIdentifier },
|
|
40105
|
+
...properties["AvailabilityZones"] !== void 0 && {
|
|
40106
|
+
AvailabilityZones: properties["AvailabilityZones"]
|
|
40107
|
+
},
|
|
40108
|
+
...properties["HealthCheckType"] !== void 0 && {
|
|
40109
|
+
HealthCheckType: properties["HealthCheckType"]
|
|
40110
|
+
},
|
|
40111
|
+
...properties["HealthCheckGracePeriod"] != null && {
|
|
40112
|
+
HealthCheckGracePeriod: Number(properties["HealthCheckGracePeriod"])
|
|
40113
|
+
},
|
|
40114
|
+
...properties["Cooldown"] != null && {
|
|
40115
|
+
DefaultCooldown: Number(properties["Cooldown"])
|
|
40116
|
+
},
|
|
40117
|
+
...properties["DefaultCooldown"] != null && {
|
|
40118
|
+
DefaultCooldown: Number(properties["DefaultCooldown"])
|
|
40119
|
+
},
|
|
40120
|
+
...properties["TerminationPolicies"] !== void 0 && {
|
|
40121
|
+
TerminationPolicies: properties["TerminationPolicies"]
|
|
40122
|
+
},
|
|
40123
|
+
...properties["NewInstancesProtectedFromScaleIn"] !== void 0 && {
|
|
40124
|
+
NewInstancesProtectedFromScaleIn: properties["NewInstancesProtectedFromScaleIn"]
|
|
40125
|
+
},
|
|
40126
|
+
...properties["CapacityRebalance"] !== void 0 && {
|
|
40127
|
+
CapacityRebalance: properties["CapacityRebalance"]
|
|
40128
|
+
},
|
|
40129
|
+
...properties["ServiceLinkedRoleARN"] !== void 0 && {
|
|
40130
|
+
ServiceLinkedRoleARN: properties["ServiceLinkedRoleARN"]
|
|
40131
|
+
},
|
|
40132
|
+
...properties["MaxInstanceLifetime"] != null && {
|
|
40133
|
+
MaxInstanceLifetime: Number(properties["MaxInstanceLifetime"])
|
|
40134
|
+
},
|
|
40135
|
+
...properties["LoadBalancerNames"] !== void 0 && {
|
|
40136
|
+
LoadBalancerNames: properties["LoadBalancerNames"]
|
|
40137
|
+
},
|
|
40138
|
+
...properties["TargetGroupARNs"] !== void 0 && {
|
|
40139
|
+
TargetGroupARNs: properties["TargetGroupARNs"]
|
|
40140
|
+
},
|
|
40141
|
+
...properties["Context"] !== void 0 && {
|
|
40142
|
+
Context: properties["Context"]
|
|
40143
|
+
},
|
|
40144
|
+
...properties["DesiredCapacityType"] !== void 0 && {
|
|
40145
|
+
DesiredCapacityType: properties["DesiredCapacityType"]
|
|
40146
|
+
},
|
|
40147
|
+
...properties["DefaultInstanceWarmup"] != null && {
|
|
40148
|
+
DefaultInstanceWarmup: Number(properties["DefaultInstanceWarmup"])
|
|
40149
|
+
},
|
|
40150
|
+
...properties["LifecycleHookSpecificationList"] !== void 0 && {
|
|
40151
|
+
LifecycleHookSpecificationList: properties["LifecycleHookSpecificationList"]
|
|
40152
|
+
},
|
|
40153
|
+
...properties["TrafficSources"] !== void 0 && {
|
|
40154
|
+
TrafficSources: properties["TrafficSources"]
|
|
40155
|
+
},
|
|
40156
|
+
...properties["AvailabilityZoneDistribution"] !== void 0 && {
|
|
40157
|
+
AvailabilityZoneDistribution: properties["AvailabilityZoneDistribution"]
|
|
40158
|
+
},
|
|
40159
|
+
...properties["AvailabilityZoneImpairmentPolicy"] !== void 0 && {
|
|
40160
|
+
AvailabilityZoneImpairmentPolicy: properties["AvailabilityZoneImpairmentPolicy"]
|
|
40161
|
+
},
|
|
40162
|
+
...properties["SkipZonalShiftValidation"] !== void 0 && {
|
|
40163
|
+
SkipZonalShiftValidation: properties["SkipZonalShiftValidation"]
|
|
40164
|
+
},
|
|
40165
|
+
...properties["CapacityReservationSpecification"] !== void 0 && {
|
|
40166
|
+
CapacityReservationSpecification: properties["CapacityReservationSpecification"]
|
|
40167
|
+
},
|
|
40168
|
+
...properties["InstanceMaintenancePolicy"] !== void 0 && {
|
|
40169
|
+
InstanceMaintenancePolicy: properties["InstanceMaintenancePolicy"]
|
|
40170
|
+
},
|
|
40171
|
+
...properties["DeletionProtection"] !== void 0 && {
|
|
40172
|
+
DeletionProtection: properties["DeletionProtection"]
|
|
40173
|
+
},
|
|
40174
|
+
...tags.length > 0 && { Tags: tags }
|
|
40175
|
+
})
|
|
40176
|
+
);
|
|
40177
|
+
this.logger.debug(`Successfully created AutoScalingGroup ${logicalId}: ${groupName}`);
|
|
40178
|
+
const arn = await this.fetchArn(groupName);
|
|
40179
|
+
const attributes = {};
|
|
40180
|
+
if (arn)
|
|
40181
|
+
attributes["Arn"] = arn;
|
|
40182
|
+
if (launchTemplate?.LaunchTemplateId) {
|
|
40183
|
+
attributes["LaunchTemplateID"] = launchTemplate.LaunchTemplateId;
|
|
40184
|
+
}
|
|
40185
|
+
return { physicalId: groupName, attributes };
|
|
40186
|
+
} catch (error) {
|
|
40187
|
+
const cause = error instanceof Error ? error : void 0;
|
|
40188
|
+
throw new ProvisioningError(
|
|
40189
|
+
`Failed to create AutoScalingGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
40190
|
+
resourceType,
|
|
40191
|
+
logicalId,
|
|
40192
|
+
groupName,
|
|
40193
|
+
cause
|
|
40194
|
+
);
|
|
40195
|
+
}
|
|
40196
|
+
}
|
|
40197
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
40198
|
+
if (resourceType !== "AWS::AutoScaling::AutoScalingGroup") {
|
|
40199
|
+
throw new ProvisioningError(
|
|
40200
|
+
`Unsupported resource type: ${resourceType}`,
|
|
40201
|
+
resourceType,
|
|
40202
|
+
logicalId,
|
|
40203
|
+
physicalId
|
|
40204
|
+
);
|
|
40205
|
+
}
|
|
40206
|
+
this.logger.debug(`Updating AutoScalingGroup ${logicalId}: ${physicalId}`);
|
|
40207
|
+
const stringEq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
40208
|
+
if (!stringEq(properties["AutoScalingGroupName"], previousProperties["AutoScalingGroupName"])) {
|
|
40209
|
+
throw new ResourceUpdateNotSupportedError(
|
|
40210
|
+
resourceType,
|
|
40211
|
+
logicalId,
|
|
40212
|
+
"AutoScalingGroupName is immutable; use cdkd deploy --replace to replace the group"
|
|
40213
|
+
);
|
|
40214
|
+
}
|
|
40215
|
+
if (!stringEq(properties["Tags"] ?? [], previousProperties["Tags"] ?? [])) {
|
|
40216
|
+
throw new ResourceUpdateNotSupportedError(
|
|
40217
|
+
resourceType,
|
|
40218
|
+
logicalId,
|
|
40219
|
+
"Tags updates on AWS::AutoScaling::AutoScalingGroup are not yet supported by cdkd; use cdkd deploy --replace, or update the tags via AWS console / CLI"
|
|
40220
|
+
);
|
|
40221
|
+
}
|
|
40222
|
+
if (!stringEq(
|
|
40223
|
+
properties["LoadBalancerNames"] ?? [],
|
|
40224
|
+
previousProperties["LoadBalancerNames"] ?? []
|
|
40225
|
+
)) {
|
|
40226
|
+
throw new ResourceUpdateNotSupportedError(
|
|
40227
|
+
resourceType,
|
|
40228
|
+
logicalId,
|
|
40229
|
+
"LoadBalancerNames diffs require Attach/Detach calls; use cdkd deploy --replace"
|
|
40230
|
+
);
|
|
40231
|
+
}
|
|
40232
|
+
if (!stringEq(properties["TargetGroupARNs"] ?? [], previousProperties["TargetGroupARNs"] ?? [])) {
|
|
40233
|
+
throw new ResourceUpdateNotSupportedError(
|
|
40234
|
+
resourceType,
|
|
40235
|
+
logicalId,
|
|
40236
|
+
"TargetGroupARNs diffs require Attach/Detach calls; use cdkd deploy --replace"
|
|
40237
|
+
);
|
|
40238
|
+
}
|
|
40239
|
+
if (!stringEq(
|
|
40240
|
+
properties["LifecycleHookSpecificationList"] ?? [],
|
|
40241
|
+
previousProperties["LifecycleHookSpecificationList"] ?? []
|
|
40242
|
+
)) {
|
|
40243
|
+
throw new ResourceUpdateNotSupportedError(
|
|
40244
|
+
resourceType,
|
|
40245
|
+
logicalId,
|
|
40246
|
+
"LifecycleHookSpecificationList diffs require PutLifecycleHook / DeleteLifecycleHook calls; use cdkd deploy --replace"
|
|
40247
|
+
);
|
|
40248
|
+
}
|
|
40249
|
+
if (!stringEq(
|
|
40250
|
+
properties["MetricsCollection"] ?? [],
|
|
40251
|
+
previousProperties["MetricsCollection"] ?? []
|
|
40252
|
+
)) {
|
|
40253
|
+
throw new ResourceUpdateNotSupportedError(
|
|
40254
|
+
resourceType,
|
|
40255
|
+
logicalId,
|
|
40256
|
+
"MetricsCollection diffs require EnableMetricsCollection / DisableMetricsCollection calls; use cdkd deploy --replace"
|
|
40257
|
+
);
|
|
40258
|
+
}
|
|
40259
|
+
if (!stringEq(properties["TrafficSources"] ?? [], previousProperties["TrafficSources"] ?? [])) {
|
|
40260
|
+
throw new ResourceUpdateNotSupportedError(
|
|
40261
|
+
resourceType,
|
|
40262
|
+
logicalId,
|
|
40263
|
+
"TrafficSources diffs require AttachTrafficSources / DetachTrafficSources calls; use cdkd deploy --replace"
|
|
40264
|
+
);
|
|
40265
|
+
}
|
|
40266
|
+
try {
|
|
40267
|
+
const launchTemplate = this.buildLaunchTemplate(properties);
|
|
40268
|
+
const vpcZoneIdentifier = this.joinVpcZoneIdentifier(properties["VPCZoneIdentifier"]);
|
|
40269
|
+
await this.getClient().send(
|
|
40270
|
+
new UpdateAutoScalingGroupCommand({
|
|
40271
|
+
AutoScalingGroupName: physicalId,
|
|
40272
|
+
...properties["MinSize"] != null && { MinSize: Number(properties["MinSize"]) },
|
|
40273
|
+
...properties["MaxSize"] != null && { MaxSize: Number(properties["MaxSize"]) },
|
|
40274
|
+
...properties["DesiredCapacity"] != null && {
|
|
40275
|
+
DesiredCapacity: Number(properties["DesiredCapacity"])
|
|
40276
|
+
},
|
|
40277
|
+
...launchTemplate && { LaunchTemplate: launchTemplate },
|
|
40278
|
+
...properties["MixedInstancesPolicy"] !== void 0 && {
|
|
40279
|
+
MixedInstancesPolicy: properties["MixedInstancesPolicy"]
|
|
40280
|
+
},
|
|
40281
|
+
...vpcZoneIdentifier !== void 0 && { VPCZoneIdentifier: vpcZoneIdentifier },
|
|
40282
|
+
...properties["AvailabilityZones"] !== void 0 && {
|
|
40283
|
+
AvailabilityZones: properties["AvailabilityZones"]
|
|
40284
|
+
},
|
|
40285
|
+
...properties["HealthCheckType"] !== void 0 && {
|
|
40286
|
+
HealthCheckType: properties["HealthCheckType"]
|
|
40287
|
+
},
|
|
40288
|
+
...properties["HealthCheckGracePeriod"] != null && {
|
|
40289
|
+
HealthCheckGracePeriod: Number(properties["HealthCheckGracePeriod"])
|
|
40290
|
+
},
|
|
40291
|
+
...properties["Cooldown"] != null && {
|
|
40292
|
+
DefaultCooldown: Number(properties["Cooldown"])
|
|
40293
|
+
},
|
|
40294
|
+
...properties["DefaultCooldown"] != null && {
|
|
40295
|
+
DefaultCooldown: Number(properties["DefaultCooldown"])
|
|
40296
|
+
},
|
|
40297
|
+
...properties["TerminationPolicies"] !== void 0 && {
|
|
40298
|
+
TerminationPolicies: properties["TerminationPolicies"]
|
|
40299
|
+
},
|
|
40300
|
+
...properties["NewInstancesProtectedFromScaleIn"] !== void 0 && {
|
|
40301
|
+
NewInstancesProtectedFromScaleIn: properties["NewInstancesProtectedFromScaleIn"]
|
|
40302
|
+
},
|
|
40303
|
+
...properties["CapacityRebalance"] !== void 0 && {
|
|
40304
|
+
CapacityRebalance: properties["CapacityRebalance"]
|
|
40305
|
+
},
|
|
40306
|
+
...properties["ServiceLinkedRoleARN"] !== void 0 && {
|
|
40307
|
+
ServiceLinkedRoleARN: properties["ServiceLinkedRoleARN"]
|
|
40308
|
+
},
|
|
40309
|
+
...properties["MaxInstanceLifetime"] != null && {
|
|
40310
|
+
MaxInstanceLifetime: Number(properties["MaxInstanceLifetime"])
|
|
40311
|
+
},
|
|
40312
|
+
...properties["Context"] !== void 0 && {
|
|
40313
|
+
Context: properties["Context"]
|
|
40314
|
+
},
|
|
40315
|
+
...properties["DesiredCapacityType"] !== void 0 && {
|
|
40316
|
+
DesiredCapacityType: properties["DesiredCapacityType"]
|
|
40317
|
+
},
|
|
40318
|
+
...properties["DefaultInstanceWarmup"] != null && {
|
|
40319
|
+
DefaultInstanceWarmup: Number(properties["DefaultInstanceWarmup"])
|
|
40320
|
+
},
|
|
40321
|
+
...properties["AvailabilityZoneDistribution"] !== void 0 && {
|
|
40322
|
+
AvailabilityZoneDistribution: properties["AvailabilityZoneDistribution"]
|
|
40323
|
+
},
|
|
40324
|
+
...properties["AvailabilityZoneImpairmentPolicy"] !== void 0 && {
|
|
40325
|
+
AvailabilityZoneImpairmentPolicy: properties["AvailabilityZoneImpairmentPolicy"]
|
|
40326
|
+
},
|
|
40327
|
+
...properties["SkipZonalShiftValidation"] !== void 0 && {
|
|
40328
|
+
SkipZonalShiftValidation: properties["SkipZonalShiftValidation"]
|
|
40329
|
+
},
|
|
40330
|
+
...properties["CapacityReservationSpecification"] !== void 0 && {
|
|
40331
|
+
CapacityReservationSpecification: properties["CapacityReservationSpecification"]
|
|
40332
|
+
},
|
|
40333
|
+
...properties["InstanceMaintenancePolicy"] !== void 0 && {
|
|
40334
|
+
InstanceMaintenancePolicy: properties["InstanceMaintenancePolicy"]
|
|
40335
|
+
},
|
|
40336
|
+
...properties["DeletionProtection"] !== void 0 && {
|
|
40337
|
+
DeletionProtection: properties["DeletionProtection"]
|
|
40338
|
+
}
|
|
40339
|
+
})
|
|
40340
|
+
);
|
|
40341
|
+
this.logger.debug(`Successfully updated AutoScalingGroup ${logicalId}`);
|
|
40342
|
+
const arn = await this.fetchArn(physicalId);
|
|
40343
|
+
const attributes = {};
|
|
40344
|
+
if (arn)
|
|
40345
|
+
attributes["Arn"] = arn;
|
|
40346
|
+
if (launchTemplate?.LaunchTemplateId) {
|
|
40347
|
+
attributes["LaunchTemplateID"] = launchTemplate.LaunchTemplateId;
|
|
40348
|
+
}
|
|
40349
|
+
return { physicalId, wasReplaced: false, attributes };
|
|
40350
|
+
} catch (error) {
|
|
40351
|
+
if (error instanceof ResourceUpdateNotSupportedError)
|
|
40352
|
+
throw error;
|
|
40353
|
+
const cause = error instanceof Error ? error : void 0;
|
|
40354
|
+
throw new ProvisioningError(
|
|
40355
|
+
`Failed to update AutoScalingGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
40356
|
+
resourceType,
|
|
40357
|
+
logicalId,
|
|
40358
|
+
physicalId,
|
|
40359
|
+
cause
|
|
40360
|
+
);
|
|
40361
|
+
}
|
|
40362
|
+
}
|
|
40363
|
+
async delete(logicalId, physicalId, resourceType, _properties, context) {
|
|
40364
|
+
this.logger.debug(`Deleting AutoScalingGroup ${logicalId}: ${physicalId}`);
|
|
40365
|
+
if (context?.removeProtection === true) {
|
|
40366
|
+
try {
|
|
40367
|
+
await this.getClient().send(
|
|
40368
|
+
new UpdateAutoScalingGroupCommand({
|
|
40369
|
+
AutoScalingGroupName: physicalId,
|
|
40370
|
+
DeletionProtection: "none"
|
|
40371
|
+
})
|
|
40372
|
+
);
|
|
40373
|
+
this.logger.debug(
|
|
40374
|
+
`Disabled DeletionProtection on AutoScalingGroup ${logicalId} before delete`
|
|
40375
|
+
);
|
|
40376
|
+
} catch (flipError) {
|
|
40377
|
+
this.logger.debug(
|
|
40378
|
+
`Could not disable DeletionProtection on ${physicalId}: ${flipError instanceof Error ? flipError.message : String(flipError)}`
|
|
40379
|
+
);
|
|
40380
|
+
}
|
|
40381
|
+
}
|
|
40382
|
+
try {
|
|
40383
|
+
await this.getClient().send(
|
|
40384
|
+
new DeleteAutoScalingGroupCommand({
|
|
40385
|
+
AutoScalingGroupName: physicalId,
|
|
40386
|
+
ForceDelete: context?.removeProtection === true
|
|
40387
|
+
})
|
|
40388
|
+
);
|
|
40389
|
+
this.logger.debug(`Successfully initiated deletion of AutoScalingGroup ${logicalId}`);
|
|
40390
|
+
await this.waitForGroupDeleted(physicalId);
|
|
40391
|
+
} catch (error) {
|
|
40392
|
+
if (this.isNotFoundError(error)) {
|
|
40393
|
+
const clientRegion = await this.getClient().config.region();
|
|
40394
|
+
assertRegionMatch(
|
|
40395
|
+
clientRegion,
|
|
40396
|
+
context?.expectedRegion,
|
|
40397
|
+
resourceType,
|
|
40398
|
+
logicalId,
|
|
40399
|
+
physicalId
|
|
40400
|
+
);
|
|
40401
|
+
this.logger.debug(`AutoScalingGroup ${physicalId} does not exist, skipping deletion`);
|
|
40402
|
+
return;
|
|
40403
|
+
}
|
|
40404
|
+
const cause = error instanceof Error ? error : void 0;
|
|
40405
|
+
throw new ProvisioningError(
|
|
40406
|
+
`Failed to delete AutoScalingGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
40407
|
+
resourceType,
|
|
40408
|
+
logicalId,
|
|
40409
|
+
physicalId,
|
|
40410
|
+
cause
|
|
40411
|
+
);
|
|
40412
|
+
}
|
|
40413
|
+
}
|
|
40414
|
+
async getAttribute(physicalId, _resourceType, attributeName) {
|
|
40415
|
+
const group = await this.describeGroup(physicalId);
|
|
40416
|
+
if (!group) {
|
|
40417
|
+
throw new ProvisioningError(
|
|
40418
|
+
`AutoScalingGroup ${physicalId} not found while resolving attribute ${attributeName}`,
|
|
40419
|
+
"AWS::AutoScaling::AutoScalingGroup",
|
|
40420
|
+
physicalId,
|
|
40421
|
+
physicalId
|
|
40422
|
+
);
|
|
40423
|
+
}
|
|
40424
|
+
switch (attributeName) {
|
|
40425
|
+
case "Arn":
|
|
40426
|
+
case "AutoScalingGroupARN":
|
|
40427
|
+
return group.AutoScalingGroupARN ?? "";
|
|
40428
|
+
case "LaunchConfigurationName":
|
|
40429
|
+
return group.LaunchConfigurationName ?? "";
|
|
40430
|
+
case "LaunchTemplateID":
|
|
40431
|
+
case "LaunchTemplateId":
|
|
40432
|
+
return group.LaunchTemplate?.LaunchTemplateId ?? "";
|
|
40433
|
+
default:
|
|
40434
|
+
return "";
|
|
40435
|
+
}
|
|
40436
|
+
}
|
|
40437
|
+
/**
|
|
40438
|
+
* Read the AWS-current AutoScalingGroup configuration in CFn-property shape.
|
|
40439
|
+
*
|
|
40440
|
+
* Surfaces the user-controllable subset of `DescribeAutoScalingGroups`,
|
|
40441
|
+
* with always-emit placeholders on user-controllable top-level keys per
|
|
40442
|
+
* the cdkd PR #145 always-emit convention so that v3 `observedProperties`
|
|
40443
|
+
* baseline catches console-side ADDs to fields a clean deploy did not
|
|
40444
|
+
* template (e.g. a console-set `DeletionProtection: 'prevent-force-deletion'`
|
|
40445
|
+
* on a group originally created without it).
|
|
40446
|
+
*
|
|
40447
|
+
* Returns `undefined` when the group is gone.
|
|
40448
|
+
*/
|
|
40449
|
+
async readCurrentState(physicalId, _logicalId, _resourceType) {
|
|
40450
|
+
let group;
|
|
40451
|
+
try {
|
|
40452
|
+
group = await this.describeGroup(physicalId);
|
|
40453
|
+
} catch (err) {
|
|
40454
|
+
if (this.isNotFoundError(err))
|
|
40455
|
+
return void 0;
|
|
40456
|
+
throw err;
|
|
40457
|
+
}
|
|
40458
|
+
if (!group)
|
|
40459
|
+
return void 0;
|
|
40460
|
+
const result = {};
|
|
40461
|
+
if (group.AutoScalingGroupName !== void 0) {
|
|
40462
|
+
result["AutoScalingGroupName"] = group.AutoScalingGroupName;
|
|
40463
|
+
}
|
|
40464
|
+
if (group.LaunchTemplate) {
|
|
40465
|
+
const lt = {};
|
|
40466
|
+
if (group.LaunchTemplate.LaunchTemplateId !== void 0) {
|
|
40467
|
+
lt["LaunchTemplateId"] = group.LaunchTemplate.LaunchTemplateId;
|
|
40468
|
+
}
|
|
40469
|
+
if (group.LaunchTemplate.LaunchTemplateName !== void 0) {
|
|
40470
|
+
lt["LaunchTemplateName"] = group.LaunchTemplate.LaunchTemplateName;
|
|
40471
|
+
}
|
|
40472
|
+
if (group.LaunchTemplate.Version !== void 0) {
|
|
40473
|
+
lt["Version"] = group.LaunchTemplate.Version;
|
|
40474
|
+
}
|
|
40475
|
+
result["LaunchTemplate"] = lt;
|
|
40476
|
+
}
|
|
40477
|
+
result["MinSize"] = group.MinSize ?? 0;
|
|
40478
|
+
result["MaxSize"] = group.MaxSize ?? 0;
|
|
40479
|
+
if (group.DesiredCapacity !== void 0)
|
|
40480
|
+
result["DesiredCapacity"] = group.DesiredCapacity;
|
|
40481
|
+
if (group.VPCZoneIdentifier !== void 0 && group.VPCZoneIdentifier !== "") {
|
|
40482
|
+
result["VPCZoneIdentifier"] = group.VPCZoneIdentifier.split(",").map((s) => s.trim());
|
|
40483
|
+
} else {
|
|
40484
|
+
result["VPCZoneIdentifier"] = [];
|
|
40485
|
+
}
|
|
40486
|
+
result["AvailabilityZones"] = group.AvailabilityZones ?? [];
|
|
40487
|
+
if (group.HealthCheckType !== void 0)
|
|
40488
|
+
result["HealthCheckType"] = group.HealthCheckType;
|
|
40489
|
+
if (group.HealthCheckGracePeriod !== void 0) {
|
|
40490
|
+
result["HealthCheckGracePeriod"] = group.HealthCheckGracePeriod;
|
|
40491
|
+
}
|
|
40492
|
+
if (group.DefaultCooldown !== void 0) {
|
|
40493
|
+
result["Cooldown"] = group.DefaultCooldown;
|
|
40494
|
+
}
|
|
40495
|
+
result["NewInstancesProtectedFromScaleIn"] = group.NewInstancesProtectedFromScaleIn ?? false;
|
|
40496
|
+
result["TerminationPolicies"] = group.TerminationPolicies ?? [];
|
|
40497
|
+
result["CapacityRebalance"] = group.CapacityRebalance ?? false;
|
|
40498
|
+
if (group.ServiceLinkedRoleARN !== void 0) {
|
|
40499
|
+
result["ServiceLinkedRoleARN"] = group.ServiceLinkedRoleARN;
|
|
40500
|
+
}
|
|
40501
|
+
if (group.MaxInstanceLifetime !== void 0) {
|
|
40502
|
+
result["MaxInstanceLifetime"] = group.MaxInstanceLifetime;
|
|
40503
|
+
}
|
|
40504
|
+
result["LoadBalancerNames"] = group.LoadBalancerNames ?? [];
|
|
40505
|
+
result["TargetGroupARNs"] = group.TargetGroupARNs ?? [];
|
|
40506
|
+
if (group.Context !== void 0)
|
|
40507
|
+
result["Context"] = group.Context;
|
|
40508
|
+
if (group.DesiredCapacityType !== void 0) {
|
|
40509
|
+
result["DesiredCapacityType"] = group.DesiredCapacityType;
|
|
40510
|
+
}
|
|
40511
|
+
if (group.DefaultInstanceWarmup !== void 0) {
|
|
40512
|
+
result["DefaultInstanceWarmup"] = group.DefaultInstanceWarmup;
|
|
40513
|
+
}
|
|
40514
|
+
if (group.MixedInstancesPolicy !== void 0) {
|
|
40515
|
+
result["MixedInstancesPolicy"] = group.MixedInstancesPolicy;
|
|
40516
|
+
}
|
|
40517
|
+
if (group.AvailabilityZoneDistribution !== void 0) {
|
|
40518
|
+
result["AvailabilityZoneDistribution"] = group.AvailabilityZoneDistribution;
|
|
40519
|
+
}
|
|
40520
|
+
if (group.AvailabilityZoneImpairmentPolicy !== void 0) {
|
|
40521
|
+
result["AvailabilityZoneImpairmentPolicy"] = group.AvailabilityZoneImpairmentPolicy;
|
|
40522
|
+
}
|
|
40523
|
+
if (group.CapacityReservationSpecification !== void 0) {
|
|
40524
|
+
result["CapacityReservationSpecification"] = group.CapacityReservationSpecification;
|
|
40525
|
+
}
|
|
40526
|
+
if (group.InstanceMaintenancePolicy !== void 0) {
|
|
40527
|
+
result["InstanceMaintenancePolicy"] = group.InstanceMaintenancePolicy;
|
|
40528
|
+
}
|
|
40529
|
+
if (group.DeletionProtection !== void 0) {
|
|
40530
|
+
result["DeletionProtection"] = group.DeletionProtection;
|
|
40531
|
+
} else {
|
|
40532
|
+
result["DeletionProtection"] = "none";
|
|
40533
|
+
}
|
|
40534
|
+
result["Tags"] = normalizeAwsTagsToCfn(group.Tags);
|
|
40535
|
+
return result;
|
|
40536
|
+
}
|
|
40537
|
+
/**
|
|
40538
|
+
* MetricsCollection / LifecycleHookSpecificationList / TrafficSources
|
|
40539
|
+
* are surfaced via separate APIs (`DescribeMetricsCollectionTypes` etc.
|
|
40540
|
+
* + `DescribeLifecycleHooks` + `DescribeTrafficSources`) that this v1
|
|
40541
|
+
* does not yet wire up — declare them as drift-unknown so the comparator
|
|
40542
|
+
* does not fire false-positive drift on every clean run.
|
|
40543
|
+
*/
|
|
40544
|
+
getDriftUnknownPaths(_resourceType) {
|
|
40545
|
+
return [
|
|
40546
|
+
"MetricsCollection",
|
|
40547
|
+
"LifecycleHookSpecificationList",
|
|
40548
|
+
"TrafficSources",
|
|
40549
|
+
"NotificationConfigurations"
|
|
40550
|
+
];
|
|
40551
|
+
}
|
|
40552
|
+
// ─── Helpers ──────────────────────────────────────────────────────
|
|
40553
|
+
buildLaunchTemplate(properties) {
|
|
40554
|
+
const lt = properties["LaunchTemplate"];
|
|
40555
|
+
if (!lt)
|
|
40556
|
+
return void 0;
|
|
40557
|
+
const out = {};
|
|
40558
|
+
if (lt.LaunchTemplateId !== void 0)
|
|
40559
|
+
out.LaunchTemplateId = lt.LaunchTemplateId;
|
|
40560
|
+
if (lt.LaunchTemplateName !== void 0)
|
|
40561
|
+
out.LaunchTemplateName = lt.LaunchTemplateName;
|
|
40562
|
+
if (lt.Version !== void 0)
|
|
40563
|
+
out.Version = lt.Version;
|
|
40564
|
+
if (out.LaunchTemplateId === void 0 && out.LaunchTemplateName === void 0) {
|
|
40565
|
+
return void 0;
|
|
40566
|
+
}
|
|
40567
|
+
return out;
|
|
40568
|
+
}
|
|
40569
|
+
/**
|
|
40570
|
+
* CFn `Tags` is `[{Key, Value, PropagateAtLaunch?}]`. AWS expects each
|
|
40571
|
+
* tag to also carry `ResourceId: <groupName>` and `ResourceType:
|
|
40572
|
+
* 'auto-scaling-group'`. We tack those on at create time so the SDK
|
|
40573
|
+
* input shape matches without forcing the user to template them.
|
|
40574
|
+
*/
|
|
40575
|
+
buildTags(groupName, properties) {
|
|
40576
|
+
const raw = properties["Tags"];
|
|
40577
|
+
if (!raw)
|
|
40578
|
+
return [];
|
|
40579
|
+
return raw.filter((t) => t.Key !== void 0).map((t) => ({
|
|
40580
|
+
ResourceId: groupName,
|
|
40581
|
+
ResourceType: "auto-scaling-group",
|
|
40582
|
+
Key: t.Key,
|
|
40583
|
+
Value: t.Value ?? "",
|
|
40584
|
+
PropagateAtLaunch: t.PropagateAtLaunch ?? false
|
|
40585
|
+
}));
|
|
40586
|
+
}
|
|
40587
|
+
/**
|
|
40588
|
+
* CFn `VPCZoneIdentifier` is a list of subnet ids; the AWS SDK input
|
|
40589
|
+
* field is a comma-joined string.
|
|
40590
|
+
*/
|
|
40591
|
+
joinVpcZoneIdentifier(value) {
|
|
40592
|
+
if (value === void 0 || value === null)
|
|
40593
|
+
return void 0;
|
|
40594
|
+
if (Array.isArray(value)) {
|
|
40595
|
+
const cleaned = value.map((v) => String(v).trim()).filter((v) => v.length > 0);
|
|
40596
|
+
if (cleaned.length === 0)
|
|
40597
|
+
return void 0;
|
|
40598
|
+
return cleaned.join(",");
|
|
40599
|
+
}
|
|
40600
|
+
if (typeof value === "string")
|
|
40601
|
+
return value;
|
|
40602
|
+
return void 0;
|
|
40603
|
+
}
|
|
40604
|
+
async describeGroup(groupName) {
|
|
40605
|
+
const response = await this.getClient().send(
|
|
40606
|
+
new DescribeAutoScalingGroupsCommand({
|
|
40607
|
+
AutoScalingGroupNames: [groupName]
|
|
40608
|
+
})
|
|
40609
|
+
);
|
|
40610
|
+
return response.AutoScalingGroups?.[0];
|
|
40611
|
+
}
|
|
40612
|
+
async fetchArn(groupName) {
|
|
40613
|
+
try {
|
|
40614
|
+
const group = await this.describeGroup(groupName);
|
|
40615
|
+
return group?.AutoScalingGroupARN;
|
|
40616
|
+
} catch (err) {
|
|
40617
|
+
this.logger.debug(
|
|
40618
|
+
`DescribeAutoScalingGroups(${groupName}) failed: ${err instanceof Error ? err.message : String(err)}`
|
|
40619
|
+
);
|
|
40620
|
+
return void 0;
|
|
40621
|
+
}
|
|
40622
|
+
}
|
|
40623
|
+
isNotFoundError(error) {
|
|
40624
|
+
if (!(error instanceof Error))
|
|
40625
|
+
return false;
|
|
40626
|
+
const name = error.name ?? "";
|
|
40627
|
+
const message = error.message.toLowerCase();
|
|
40628
|
+
return name === "ValidationError" && (message.includes("autoscalinggroup name not found") || message.includes("not found") || message.includes("does not exist"));
|
|
40629
|
+
}
|
|
40630
|
+
async waitForGroupDeleted(groupName, maxWaitMs = 9e5) {
|
|
40631
|
+
const startTime = Date.now();
|
|
40632
|
+
let delay = 5e3;
|
|
40633
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
40634
|
+
try {
|
|
40635
|
+
const group = await this.describeGroup(groupName);
|
|
40636
|
+
if (!group)
|
|
40637
|
+
return;
|
|
40638
|
+
} catch (error) {
|
|
40639
|
+
if (this.isNotFoundError(error))
|
|
40640
|
+
return;
|
|
40641
|
+
throw error;
|
|
40642
|
+
}
|
|
40643
|
+
await this.sleep(delay);
|
|
40644
|
+
delay = Math.min(delay * 2, 3e4);
|
|
40645
|
+
}
|
|
40646
|
+
throw new Error(
|
|
40647
|
+
`Timed out waiting for AutoScalingGroup ${groupName} to be deleted (15 minute cap)`
|
|
40648
|
+
);
|
|
40649
|
+
}
|
|
40650
|
+
sleep(ms) {
|
|
40651
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
40652
|
+
}
|
|
40653
|
+
};
|
|
40654
|
+
|
|
39872
40655
|
// src/provisioning/register-providers.ts
|
|
39873
40656
|
function registerAllProviders(registry) {
|
|
39874
40657
|
registry.register("AWS::IAM::Role", new IAMRoleProvider());
|
|
@@ -39975,6 +40758,7 @@ function registerAllProviders(registry) {
|
|
|
39975
40758
|
registry.register("AWS::CodeBuild::Project", new CodeBuildProvider());
|
|
39976
40759
|
registry.register("AWS::S3Vectors::VectorBucket", new S3VectorsProvider());
|
|
39977
40760
|
registry.register("AWS::ECR::Repository", new ECRProvider());
|
|
40761
|
+
registry.register("AWS::AutoScaling::AutoScalingGroup", new ASGProvider());
|
|
39978
40762
|
const s3TablesProvider = new S3TablesProvider();
|
|
39979
40763
|
registry.register("AWS::S3Tables::TableBucket", s3TablesProvider);
|
|
39980
40764
|
registry.register("AWS::S3Tables::Namespace", s3TablesProvider);
|
|
@@ -42937,6 +43721,43 @@ init_aws_clients();
|
|
|
42937
43721
|
// src/cli/commands/destroy-runner.ts
|
|
42938
43722
|
import * as readline2 from "node:readline/promises";
|
|
42939
43723
|
init_aws_clients();
|
|
43724
|
+
var PROTECTION_PROPERTY_BY_TYPE = {
|
|
43725
|
+
"AWS::Logs::LogGroup": "DeletionProtectionEnabled",
|
|
43726
|
+
"AWS::RDS::DBInstance": "DeletionProtection",
|
|
43727
|
+
"AWS::RDS::DBCluster": "DeletionProtection",
|
|
43728
|
+
"AWS::DynamoDB::Table": "DeletionProtectionEnabled",
|
|
43729
|
+
"AWS::EC2::Instance": "DisableApiTermination",
|
|
43730
|
+
"AWS::Cognito::UserPool": "DeletionProtection",
|
|
43731
|
+
"AWS::AutoScaling::AutoScalingGroup": "DeletionProtection"
|
|
43732
|
+
};
|
|
43733
|
+
var PROTECTION_ACTIVE_VALUES_BY_TYPE = {
|
|
43734
|
+
"AWS::Cognito::UserPool": /* @__PURE__ */ new Set(["ACTIVE"]),
|
|
43735
|
+
"AWS::AutoScaling::AutoScalingGroup": /* @__PURE__ */ new Set(["prevent-force-deletion", "prevent-all-deletion"])
|
|
43736
|
+
};
|
|
43737
|
+
function countProtectedResources(state) {
|
|
43738
|
+
let count = 0;
|
|
43739
|
+
for (const resource of Object.values(state.resources ?? {})) {
|
|
43740
|
+
const propName = PROTECTION_PROPERTY_BY_TYPE[resource.resourceType];
|
|
43741
|
+
if (propName) {
|
|
43742
|
+
const recorded = resource.properties?.[propName] ?? resource.observedProperties?.[propName];
|
|
43743
|
+
const activeValues = PROTECTION_ACTIVE_VALUES_BY_TYPE[resource.resourceType];
|
|
43744
|
+
if (activeValues) {
|
|
43745
|
+
if (activeValues.has(recorded))
|
|
43746
|
+
count++;
|
|
43747
|
+
} else if (recorded === true) {
|
|
43748
|
+
count++;
|
|
43749
|
+
}
|
|
43750
|
+
continue;
|
|
43751
|
+
}
|
|
43752
|
+
if (resource.resourceType === "AWS::ElasticLoadBalancingV2::LoadBalancer") {
|
|
43753
|
+
const attrs = resource.properties?.["LoadBalancerAttributes"] ?? resource.observedProperties?.["LoadBalancerAttributes"];
|
|
43754
|
+
const enabled = attrs?.find((a) => a?.Key === "deletion_protection.enabled");
|
|
43755
|
+
if (enabled?.Value === "true")
|
|
43756
|
+
count++;
|
|
43757
|
+
}
|
|
43758
|
+
}
|
|
43759
|
+
return count;
|
|
43760
|
+
}
|
|
42940
43761
|
async function runDestroyForStack(stackName, state, ctx) {
|
|
42941
43762
|
const logger = getLogger();
|
|
42942
43763
|
const result = {
|
|
@@ -42960,18 +43781,25 @@ Resources to be deleted (${resourceCount}):`);
|
|
|
42960
43781
|
for (const [logicalId, resource] of Object.entries(state.resources)) {
|
|
42961
43782
|
logger.info(` - ${logicalId} (${resource.resourceType})`);
|
|
42962
43783
|
}
|
|
43784
|
+
const protectedCount = ctx.removeProtection ? countProtectedResources(state) : 0;
|
|
42963
43785
|
if (!ctx.skipConfirmation) {
|
|
42964
43786
|
const rl = readline2.createInterface({
|
|
42965
43787
|
input: process.stdin,
|
|
42966
43788
|
output: process.stdout
|
|
42967
43789
|
});
|
|
42968
|
-
const
|
|
42969
|
-
|
|
42970
|
-
Are you sure you want to destroy stack "${stackName}" and delete all ${resourceCount} resources? (Y/n):
|
|
42971
|
-
);
|
|
43790
|
+
const prompt = ctx.removeProtection ? `
|
|
43791
|
+
About to destroy ${resourceCount} resources from stack "${stackName}", REMOVING DELETION PROTECTION on ${protectedCount} of them. Continue? (y/N): ` : `
|
|
43792
|
+
Are you sure you want to destroy stack "${stackName}" and delete all ${resourceCount} resources? (Y/n): `;
|
|
43793
|
+
const answer = await rl.question(prompt);
|
|
42972
43794
|
rl.close();
|
|
42973
43795
|
const trimmed = answer.trim().toLowerCase();
|
|
42974
|
-
if (
|
|
43796
|
+
if (ctx.removeProtection) {
|
|
43797
|
+
if (trimmed !== "y" && trimmed !== "yes") {
|
|
43798
|
+
logger.info("Destroy cancelled");
|
|
43799
|
+
result.cancelled = true;
|
|
43800
|
+
return result;
|
|
43801
|
+
}
|
|
43802
|
+
} else if (trimmed === "n" || trimmed === "no") {
|
|
42975
43803
|
logger.info("Destroy cancelled");
|
|
42976
43804
|
result.cancelled = true;
|
|
42977
43805
|
return result;
|
|
@@ -43078,7 +43906,11 @@ Acquiring lock for stack ${stackName}...`);
|
|
|
43078
43906
|
logicalId,
|
|
43079
43907
|
resource.physicalId,
|
|
43080
43908
|
resource.resourceType,
|
|
43081
|
-
resource.properties
|
|
43909
|
+
resource.properties,
|
|
43910
|
+
{
|
|
43911
|
+
...state.region !== void 0 && { expectedRegion: state.region },
|
|
43912
|
+
...ctx.removeProtection === true && { removeProtection: true }
|
|
43913
|
+
}
|
|
43082
43914
|
);
|
|
43083
43915
|
lastDeleteError = null;
|
|
43084
43916
|
break;
|
|
@@ -43237,7 +44069,10 @@ async function destroyCommand(stackArgs, options) {
|
|
|
43237
44069
|
appStacks = result.stacks.map((s) => ({
|
|
43238
44070
|
stackName: s.stackName,
|
|
43239
44071
|
displayName: s.displayName,
|
|
43240
|
-
...s.region && { region: s.region }
|
|
44072
|
+
...s.region && { region: s.region },
|
|
44073
|
+
...s.terminationProtection !== void 0 && {
|
|
44074
|
+
terminationProtection: s.terminationProtection
|
|
44075
|
+
}
|
|
43241
44076
|
}));
|
|
43242
44077
|
} catch {
|
|
43243
44078
|
logger.debug("Could not synthesize app, falling back to state-based stack list");
|
|
@@ -43296,6 +44131,18 @@ Preparing to destroy stack: ${stackName}`);
|
|
|
43296
44131
|
const refs = stateRefsByName.get(stackName) ?? [];
|
|
43297
44132
|
const synthStack = appStacks.find((s) => s.stackName === stackName);
|
|
43298
44133
|
const synthRegion = synthStack?.region;
|
|
44134
|
+
if (synthStack?.terminationProtection === true) {
|
|
44135
|
+
if (options.removeProtection) {
|
|
44136
|
+
logger.warn(
|
|
44137
|
+
`Stack ${stackName} has terminationProtection: true \u2014 bypassing because --remove-protection set`
|
|
44138
|
+
);
|
|
44139
|
+
} else {
|
|
44140
|
+
const err = new StackTerminationProtectionError(stackName);
|
|
44141
|
+
logger.error(` \u2717 ${err.message}`);
|
|
44142
|
+
totalErrors++;
|
|
44143
|
+
continue;
|
|
44144
|
+
}
|
|
44145
|
+
}
|
|
43299
44146
|
let stackTargetRegion;
|
|
43300
44147
|
if (refs.length === 0) {
|
|
43301
44148
|
logger.warn(`No state found for stack ${stackName}, skipping`);
|
|
@@ -43329,6 +44176,7 @@ Preparing to destroy stack: ${stackName}`);
|
|
|
43329
44176
|
...options.profile && { profile: options.profile },
|
|
43330
44177
|
stateBucket,
|
|
43331
44178
|
skipConfirmation: options.yes || options.force,
|
|
44179
|
+
removeProtection: options.removeProtection === true,
|
|
43332
44180
|
...options.resourceWarnAfter?.globalMs !== void 0 && {
|
|
43333
44181
|
resourceWarnAfterMs: options.resourceWarnAfter.globalMs
|
|
43334
44182
|
},
|
|
@@ -45166,6 +46014,7 @@ Preparing to destroy stack: ${stackName}${ref.region ? ` (${ref.region})` : ""}`
|
|
|
45166
46014
|
// skipped when `options.yes` is set OR `--all` was set (the user
|
|
45167
46015
|
// already accepted the batch prompt).
|
|
45168
46016
|
skipConfirmation: options.yes || options.all === true,
|
|
46017
|
+
removeProtection: options.removeProtection === true,
|
|
45169
46018
|
...options.resourceWarnAfter?.globalMs !== void 0 && {
|
|
45170
46019
|
resourceWarnAfterMs: options.resourceWarnAfter.globalMs
|
|
45171
46020
|
},
|
|
@@ -45194,7 +46043,11 @@ Preparing to destroy stack: ${stackName}${ref.region ? ` (${ref.region})` : ""}`
|
|
|
45194
46043
|
function createStateDestroyCommand() {
|
|
45195
46044
|
const cmd = new Command12("destroy").description(
|
|
45196
46045
|
"Destroy a stack's AWS resources and remove its state record without requiring the CDK app. For removing only the state record (keeping AWS resources intact), use 'cdkd state orphan'."
|
|
45197
|
-
).argument("[stacks...]", "Stack name(s) to destroy (physical CloudFormation names)").option("--all", "Destroy every stack in the state bucket", false).
|
|
46046
|
+
).argument("[stacks...]", "Stack name(s) to destroy (physical CloudFormation names)").option("--all", "Destroy every stack in the state bucket", false).option(
|
|
46047
|
+
"--remove-protection",
|
|
46048
|
+
"Bypass deletion protection on protected resources by flipping the per-resource protection flag off in-place before delete. Covers AWS::Logs::LogGroup, AWS::RDS::DBInstance, AWS::RDS::DBCluster, AWS::DynamoDB::Table, AWS::EC2::Instance, and AWS::ElasticLoadBalancingV2::LoadBalancer.",
|
|
46049
|
+
false
|
|
46050
|
+
).addOption(stackRegionOption2()).addHelpText(
|
|
45198
46051
|
"after",
|
|
45199
46052
|
[
|
|
45200
46053
|
"",
|
|
@@ -46334,7 +47187,7 @@ function reorderArgs(argv) {
|
|
|
46334
47187
|
}
|
|
46335
47188
|
async function main() {
|
|
46336
47189
|
const program = new Command14();
|
|
46337
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
47190
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.59.0");
|
|
46338
47191
|
program.addCommand(createBootstrapCommand());
|
|
46339
47192
|
program.addCommand(createSynthCommand());
|
|
46340
47193
|
program.addCommand(createListCommand());
|