@go-to-k/cdkd 0.275.5 → 0.276.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 +15 -0
- package/dist/{asg-provider-D6Y0zhd3.js → asg-provider-DeLTuWlm.js} +2 -2
- package/dist/{asg-provider-D6Y0zhd3.js.map → asg-provider-DeLTuWlm.js.map} +1 -1
- package/dist/cli.js +92 -34
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-rn1TQits.js → deploy-engine-j0TmnJYJ.js} +605 -21
- package/dist/deploy-engine-j0TmnJYJ.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-rn1TQits.js.map +0 -1
|
@@ -7,7 +7,7 @@ import { SQSClient } from "@aws-sdk/client-sqs";
|
|
|
7
7
|
import { PublishCommand, SNSClient } from "@aws-sdk/client-sns";
|
|
8
8
|
import { GetFunctionCommand, GetFunctionUrlConfigCommand, InvokeCommand, LambdaClient, UpdateFunctionConfigurationCommand, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
|
|
9
9
|
import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
10
|
-
import { DescribeAvailabilityZonesCommand, DescribeImagesCommand, DescribeInstancesCommand, DescribeLaunchTemplatesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVpcsCommand, DescribeVpnGatewaysCommand, EC2Client, ModifyInstanceAttributeCommand } from "@aws-sdk/client-ec2";
|
|
10
|
+
import { CreateSnapshotCommand, DescribeAvailabilityZonesCommand, DescribeImagesCommand, DescribeInstancesCommand, DescribeLaunchTemplatesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSnapshotsCommand, DescribeSubnetsCommand, DescribeVpcsCommand, DescribeVpnGatewaysCommand, EC2Client, ModifyInstanceAttributeCommand } from "@aws-sdk/client-ec2";
|
|
11
11
|
import { DescribeTableCommand, DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
12
12
|
import { CloudFormationClient, CreateChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, DescribeTypeCommand, GetTemplateCommand, waitUntilChangeSetCreateComplete } from "@aws-sdk/client-cloudformation";
|
|
13
13
|
import { APIGatewayClient, GetRestApiCommand } from "@aws-sdk/client-api-gateway";
|
|
@@ -18,6 +18,8 @@ import { CloudFrontClient, GetCloudFrontOriginAccessIdentityCommand } from "@aws
|
|
|
18
18
|
import { CloudWatchClient } from "@aws-sdk/client-cloudwatch";
|
|
19
19
|
import { CloudWatchLogsClient } from "@aws-sdk/client-cloudwatch-logs";
|
|
20
20
|
import { BedrockAgentCoreControlClient } from "@aws-sdk/client-bedrock-agentcore-control";
|
|
21
|
+
import { CreateClusterSnapshotCommand, DescribeClusterSnapshotsCommand, DescribeClustersCommand, RedshiftClient } from "@aws-sdk/client-redshift";
|
|
22
|
+
import { CreateSnapshotCommand as CreateSnapshotCommand$1, DescribeReplicationGroupsCommand, DescribeSnapshotsCommand as DescribeSnapshotsCommand$1, ElastiCacheClient } from "@aws-sdk/client-elasticache";
|
|
21
23
|
import { ACMClient } from "@aws-sdk/client-acm";
|
|
22
24
|
import { LambdaMicrovmsClient } from "@aws-sdk/client-lambda-microvms";
|
|
23
25
|
import { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -33,8 +35,6 @@ import { CreateRepositoryCommand, DescribeImagesCommand as DescribeImagesCommand
|
|
|
33
35
|
import { hostname } from "os";
|
|
34
36
|
import graphlib from "graphlib";
|
|
35
37
|
import { DescribeDBClustersCommand, DescribeDBInstancesCommand, RDSClient } from "@aws-sdk/client-rds";
|
|
36
|
-
import { DescribeReplicationGroupsCommand, ElastiCacheClient } from "@aws-sdk/client-elasticache";
|
|
37
|
-
import { DescribeClustersCommand, RedshiftClient } from "@aws-sdk/client-redshift";
|
|
38
38
|
import { DescribeDomainCommand, OpenSearchClient } from "@aws-sdk/client-opensearch";
|
|
39
39
|
import { CreateWebACLCommand, DeleteWebACLCommand, GetWebACLCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$11, TagResourceCommand as TagResourceCommand$13, UntagResourceCommand as UntagResourceCommand$13, UpdateWebACLCommand, WAFNonexistentItemException, WAFV2Client } from "@aws-sdk/client-wafv2";
|
|
40
40
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
@@ -627,6 +627,8 @@ var AwsClients = class {
|
|
|
627
627
|
cloudWatchClient;
|
|
628
628
|
cloudWatchLogsClient;
|
|
629
629
|
bedrockAgentCoreControlClient;
|
|
630
|
+
redshiftClient;
|
|
631
|
+
elastiCacheClient;
|
|
630
632
|
acmClient;
|
|
631
633
|
lambdaMicrovmsClient;
|
|
632
634
|
config;
|
|
@@ -935,6 +937,32 @@ var AwsClients = class {
|
|
|
935
937
|
return this.getBedrockAgentCoreControlClient();
|
|
936
938
|
}
|
|
937
939
|
/**
|
|
940
|
+
* Get Redshift client
|
|
941
|
+
*/
|
|
942
|
+
getRedshiftClient() {
|
|
943
|
+
if (!this.redshiftClient) this.redshiftClient = new RedshiftClient({ ...this.clientOptions });
|
|
944
|
+
return this.redshiftClient;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Convenience getter for Redshift client
|
|
948
|
+
*/
|
|
949
|
+
get redshift() {
|
|
950
|
+
return this.getRedshiftClient();
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Get ElastiCache client
|
|
954
|
+
*/
|
|
955
|
+
getElastiCacheClient() {
|
|
956
|
+
if (!this.elastiCacheClient) this.elastiCacheClient = new ElastiCacheClient({ ...this.clientOptions });
|
|
957
|
+
return this.elastiCacheClient;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Convenience getter for ElastiCache client
|
|
961
|
+
*/
|
|
962
|
+
get elastiCache() {
|
|
963
|
+
return this.getElastiCacheClient();
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
938
966
|
* Destroy all clients
|
|
939
967
|
*/
|
|
940
968
|
destroy() {
|
|
@@ -956,6 +984,8 @@ var AwsClients = class {
|
|
|
956
984
|
this.cloudWatchClient?.destroy();
|
|
957
985
|
this.cloudWatchLogsClient?.destroy();
|
|
958
986
|
this.bedrockAgentCoreControlClient?.destroy();
|
|
987
|
+
this.redshiftClient?.destroy();
|
|
988
|
+
this.elastiCacheClient?.destroy();
|
|
959
989
|
this.acmClient?.destroy();
|
|
960
990
|
this.lambdaMicrovmsClient?.destroy();
|
|
961
991
|
}
|
|
@@ -7769,7 +7799,8 @@ const OTHER_TRANSIENT_ERROR_MESSAGE_PATTERNS = [
|
|
|
7769
7799
|
"wait 60 seconds",
|
|
7770
7800
|
"concurrent update operation",
|
|
7771
7801
|
"because it is in use",
|
|
7772
|
-
"Rate exceeded"
|
|
7802
|
+
"Rate exceeded",
|
|
7803
|
+
"There is an operation running on the Cluster"
|
|
7773
7804
|
];
|
|
7774
7805
|
/**
|
|
7775
7806
|
* Patterns that mark an AWS error as a transient/retryable failure.
|
|
@@ -12038,9 +12069,10 @@ var CloudControlProvider = class {
|
|
|
12038
12069
|
*/
|
|
12039
12070
|
async delete(logicalId, physicalId, resourceType, _properties, context) {
|
|
12040
12071
|
this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
|
|
12072
|
+
if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
|
|
12041
12073
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
12042
12074
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
12043
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
12075
|
+
const { ASGProvider } = await import("./asg-provider-DeLTuWlm.js").then((n) => n.n);
|
|
12044
12076
|
await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
12045
12077
|
return;
|
|
12046
12078
|
}
|
|
@@ -15101,7 +15133,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
15101
15133
|
["DBClusterParameterGroupName", "not yet implemented by cdkd"],
|
|
15102
15134
|
["DBInstanceParameterGroupName", "not yet implemented by cdkd"],
|
|
15103
15135
|
["DBSystemId", "not yet implemented by cdkd"],
|
|
15104
|
-
["DeleteAutomatedBackups", "
|
|
15136
|
+
["DeleteAutomatedBackups", "delete-time lifecycle flag not threaded through cdkd destroy; final snapshots are governed by DeletionPolicy: Snapshot (issue #1352), which this flag does not control"],
|
|
15105
15137
|
["Domain", "not yet implemented by cdkd"],
|
|
15106
15138
|
["DomainIAMRoleName", "not yet implemented by cdkd"],
|
|
15107
15139
|
["EnableCloudwatchLogsExports", "not yet implemented by cdkd"],
|
|
@@ -15180,7 +15212,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
15180
15212
|
["DBSnapshotIdentifier", "not yet implemented by cdkd"],
|
|
15181
15213
|
["DBSystemId", "not yet implemented by cdkd"],
|
|
15182
15214
|
["DedicatedLogVolume", "not yet implemented by cdkd"],
|
|
15183
|
-
["DeleteAutomatedBackups", "
|
|
15215
|
+
["DeleteAutomatedBackups", "delete-time lifecycle flag not threaded through cdkd destroy; final snapshots are governed by DeletionPolicy: Snapshot (issue #1352), which this flag does not control"],
|
|
15184
15216
|
["Domain", "not yet implemented by cdkd"],
|
|
15185
15217
|
["DomainAuthSecretArn", "not yet implemented by cdkd"],
|
|
15186
15218
|
["DomainDnsIps", "not yet implemented by cdkd"],
|
|
@@ -17030,6 +17062,478 @@ function extractDeploymentEventError(err) {
|
|
|
17030
17062
|
return result;
|
|
17031
17063
|
}
|
|
17032
17064
|
|
|
17065
|
+
//#endregion
|
|
17066
|
+
//#region src/provisioning/final-snapshot.ts
|
|
17067
|
+
/**
|
|
17068
|
+
* `DeletionPolicy: Snapshot` support (issues #1352 / #1353).
|
|
17069
|
+
*
|
|
17070
|
+
* CloudFormation creates a final snapshot BEFORE deleting a resource whose
|
|
17071
|
+
* `DeletionPolicy` is `Snapshot`. cdkd historically treated the policy as
|
|
17072
|
+
* `Delete` (no snapshot — silent data loss vs CFn, live-A/B-confirmed on
|
|
17073
|
+
* `AWS::EC2::Volume`). The delete paths now honor the policy through the
|
|
17074
|
+
* two mechanisms below; a Snapshot-tagged type supported by NEITHER (or one
|
|
17075
|
+
* routed via Cloud Control, which has no snapshot notion) is refused with an
|
|
17076
|
+
* actionable error unless `--skip-final-snapshot` opts into the data loss.
|
|
17077
|
+
*
|
|
17078
|
+
* The full CFn-documented Snapshot-capable type list (CFn refuses the
|
|
17079
|
+
* attribute on anything else): AWS::EC2::Volume, AWS::ElastiCache::CacheCluster,
|
|
17080
|
+
* AWS::ElastiCache::ReplicationGroup, AWS::Neptune::DBCluster,
|
|
17081
|
+
* AWS::RDS::DBCluster, AWS::RDS::DBInstance, AWS::Redshift::Cluster,
|
|
17082
|
+
* AWS::DocDB::DBCluster — all covered by one of the two mechanisms.
|
|
17083
|
+
*/
|
|
17084
|
+
/**
|
|
17085
|
+
* Types whose delete API takes an atomic final-snapshot parameter. The
|
|
17086
|
+
* destroy paths generate a snapshot identifier and pass it via
|
|
17087
|
+
* `DeleteContext.finalSnapshotIdentifier`; the type's SDK provider flips its
|
|
17088
|
+
* delete call from `SkipFinalSnapshot: true` to the final-snapshot form.
|
|
17089
|
+
*/
|
|
17090
|
+
const ATOMIC_FINAL_SNAPSHOT_TYPES = /* @__PURE__ */ new Set([
|
|
17091
|
+
"AWS::RDS::DBInstance",
|
|
17092
|
+
"AWS::RDS::DBCluster",
|
|
17093
|
+
"AWS::Neptune::DBCluster",
|
|
17094
|
+
"AWS::DocDB::DBCluster",
|
|
17095
|
+
"AWS::ElastiCache::CacheCluster"
|
|
17096
|
+
]);
|
|
17097
|
+
/**
|
|
17098
|
+
* Types with no atomic delete parameter, where cdkd snapshots explicitly
|
|
17099
|
+
* BEFORE the (routing-layer-agnostic) delete via
|
|
17100
|
+
* {@link createPreDeleteFinalSnapshot}. All three are CC-API-routed (no SDK
|
|
17101
|
+
* provider), so the pre-delete snapshot lives at the delete call sites, not
|
|
17102
|
+
* in a provider: `AWS::EC2::Volume` (EC2 `CreateSnapshot`, issue #1352),
|
|
17103
|
+
* `AWS::Redshift::Cluster` (`CreateClusterSnapshot`, issue #1353), and
|
|
17104
|
+
* `AWS::ElastiCache::ReplicationGroup` (ElastiCache `CreateSnapshot`
|
|
17105
|
+
* against the replication group, issue #1353).
|
|
17106
|
+
*/
|
|
17107
|
+
const PRE_DELETE_SNAPSHOT_TYPES = /* @__PURE__ */ new Set([
|
|
17108
|
+
"AWS::EC2::Volume",
|
|
17109
|
+
"AWS::Redshift::Cluster",
|
|
17110
|
+
"AWS::ElastiCache::ReplicationGroup"
|
|
17111
|
+
]);
|
|
17112
|
+
/** Sanitize a physical id into the snapshot-identifier charset (shared by the
|
|
17113
|
+
* identifier builder and the pre-delete reuse-probe prefix match). */
|
|
17114
|
+
function sanitizeSnapshotBase(physicalId) {
|
|
17115
|
+
let base = physicalId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
17116
|
+
if (!/^[a-z]/.test(base)) base = `r${base}`;
|
|
17117
|
+
return base;
|
|
17118
|
+
}
|
|
17119
|
+
/**
|
|
17120
|
+
* The `<sanitized-physical-id>-final-` prefix every cdkd final snapshot of a
|
|
17121
|
+
* given source shares — the reuse-probe key for the name-keyed snapshot APIs
|
|
17122
|
+
* (Redshift / ElastiCache), the analogue of the EBS tag. Applies the same
|
|
17123
|
+
* per-service length cap as {@link buildFinalSnapshotIdentifier} so a
|
|
17124
|
+
* truncated generated name still matches its own prefix.
|
|
17125
|
+
*/
|
|
17126
|
+
function finalSnapshotNamePrefix(physicalId, resourceType) {
|
|
17127
|
+
const maxLength = resourceType === "AWS::ElastiCache::CacheCluster" || resourceType === "AWS::ElastiCache::ReplicationGroup" ? 50 : 255;
|
|
17128
|
+
return `${sanitizeSnapshotBase(physicalId).slice(0, maxLength - 22).replace(/-+$/, "")}-final-`;
|
|
17129
|
+
}
|
|
17130
|
+
/**
|
|
17131
|
+
* Build the final-snapshot identifier for a snapshot-creating delete.
|
|
17132
|
+
*
|
|
17133
|
+
* Constraints are the intersection of the RDS / Neptune / DocDB snapshot
|
|
17134
|
+
* identifier rules and the ElastiCache snapshot name rules: start with a
|
|
17135
|
+
* letter, letters/digits/hyphens only, no consecutive or trailing hyphen.
|
|
17136
|
+
* The physical id already satisfies the source API's naming rules; it is
|
|
17137
|
+
* lowercased (RDS canonicalizes to lowercase anyway) and defensively
|
|
17138
|
+
* re-sanitized. A UTC timestamp suffix keeps repeated deletes of a
|
|
17139
|
+
* re-created same-name resource from colliding; within ONE run the
|
|
17140
|
+
* identifier is generated once per resource, so delete retries reuse it.
|
|
17141
|
+
*/
|
|
17142
|
+
function buildFinalSnapshotIdentifier(physicalId, resourceType, now = /* @__PURE__ */ new Date()) {
|
|
17143
|
+
const ts = now.toISOString().replace(/[-:]/g, "").replace(/\..*$/, "").replace("T", "-").toLowerCase();
|
|
17144
|
+
return `${finalSnapshotNamePrefix(physicalId, resourceType)}${ts}`;
|
|
17145
|
+
}
|
|
17146
|
+
/** Tag key marking a cdkd-created final snapshot of an EBS volume. */
|
|
17147
|
+
const EBS_FINAL_SNAPSHOT_TAG_KEY = "cdkd:final-snapshot-of";
|
|
17148
|
+
/** Test seam (matches the `describe-type.ts` / macro-expander pattern). */
|
|
17149
|
+
const finalSnapshotDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
|
|
17150
|
+
const PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS = 5e3;
|
|
17151
|
+
const PRE_DELETE_SNAPSHOT_TIMEOUT_MS = 3600 * 1e3;
|
|
17152
|
+
/** Post-snapshot settle budget for a Redshift cluster before its delete. */
|
|
17153
|
+
const REDSHIFT_CLUSTER_SETTLE_TIMEOUT_MS = 1200 * 1e3;
|
|
17154
|
+
function errMsg(error) {
|
|
17155
|
+
return error instanceof Error ? error.message : String(error);
|
|
17156
|
+
}
|
|
17157
|
+
function isVolumeNotFound(error) {
|
|
17158
|
+
return (error instanceof Error ? error.name : "") === "InvalidVolume.NotFound" || errMsg(error).includes("InvalidVolume.NotFound");
|
|
17159
|
+
}
|
|
17160
|
+
/**
|
|
17161
|
+
* Create (or resume waiting on) the final EBS snapshot of a volume and wait
|
|
17162
|
+
* until it completes, mirroring CloudFormation's observable behavior — CFn
|
|
17163
|
+
* deletes the volume only after the final snapshot is done.
|
|
17164
|
+
*
|
|
17165
|
+
* Idempotent across destroy re-runs: an existing pending/completed snapshot
|
|
17166
|
+
* carrying the `cdkd:final-snapshot-of: <volumeId>` tag is reused instead of
|
|
17167
|
+
* creating a second one. Reusing a `completed` snapshot is safe HERE (and
|
|
17168
|
+
* only here) because an EBS volume id is AWS-generated and never reused, so
|
|
17169
|
+
* the tag can only ever point at THIS volume's data — unlike the name-keyed
|
|
17170
|
+
* Redshift / ElastiCache APIs, whose user-chosen ids repeat across
|
|
17171
|
+
* generations and therefore only resume an in-flight snapshot (see
|
|
17172
|
+
* {@link RESUMABLE_SNAPSHOT_STATUS}).
|
|
17173
|
+
*
|
|
17174
|
+
* @returns the snapshot id, or `null` when the volume no longer exists
|
|
17175
|
+
* (nothing to snapshot; the subsequent delete is idempotent too).
|
|
17176
|
+
*/
|
|
17177
|
+
async function createEbsFinalSnapshot(client, volumeId, logicalId, logger) {
|
|
17178
|
+
let snapshotId;
|
|
17179
|
+
try {
|
|
17180
|
+
snapshotId = (await client.send(new DescribeSnapshotsCommand({
|
|
17181
|
+
OwnerIds: ["self"],
|
|
17182
|
+
Filters: [{
|
|
17183
|
+
Name: `tag:${EBS_FINAL_SNAPSHOT_TAG_KEY}`,
|
|
17184
|
+
Values: [volumeId]
|
|
17185
|
+
}, {
|
|
17186
|
+
Name: "status",
|
|
17187
|
+
Values: ["pending", "completed"]
|
|
17188
|
+
}]
|
|
17189
|
+
}))).Snapshots?.[0]?.SnapshotId;
|
|
17190
|
+
if (snapshotId) logger.debug(`Reusing existing final snapshot ${snapshotId} for ${logicalId} (${volumeId}) from a previous destroy attempt`);
|
|
17191
|
+
} catch (probeError) {
|
|
17192
|
+
logger.debug(`Final-snapshot reuse probe failed for ${volumeId}: ${errMsg(probeError)}`);
|
|
17193
|
+
}
|
|
17194
|
+
if (!snapshotId) try {
|
|
17195
|
+
snapshotId = (await client.send(new CreateSnapshotCommand({
|
|
17196
|
+
VolumeId: volumeId,
|
|
17197
|
+
Description: `cdkd final snapshot for ${volumeId} (DeletionPolicy: Snapshot)`,
|
|
17198
|
+
TagSpecifications: [{
|
|
17199
|
+
ResourceType: "snapshot",
|
|
17200
|
+
Tags: [{
|
|
17201
|
+
Key: EBS_FINAL_SNAPSHOT_TAG_KEY,
|
|
17202
|
+
Value: volumeId
|
|
17203
|
+
}]
|
|
17204
|
+
}]
|
|
17205
|
+
}))).SnapshotId;
|
|
17206
|
+
} catch (createError) {
|
|
17207
|
+
if (isVolumeNotFound(createError)) {
|
|
17208
|
+
logger.debug(`Volume ${volumeId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
17209
|
+
return null;
|
|
17210
|
+
}
|
|
17211
|
+
throw new CdkdError(`Failed to create the final snapshot for ${logicalId} (${volumeId}) required by DeletionPolicy: Snapshot: ${errMsg(createError)}. The volume was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
17212
|
+
}
|
|
17213
|
+
if (!snapshotId) throw new CdkdError(`CreateSnapshot for ${logicalId} (${volumeId}) returned no snapshot id. The volume was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
17214
|
+
logger.info(`Creating final snapshot ${snapshotId} for ${logicalId} (${volumeId}) — DeletionPolicy: Snapshot`);
|
|
17215
|
+
const deadline = Date.now() + PRE_DELETE_SNAPSHOT_TIMEOUT_MS;
|
|
17216
|
+
let notFoundPolls = 0;
|
|
17217
|
+
for (;;) {
|
|
17218
|
+
let state;
|
|
17219
|
+
try {
|
|
17220
|
+
state = (await client.send(new DescribeSnapshotsCommand({ SnapshotIds: [snapshotId] }))).Snapshots?.[0]?.State;
|
|
17221
|
+
notFoundPolls = 0;
|
|
17222
|
+
} catch (pollError) {
|
|
17223
|
+
if (((pollError instanceof Error ? pollError.name : "") === "InvalidSnapshot.NotFound" || errMsg(pollError).includes("InvalidSnapshot.NotFound")) && ++notFoundPolls <= 24) state = void 0;
|
|
17224
|
+
else throw new CdkdError(`Failed while waiting for final snapshot ${snapshotId} of ${logicalId} (${volumeId}): ${errMsg(pollError)}. The volume was NOT deleted; re-run the destroy (the snapshot is reused, not duplicated).`, "FINAL_SNAPSHOT_FAILED", pollError instanceof Error ? pollError : void 0);
|
|
17225
|
+
}
|
|
17226
|
+
if (state === "completed") {
|
|
17227
|
+
logger.info(`Final snapshot ${snapshotId} completed for ${logicalId} (${volumeId})`);
|
|
17228
|
+
return snapshotId;
|
|
17229
|
+
}
|
|
17230
|
+
if (state === "error") throw new CdkdError(`Final snapshot ${snapshotId} for ${logicalId} (${volumeId}) entered 'error' state. The volume was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
17231
|
+
if (Date.now() >= deadline) throw new CdkdError(`Timed out waiting for final snapshot ${snapshotId} of ${logicalId} (${volumeId}) to complete. The volume was NOT deleted; the snapshot continues in AWS — re-run the destroy once it completes (the existing snapshot is reused, not duplicated).`, "FINAL_SNAPSHOT_TIMEOUT");
|
|
17232
|
+
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
17233
|
+
}
|
|
17234
|
+
}
|
|
17235
|
+
/**
|
|
17236
|
+
* Is this one of the typed final-snapshot failures the destroy call sites
|
|
17237
|
+
* must RETHROW before applying their "not found = already deleted"
|
|
17238
|
+
* idempotency heuristics? (Those heuristics match message substrings; a raw
|
|
17239
|
+
* snapshot-poll NotFound would otherwise be read as "resource already
|
|
17240
|
+
* deleted" and strand a live, un-snapshotted volume outside state.)
|
|
17241
|
+
*/
|
|
17242
|
+
function isFinalSnapshotError(error) {
|
|
17243
|
+
return error instanceof CdkdError && (error.code === "FINAL_SNAPSHOT_FAILED" || error.code === "FINAL_SNAPSHOT_TIMEOUT" || error.code === "FINAL_SNAPSHOT_UNSUPPORTED");
|
|
17244
|
+
}
|
|
17245
|
+
/**
|
|
17246
|
+
* Refusal for an atomic-final-snapshot type whose state records
|
|
17247
|
+
* `provisionedBy: 'cc-api'` (#614 silent-drop routing / `--recreate-via-cc-api`).
|
|
17248
|
+
* Cloud Control `DeleteResource` has no final-snapshot parameter and
|
|
17249
|
+
* `CloudControlProvider` cannot honor `finalSnapshotIdentifier`, so deleting
|
|
17250
|
+
* via that route would silently drop the promised snapshot — refuse instead.
|
|
17251
|
+
*/
|
|
17252
|
+
function ccRoutedFinalSnapshotError(logicalId, resourceType, skipFlagHint) {
|
|
17253
|
+
return new CdkdError(`${logicalId} (${resourceType}) has DeletionPolicy: Snapshot, but the resource is managed via the Cloud Control API route (provisionedBy: cc-api), which has no final-snapshot delete parameter — deleting it now would destroy its data WITHOUT the final snapshot the policy promises. Create a snapshot manually (e.g. aws rds create-db-snapshot / aws elasticache create-snapshot), then re-run with ${skipFlagHint} to delete WITHOUT cdkd's final snapshot (DATA LOSS otherwise), or retain the resource (DeletionPolicy: Retain) and delete it manually.`, "FINAL_SNAPSHOT_UNSUPPORTED");
|
|
17254
|
+
}
|
|
17255
|
+
/**
|
|
17256
|
+
* Build the refusal error for a Snapshot-tagged type cdkd cannot snapshot.
|
|
17257
|
+
* Thrown by the delete call sites BEFORE any delete is issued. Since #1353
|
|
17258
|
+
* every CFn-documented Snapshot-capable type is covered, so this only fires
|
|
17259
|
+
* for a type CloudFormation itself would refuse the attribute on (hand-
|
|
17260
|
+
* edited templates / imported state).
|
|
17261
|
+
*/
|
|
17262
|
+
function unsupportedFinalSnapshotError(logicalId, resourceType, skipFlagHint) {
|
|
17263
|
+
return new CdkdError(`${logicalId} (${resourceType}) has DeletionPolicy: Snapshot, but cdkd does not implement final snapshots for this type (CloudFormation itself only supports Snapshot on: EC2 Volume, ElastiCache CacheCluster / ReplicationGroup, Neptune / RDS / DocDB clusters, RDS instances, Redshift clusters). Deleting it now would destroy its data WITHOUT the final snapshot the policy promises. Re-run with ${skipFlagHint} to delete WITHOUT a final snapshot (DATA LOSS), or retain the resource (DeletionPolicy: Retain) and delete it manually after snapshotting.`, "FINAL_SNAPSHOT_UNSUPPORTED");
|
|
17264
|
+
}
|
|
17265
|
+
/**
|
|
17266
|
+
* Dispatch the pre-delete final snapshot for a `PRE_DELETE_SNAPSHOT_TYPES`
|
|
17267
|
+
* member: create (or resume) the snapshot and wait until it is ready, so the
|
|
17268
|
+
* caller's subsequent (routing-layer-agnostic) delete cannot lose data.
|
|
17269
|
+
*
|
|
17270
|
+
* @returns the snapshot id, or `null` when the source resource no longer
|
|
17271
|
+
* exists (nothing to snapshot; the subsequent delete is idempotent too).
|
|
17272
|
+
*/
|
|
17273
|
+
async function createPreDeleteFinalSnapshot(resourceType, physicalId, logicalId, clients, logger) {
|
|
17274
|
+
switch (resourceType) {
|
|
17275
|
+
case "AWS::EC2::Volume": return createEbsFinalSnapshot(clients.ec2, physicalId, logicalId, logger);
|
|
17276
|
+
case "AWS::Redshift::Cluster": return createRedshiftFinalSnapshot(clients.redshift, physicalId, logicalId, logger);
|
|
17277
|
+
case "AWS::ElastiCache::ReplicationGroup": return createReplicationGroupFinalSnapshot(clients.elastiCache, physicalId, logicalId, logger);
|
|
17278
|
+
default: throw new CdkdError(`No pre-delete final-snapshot implementation for ${logicalId} (${resourceType}). The resource was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
17279
|
+
}
|
|
17280
|
+
}
|
|
17281
|
+
/**
|
|
17282
|
+
* Shared wait loop for the name-keyed snapshot APIs (Redshift / ElastiCache):
|
|
17283
|
+
* poll a status getter until the ready status, tolerating a bounded window of
|
|
17284
|
+
* eventual-consistency NotFound right after creation, and wrapping every
|
|
17285
|
+
* other poll failure as a typed FINAL_SNAPSHOT error (see the EBS loop's
|
|
17286
|
+
* comment for why the wrap is load-bearing).
|
|
17287
|
+
*/
|
|
17288
|
+
async function waitForNamedSnapshot(opts) {
|
|
17289
|
+
const { snapshotId, logicalId, sourceId, noun, logger } = opts;
|
|
17290
|
+
logger.info(`${opts.reused ? "Resuming" : "Creating"} final snapshot ${snapshotId} for ${logicalId} (${sourceId}) — DeletionPolicy: Snapshot`);
|
|
17291
|
+
const deadline = Date.now() + PRE_DELETE_SNAPSHOT_TIMEOUT_MS;
|
|
17292
|
+
let notFoundPolls = 0;
|
|
17293
|
+
for (;;) {
|
|
17294
|
+
let status;
|
|
17295
|
+
try {
|
|
17296
|
+
status = await opts.pollStatus();
|
|
17297
|
+
notFoundPolls = 0;
|
|
17298
|
+
} catch (pollError) {
|
|
17299
|
+
if (opts.isTransientNotFound(pollError) && ++notFoundPolls <= 24) status = void 0;
|
|
17300
|
+
else throw new CdkdError(`Failed while waiting for final snapshot ${snapshotId} of ${logicalId} (${sourceId}): ${errMsg(pollError)}. The ${noun} was NOT deleted; re-run the delete (the snapshot is reused, not duplicated).`, "FINAL_SNAPSHOT_FAILED", pollError instanceof Error ? pollError : void 0);
|
|
17301
|
+
}
|
|
17302
|
+
if (status === opts.readyStatus) {
|
|
17303
|
+
logger.info(`Final snapshot ${snapshotId} ready for ${logicalId} (${sourceId})`);
|
|
17304
|
+
return snapshotId;
|
|
17305
|
+
}
|
|
17306
|
+
if (status !== void 0 && opts.failedStatuses.includes(status)) throw new CdkdError(`Final snapshot ${snapshotId} for ${logicalId} (${sourceId}) entered '${status}' state. The ${noun} was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
17307
|
+
if (Date.now() >= deadline) throw new CdkdError(`Timed out waiting for final snapshot ${snapshotId} of ${logicalId} (${sourceId}) to become ready. The ${noun} was NOT deleted; the snapshot continues in AWS — re-run the delete once it is ready (the existing snapshot is reused, not duplicated).`, "FINAL_SNAPSHOT_TIMEOUT");
|
|
17308
|
+
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
17309
|
+
}
|
|
17310
|
+
}
|
|
17311
|
+
function errorNameOrMessageIncludes(error, token) {
|
|
17312
|
+
return (error instanceof Error ? error.name : "") === token || errMsg(error).includes(token);
|
|
17313
|
+
}
|
|
17314
|
+
/** Max reuse-probe pages to walk (bounded so a huge snapshot history cannot
|
|
17315
|
+
* stall a delete; a miss only costs one extra snapshot, never data). */
|
|
17316
|
+
const REUSE_PROBE_MAX_PAGES = 10;
|
|
17317
|
+
/**
|
|
17318
|
+
* Matcher for a snapshot cdkd itself generated for `physicalId`: the exact
|
|
17319
|
+
* `<sanitized-id>-final-<yyyymmdd>-<hhmmss>` shape, NOT a bare prefix. A bare
|
|
17320
|
+
* prefix would also match a USER's snapshot named e.g. `mydb-final-backup`
|
|
17321
|
+
* and silently adopt it as "the" final snapshot.
|
|
17322
|
+
*/
|
|
17323
|
+
function generatedSnapshotNameMatcher(physicalId, resourceType) {
|
|
17324
|
+
const escaped = finalSnapshotNamePrefix(physicalId, resourceType).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17325
|
+
return new RegExp(`^${escaped}\\d{8}-\\d{6}$`);
|
|
17326
|
+
}
|
|
17327
|
+
/**
|
|
17328
|
+
* Is this snapshot resumable from an interrupted delete attempt?
|
|
17329
|
+
*
|
|
17330
|
+
* ONLY an in-flight (`creating`) snapshot qualifies. This is load-bearing,
|
|
17331
|
+
* not conservatism: Redshift / ElastiCache identifiers are USER-CHOSEN and
|
|
17332
|
+
* REUSABLE, and a manual snapshot outlives the resource it came from — so a
|
|
17333
|
+
* deploy -> destroy -> deploy -> destroy cycle of a fixed-name cluster would
|
|
17334
|
+
* find generation 1's `available` snapshot, skip `CreateSnapshot`, and delete
|
|
17335
|
+
* generation 2 with NO snapshot of its data. That is precisely the silent
|
|
17336
|
+
* data loss this whole feature exists to prevent (reviewer catch).
|
|
17337
|
+
*
|
|
17338
|
+
* A `creating` snapshot cannot span generations: the new generation can only
|
|
17339
|
+
* be created after the old resource is gone, by which time its snapshot has
|
|
17340
|
+
* settled to `available`. The cost of the narrower rule is one redundant
|
|
17341
|
+
* snapshot when a delete is re-run after its snapshot already completed —
|
|
17342
|
+
* cost, never data loss, and the timestamped identifier never collides.
|
|
17343
|
+
*/
|
|
17344
|
+
const RESUMABLE_SNAPSHOT_STATUS = "creating";
|
|
17345
|
+
/**
|
|
17346
|
+
* Redshift `CreateClusterSnapshot` + wait to `available` (issue #1353).
|
|
17347
|
+
* Idempotent across delete re-runs: a creating/available manual snapshot of
|
|
17348
|
+
* this cluster whose identifier carries cdkd's `<cluster>-final-` prefix is
|
|
17349
|
+
* reused instead of creating a second one.
|
|
17350
|
+
*/
|
|
17351
|
+
async function createRedshiftFinalSnapshot(client, clusterId, logicalId, logger) {
|
|
17352
|
+
const matches = generatedSnapshotNameMatcher(clusterId, "AWS::Redshift::Cluster");
|
|
17353
|
+
let snapshotId;
|
|
17354
|
+
try {
|
|
17355
|
+
let marker;
|
|
17356
|
+
for (let page = 0; page < REUSE_PROBE_MAX_PAGES && !snapshotId; page++) {
|
|
17357
|
+
const existing = await client.send(new DescribeClusterSnapshotsCommand({
|
|
17358
|
+
ClusterIdentifier: clusterId,
|
|
17359
|
+
SnapshotType: "manual",
|
|
17360
|
+
...marker !== void 0 && { Marker: marker }
|
|
17361
|
+
}));
|
|
17362
|
+
snapshotId = existing.Snapshots?.find((s) => s.SnapshotIdentifier !== void 0 && matches.test(s.SnapshotIdentifier) && s.Status === RESUMABLE_SNAPSHOT_STATUS)?.SnapshotIdentifier;
|
|
17363
|
+
marker = existing.Marker;
|
|
17364
|
+
if (marker === void 0) break;
|
|
17365
|
+
}
|
|
17366
|
+
if (snapshotId) logger.debug(`Resuming in-flight final snapshot ${snapshotId} for ${logicalId} (${clusterId}) from a previous delete attempt`);
|
|
17367
|
+
} catch (probeError) {
|
|
17368
|
+
logger.debug(`Final-snapshot reuse probe failed for ${clusterId}: ${errMsg(probeError)}`);
|
|
17369
|
+
}
|
|
17370
|
+
const reused = snapshotId !== void 0;
|
|
17371
|
+
if (!snapshotId) {
|
|
17372
|
+
const newId = buildFinalSnapshotIdentifier(clusterId, "AWS::Redshift::Cluster");
|
|
17373
|
+
try {
|
|
17374
|
+
await client.send(new CreateClusterSnapshotCommand({
|
|
17375
|
+
SnapshotIdentifier: newId,
|
|
17376
|
+
ClusterIdentifier: clusterId
|
|
17377
|
+
}));
|
|
17378
|
+
snapshotId = newId;
|
|
17379
|
+
} catch (createError) {
|
|
17380
|
+
if (errorNameOrMessageIncludes(createError, "ClusterNotFoundFault")) {
|
|
17381
|
+
logger.debug(`Redshift cluster ${clusterId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
17382
|
+
return null;
|
|
17383
|
+
}
|
|
17384
|
+
if (errorNameOrMessageIncludes(createError, "InvalidClusterStateFault")) {
|
|
17385
|
+
logger.info(`Redshift cluster ${clusterId} is not in a snapshottable state (likely already deleting) — skipping the final snapshot for ${logicalId}: ${errMsg(createError)}`);
|
|
17386
|
+
return null;
|
|
17387
|
+
}
|
|
17388
|
+
throw new CdkdError(`Failed to create the final snapshot for ${logicalId} (${clusterId}) required by DeletionPolicy: Snapshot: ${errMsg(createError)}. The cluster was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
17389
|
+
}
|
|
17390
|
+
}
|
|
17391
|
+
const readySnapshotId = await waitForNamedSnapshot({
|
|
17392
|
+
snapshotId,
|
|
17393
|
+
logicalId,
|
|
17394
|
+
sourceId: clusterId,
|
|
17395
|
+
noun: "cluster",
|
|
17396
|
+
reused,
|
|
17397
|
+
readyStatus: "available",
|
|
17398
|
+
failedStatuses: [
|
|
17399
|
+
"failed",
|
|
17400
|
+
"cancelled",
|
|
17401
|
+
"deleted"
|
|
17402
|
+
],
|
|
17403
|
+
pollStatus: async () => {
|
|
17404
|
+
return (await client.send(new DescribeClusterSnapshotsCommand({ SnapshotIdentifier: snapshotId }))).Snapshots?.[0]?.Status;
|
|
17405
|
+
},
|
|
17406
|
+
isTransientNotFound: (e) => errorNameOrMessageIncludes(e, "ClusterSnapshotNotFoundFault"),
|
|
17407
|
+
logger
|
|
17408
|
+
});
|
|
17409
|
+
await waitForRedshiftClusterSettled(client, clusterId, logicalId, logger);
|
|
17410
|
+
return readySnapshotId;
|
|
17411
|
+
}
|
|
17412
|
+
/**
|
|
17413
|
+
* Poll a Redshift cluster until it is out of the transient state a just-taken
|
|
17414
|
+
* snapshot leaves it in, so the caller's delete is not rejected. Bounded and
|
|
17415
|
+
* NEVER fatal: a NotFound means the cluster is already gone, and exhausting
|
|
17416
|
+
* the budget only logs — the delete (and its own retry) still runs.
|
|
17417
|
+
*/
|
|
17418
|
+
async function waitForRedshiftClusterSettled(client, clusterId, logicalId, logger) {
|
|
17419
|
+
const deadline = Date.now() + REDSHIFT_CLUSTER_SETTLE_TIMEOUT_MS;
|
|
17420
|
+
for (;;) {
|
|
17421
|
+
let status;
|
|
17422
|
+
try {
|
|
17423
|
+
status = (await client.send(new DescribeClustersCommand({ ClusterIdentifier: clusterId }))).Clusters?.[0]?.ClusterStatus;
|
|
17424
|
+
} catch (pollError) {
|
|
17425
|
+
if (errorNameOrMessageIncludes(pollError, "ClusterNotFoundFault")) return;
|
|
17426
|
+
logger.debug(`Could not read Redshift cluster ${clusterId} while waiting for it to settle: ${errMsg(pollError)}`);
|
|
17427
|
+
return;
|
|
17428
|
+
}
|
|
17429
|
+
if (status === void 0 || status === "available") {
|
|
17430
|
+
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
17431
|
+
return;
|
|
17432
|
+
}
|
|
17433
|
+
if (Date.now() >= deadline) {
|
|
17434
|
+
logger.info(`Redshift cluster ${clusterId} (${logicalId}) is still '${status}' after the post-snapshot settle wait — proceeding with the delete anyway.`);
|
|
17435
|
+
return;
|
|
17436
|
+
}
|
|
17437
|
+
logger.debug(`Waiting for Redshift cluster ${clusterId} to settle (status: ${status})`);
|
|
17438
|
+
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
17439
|
+
}
|
|
17440
|
+
}
|
|
17441
|
+
/**
|
|
17442
|
+
* ElastiCache final snapshot for a replication group + wait to `available`
|
|
17443
|
+
* (issue #1353), then the caller's delete proceeds.
|
|
17444
|
+
*
|
|
17445
|
+
* **Cluster-mode matters** (found live 2026-08-03, not by the mocked unit
|
|
17446
|
+
* tests): `CreateSnapshot` accepts `ReplicationGroupId` ONLY for a
|
|
17447
|
+
* cluster-mode-ENABLED (sharded) group. For the cluster-mode-DISABLED
|
|
17448
|
+
* default — which is what a plain CDK / CFn replication group is — AWS
|
|
17449
|
+
* rejects it with "Cannot snapshot a replication group with cluster-mode
|
|
17450
|
+
* disabled. Please specify a cache cluster instead.", and the snapshot must
|
|
17451
|
+
* name the PRIMARY member cache cluster instead. Both shapes produce one
|
|
17452
|
+
* snapshot of the group's data; only the parameter differs.
|
|
17453
|
+
*
|
|
17454
|
+
* Idempotent across delete re-runs via the `<group>-final-` name prefix,
|
|
17455
|
+
* probed against whichever source this group's mode uses.
|
|
17456
|
+
*
|
|
17457
|
+
* Redis only — Memcached / snapshot-incapable node types surface AWS's
|
|
17458
|
+
* rejection wrapped, matching CloudFormation's DELETE_FAILED for the same
|
|
17459
|
+
* template.
|
|
17460
|
+
*/
|
|
17461
|
+
async function createReplicationGroupFinalSnapshot(client, replicationGroupId, logicalId, logger) {
|
|
17462
|
+
let snapshotSource;
|
|
17463
|
+
try {
|
|
17464
|
+
const group = (await client.send(new DescribeReplicationGroupsCommand({ ReplicationGroupId: replicationGroupId }))).ReplicationGroups?.[0];
|
|
17465
|
+
if (group === void 0) {
|
|
17466
|
+
logger.debug(`Replication group ${replicationGroupId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
17467
|
+
return null;
|
|
17468
|
+
}
|
|
17469
|
+
if (group.ClusterEnabled === true) snapshotSource = { ReplicationGroupId: replicationGroupId };
|
|
17470
|
+
else {
|
|
17471
|
+
const member = group.NodeGroups?.flatMap((n) => n.NodeGroupMembers ?? []).find((m) => m.CurrentRole === "primary")?.CacheClusterId ?? group.MemberClusters?.[0];
|
|
17472
|
+
if (member === void 0) throw new CdkdError(`Replication group ${replicationGroupId} (${logicalId}) has cluster-mode disabled but reports no member cache cluster to snapshot. The replication group was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
17473
|
+
snapshotSource = { CacheClusterId: member };
|
|
17474
|
+
}
|
|
17475
|
+
} catch (describeError) {
|
|
17476
|
+
if (describeError instanceof CdkdError) throw describeError;
|
|
17477
|
+
if (errorNameOrMessageIncludes(describeError, "ReplicationGroupNotFoundFault")) {
|
|
17478
|
+
logger.debug(`Replication group ${replicationGroupId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
17479
|
+
return null;
|
|
17480
|
+
}
|
|
17481
|
+
throw new CdkdError(`Failed to read replication group ${replicationGroupId} (${logicalId}) to pick the final snapshot source required by DeletionPolicy: Snapshot: ${errMsg(describeError)}. The replication group was NOT deleted.`, "FINAL_SNAPSHOT_FAILED", describeError instanceof Error ? describeError : void 0);
|
|
17482
|
+
}
|
|
17483
|
+
const matches = generatedSnapshotNameMatcher(replicationGroupId, "AWS::ElastiCache::ReplicationGroup");
|
|
17484
|
+
let snapshotId;
|
|
17485
|
+
try {
|
|
17486
|
+
let marker;
|
|
17487
|
+
for (let page = 0; page < REUSE_PROBE_MAX_PAGES && !snapshotId; page++) {
|
|
17488
|
+
const existing = await client.send(new DescribeSnapshotsCommand$1({
|
|
17489
|
+
...snapshotSource,
|
|
17490
|
+
...marker !== void 0 && { Marker: marker }
|
|
17491
|
+
}));
|
|
17492
|
+
snapshotId = existing.Snapshots?.find((s) => s.SnapshotName !== void 0 && matches.test(s.SnapshotName) && s.SnapshotStatus === RESUMABLE_SNAPSHOT_STATUS)?.SnapshotName;
|
|
17493
|
+
marker = existing.Marker;
|
|
17494
|
+
if (marker === void 0) break;
|
|
17495
|
+
}
|
|
17496
|
+
if (snapshotId) logger.debug(`Resuming in-flight final snapshot ${snapshotId} for ${logicalId} (${replicationGroupId}) from a previous delete attempt`);
|
|
17497
|
+
} catch (probeError) {
|
|
17498
|
+
logger.debug(`Final-snapshot reuse probe failed for ${replicationGroupId}: ${errMsg(probeError)}`);
|
|
17499
|
+
}
|
|
17500
|
+
const reused = snapshotId !== void 0;
|
|
17501
|
+
if (!snapshotId) {
|
|
17502
|
+
const newId = buildFinalSnapshotIdentifier(replicationGroupId, "AWS::ElastiCache::ReplicationGroup");
|
|
17503
|
+
try {
|
|
17504
|
+
await client.send(new CreateSnapshotCommand$1({
|
|
17505
|
+
...snapshotSource,
|
|
17506
|
+
SnapshotName: newId
|
|
17507
|
+
}));
|
|
17508
|
+
snapshotId = newId;
|
|
17509
|
+
} catch (createError) {
|
|
17510
|
+
if (errorNameOrMessageIncludes(createError, "ReplicationGroupNotFoundFault")) {
|
|
17511
|
+
logger.debug(`Replication group ${replicationGroupId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
17512
|
+
return null;
|
|
17513
|
+
}
|
|
17514
|
+
if (errorNameOrMessageIncludes(createError, "InvalidReplicationGroupStateFault")) {
|
|
17515
|
+
logger.info(`Replication group ${replicationGroupId} is not in a snapshottable state (likely already deleting) — skipping the final snapshot for ${logicalId}: ${errMsg(createError)}`);
|
|
17516
|
+
return null;
|
|
17517
|
+
}
|
|
17518
|
+
throw new CdkdError(`Failed to create the final snapshot for ${logicalId} (${replicationGroupId}) required by DeletionPolicy: Snapshot: ${errMsg(createError)}. The replication group was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
17519
|
+
}
|
|
17520
|
+
}
|
|
17521
|
+
return waitForNamedSnapshot({
|
|
17522
|
+
snapshotId,
|
|
17523
|
+
logicalId,
|
|
17524
|
+
sourceId: replicationGroupId,
|
|
17525
|
+
noun: "replication group",
|
|
17526
|
+
reused,
|
|
17527
|
+
readyStatus: "available",
|
|
17528
|
+
failedStatuses: ["failed"],
|
|
17529
|
+
pollStatus: async () => {
|
|
17530
|
+
return (await client.send(new DescribeSnapshotsCommand$1({ SnapshotName: snapshotId }))).Snapshots?.[0]?.SnapshotStatus;
|
|
17531
|
+
},
|
|
17532
|
+
isTransientNotFound: (e) => errorNameOrMessageIncludes(e, "SnapshotNotFoundFault"),
|
|
17533
|
+
logger
|
|
17534
|
+
});
|
|
17535
|
+
}
|
|
17536
|
+
|
|
17033
17537
|
//#endregion
|
|
17034
17538
|
//#region src/analyzer/implicit-delete-deps.ts
|
|
17035
17539
|
/**
|
|
@@ -17230,6 +17734,23 @@ async function withResourceDeadline(operation, opts) {
|
|
|
17230
17734
|
//#endregion
|
|
17231
17735
|
//#region src/deployment/rollback-executor.ts
|
|
17232
17736
|
/**
|
|
17737
|
+
* `UpdateReplacePolicy: Snapshot` on a rollback's delete-of-the-NEW-resource
|
|
17738
|
+
* (issue #1354): honor it where it costs nothing — an atomic-final-snapshot
|
|
17739
|
+
* type on the SDK route gets a generated identifier threaded into the delete
|
|
17740
|
+
* context. Every other Snapshot shape (pre-delete types, cc-api routing)
|
|
17741
|
+
* keeps the plain delete DELIBERATELY: the rollback executor's delete-new is
|
|
17742
|
+
* load-bearing for same-name re-creation (orphaning would break the revert),
|
|
17743
|
+
* it has no snapshot-client plumbing by design (registry + region only), and
|
|
17744
|
+
* the new resource was created by the deploy being reverted. Recorded as a
|
|
17745
|
+
* scope decision on issue #1354.
|
|
17746
|
+
*/
|
|
17747
|
+
function rollbackFinalSnapshotId(resourceType, record, fallbackProvisionedBy) {
|
|
17748
|
+
if (record.updateReplacePolicy !== "Snapshot") return void 0;
|
|
17749
|
+
if (!ATOMIC_FINAL_SNAPSHOT_TYPES.has(resourceType)) return void 0;
|
|
17750
|
+
if ((record.provisionedBy ?? fallbackProvisionedBy) === "cc-api") return void 0;
|
|
17751
|
+
return buildFinalSnapshotIdentifier(record.physicalId, resourceType);
|
|
17752
|
+
}
|
|
17753
|
+
/**
|
|
17233
17754
|
* Retry schedule for a re-create that must wait out a name-release delay:
|
|
17234
17755
|
* an async delete's late name release ("already exists") or the SQS 60s
|
|
17235
17756
|
* same-name cooldown (issue #1206). 2s/4s/8s then capped at 10s over 8
|
|
@@ -17480,7 +18001,13 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
17480
18001
|
resourceType: op.resourceType,
|
|
17481
18002
|
provisionedBy: current.provisionedBy ?? op.provisionedBy
|
|
17482
18003
|
});
|
|
17483
|
-
|
|
18004
|
+
{
|
|
18005
|
+
const finalSnapshotIdentifier = rollbackFinalSnapshotId(op.resourceType, current, op.provisionedBy);
|
|
18006
|
+
await newDeleteProvider.delete(op.logicalId, current.physicalId, op.resourceType, current.properties, {
|
|
18007
|
+
expectedRegion: ctx.region,
|
|
18008
|
+
...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
|
|
18009
|
+
});
|
|
18010
|
+
}
|
|
17484
18011
|
stateResources[op.logicalId] = prev;
|
|
17485
18012
|
logger.info(` Rollback: ${op.logicalId} restored to the retained old resource`);
|
|
17486
18013
|
await afterOp?.(op.logicalId);
|
|
@@ -17522,7 +18049,13 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
17522
18049
|
} catch (createError) {
|
|
17523
18050
|
if (!isNameCollisionError(createError instanceof Error ? createError.message : String(createError))) throw createError;
|
|
17524
18051
|
logger.info(` Rollback: re-create collided with the new resource's name — deleting the new resource (${current.physicalId}) first...`);
|
|
17525
|
-
|
|
18052
|
+
{
|
|
18053
|
+
const finalSnapshotIdentifier = rollbackFinalSnapshotId(op.resourceType, current, op.provisionedBy);
|
|
18054
|
+
await newDeleteProvider.delete(op.logicalId, current.physicalId, op.resourceType, current.properties, {
|
|
18055
|
+
expectedRegion: ctx.region,
|
|
18056
|
+
...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
|
|
18057
|
+
});
|
|
18058
|
+
}
|
|
17526
18059
|
deletedNewFirst = true;
|
|
17527
18060
|
delete stateResources[op.logicalId];
|
|
17528
18061
|
await afterOp?.(op.logicalId);
|
|
@@ -17553,7 +18086,11 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
17553
18086
|
};
|
|
17554
18087
|
await afterOp?.(op.logicalId);
|
|
17555
18088
|
if (!deletedNewFirst && !adoptedLiveNewResource) try {
|
|
17556
|
-
|
|
18089
|
+
const finalSnapshotIdentifier = rollbackFinalSnapshotId(op.resourceType, current, op.provisionedBy);
|
|
18090
|
+
await newDeleteProvider.delete(op.logicalId, current.physicalId, op.resourceType, current.properties, {
|
|
18091
|
+
expectedRegion: ctx.region,
|
|
18092
|
+
...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
|
|
18093
|
+
});
|
|
17557
18094
|
} catch (deleteError) {
|
|
17558
18095
|
logger.warn(` Rollback: old ${op.logicalId} re-created, but deleting the new resource (${current.physicalId}) failed: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}. Delete it manually — it is no longer tracked in state.`);
|
|
17559
18096
|
result.warnings++;
|
|
@@ -17817,7 +18354,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
17817
18354
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
17818
18355
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
17819
18356
|
function getCdkdVersion() {
|
|
17820
|
-
return "0.
|
|
18357
|
+
return "0.276.0";
|
|
17821
18358
|
}
|
|
17822
18359
|
/**
|
|
17823
18360
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -19341,17 +19878,46 @@ var DeployEngine = class {
|
|
|
19341
19878
|
throw new ProvisioningError(`Unrewritten asset reference on '${logicalId}' (${resourceType}): this region uses cdkd-owned asset storage, but the following resolved properties still point at the CDK bootstrap storage that 'cdk gc' may garbage-collect:\n${findings.map((f) => ` - ${f.path}: still references '${f.source}'`).join("\n")}\nThis is a template shape cdkd's asset-reference rewrite did not cover — deploying it would split-brain the stack (assets in cdkd storage, properties reading the CDK bucket). Please report this at https://github.com/go-to-k/cdkd/issues with the property shape. Workaround: deploy with --use-cdk-bootstrap-assets to pin the legacy destinations for this app.`, resourceType, logicalId);
|
|
19342
19879
|
}
|
|
19343
19880
|
/**
|
|
19881
|
+
* The `Snapshot`-policy gate every engine delete site runs BEFORE its
|
|
19882
|
+
* delete (issues #1352 / #1353 / #1354). Given the resource's effective
|
|
19883
|
+
* policy for THIS delete (`DeletionPolicy` on the destroy / removal paths,
|
|
19884
|
+
* `UpdateReplacePolicy` on the replacement paths):
|
|
19885
|
+
*
|
|
19886
|
+
* - not `Snapshot` (or `--skip-final-snapshot`) → no-op.
|
|
19887
|
+
* - atomic type, SDK-routed → returns the generated identifier for the
|
|
19888
|
+
* provider's atomic final-snapshot delete parameter.
|
|
19889
|
+
* - atomic type, cc-api-routed → refuses (Cloud Control has no
|
|
19890
|
+
* final-snapshot parameter; `CloudControlProvider.delete` also
|
|
19891
|
+
* fail-closes on the context field as defense-in-depth).
|
|
19892
|
+
* - `PRE_DELETE_SNAPSHOT_TYPES` (EC2 Volume / Redshift Cluster /
|
|
19893
|
+
* ElastiCache ReplicationGroup) → creates the snapshot and waits for
|
|
19894
|
+
* it here, then returns undefined (the subsequent delete is plain).
|
|
19895
|
+
* - anything else Snapshot-tagged → refuses.
|
|
19896
|
+
*/
|
|
19897
|
+
async prepareFinalSnapshotForDelete(logicalId, resourceType, currentResource, policy) {
|
|
19898
|
+
if (policy !== "Snapshot" || this.options.skipFinalSnapshot === true) return void 0;
|
|
19899
|
+
if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resourceType) && currentResource.provisionedBy !== "cc-api") return buildFinalSnapshotIdentifier(currentResource.physicalId, resourceType);
|
|
19900
|
+
if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resourceType)) throw ccRoutedFinalSnapshotError(logicalId, resourceType, "--skip-final-snapshot");
|
|
19901
|
+
if (PRE_DELETE_SNAPSHOT_TYPES.has(resourceType)) {
|
|
19902
|
+
await createPreDeleteFinalSnapshot(resourceType, currentResource.physicalId, logicalId, this.options.finalSnapshotClients ?? getAwsClients(), this.logger);
|
|
19903
|
+
return;
|
|
19904
|
+
}
|
|
19905
|
+
throw unsupportedFinalSnapshotError(logicalId, resourceType, "--skip-final-snapshot");
|
|
19906
|
+
}
|
|
19907
|
+
/**
|
|
19344
19908
|
* `--replace` delete-first fallback for a property-driven replacement of a
|
|
19345
19909
|
* custom-named resource: delete the old name holder, then re-create it
|
|
19346
19910
|
* under the same name. Shared by the create-first collision catch (issue
|
|
19347
19911
|
* #960 follow-up) and the name-idempotent same-id guard (issue #1238) so
|
|
19348
19912
|
* the two --replace escape hatches cannot drift apart.
|
|
19349
19913
|
*/
|
|
19350
|
-
async replaceDeleteFirstAndRecreate(logicalId, resourceType, currentResource, oldDeleteProvider, replaceProvider, replaceProps) {
|
|
19914
|
+
async replaceDeleteFirstAndRecreate(logicalId, resourceType, currentResource, oldDeleteProvider, replaceProvider, replaceProps, updateReplacePolicy) {
|
|
19915
|
+
const finalSnapshotIdentifier = await this.prepareFinalSnapshotForDelete(logicalId, resourceType, currentResource, updateReplacePolicy);
|
|
19351
19916
|
try {
|
|
19352
19917
|
await oldDeleteProvider.delete(logicalId, currentResource.physicalId, resourceType, currentResource.properties, {
|
|
19353
19918
|
expectedRegion: this.stackRegion,
|
|
19354
|
-
forceDataDelete: this.options.forceStatefulRecreation === true
|
|
19919
|
+
forceDataDelete: this.options.forceStatefulRecreation === true,
|
|
19920
|
+
...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
|
|
19355
19921
|
});
|
|
19356
19922
|
} catch (deleteError) {
|
|
19357
19923
|
throw new Error(`Failed to delete old resource ${logicalId} (${currentResource.physicalId}) during the --replace delete-first fallback: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`);
|
|
@@ -19492,10 +20058,12 @@ var DeployEngine = class {
|
|
|
19492
20058
|
if (updateReplacePolicy === "Retain") this.logger.warn(` ⚠ ${logicalId} has UpdateReplacePolicy: Retain — ${recreateFlagName} will leak the old physical resource (${currentResource.physicalId}). The new resource shares the same name where applicable; if the type has user-supplied names (e.g. functionName, bucketName), the create will deterministically collide with the retained orphan.`);
|
|
19493
20059
|
else {
|
|
19494
20060
|
this.logger.info(` Destroying old ${logicalId} (${currentResource.physicalId}) before recreate...`);
|
|
20061
|
+
const recreateFinalSnapshotId = await this.prepareFinalSnapshotForDelete(logicalId, resourceType, currentResource, updateReplacePolicy);
|
|
19495
20062
|
try {
|
|
19496
20063
|
await oldDeleteProvider.delete(logicalId, currentResource.physicalId, resourceType, currentResource.properties, {
|
|
19497
20064
|
expectedRegion: this.stackRegion,
|
|
19498
|
-
forceDataDelete: this.options.forceStatefulRecreation === true
|
|
20065
|
+
forceDataDelete: this.options.forceStatefulRecreation === true,
|
|
20066
|
+
...recreateFinalSnapshotId !== void 0 && { finalSnapshotIdentifier: recreateFinalSnapshotId }
|
|
19499
20067
|
});
|
|
19500
20068
|
this.logger.info(` ${green("✓")} Old resource deleted`);
|
|
19501
20069
|
} catch (deleteError) {
|
|
@@ -19525,22 +20093,32 @@ var DeployEngine = class {
|
|
|
19525
20093
|
if (this.options.replace !== true) throw new CdkdError(`${logicalId} (${resourceType}) requires replacement, but the create-first attempt collided with the existing resource: ${createMsg}. The resource has a user-supplied physical name, so the CloudFormation-style safe replacement order (create the new resource before deleting the old) cannot reuse the occupied name — CloudFormation refuses this shape with "cannot update a stack when a custom-named resource requires replacing". Either rename the resource in your CDK code (a fresh name lets the safe create-first order proceed), or re-run with \`cdkd deploy --replace\` to delete the old resource FIRST and recreate it under the same name (the resource is briefly unavailable while it is recreated).`, "NAMED_REPLACEMENT_COLLISION");
|
|
19526
20094
|
this.logger.info(` Create-first collided with the custom-named resource and --replace is set — deleting old ${logicalId} (${currentResource.physicalId}) first...`);
|
|
19527
20095
|
deletedOldFirst = true;
|
|
19528
|
-
createResult = await this.replaceDeleteFirstAndRecreate(logicalId, resourceType, currentResource, oldDeleteProvider, replaceProvider, replaceProps);
|
|
20096
|
+
createResult = await this.replaceDeleteFirstAndRecreate(logicalId, resourceType, currentResource, oldDeleteProvider, replaceProvider, replaceProps, updateReplacePolicy);
|
|
19529
20097
|
}
|
|
19530
20098
|
if (!deletedOldFirst && createResult.physicalId === currentResource.physicalId) {
|
|
19531
20099
|
if (updateReplacePolicy === "Retain") throw new CdkdError(`${logicalId} (${resourceType}) requires replacement, but its Create API is name-idempotent: the create-first attempt returned the existing resource (${currentResource.physicalId}) instead of creating a new one, and UpdateReplacePolicy: Retain pins that resource in place. Rename the resource in your CDK code — with Retain, the old resource keeps the name, so a same-name replacement can never proceed.`, "NAMED_REPLACEMENT_IDEMPOTENT_CREATE");
|
|
19532
20100
|
if (this.options.replace !== true) throw new CdkdError(`${logicalId} (${resourceType}) requires replacement, but its Create API is name-idempotent: the create-first attempt returned the EXISTING resource (${currentResource.physicalId}) instead of creating a new one, so deleting the "old" resource would silently destroy the resource the deploy just reported as created. The resource has a user-supplied physical name; either change or remove the explicit name in your CDK code (a fresh or generated name lets the safe create-first order proceed), or re-run with \`cdkd deploy --replace\` to delete the old resource FIRST and recreate it under the same name (the resource is briefly unavailable while it is recreated). Note: this branch is also reached when the old resource was deleted out-of-band and the physical id is name-derived — there the create was a genuine fresh create; \`--replace\` converges that case too.`, "NAMED_REPLACEMENT_IDEMPOTENT_CREATE");
|
|
19533
20101
|
this.logger.info(` Create-first returned the existing resource (name-idempotent Create API) and --replace is set — deleting old ${logicalId} (${currentResource.physicalId}) first...`);
|
|
19534
20102
|
deletedOldFirst = true;
|
|
19535
|
-
createResult = await this.replaceDeleteFirstAndRecreate(logicalId, resourceType, currentResource, oldDeleteProvider, replaceProvider, replaceProps);
|
|
20103
|
+
createResult = await this.replaceDeleteFirstAndRecreate(logicalId, resourceType, currentResource, oldDeleteProvider, replaceProvider, replaceProps, updateReplacePolicy);
|
|
19536
20104
|
}
|
|
19537
20105
|
if (deletedOldFirst) {} else if (updateReplacePolicy === "Retain") this.logger.info(` Retaining old ${logicalId} (${currentResource.physicalId}) - UpdateReplacePolicy: Retain`);
|
|
19538
20106
|
else {
|
|
19539
20107
|
this.logger.info(` Deleting old ${logicalId} (${currentResource.physicalId})...`);
|
|
20108
|
+
let cleanupFinalSnapshotId;
|
|
20109
|
+
let snapshotBlockedDelete = false;
|
|
19540
20110
|
try {
|
|
20111
|
+
cleanupFinalSnapshotId = await this.prepareFinalSnapshotForDelete(logicalId, resourceType, currentResource, updateReplacePolicy);
|
|
20112
|
+
} catch (snapshotError) {
|
|
20113
|
+
if (snapshotError instanceof CdkdError && snapshotError.code === "FINAL_SNAPSHOT_UNSUPPORTED") throw snapshotError;
|
|
20114
|
+
snapshotBlockedDelete = true;
|
|
20115
|
+
this.logger.warn(` ⚠ Final snapshot for old ${logicalId} (${currentResource.physicalId}) failed: ${snapshotError instanceof Error ? snapshotError.message : String(snapshotError)}. The old resource was NOT deleted (UpdateReplacePolicy: Snapshot) — delete it manually once you have a snapshot; it is no longer tracked in state.`);
|
|
20116
|
+
}
|
|
20117
|
+
if (!snapshotBlockedDelete) try {
|
|
19541
20118
|
await oldDeleteProvider.delete(logicalId, currentResource.physicalId, resourceType, currentResource.properties, {
|
|
19542
20119
|
expectedRegion: this.stackRegion,
|
|
19543
|
-
forceDataDelete: this.options.forceStatefulRecreation === true
|
|
20120
|
+
forceDataDelete: this.options.forceStatefulRecreation === true,
|
|
20121
|
+
...cleanupFinalSnapshotId !== void 0 && { finalSnapshotIdentifier: cleanupFinalSnapshotId }
|
|
19544
20122
|
});
|
|
19545
20123
|
this.logger.info(` ${green("✓")} Old resource deleted`);
|
|
19546
20124
|
} catch (deleteError) {
|
|
@@ -19586,10 +20164,12 @@ var DeployEngine = class {
|
|
|
19586
20164
|
if (statefulReason && this.options.forceStatefulRecreation !== true) throw new CdkdError(`--replace would DELETE + CREATE the stateful resource ${logicalId} (${resourceType}) — ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the immutable-property change.`, "STATEFUL_REPLACE_BLOCKED");
|
|
19587
20165
|
}
|
|
19588
20166
|
this.logger.info(`UPDATE not supported for ${logicalId} (${resourceType}), replacing (DELETE → CREATE)`);
|
|
20167
|
+
const fallbackFinalSnapshotId = await this.prepareFinalSnapshotForDelete(logicalId, resourceType, currentResource, template?.Resources?.[logicalId]?.UpdateReplacePolicy ?? currentResource.updateReplacePolicy);
|
|
19589
20168
|
try {
|
|
19590
20169
|
await updateProvider.delete(logicalId, currentResource.physicalId, resourceType, currentProps, {
|
|
19591
20170
|
expectedRegion: this.stackRegion,
|
|
19592
|
-
forceDataDelete: this.options.forceStatefulRecreation === true
|
|
20171
|
+
forceDataDelete: this.options.forceStatefulRecreation === true,
|
|
20172
|
+
...fallbackFinalSnapshotId !== void 0 && { finalSnapshotIdentifier: fallbackFinalSnapshotId }
|
|
19593
20173
|
});
|
|
19594
20174
|
} catch (deleteError) {
|
|
19595
20175
|
const deleteMsg = deleteError instanceof Error ? deleteError.message : String(deleteError);
|
|
@@ -19641,13 +20221,17 @@ var DeployEngine = class {
|
|
|
19641
20221
|
delete stateResources[logicalId];
|
|
19642
20222
|
break;
|
|
19643
20223
|
}
|
|
20224
|
+
const finalSnapshotIdentifier = await this.prepareFinalSnapshotForDelete(logicalId, resourceType, currentResource, deletionPolicy);
|
|
19644
20225
|
const deleteProvider = this.providerRegistry.getProviderFor({
|
|
19645
20226
|
resourceType,
|
|
19646
20227
|
provisionedBy: currentResource.provisionedBy
|
|
19647
20228
|
}).provider;
|
|
19648
20229
|
this.logger.debug(`Deleting ${logicalId} (${resourceType})`);
|
|
19649
20230
|
try {
|
|
19650
|
-
await this.withRetry(() => deleteProvider.delete(logicalId, currentResource.physicalId, resourceType, currentResource.properties, {
|
|
20231
|
+
await this.withRetry(() => deleteProvider.delete(logicalId, currentResource.physicalId, resourceType, currentResource.properties, {
|
|
20232
|
+
expectedRegion: this.stackRegion,
|
|
20233
|
+
...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
|
|
20234
|
+
}), logicalId, 3, 5e3, deleteProvider);
|
|
19651
20235
|
} catch (deleteError) {
|
|
19652
20236
|
const msg = deleteError instanceof Error ? deleteError.message : String(deleteError);
|
|
19653
20237
|
if (msg.includes("does not exist") || msg.includes("was not found") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException") || msg.includes("ResourceNotFoundException")) this.logger.debug(`Resource ${logicalId} already deleted (${msg}), removing from state`);
|
|
@@ -19879,5 +20463,5 @@ var DeployEngine = class {
|
|
|
19879
20463
|
};
|
|
19880
20464
|
|
|
19881
20465
|
//#endregion
|
|
19882
|
-
export {
|
|
19883
|
-
//# sourceMappingURL=deploy-engine-
|
|
20466
|
+
export { TemplateParser as $, CdkdError as $t, yellow as A, resolveAutoAssetStorage as At, IntrinsicFunctionResolver as B, MIGRATE_TMP_PREFIX as Bt, renderStatefulReason as C, AssetManifestLoader as Ct, gray as D, getDefaultStateBucketName as Dt, cyan as E, synthesisStatusMessage as Et, findActionableSilentDrops as F, resolveUseCdkBootstrapAssets as Ft, resolveExplicitPhysicalId as G, processStackMessages as Gt, refStateLookupFromResource as H, uploadCfnTemplate as Ht, CloudControlProvider as I, stateBucketExistenceConfirmed as It, DiffCalculator as J, AwsClients as Jt, assertRegionMatch as K, clearBucketRegionCache as Kt, slowCcOperationTimeoutMs as L, warnDeprecatedNoPrefixCliFlag as Lt, collectInlinePolicyNamesManagedBySiblings as M, resolveSkipPrefix as Mt, clearOnUpdateRemoval as N, resolveStateBucketWithDefault as Nt, green as O, getLegacyStateBucketName as Ot, ProviderRegistry as P, resolveStateBucketWithDefaultAndSource as Pt, DagBuilder as Q, AssetError as Qt, disableInstanceApiTermination as R, CFN_TEMPLATE_BODY_LIMIT as Rt, isStatefulRecreateTargetSync as S, runDockerStreaming as St, bold as T, Synthesizer as Tt, WAFv2WebACLProvider as U, expectedOwnerParam as Ut, cfnRefValueFromPhysicalId as V, findLargeInlineResources as Vt, normalizeAwsTagsToCfn as W, AssemblyReader as Wt, withRetry as X, resetAwsClients as Xt, describeTypeWithThrottleRetry as Y, getAwsClients as Yt, isRetryableTransientError as Z, setAwsClients as Zt, createPreDeleteFinalSnapshot as _, formatError as _n, validateContainerRepoName as _t, DeploymentEventsStore as a, LocalStartServiceError as an, stringifyValue as at, extractDeploymentEventError as b, withErrorHandling as bn, getDockerCmd as bt, replayFailedOperations as c, NestedStackChildDirectDestroyError as cn, createAssetRedirectResolver as ct, IMPLICIT_DELETE_DEPENDENCIES as d, ResourceTimeoutError as dn, AssetModeResolver as dt, ConfigError as en, LockManager as et, computeImplicitDeleteEdges as f, ResourceUpdateNotSupportedError as fn, BOOTSTRAP_MARKER_PREFIX as ft, ccRoutedFinalSnapshotError as g, SynthesisError as gn, validateAssetBucketName as gt, buildFinalSnapshotIdentifier as h, StateError as hn, parseBootstrapMarker as ht, DeploymentEventsReader as i, LocalMigrateError as in, AssetPublisher as it, IAMRoleProvider as j, resolveCaptureObservedState as jt, red as k, resolveApp as kt, replayRollback as l, PartialFailureError as ln, loadPublishableAssetManifest as lt, PRE_DELETE_SNAPSHOT_TYPES as m, StackTerminationProtectionError as mn, getBootstrapMarkerKey as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, DeployCancelledError as nn, rebuildClientForBucketRegion as nt, planFailedOps as o, LockError as on, WorkGraph as ot, ATOMIC_FINAL_SNAPSHOT_TYPES as p, StackHasActiveImportsError as pn, ensureAssetStorage as pt, applyRoleArnIfSet as q, resolveBucketRegion as qt, DeployEngine as r, LocalInvokeBuildError as rn, shouldRetainResource as rt, planRollback as s, MissingCdkCliError as sn, buildAssetRedirectMap as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, DependencyError as tn, S3StateBackend as tt, withResourceDeadline as u, ProvisioningError as un, rewriteTemplateAssetReferences as ut, isFinalSnapshotError as v, isCdkdError as vn, buildDockerImage as vt, formatResourceLine as w, getDockerImageBySourceHash as wt, MULTI_REGION_RECREATE_BLOCKED_TYPES as x, __exportAll as xn, runDockerForeground as xt, unsupportedFinalSnapshotError as y, normalizeAwsError as yn, formatDockerLoginError as yt, isTerminationProtectionPropagationError as z, CFN_TEMPLATE_URL_LIMIT as zt };
|
|
20467
|
+
//# sourceMappingURL=deploy-engine-j0TmnJYJ.js.map
|