@go-to-k/cdkd 0.296.27 → 0.296.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{asg-provider-BiMLPagi.js → asg-provider-B3zcS87C.js} +2 -2
- package/dist/{asg-provider-BiMLPagi.js.map → asg-provider-B3zcS87C.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-DC4Yblxp.js → deploy-engine-D4FdwPCi.js} +1711 -1588
- package/dist/deploy-engine-D4FdwPCi.js.map +1 -0
- package/dist/iam-policy-targets-B61e5_Sk.js.map +1 -1
- package/dist/{iam-role-provider-BA2eujXF.js → iam-role-provider-0U4kMuzh.js} +2 -2
- package/dist/{iam-role-provider-BA2eujXF.js.map → iam-role-provider-0U4kMuzh.js.map} +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{neptune-provider-DME95IcZ.js → neptune-provider-DF6Eh4Tl.js} +2 -2
- package/dist/{neptune-provider-DME95IcZ.js.map → neptune-provider-DF6Eh4Tl.js.map} +1 -1
- package/dist/{program-BwNb3_Ot.js → program-v_QNBjtZ.js} +39 -18
- package/dist/program-v_QNBjtZ.js.map +1 -0
- package/dist/{provider-classes-BcSDENeH.js → provider-classes-wbKBBEol.js} +7 -7
- package/dist/{provider-classes-BcSDENeH.js.map → provider-classes-wbKBBEol.js.map} +1 -1
- package/dist/{rds-provider-iouJXdXX.js → rds-provider-BfQsw4hw.js} +2 -2
- package/dist/{rds-provider-iouJXdXX.js.map → rds-provider-BfQsw4hw.js.map} +1 -1
- package/dist/{version-Cs9QbaaH.js → version-B0JEGOLV.js} +2 -2
- package/dist/{version-Cs9QbaaH.js.map → version-B0JEGOLV.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DC4Yblxp.js.map +0 -1
- package/dist/program-BwNb3_Ot.js.map +0 -1
|
@@ -2,7 +2,7 @@ import { r as getAwsClients, s as __exportAll } from "./aws-clients-CnmtbrxD.js"
|
|
|
2
2
|
import { A as UNRENDERABLE, C as looksLikeCdkdGeneratedName, E as withoutGeneratedFallbackName, F as displayStackName, I as isPasteableIdent, M as displayAwsMessage, N as displayIdent, O as SECRET_REF_MAX_CODE_POINTS, P as displaySafe, R as plainIdentOr, S as getCurrentStackName, T as withStackName, V as truncateCodePoints, a as getLogger, b as generateResourceNameWithFallback, d as installLogLineMaskerSource, f as getLiveRenderer, g as applyDefaultNameForFallback, k as STACK_REF_MAX_CODE_POINTS, o as isStdoutReservedForPayload, r as safeStringify$1, t as describeAwsFailure, u as currentLogLineMasker, v as explicitNamePropertyFor, w as withSkipPrefix, x as getCurrentSkipPrefix, y as generateResourceName, z as safeMsg } from "./aws-failure-text-BaWeCWRz.js";
|
|
3
3
|
import { $ as protectedReplacementAdvice, $n as isIamPropagationError, $t as AccountIdUnavailableError, A as isQuotableImportValue, An as commandHole, At as isAuxiliaryMarkOf, B as isTerminalDeleteFailure, Bt as sanitizeRecoveryValue, Ct as RETRY_AUXILIARY_OWNER, Dn as SHELL_ACTIVE, Dt as createdResourceIdentityBeforeFailure, E as assertRegionMatch, Et as createdBeforeFailure, F as resolveExplicitPhysicalId, Fn as plainOrDescribed, G as runDeleteAttempt, Gn as isNameCooldownError, H as observeThenDisableProtection, Hn as isUpdateUnsupportedError, Ht as DEFAULT_STATE_PREFIX, In as quotedOrDescribed, J as DELETION_PROTECTION_DOC_POINTER, Jt as withPasteableAwsProfile, Kn as isRecreateRetryableError, Kt as producerRecordKey, L as ProtectionFlipRegistry, Ln as shellBoundedDisplay, Lt as formatLockExpiry, M as quotedRemedyLogicalId, Mn as isInertUnquoted, Mt as markCreatedBeforeFailure, N as refusalTypeShown, Nn as pasteableCommand, Nt as UNREPRODUCIBLE_LOCK_CLAUSE, On as SHELL_ACTIVE_WHY, Ot as hasCreatedBeforeFailure, Pn as physicalIdShownBesideCommand, Pt as buildForceUnlockCommand, Q as pasteableAwsCommand, Qn as isAmbiguousOutcomeError, Qt as credentialFingerprint, R as compensateProtectionFlip, Rn as shellQuote, Rt as formatLockOperation, St as withDerivedNameMasks, T as RESOURCE_NOT_FOUND, U as protectionFlipKey, Un as hasReplayMayCollide, Ut as injectiveKey, Vt as CUSTOM_RESOURCE_RESPONSE_PREFIX, Wn as isNameCollisionErrorFrom, Xn as formatRetryClassificationSignals, Xt as ambientCredentialConfig, Yn as markReplayMayCollide, Yt as ambientClientDefaults, Zn as isAmbiguousCcHandlerErrorCode, Zt as clientDefaultsFor, _n as ResourceUpdateNotSupportedError, _t as createMaskedLogSinks, ar as isThrottlingError, at as startInterruptWatch, bn as StateError, bt as maskDeep, cr as wrapMaskedAwsError, ct as canonicalizeRoute53QueryName, d as prepareRemovalForUpdate, dn as MacroExpansionError, en as AssetError, er as isRetryableTransientError, f as removalWarning, fn as MalformedProducerRecordRefusalError, ft as logicalIdShown, gn as ResourceTimeoutError, gt as stateOrphanRecordRemedy, hn as ProvisioningError, ht as segmentAfterAnchor, i as holdsSecretDerivedEntry, in as DependencyError, ir as isMarkedNonRetryable, it as isInterruptedWaitError, j as normalizeAwsTagsToCfn, jt as markAuxiliaryFailure, kn as UNSHOWABLE_VALUE, kt as isAuxiliaryFailure, l as withUnchangedSecretPrincipalLists, m as withoutInjectedRemovals, mt as resourceTypeShown, nr as retryClassificationText, on as DynamicReferenceRegionAmbiguousError, or as markNonRetryable, ot as ElapsedBudgetRegistry, qn as isReplayedNameCollisionFrom, rn as CrossAccountSecretRefusalError, sn as IntrinsicResolutionRefusalError, sr as markRedactedCause, st as monotonicNowMs, tn as CdkdError, tr as isTransientServerError, un as LockError, ut as compositeIdSeparatorRefusal, vt as createMaskedRetryLogger, w as withoutServerErrorRetries, wn as normalizeAwsError, wt as carryCreatedBeforeFailure, xn as SynthesisError, xt as maskerOrIdentity, yt as isSecretDerivedValue, z as deleteWithProtectionCompensation, zn as withheldTargetClause, zt as formatLockOwner } from "./iam-policy-targets-B61e5_Sk.js";
|
|
4
4
|
import { a as setAssumedRoleCredentials, i as getPreAssumeEnvCredentials, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, s as ambientRegion, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-D0q0z4dG.js";
|
|
5
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
+
import { t as getCdkdVersion } from "./version-B0JEGOLV.js";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
8
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -4812,6 +4812,7 @@ function refuseMalformedOperation(shownStack, where, op) {
|
|
|
4812
4812
|
if (o["changeType"] !== "UPDATE") refuseMalformed(shownStack, `${where}.replacementOrphaned is only valid on an UPDATE.`);
|
|
4813
4813
|
}
|
|
4814
4814
|
if (o["wasReplaced"] !== void 0 && typeof o["wasReplaced"] !== "boolean") fail("wasReplaced", o["wasReplaced"], "a boolean when present");
|
|
4815
|
+
if (o["oldDeletedBeforeCreate"] !== void 0 && typeof o["oldDeletedBeforeCreate"] !== "boolean") fail("oldDeletedBeforeCreate", o["oldDeletedBeforeCreate"], "a boolean when present");
|
|
4815
4816
|
if (o["physicalIdRecoveredFromError"] !== void 0 && typeof o["physicalIdRecoveredFromError"] !== "boolean") fail("physicalIdRecoveredFromError", o["physicalIdRecoveredFromError"], "a boolean when present");
|
|
4816
4817
|
if (o["physicalIdRecoveredFromError"] !== void 0 && o["changeType"] !== "CREATE") refuseMalformed(shownStack, `${where}.physicalIdRecoveredFromError is only valid on a CREATE (got changeType ${kind(o["changeType"])}).`);
|
|
4817
4818
|
const identity = o["createdResourceIdentity"];
|
|
@@ -6238,6 +6239,25 @@ const MIN_NEEDLE_LENGTH = 4;
|
|
|
6238
6239
|
function escapeRegExp$2(value) {
|
|
6239
6240
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6240
6241
|
}
|
|
6242
|
+
function maskMatchSpans(text, needles, mask) {
|
|
6243
|
+
const spans = [];
|
|
6244
|
+
for (const needle of needles) for (let at = text.indexOf(needle); at !== -1; at = text.indexOf(needle, at + 1)) spans.push([at, at + needle.length]);
|
|
6245
|
+
if (spans.length === 0) return text;
|
|
6246
|
+
spans.sort((a, b) => a[0] - b[0] || b[1] - a[1]);
|
|
6247
|
+
let out = "";
|
|
6248
|
+
let cursor = 0;
|
|
6249
|
+
let [start, end] = spans[0];
|
|
6250
|
+
for (const [nextStart, nextEnd] of spans.slice(1)) {
|
|
6251
|
+
if (nextStart < end) {
|
|
6252
|
+
end = Math.max(end, nextEnd);
|
|
6253
|
+
continue;
|
|
6254
|
+
}
|
|
6255
|
+
out += text.slice(cursor, start) + mask;
|
|
6256
|
+
cursor = end;
|
|
6257
|
+
[start, end] = [nextStart, nextEnd];
|
|
6258
|
+
}
|
|
6259
|
+
return out + text.slice(cursor, start) + mask + text.slice(end);
|
|
6260
|
+
}
|
|
6241
6261
|
function buildNeedleRegex(values) {
|
|
6242
6262
|
const needles = Array.from(new Set(values)).filter((v) => v.length >= 4).sort((a, b) => b.length - a.length);
|
|
6243
6263
|
if (needles.length === 0) return void 0;
|
|
@@ -8012,16 +8032,15 @@ function maskSecretsInText(text, secrets) {
|
|
|
8012
8032
|
const logOnly = logOnlyValuesOf.get(secrets);
|
|
8013
8033
|
if (logOnly === void 0 || logOnly.size === 0) return maskRecordedSecretsInText(text, secrets);
|
|
8014
8034
|
if (text !== "" && (secrets.has(text) || logOnly.has(text))) return "***";
|
|
8015
|
-
|
|
8016
|
-
if (!regex) return text;
|
|
8017
|
-
return text.replace(regex, "***");
|
|
8035
|
+
return maskMatchSpans(text, substringNeedles([...secrets.keys(), ...logOnly]), "***");
|
|
8018
8036
|
}
|
|
8019
8037
|
function maskRecordedSecretsInText(text, secrets) {
|
|
8020
8038
|
if (secrets.size === 0) return text;
|
|
8021
8039
|
if (text !== "" && secrets.has(text)) return "***";
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8040
|
+
return maskMatchSpans(text, substringNeedles(secrets.keys()), "***");
|
|
8041
|
+
}
|
|
8042
|
+
function substringNeedles(values) {
|
|
8043
|
+
return [...new Set(values)].filter((value) => value.length >= 4);
|
|
8025
8044
|
}
|
|
8026
8045
|
const ERROR_CAUSE_MASK_MAX_DEPTH = 20;
|
|
8027
8046
|
function errorCauseChain(root) {
|
|
@@ -8368,7 +8387,7 @@ function createUnionSecretMasker(bags) {
|
|
|
8368
8387
|
const present = bags.filter((bag) => bag !== void 0);
|
|
8369
8388
|
let stamp;
|
|
8370
8389
|
let needles = /* @__PURE__ */ new Set();
|
|
8371
|
-
let
|
|
8390
|
+
let scanned = [];
|
|
8372
8391
|
return (text) => {
|
|
8373
8392
|
const now = present.map((bag) => `${bag.size}:${logOnlyValueCount(bag)}`).join(",");
|
|
8374
8393
|
if (now !== stamp) {
|
|
@@ -8377,11 +8396,11 @@ function createUnionSecretMasker(bags) {
|
|
|
8377
8396
|
for (const plaintext of bag.keys()) needles.add(plaintext);
|
|
8378
8397
|
for (const plaintext of logOnlyValuesOf.get(bag) ?? []) needles.add(plaintext);
|
|
8379
8398
|
}
|
|
8380
|
-
|
|
8399
|
+
scanned = [...needles].filter((needle) => needle.length >= 4);
|
|
8381
8400
|
stamp = now;
|
|
8382
8401
|
}
|
|
8383
8402
|
if (text !== "" && needles.has(text)) return "***";
|
|
8384
|
-
return
|
|
8403
|
+
return maskMatchSpans(text, scanned, "***");
|
|
8385
8404
|
};
|
|
8386
8405
|
}
|
|
8387
8406
|
function hasLogOnlyValues(secrets) {
|
|
@@ -22953,7 +22972,7 @@ var CloudControlProvider = class {
|
|
|
22953
22972
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
22954
22973
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
22955
22974
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
22956
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
22975
|
+
const { ASGProvider } = await import("./asg-provider-B3zcS87C.js");
|
|
22957
22976
|
const delegateRegion = ambientRegion();
|
|
22958
22977
|
let asgProvider = this.asgDeleteDelegates.get(delegateRegion);
|
|
22959
22978
|
if (asgProvider === void 0) {
|
|
@@ -23059,7 +23078,7 @@ var CloudControlProvider = class {
|
|
|
23059
23078
|
const family = SDK_DELETE_FAMILIES.get(resourceType);
|
|
23060
23079
|
const cached = this.sdkDeleteDelegates.get(family);
|
|
23061
23080
|
if (cached !== void 0 && cached.region === ccRegion) return cached.provider;
|
|
23062
|
-
const provider = family === "rds" ? new (await (import("./rds-provider-
|
|
23081
|
+
const provider = family === "rds" ? new (await (import("./rds-provider-BfQsw4hw.js"))).RDSProvider({ region: ccRegion }) : new (await (import("./neptune-provider-DF6Eh4Tl.js"))).NeptuneProvider({ region: ccRegion });
|
|
23063
23082
|
this.sdkDeleteDelegates.set(family, {
|
|
23064
23083
|
region: ccRegion,
|
|
23065
23084
|
provider
|
|
@@ -27830,537 +27849,334 @@ function maskingRetryLogger(logger, secrets) {
|
|
|
27830
27849
|
}
|
|
27831
27850
|
|
|
27832
27851
|
//#endregion
|
|
27833
|
-
//#region src/
|
|
27834
|
-
const
|
|
27835
|
-
|
|
27836
|
-
|
|
27837
|
-
|
|
27838
|
-
|
|
27839
|
-
|
|
27840
|
-
|
|
27841
|
-
|
|
27842
|
-
|
|
27843
|
-
|
|
27844
|
-
|
|
27845
|
-
|
|
27846
|
-
|
|
27847
|
-
|
|
27852
|
+
//#region src/provisioning/final-snapshot.ts
|
|
27853
|
+
const ATOMIC_FINAL_SNAPSHOT_TYPES = /* @__PURE__ */ new Set([
|
|
27854
|
+
"AWS::RDS::DBInstance",
|
|
27855
|
+
"AWS::RDS::DBCluster",
|
|
27856
|
+
"AWS::Neptune::DBCluster",
|
|
27857
|
+
"AWS::DocDB::DBCluster",
|
|
27858
|
+
"AWS::ElastiCache::CacheCluster"
|
|
27859
|
+
]);
|
|
27860
|
+
const PRE_DELETE_SNAPSHOT_TYPES = /* @__PURE__ */ new Set([
|
|
27861
|
+
"AWS::EC2::Volume",
|
|
27862
|
+
"AWS::Redshift::Cluster",
|
|
27863
|
+
"AWS::ElastiCache::ReplicationGroup"
|
|
27864
|
+
]);
|
|
27865
|
+
function effectiveDeletionPolicy(resourceType, recorded, properties) {
|
|
27866
|
+
if (recorded !== void 0) return recorded;
|
|
27867
|
+
if (resourceType === "AWS::RDS::DBCluster") return "Snapshot";
|
|
27868
|
+
if (resourceType === "AWS::RDS::DBInstance" && properties?.["DBClusterIdentifier"] === void 0) return "Snapshot";
|
|
27848
27869
|
}
|
|
27849
|
-
function
|
|
27850
|
-
|
|
27851
|
-
if (!(error instanceof ProvisioningError)) return void 0;
|
|
27852
|
-
const physicalId = error.physicalId;
|
|
27853
|
-
if (!physicalId) return void 0;
|
|
27854
|
-
const replayed = isReplayedNameCollisionFrom(error, logicalId, (link) => isAuxiliaryMarkOf(link, RETRY_AUXILIARY_OWNER));
|
|
27855
|
-
if (!replayed && !isNameCollisionErrorFrom(error, logicalId)) return void 0;
|
|
27856
|
-
const stackName = getCurrentStackName();
|
|
27857
|
-
if (!looksLikeCdkdGeneratedName(physicalId, logicalId, stackName)) return void 0;
|
|
27858
|
-
if (!stackName) return void 0;
|
|
27859
|
-
const safeId = displaySafe(physicalId, { asciiOnly: true });
|
|
27860
|
-
const safeStack = displaySafe(stackName, { asciiOnly: true });
|
|
27861
|
-
const safeLogicalId = displaySafe(logicalId, { asciiOnly: true });
|
|
27862
|
-
const idShown = isInertUnquoted(safeId);
|
|
27863
|
-
const shownId = idShown ? safeId : "a name that cannot be shown safely here";
|
|
27864
|
-
const shownLogicalId = isInertUnquoted(safeLogicalId) ? safeLogicalId : "A resource whose logical id cannot be shown safely here";
|
|
27865
|
-
const diagnosis = replayed ? `${shownLogicalId}: the name AWS reports as taken (${shownId}) is most likely held by a resource THIS create made. An earlier attempt of it ended without a clear verdict (a server error, a dropped connection or a timeout, or a failure after the create call itself may have succeeded), and the retry then found the name taken. A dropped connection can also happen before the request reached AWS, so the holder may be another resource: confirm it is yours before deleting or importing it. cdkd records a resource only once its create succeeds, so this one is in no state file: no rollback or cdkd destroy will remove it, and re-running collides with it again.` : `${shownLogicalId}: the name AWS reports as taken (${shownId}) is one cdkd DERIVED from the logical id, and that derivation has no random component — so this is most likely a resource an earlier cdkd run left behind. A rollback leaves a resource carrying DeletionPolicy: Retain in AWS and drops it from state, as CloudFormation does; what differs is the name. CloudFormation would generate a fresh one for an unnamed resource and redeploy clean, whereas cdkd asks again for the name the orphan still holds — so re-running does not clear this.`;
|
|
27866
|
-
const deleteArm = `If it is not a resource you want to keep, delete ${idShown ? safeId : "it"} in AWS — after confirming it holds nothing you need${replayed ? "" : ", since Retain is what kept it"} — and re-deploy.`;
|
|
27867
|
-
let canImport;
|
|
27868
|
-
try {
|
|
27869
|
-
canImport = typeof this.providerRegistry.getProvider(error.resourceType).import === "function";
|
|
27870
|
-
} catch {
|
|
27871
|
-
canImport = false;
|
|
27872
|
-
}
|
|
27873
|
-
const commandNamesTheRightResource = safeId === physicalId && safeStack === stackName && safeLogicalId === logicalId;
|
|
27874
|
-
const importableTarget = !stackName.includes("~");
|
|
27875
|
-
if (!canImport || !commandNamesTheRightResource || !importableTarget) return `${diagnosis} ${!canImport ? `cdkd cannot adopt ${displaySafe(error.resourceType, { asciiOnly: true })} back into state (its provider implements no import)` : !importableTarget ? `this is a nested-stack child, whose stack name cdkd import cannot resolve` : `cdkd cannot render an import command that provably names this resource`}, so the way forward is to delete it. ${deleteArm}`;
|
|
27876
|
-
const adopt = pasteableCommand("cdkd import", [{
|
|
27877
|
-
value: stackName,
|
|
27878
|
-
hole: "stack"
|
|
27879
|
-
}, {
|
|
27880
|
-
flag: "--resource",
|
|
27881
|
-
value: `${logicalId}=${physicalId}`,
|
|
27882
|
-
hole: "logicalId=physicalId"
|
|
27883
|
-
}]);
|
|
27884
|
-
return `${diagnosis} To recover, adopt it back into state instead of re-creating it, with the command below (a selective import merges into existing state and needs no --force while the resource is absent from it). CONFIRM IT IS YOURS FIRST — a name cdkd derives is predictable, so for a globally-namespaced type it can belong to another account, and the same stack deployed in another region derives the same name. ${deleteArm}${adopt.withheld.length === 0 ? "" : " The command below prints a quoted hole in place of a value cdkd will not name on a command line; replace it whole, quotes included, with your stack name or '<logicalId>=<name>', shell-quoted."}\nAdopt with: ${adopt.command}`;
|
|
27870
|
+
function replacementDeletePolicy(updateReplacePolicy) {
|
|
27871
|
+
return updateReplacePolicy ?? "Delete";
|
|
27885
27872
|
}
|
|
27886
|
-
|
|
27887
|
-
|
|
27888
|
-
|
|
27889
|
-
|
|
27890
|
-
return JSON.stringify(value, (_key, v) => {
|
|
27891
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
27892
|
-
const sorted = Object.create(null);
|
|
27893
|
-
for (const k of Object.keys(v).sort()) sorted[k] = v[k];
|
|
27894
|
-
return sorted;
|
|
27895
|
-
}
|
|
27896
|
-
return v;
|
|
27897
|
-
});
|
|
27873
|
+
function finalSnapshotMechanism(resourceType, provisionedBy) {
|
|
27874
|
+
if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resourceType)) return provisionedBy === "cc-api" ? "refuse-cc-routed" : "atomic-delete-parameter";
|
|
27875
|
+
if (PRE_DELETE_SNAPSHOT_TYPES.has(resourceType)) return "pre-delete-snapshot";
|
|
27876
|
+
return "refuse-unsupported-type";
|
|
27898
27877
|
}
|
|
27899
|
-
|
|
27900
|
-
|
|
27901
|
-
|
|
27902
|
-
const masked = Object.create(null);
|
|
27903
|
-
for (const [name, decl] of Object.entries(parameters)) {
|
|
27904
|
-
const body = decl;
|
|
27905
|
-
masked[name] = typeof body === "object" && body !== null && body["NoEcho"] === true && "Default" in body ? {
|
|
27906
|
-
...body,
|
|
27907
|
-
Default: NO_ECHO_DEFAULT_MASK
|
|
27908
|
-
} : decl;
|
|
27909
|
-
}
|
|
27910
|
-
return masked;
|
|
27878
|
+
function refusesFinalSnapshot(resourceType, provisionedBy) {
|
|
27879
|
+
const mechanism = finalSnapshotMechanism(resourceType, provisionedBy);
|
|
27880
|
+
return mechanism === "refuse-cc-routed" || mechanism === "refuse-unsupported-type";
|
|
27911
27881
|
}
|
|
27912
|
-
function
|
|
27913
|
-
|
|
27914
|
-
|
|
27915
|
-
|
|
27916
|
-
sections[section] = section === "Parameters" ? withNoEchoDefaultsMasked(body) : body;
|
|
27917
|
-
}
|
|
27918
|
-
const entry = template.Outputs?.[outputKey];
|
|
27919
|
-
return createHash("sha256").update(canonicalJson$1({
|
|
27920
|
-
output: entry,
|
|
27921
|
-
template: sections
|
|
27922
|
-
})).digest("hex");
|
|
27882
|
+
function sanitizeSnapshotBase(physicalId) {
|
|
27883
|
+
let base = physicalId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
27884
|
+
if (!/^[a-z]/.test(base)) base = `r${base}`;
|
|
27885
|
+
return base;
|
|
27923
27886
|
}
|
|
27924
|
-
function
|
|
27925
|
-
|
|
27926
|
-
for (const [outputKey, value] of Object.entries(resolvedOutputs)) {
|
|
27927
|
-
if (value !== void 0) continue;
|
|
27928
|
-
record ??= Object.create(null);
|
|
27929
|
-
record[outputKey] = skippedOutputDigest(template, outputKey);
|
|
27930
|
-
}
|
|
27931
|
-
return record;
|
|
27887
|
+
function finalSnapshotNamePrefix(physicalId, resourceType) {
|
|
27888
|
+
return finalSnapshotNameCut(physicalId, resourceType).prefix;
|
|
27932
27889
|
}
|
|
27933
|
-
function
|
|
27934
|
-
const
|
|
27935
|
-
const
|
|
27936
|
-
|
|
27937
|
-
|
|
27938
|
-
|
|
27939
|
-
|
|
27940
|
-
|
|
27941
|
-
for (const item of value) walk(item);
|
|
27942
|
-
return;
|
|
27943
|
-
}
|
|
27944
|
-
if (value === null || typeof value !== "object") return;
|
|
27945
|
-
const obj = value;
|
|
27946
|
-
if (typeof obj["Ref"] === "string") addName(obj["Ref"]);
|
|
27947
|
-
const getAtt = obj["Fn::GetAtt"];
|
|
27948
|
-
if (typeof getAtt === "string") addName(getAtt);
|
|
27949
|
-
else if (Array.isArray(getAtt) && typeof getAtt[0] === "string") addName(getAtt[0]);
|
|
27950
|
-
for (const [key, nested] of Object.entries(obj)) {
|
|
27951
|
-
if (key === "Fn::Sub") {
|
|
27952
|
-
walkSub(nested);
|
|
27953
|
-
continue;
|
|
27954
|
-
}
|
|
27955
|
-
walk(nested);
|
|
27956
|
-
}
|
|
27957
|
-
};
|
|
27958
|
-
const walkSub = (sub) => {
|
|
27959
|
-
const isPair = Array.isArray(sub);
|
|
27960
|
-
const subTemplate = typeof sub === "string" ? sub : isPair ? sub[0] : void 0;
|
|
27961
|
-
const variableMap = isPair && sub[1] !== null && typeof sub[1] === "object" ? sub[1] : void 0;
|
|
27962
|
-
if (typeof subTemplate === "string") {
|
|
27963
|
-
const declaredVars = new Set(variableMap === void 0 ? [] : Object.keys(variableMap));
|
|
27964
|
-
for (const [, placeholder] of subTemplate.matchAll(/\$\{([^}]*)\}/g)) {
|
|
27965
|
-
if (placeholder === void 0 || placeholder.startsWith("!")) continue;
|
|
27966
|
-
if (declaredVars.has(placeholder)) continue;
|
|
27967
|
-
addName(placeholder);
|
|
27968
|
-
}
|
|
27969
|
-
}
|
|
27970
|
-
if (isPair) for (const [index, element] of sub.entries()) {
|
|
27971
|
-
if (index === 1) {
|
|
27972
|
-
if (variableMap !== void 0) for (const declared of Object.values(variableMap)) walk(declared);
|
|
27973
|
-
continue;
|
|
27974
|
-
}
|
|
27975
|
-
walk(element);
|
|
27976
|
-
}
|
|
27977
|
-
else walk(sub);
|
|
27890
|
+
function finalSnapshotNameCut(physicalId, resourceType) {
|
|
27891
|
+
const maxLength = resourceType === "AWS::ElastiCache::CacheCluster" || resourceType === "AWS::ElastiCache::ReplicationGroup" ? 50 : 255;
|
|
27892
|
+
const sanitized = sanitizeSnapshotBase(physicalId);
|
|
27893
|
+
const base = sanitized.slice(0, maxLength - 22).replace(/-+$/, "");
|
|
27894
|
+
return {
|
|
27895
|
+
prefix: `${base}-final-`,
|
|
27896
|
+
sanitized,
|
|
27897
|
+
kept: base.length
|
|
27978
27898
|
};
|
|
27979
|
-
walk(entry);
|
|
27980
|
-
return ids;
|
|
27981
|
-
}
|
|
27982
|
-
function bindingSkippedOutputs(pristineTemplate, record, changedLogicalIds) {
|
|
27983
|
-
const binding = /* @__PURE__ */ new Set();
|
|
27984
|
-
if (record === null || record === void 0) return binding;
|
|
27985
|
-
const declared = pristineTemplate.Outputs ?? {};
|
|
27986
|
-
for (const [outputKey, digest] of Object.entries(record)) {
|
|
27987
|
-
if (!Object.prototype.hasOwnProperty.call(declared, outputKey)) continue;
|
|
27988
|
-
const entry = declared[outputKey];
|
|
27989
|
-
if (digest !== skippedOutputDigest(pristineTemplate, outputKey)) continue;
|
|
27990
|
-
if (changedLogicalIds !== void 0 && [...referencedLogicalIds(entry)].some((id) => changedLogicalIds.has(id))) continue;
|
|
27991
|
-
binding.add(outputKey);
|
|
27992
|
-
}
|
|
27993
|
-
return binding;
|
|
27994
|
-
}
|
|
27995
|
-
function skippedOutputsEqual(a, b) {
|
|
27996
|
-
const aKeys = a === null || a === void 0 ? [] : Object.keys(a);
|
|
27997
|
-
const bKeys = b === null || b === void 0 ? [] : Object.keys(b);
|
|
27998
|
-
if (aKeys.length !== bKeys.length) return false;
|
|
27999
|
-
return aKeys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && a?.[k] === b?.[k]);
|
|
28000
27899
|
}
|
|
28001
|
-
|
|
28002
|
-
|
|
28003
|
-
|
|
28004
|
-
function isOutputSuppressedByCondition(output, conditions) {
|
|
28005
|
-
return output.Condition !== void 0 && conditions?.[output.Condition] === false;
|
|
28006
|
-
}
|
|
28007
|
-
function collectPublishedOutputNames(outputs, conditions) {
|
|
28008
|
-
const names = /* @__PURE__ */ new Set();
|
|
28009
|
-
for (const [name, output] of Object.entries(outputs)) if (!isOutputSuppressedByCondition(output, conditions)) names.add(name);
|
|
28010
|
-
return names;
|
|
28011
|
-
}
|
|
28012
|
-
function collectDeclaredOutputNames(outputs) {
|
|
28013
|
-
return new Set(Object.keys(outputs));
|
|
27900
|
+
function buildFinalSnapshotIdentifier(physicalId, resourceType, now = /* @__PURE__ */ new Date()) {
|
|
27901
|
+
const ts = now.toISOString().replace(/[-:]/g, "").replace(/\..*$/, "").replace("T", "-").toLowerCase();
|
|
27902
|
+
return `${finalSnapshotNamePrefix(physicalId, resourceType)}${ts}`;
|
|
28014
27903
|
}
|
|
28015
|
-
|
|
28016
|
-
|
|
27904
|
+
const EBS_FINAL_SNAPSHOT_TAG_KEY = "cdkd:final-snapshot-of";
|
|
27905
|
+
const finalSnapshotDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
|
|
27906
|
+
const PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS = 5e3;
|
|
27907
|
+
const PRE_DELETE_SNAPSHOT_TIMEOUT_MS = 36e5;
|
|
27908
|
+
const REDSHIFT_CLUSTER_SETTLE_TIMEOUT_MS = 12e5;
|
|
27909
|
+
function errMsg(error) {
|
|
27910
|
+
return describeAwsFailure(error).detail;
|
|
28017
27911
|
}
|
|
28018
|
-
|
|
28019
|
-
|
|
28020
|
-
//#region src/deployment/outputs-export-alias/secret-scan.ts
|
|
28021
|
-
const MIN_SECRET_NEEDLE = 4;
|
|
28022
|
-
const SECRET_SCAN_INVISIBLES = /[\p{Cc}\p{Cf}\p{Me}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/gu;
|
|
28023
|
-
const SECRET_DETECTION_ONLY = /\p{Mn}/gu;
|
|
28024
|
-
function detectionForms(canonical) {
|
|
28025
|
-
return [canonical.normalize("NFD").replace(SECRET_DETECTION_ONLY, "").normalize("NFC"), canonical.normalize("NFKD").replace(SECRET_DETECTION_ONLY, "").normalize("NFKC")];
|
|
27912
|
+
function isVolumeNotFound(error) {
|
|
27913
|
+
return (error instanceof Error ? error.name : "") === "InvalidVolume.NotFound" || errMsg(error).includes("InvalidVolume.NotFound");
|
|
28026
27914
|
}
|
|
28027
|
-
|
|
28028
|
-
|
|
28029
|
-
|
|
28030
|
-
|
|
28031
|
-
|
|
28032
|
-
|
|
27915
|
+
async function createEbsFinalSnapshot(client, volumeId, logicalId, logger) {
|
|
27916
|
+
let snapshotId;
|
|
27917
|
+
try {
|
|
27918
|
+
snapshotId = (await client.send(new DescribeSnapshotsCommand({
|
|
27919
|
+
OwnerIds: ["self"],
|
|
27920
|
+
Filters: [{
|
|
27921
|
+
Name: `tag:${EBS_FINAL_SNAPSHOT_TAG_KEY}`,
|
|
27922
|
+
Values: [volumeId]
|
|
27923
|
+
}, {
|
|
27924
|
+
Name: "status",
|
|
27925
|
+
Values: ["pending", "completed"]
|
|
27926
|
+
}]
|
|
27927
|
+
}))).Snapshots?.[0]?.SnapshotId;
|
|
27928
|
+
if (snapshotId) logger.debug(`Reusing existing final snapshot ${snapshotId} for ${logicalId} (${volumeId}) from a previous destroy attempt`);
|
|
27929
|
+
} catch (probeError) {
|
|
27930
|
+
logger.debug(`Final-snapshot reuse probe failed for ${volumeId}: ${errMsg(probeError)}`);
|
|
28033
27931
|
}
|
|
28034
|
-
|
|
28035
|
-
|
|
28036
|
-
|
|
28037
|
-
|
|
27932
|
+
if (!snapshotId) try {
|
|
27933
|
+
snapshotId = (await client.send(new CreateSnapshotCommand({
|
|
27934
|
+
VolumeId: volumeId,
|
|
27935
|
+
Description: `cdkd final snapshot for ${volumeId} (DeletionPolicy: Snapshot)`,
|
|
27936
|
+
TagSpecifications: [{
|
|
27937
|
+
ResourceType: "snapshot",
|
|
27938
|
+
Tags: [{
|
|
27939
|
+
Key: EBS_FINAL_SNAPSHOT_TAG_KEY,
|
|
27940
|
+
Value: volumeId
|
|
27941
|
+
}]
|
|
27942
|
+
}]
|
|
27943
|
+
}))).SnapshotId;
|
|
27944
|
+
} catch (createError) {
|
|
27945
|
+
if (isVolumeNotFound(createError)) {
|
|
27946
|
+
logger.debug(`Volume ${volumeId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
27947
|
+
return null;
|
|
27948
|
+
}
|
|
27949
|
+
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");
|
|
27950
|
+
}
|
|
27951
|
+
if (!snapshotId) throw new CdkdError(`CreateSnapshot for ${logicalId} (${volumeId}) returned no snapshot id. The volume was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
27952
|
+
logger.info(`Creating final snapshot ${snapshotId} for ${logicalId} (${volumeId}) — DeletionPolicy: Snapshot`);
|
|
27953
|
+
const deadline = Date.now() + PRE_DELETE_SNAPSHOT_TIMEOUT_MS;
|
|
27954
|
+
let notFoundPolls = 0;
|
|
27955
|
+
for (;;) {
|
|
27956
|
+
let state;
|
|
27957
|
+
try {
|
|
27958
|
+
state = (await client.send(new DescribeSnapshotsCommand({ SnapshotIds: [snapshotId] }))).Snapshots?.[0]?.State;
|
|
27959
|
+
notFoundPolls = 0;
|
|
27960
|
+
} catch (pollError) {
|
|
27961
|
+
if (((pollError instanceof Error ? pollError.name : "") === "InvalidSnapshot.NotFound" || errMsg(pollError).includes("InvalidSnapshot.NotFound")) && ++notFoundPolls <= 24) state = void 0;
|
|
27962
|
+
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);
|
|
27963
|
+
}
|
|
27964
|
+
if (state === "completed") {
|
|
27965
|
+
logger.info(`Final snapshot ${snapshotId} completed for ${logicalId} (${volumeId})`);
|
|
27966
|
+
return snapshotId;
|
|
27967
|
+
}
|
|
27968
|
+
if (state === "error") throw new CdkdError(`Final snapshot ${snapshotId} for ${logicalId} (${volumeId}) entered 'error' state. The volume was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
27969
|
+
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");
|
|
27970
|
+
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
28038
27971
|
}
|
|
28039
|
-
return forms;
|
|
28040
|
-
}
|
|
28041
|
-
function canonicalForSecretScan(text) {
|
|
28042
|
-
return secretScanHaystacks(text)[0];
|
|
28043
27972
|
}
|
|
28044
|
-
function
|
|
28045
|
-
|
|
28046
|
-
return [untrimmed.trim(), untrimmed];
|
|
27973
|
+
function isFinalSnapshotError(error) {
|
|
27974
|
+
return error instanceof CdkdError && (error.code === "FINAL_SNAPSHOT_FAILED" || error.code === "FINAL_SNAPSHOT_TIMEOUT" || error.code === "FINAL_SNAPSHOT_UNSUPPORTED");
|
|
28047
27975
|
}
|
|
28048
|
-
function
|
|
28049
|
-
return
|
|
27976
|
+
function ccRoutedFinalSnapshotError(logicalId, resourceType, skipFlagHint) {
|
|
27977
|
+
return new CdkdError(`${logicalIdShown(logicalId)} (${resourceTypeShown(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");
|
|
28050
27978
|
}
|
|
28051
|
-
function
|
|
28052
|
-
|
|
28053
|
-
for (const [plaintext, expression] of secrets ?? []) {
|
|
28054
|
-
const needle = canonicalNeedle(plaintext);
|
|
28055
|
-
if (needle.length > 0) out.set(needle, expression);
|
|
28056
|
-
}
|
|
28057
|
-
return out;
|
|
27979
|
+
function unsupportedFinalSnapshotError(logicalId, resourceType, skipFlagHint) {
|
|
27980
|
+
return new CdkdError(`${logicalIdShown(logicalId)} (${resourceTypeShown(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");
|
|
28058
27981
|
}
|
|
28059
|
-
function
|
|
28060
|
-
|
|
28061
|
-
|
|
28062
|
-
|
|
28063
|
-
|
|
28064
|
-
|
|
28065
|
-
markedUntrimmed.trim(),
|
|
28066
|
-
markedUntrimmed,
|
|
28067
|
-
markedTrimmed
|
|
28068
|
-
], [
|
|
28069
|
-
foldedUntrimmed.trim(),
|
|
28070
|
-
foldedUntrimmed,
|
|
28071
|
-
foldedTrimmed
|
|
28072
|
-
]];
|
|
28073
|
-
const exposure = /* @__PURE__ */ new Map();
|
|
28074
|
-
for (const [plaintext, expression] of secrets) {
|
|
28075
|
-
const needle = canonicalNeedle(plaintext);
|
|
28076
|
-
const canonicalHit = haystacks.some((haystack) => haystack === needle || needle.length >= 4 && haystack.includes(needle));
|
|
28077
|
-
const [markedNeedle, foldedNeedle] = detectionNeedlesOf(secrets, plaintext, needle);
|
|
28078
|
-
const hitIn = (wides, wideNeedle, floor) => wides.some((wide) => wide === wideNeedle || floor >= 4 && wide.includes(wideNeedle));
|
|
28079
|
-
const wideHit = hitIn(wideHaystacks[0], markedNeedle, markedNeedle.length) || hitIn(wideHaystacks[1], foldedNeedle, Math.min(markedNeedle.length, foldedNeedle.length));
|
|
28080
|
-
const rawHit = plaintext.length >= 4 && text.includes(plaintext);
|
|
28081
|
-
if (canonicalHit || wideHit || rawHit) exposure.set(plaintext, expression);
|
|
27982
|
+
async function createPreDeleteFinalSnapshot(resourceType, physicalId, logicalId, clients, logger) {
|
|
27983
|
+
switch (resourceType) {
|
|
27984
|
+
case "AWS::EC2::Volume": return createEbsFinalSnapshot(clients.ec2, physicalId, logicalId, logger);
|
|
27985
|
+
case "AWS::Redshift::Cluster": return createRedshiftFinalSnapshot(clients.redshift, physicalId, logicalId, logger);
|
|
27986
|
+
case "AWS::ElastiCache::ReplicationGroup": return createReplicationGroupFinalSnapshot(clients.elastiCache, physicalId, logicalId, logger);
|
|
27987
|
+
default: throw new CdkdError(`No pre-delete final-snapshot implementation for ${logicalId} (${resourceType}). The resource was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
28082
27988
|
}
|
|
28083
|
-
return exposure.size > 0 ? exposure : void 0;
|
|
28084
27989
|
}
|
|
28085
|
-
function
|
|
28086
|
-
const
|
|
28087
|
-
|
|
28088
|
-
|
|
28089
|
-
|
|
28090
|
-
|
|
27990
|
+
async function waitForNamedSnapshot(opts) {
|
|
27991
|
+
const { snapshotId, logicalId, sourceId, noun, logger } = opts;
|
|
27992
|
+
logger.info(`${opts.reused ? "Resuming" : "Creating"} final snapshot ${snapshotId} for ${logicalId} (${sourceId}) — DeletionPolicy: Snapshot`);
|
|
27993
|
+
const deadline = Date.now() + PRE_DELETE_SNAPSHOT_TIMEOUT_MS;
|
|
27994
|
+
let notFoundPolls = 0;
|
|
27995
|
+
for (;;) {
|
|
27996
|
+
let status;
|
|
27997
|
+
try {
|
|
27998
|
+
status = await opts.pollStatus();
|
|
27999
|
+
notFoundPolls = 0;
|
|
28000
|
+
} catch (pollError) {
|
|
28001
|
+
if (opts.isTransientNotFound(pollError) && ++notFoundPolls <= 24) status = void 0;
|
|
28002
|
+
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);
|
|
28003
|
+
}
|
|
28004
|
+
if (status === opts.readyStatus) {
|
|
28005
|
+
logger.info(`Final snapshot ${snapshotId} ready for ${logicalId} (${sourceId})`);
|
|
28006
|
+
return snapshotId;
|
|
28007
|
+
}
|
|
28008
|
+
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");
|
|
28009
|
+
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");
|
|
28010
|
+
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
28091
28011
|
}
|
|
28092
|
-
return exposure.size > 0 ? exposure : void 0;
|
|
28093
|
-
}
|
|
28094
|
-
function carriedExportAliasExposure(exportName, recordedThisPass, noEchoParameterValues) {
|
|
28095
|
-
const carried = /* @__PURE__ */ new Map();
|
|
28096
|
-
shareLogOnlyValues(carried, recordedThisPass);
|
|
28097
|
-
const exposure = exportNameSecretExposure(exportName, carried, recordedThisPass, noEchoParameterValues);
|
|
28098
|
-
if (exposure === void 0) return void 0;
|
|
28099
|
-
return {
|
|
28100
|
-
exposure,
|
|
28101
|
-
noEchoOnly: isNoEchoOnlyExposure(exposure, carried, recordedThisPass)
|
|
28102
|
-
};
|
|
28103
28012
|
}
|
|
28104
|
-
function
|
|
28105
|
-
|
|
28106
|
-
return noEchoParametersReadBy(exportName, sources);
|
|
28013
|
+
function errorNameOrMessageIncludes(error, token) {
|
|
28014
|
+
return (error instanceof Error ? error.name : "") === token || errMsg(error).includes(token);
|
|
28107
28015
|
}
|
|
28108
|
-
|
|
28109
|
-
|
|
28110
|
-
|
|
28016
|
+
const REUSE_PROBE_MAX_PAGES = 10;
|
|
28017
|
+
function generatedSnapshotNameMatcher(physicalId, resourceType) {
|
|
28018
|
+
const escaped = finalSnapshotNamePrefix(physicalId, resourceType).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28019
|
+
return new RegExp(`^${escaped}\\d{8}-\\d{6}$`);
|
|
28111
28020
|
}
|
|
28112
|
-
|
|
28113
|
-
|
|
28114
|
-
|
|
28115
|
-
|
|
28116
|
-
|
|
28117
|
-
|
|
28118
|
-
|
|
28119
|
-
const
|
|
28120
|
-
|
|
28121
|
-
|
|
28122
|
-
|
|
28021
|
+
const RESUMABLE_SNAPSHOT_STATUS = "creating";
|
|
28022
|
+
async function createRedshiftFinalSnapshot(client, clusterId, logicalId, logger) {
|
|
28023
|
+
const matches = generatedSnapshotNameMatcher(clusterId, "AWS::Redshift::Cluster");
|
|
28024
|
+
let snapshotId;
|
|
28025
|
+
try {
|
|
28026
|
+
let marker;
|
|
28027
|
+
for (let page = 0; page < REUSE_PROBE_MAX_PAGES && !snapshotId; page++) {
|
|
28028
|
+
const existing = await client.send(new DescribeClusterSnapshotsCommand({
|
|
28029
|
+
ClusterIdentifier: clusterId,
|
|
28030
|
+
SnapshotType: "manual",
|
|
28031
|
+
...marker !== void 0 && { Marker: marker }
|
|
28032
|
+
}));
|
|
28033
|
+
snapshotId = existing.Snapshots?.find((s) => s.SnapshotIdentifier !== void 0 && matches.test(s.SnapshotIdentifier) && s.Status === RESUMABLE_SNAPSHOT_STATUS)?.SnapshotIdentifier;
|
|
28034
|
+
marker = existing.Marker;
|
|
28035
|
+
if (marker === void 0) break;
|
|
28123
28036
|
}
|
|
28124
|
-
if (
|
|
28037
|
+
if (snapshotId) logger.debug(`Resuming in-flight final snapshot ${snapshotId} for ${logicalId} (${clusterId}) from a previous delete attempt`);
|
|
28038
|
+
} catch (probeError) {
|
|
28039
|
+
logger.debug(`Final-snapshot reuse probe failed for ${clusterId}: ${errMsg(probeError)}`);
|
|
28125
28040
|
}
|
|
28126
|
-
|
|
28127
|
-
|
|
28128
|
-
|
|
28129
|
-
|
|
28130
|
-
|
|
28131
|
-
|
|
28132
|
-
|
|
28133
|
-
|
|
28134
|
-
|
|
28135
|
-
|
|
28136
|
-
|
|
28137
|
-
|
|
28138
|
-
|
|
28139
|
-
}
|
|
28140
|
-
|
|
28141
|
-
|
|
28142
|
-
|
|
28143
|
-
|
|
28144
|
-
|
|
28145
|
-
|
|
28146
|
-
const shown = name.kind === "masked" ? `${maskedLabel(name.text)} ` : "";
|
|
28147
|
-
const ownerForceMask = /* @__PURE__ */ new Map();
|
|
28148
|
-
const ownerHaystacks = secretScanHaystacks(outputKey);
|
|
28149
|
-
for (const [plaintext, expression] of exposure) {
|
|
28150
|
-
const needle = canonicalNeedle(plaintext);
|
|
28151
|
-
if (ownerHaystacks.some((haystack) => haystack === needle) || plaintext.length >= 4) ownerForceMask.set(plaintext, expression);
|
|
28041
|
+
const reused = snapshotId !== void 0;
|
|
28042
|
+
if (!snapshotId) {
|
|
28043
|
+
const newId = buildFinalSnapshotIdentifier(clusterId, "AWS::Redshift::Cluster");
|
|
28044
|
+
try {
|
|
28045
|
+
await client.send(new CreateClusterSnapshotCommand({
|
|
28046
|
+
SnapshotIdentifier: newId,
|
|
28047
|
+
ClusterIdentifier: clusterId
|
|
28048
|
+
}));
|
|
28049
|
+
snapshotId = newId;
|
|
28050
|
+
} catch (createError) {
|
|
28051
|
+
if (errorNameOrMessageIncludes(createError, "ClusterNotFoundFault")) {
|
|
28052
|
+
logger.debug(`Redshift cluster ${clusterId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
28053
|
+
return null;
|
|
28054
|
+
}
|
|
28055
|
+
if (errorNameOrMessageIncludes(createError, "InvalidClusterStateFault")) {
|
|
28056
|
+
logger.info(`Redshift cluster ${clusterId} is not in a snapshottable state (likely already deleting) — skipping the final snapshot for ${logicalId}: ${errMsg(createError)}`);
|
|
28057
|
+
return null;
|
|
28058
|
+
}
|
|
28059
|
+
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");
|
|
28060
|
+
}
|
|
28152
28061
|
}
|
|
28153
|
-
const
|
|
28154
|
-
|
|
28155
|
-
|
|
28062
|
+
const readySnapshotId = await waitForNamedSnapshot({
|
|
28063
|
+
snapshotId,
|
|
28064
|
+
logicalId,
|
|
28065
|
+
sourceId: clusterId,
|
|
28066
|
+
noun: "cluster",
|
|
28067
|
+
reused,
|
|
28068
|
+
readyStatus: "available",
|
|
28069
|
+
failedStatuses: [
|
|
28070
|
+
"failed",
|
|
28071
|
+
"cancelled",
|
|
28072
|
+
"deleted"
|
|
28073
|
+
],
|
|
28074
|
+
pollStatus: async () => {
|
|
28075
|
+
return (await client.send(new DescribeClusterSnapshotsCommand({ SnapshotIdentifier: snapshotId }))).Snapshots?.[0]?.Status;
|
|
28076
|
+
},
|
|
28077
|
+
isTransientNotFound: (e) => errorNameOrMessageIncludes(e, "ClusterSnapshotNotFoundFault"),
|
|
28078
|
+
logger
|
|
28079
|
+
});
|
|
28080
|
+
await waitForRedshiftClusterSettled(client, clusterId, logicalId, logger);
|
|
28081
|
+
return readySnapshotId;
|
|
28156
28082
|
}
|
|
28157
|
-
function
|
|
28158
|
-
const
|
|
28159
|
-
|
|
28160
|
-
|
|
28161
|
-
|
|
28162
|
-
|
|
28163
|
-
|
|
28164
|
-
|
|
28165
|
-
|
|
28166
|
-
|
|
28167
|
-
|
|
28168
|
-
|
|
28169
|
-
|
|
28170
|
-
|
|
28171
|
-
|
|
28172
|
-
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
|
|
28178
|
-
};
|
|
28179
|
-
const masked = maskEveryOccurrence(untrimmed, mask);
|
|
28180
|
-
if (masked === untrimmed) return exposure ? { kind: "withheld" } : {
|
|
28181
|
-
kind: "safe",
|
|
28182
|
-
text: shown
|
|
28183
|
-
};
|
|
28184
|
-
if (stateKeySecretExposure(masked, secrets) || forceMask !== void 0 && secretsPresentIn(masked, forceMask)) return { kind: "withheld" };
|
|
28185
|
-
return {
|
|
28186
|
-
kind: "masked",
|
|
28187
|
-
text: canonicalForSecretScan(masked)
|
|
28188
|
-
};
|
|
28189
|
-
}
|
|
28190
|
-
function secretBearing(display) {
|
|
28191
|
-
return display.kind !== "safe";
|
|
28192
|
-
}
|
|
28193
|
-
const WITHHELD_NAME_DISPLAY = "<name withheld: contains a secret>";
|
|
28194
|
-
function displayTextOrWithheld(display) {
|
|
28195
|
-
return display.kind === "withheld" ? WITHHELD_NAME_DISPLAY : display.text;
|
|
28196
|
-
}
|
|
28197
|
-
function maskedLabel(maskedText) {
|
|
28198
|
-
return /[^ -~]/.test(maskedText) ? "(masked, name withheld: it carries characters this line cannot show as tested)" : `(masked: ${displayIdent(maskedText)})`;
|
|
28199
|
-
}
|
|
28200
|
-
function secretBearingStateKeyWarning(stackName, display) {
|
|
28201
|
-
const clause = display.kind === "masked" ? `${maskedLabel(display.text)} ` : `(the name is withheld: masking it would leave the secret readable) `;
|
|
28202
|
-
return `State for ${displayStackName(canonicalForSecretScan(stackName))} holds an output KEY that renders a secret ${clause}— cdkd scrub cannot rewrite a key, only a value, because the key IS the export name consumers resolve by. Give that output a non-secret Export.Name and redeploy: the next deploy replaces state.outputs and the exports index entirely. ROTATE the exposed secret.`;
|
|
28203
|
-
}
|
|
28204
|
-
function exportAliasCollisionWarning(outputKey, exportName, secrets) {
|
|
28205
|
-
const corpus = printingCorpusOf(secrets);
|
|
28206
|
-
const shown = displayTextOrWithheld(secretSafeKeyDisplay(exportName, corpus));
|
|
28207
|
-
const from = displayTextOrWithheld(secretSafeKeyDisplay(outputKey, corpus));
|
|
28208
|
-
return `Output ${from} exports as "${shown}", which is also the name of another output in this stack — skipping the export alias, so output ${shown} keeps its own value and the export is not published. A consumer's Fn::ImportValue on "${shown}" therefore resolves to output ${shown}, NOT to ${from} (CloudFormation would publish both). Rename the export, or the colliding output.`;
|
|
28209
|
-
}
|
|
28210
|
-
function exportAliasCollisionScrubWarning(outputKey, exportName, secrets) {
|
|
28211
|
-
const corpus = printingCorpusOf(secrets);
|
|
28212
|
-
const nameDisplay = (name) => secretSafeKeyDisplay(name, corpus);
|
|
28213
|
-
const exportDisplay = nameDisplay(exportName);
|
|
28214
|
-
const shown = displayTextOrWithheld(exportDisplay);
|
|
28215
|
-
const storedUnder = exportDisplay.kind === "withheld" ? "the stored value under that name" : `the stored value under "${shown}"`;
|
|
28216
|
-
const ownerDisplay = nameDisplay(outputKey);
|
|
28217
|
-
return `Output ${ownerDisplay.kind === "withheld" && exportDisplay.kind === "withheld" ? "<the owning output, name withheld: contains a secret>" : displayTextOrWithheld(ownerDisplay)} exports as ${exportDisplay.kind === "withheld" ? shown : `"${shown}"`}, which is also the name of another output in this stack — state cannot say which of the two ${storedUnder} came from, so that key is redacted by value match instead of by template position, and two references resolving to the same value could still collapse there. Rename the export, or the colliding output, and redeploy.`;
|
|
28218
|
-
}
|
|
28219
|
-
|
|
28220
|
-
//#endregion
|
|
28221
|
-
//#region src/deployment/deploy-engine/outputs.ts
|
|
28222
|
-
var ForwardingSecrets = class extends Map {
|
|
28223
|
-
target;
|
|
28224
|
-
constructor(target) {
|
|
28225
|
-
super();
|
|
28226
|
-
this.target = target;
|
|
28227
|
-
}
|
|
28228
|
-
set(plaintext, expression) {
|
|
28229
|
-
super.set(plaintext, expression);
|
|
28230
|
-
this.target?.set(plaintext, expression);
|
|
28231
|
-
return this;
|
|
28232
|
-
}
|
|
28233
|
-
};
|
|
28234
|
-
function handleOutputResolutionFailure(error, outputKey, outputs, secrets, inheritedSecrets) {
|
|
28235
|
-
const union = unionOfSecretBags([inheritedSecrets, secrets]);
|
|
28236
|
-
const detail = maskSecretsInText(error instanceof Error ? error.message || error.name : String(error), union);
|
|
28237
|
-
if (this.options.strictGetAtt) {
|
|
28238
|
-
const cause = typeof error === "string" ? maskSecretsInText(error, union) : maskSecretsInError(error, union);
|
|
28239
|
-
throw new Error(`Failed to resolve output ${outputKey}: ${detail} (--strict-getatt promotes output resolution failures to deploy errors; drop the flag to skip the output instead)`, cause instanceof Error || typeof cause === "string" ? { cause } : {});
|
|
28083
|
+
async function waitForRedshiftClusterSettled(client, clusterId, logicalId, logger) {
|
|
28084
|
+
const deadline = Date.now() + REDSHIFT_CLUSTER_SETTLE_TIMEOUT_MS;
|
|
28085
|
+
for (;;) {
|
|
28086
|
+
let status;
|
|
28087
|
+
try {
|
|
28088
|
+
status = (await client.send(new DescribeClustersCommand({ ClusterIdentifier: clusterId }))).Clusters?.[0]?.ClusterStatus;
|
|
28089
|
+
} catch (pollError) {
|
|
28090
|
+
if (errorNameOrMessageIncludes(pollError, "ClusterNotFoundFault")) return;
|
|
28091
|
+
logger.debug(`Could not read Redshift cluster ${clusterId} while waiting for it to settle: ${errMsg(pollError)}`);
|
|
28092
|
+
return;
|
|
28093
|
+
}
|
|
28094
|
+
if (status === void 0 || status === "available") {
|
|
28095
|
+
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
28096
|
+
return;
|
|
28097
|
+
}
|
|
28098
|
+
if (Date.now() >= deadline) {
|
|
28099
|
+
logger.info(`Redshift cluster ${clusterId} (${logicalId}) is still '${status}' after the post-snapshot settle wait — proceeding with the delete anyway.`);
|
|
28100
|
+
return;
|
|
28101
|
+
}
|
|
28102
|
+
logger.debug(`Waiting for Redshift cluster ${clusterId} to settle (status: ${status})`);
|
|
28103
|
+
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
28240
28104
|
}
|
|
28241
|
-
this.logger.warn(`Failed to resolve output ${outputKey}: ${detail}`);
|
|
28242
|
-
outputs[outputKey] = void 0;
|
|
28243
28105
|
}
|
|
28244
|
-
async function
|
|
28245
|
-
|
|
28246
|
-
this.carriedExportAliasRefusal = void 0;
|
|
28247
|
-
if (!template.Outputs) {
|
|
28248
|
-
this.carriedExportAliasRefusal = seedOnlyAliasRefusal(noEchoParameterValueSeed(template.Parameters, parameterValues, this.options.inheritedSecrets, this.options.parameters));
|
|
28249
|
-
return {};
|
|
28250
|
-
}
|
|
28251
|
-
const outputs = Object.create(null);
|
|
28252
|
-
const context = this.buildResolverContext({
|
|
28253
|
-
template,
|
|
28254
|
-
resources,
|
|
28255
|
-
...parameterValues && { parameters: parameterValues },
|
|
28256
|
-
...conditions && { conditions }
|
|
28257
|
-
}, stackName);
|
|
28258
|
-
context.printingSecrets = /* @__PURE__ */ new Map();
|
|
28259
|
-
const refuseMaskedOutputReads = (outputKey, ownReads) => {
|
|
28260
|
-
const added = ownReads.filter((read) => read.kind === "ref-state-key");
|
|
28261
|
-
if (added.length === 0) return;
|
|
28262
|
-
throw markNonRetryable(/* @__PURE__ */ new Error(`Cannot resolve ${added.map((read) => read.display).join(", ")} for output ${outputKey}: cdkd's recorded state holds only the redaction mask there, so this output would publish a value cdkd cannot confirm (for most such types the resource's raw physical id) instead of the value CloudFormation's Ref returns — a possibly wrong value that a consuming stack's Fn::ImportValue would accept and send to AWS. The output is not published. ${DeployEngine.maskedRecordRemedyFor(added, context.resources)}`));
|
|
28263
|
-
};
|
|
28264
|
-
const outputsPassSecrets = context.recordedSecretValues ?? EMPTY_SECRETS;
|
|
28265
|
-
const outputsPassInherited = context.inheritedSecrets ?? EMPTY_SECRETS;
|
|
28266
|
-
if (context.recordedSecretValues) this.outputsPassSecretMaps.push(context.recordedSecretValues);
|
|
28267
|
-
const publishedOutputNames = collectPublishedOutputNames(template.Outputs, conditions);
|
|
28268
|
-
let outputsPassCompleted = false;
|
|
28106
|
+
async function createReplicationGroupFinalSnapshot(client, replicationGroupId, logicalId, logger) {
|
|
28107
|
+
let snapshotSource;
|
|
28269
28108
|
try {
|
|
28270
|
-
|
|
28271
|
-
|
|
28272
|
-
|
|
28273
|
-
|
|
28274
|
-
}
|
|
28275
|
-
const ownReads = [];
|
|
28276
|
-
try {
|
|
28277
|
-
const resolved = await this.resolver.resolve(output.Value, {
|
|
28278
|
-
...context,
|
|
28279
|
-
redactedAttributeReads: ownReads
|
|
28280
|
-
});
|
|
28281
|
-
refuseMaskedOutputReads(outputKey, ownReads);
|
|
28282
|
-
if (resolved === void 0 && this.options.strictGetAtt) throw markNonRetryable(/* @__PURE__ */ new Error("the value resolved to nothing"));
|
|
28283
|
-
outputs[outputKey] = resolved;
|
|
28284
|
-
} catch (error) {
|
|
28285
|
-
this.handleOutputResolutionFailure(error, outputKey, outputs, outputsPassSecrets, outputsPassInherited);
|
|
28286
|
-
}
|
|
28109
|
+
const group = (await client.send(new DescribeReplicationGroupsCommand({ ReplicationGroupId: replicationGroupId }))).ReplicationGroups?.[0];
|
|
28110
|
+
if (group === void 0) {
|
|
28111
|
+
logger.debug(`Replication group ${replicationGroupId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
28112
|
+
return null;
|
|
28287
28113
|
}
|
|
28288
|
-
|
|
28289
|
-
|
|
28290
|
-
|
|
28291
|
-
|
|
28292
|
-
|
|
28293
|
-
return secretBearingExportNameWarning(outputKey, exportName, verdict.exposure, context.recordedSecretValues, verdict.noEchoOnly);
|
|
28294
|
-
};
|
|
28295
|
-
const resolvedNames = [];
|
|
28296
|
-
for (const [outputKey, output] of Object.entries(template.Outputs)) {
|
|
28297
|
-
if (isOutputSuppressedByCondition(output, conditions)) continue;
|
|
28298
|
-
if (!output.Export?.Name) continue;
|
|
28299
|
-
const value = outputs[outputKey];
|
|
28300
|
-
if (value === void 0) continue;
|
|
28301
|
-
const nameSecrets = new ForwardingSecrets(context.recordedSecretValues);
|
|
28302
|
-
if (context.recordedSecretValues) shareLogOnlyValues(nameSecrets, context.recordedSecretValues);
|
|
28303
|
-
const nameReads = [];
|
|
28304
|
-
let exportName;
|
|
28305
|
-
try {
|
|
28306
|
-
exportName = typeof output.Export.Name === "string" ? output.Export.Name : await this.resolver.resolve(output.Export.Name, {
|
|
28307
|
-
...context,
|
|
28308
|
-
recordedSecretValues: nameSecrets,
|
|
28309
|
-
redactedAttributeReads: nameReads
|
|
28310
|
-
});
|
|
28311
|
-
refuseMaskedOutputReads(outputKey, nameReads);
|
|
28312
|
-
} catch (error) {
|
|
28313
|
-
this.handleOutputResolutionFailure(error, outputKey, outputs, outputsPassSecrets, outputsPassInherited);
|
|
28314
|
-
continue;
|
|
28315
|
-
}
|
|
28316
|
-
if (typeof exportName !== "string") continue;
|
|
28317
|
-
resolvedNames.push({
|
|
28318
|
-
outputKey,
|
|
28319
|
-
exportName,
|
|
28320
|
-
value,
|
|
28321
|
-
outputValue: output.Value,
|
|
28322
|
-
nameSource: output.Export.Name,
|
|
28323
|
-
nameSecrets
|
|
28324
|
-
});
|
|
28114
|
+
if (group.ClusterEnabled === true) snapshotSource = { ReplicationGroupId: replicationGroupId };
|
|
28115
|
+
else {
|
|
28116
|
+
const member = group.NodeGroups?.flatMap((n) => n.NodeGroupMembers ?? []).find((m) => m.CurrentRole === "primary")?.CacheClusterId ?? group.MemberClusters?.[0];
|
|
28117
|
+
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");
|
|
28118
|
+
snapshotSource = { CacheClusterId: member };
|
|
28325
28119
|
}
|
|
28326
|
-
|
|
28327
|
-
|
|
28328
|
-
|
|
28329
|
-
|
|
28330
|
-
|
|
28331
|
-
else if (isExportAliasCollision(exportName, outputKey, publishedOutputNames)) this.logger.warn(exportAliasCollisionWarning(outputKey, exportName, outputsPassSecrets));
|
|
28332
|
-
else {
|
|
28333
|
-
outputs[exportName] = value;
|
|
28334
|
-
if (!this.resolvedExportNames.includes(exportName)) this.resolvedExportNames.push(exportName);
|
|
28335
|
-
this.outputsTemplateSource[exportName] = outputValue;
|
|
28336
|
-
}
|
|
28120
|
+
} catch (describeError) {
|
|
28121
|
+
if (describeError instanceof CdkdError) throw describeError;
|
|
28122
|
+
if (errorNameOrMessageIncludes(describeError, "ReplicationGroupNotFoundFault")) {
|
|
28123
|
+
logger.debug(`Replication group ${replicationGroupId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
28124
|
+
return null;
|
|
28337
28125
|
}
|
|
28338
|
-
|
|
28339
|
-
} finally {
|
|
28340
|
-
if (!outputsPassCompleted) this.outputsSourceUsable = false;
|
|
28126
|
+
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);
|
|
28341
28127
|
}
|
|
28342
|
-
|
|
28343
|
-
|
|
28344
|
-
|
|
28128
|
+
const matches = generatedSnapshotNameMatcher(replicationGroupId, "AWS::ElastiCache::ReplicationGroup");
|
|
28129
|
+
let snapshotId;
|
|
28130
|
+
try {
|
|
28131
|
+
let marker;
|
|
28132
|
+
for (let page = 0; page < REUSE_PROBE_MAX_PAGES && !snapshotId; page++) {
|
|
28133
|
+
const existing = await client.send(new DescribeSnapshotsCommand$1({
|
|
28134
|
+
...snapshotSource,
|
|
28135
|
+
...marker !== void 0 && { Marker: marker }
|
|
28136
|
+
}));
|
|
28137
|
+
snapshotId = existing.Snapshots?.find((s) => s.SnapshotName !== void 0 && matches.test(s.SnapshotName) && s.SnapshotStatus === RESUMABLE_SNAPSHOT_STATUS)?.SnapshotName;
|
|
28138
|
+
marker = existing.Marker;
|
|
28139
|
+
if (marker === void 0) break;
|
|
28140
|
+
}
|
|
28141
|
+
if (snapshotId) logger.debug(`Resuming in-flight final snapshot ${snapshotId} for ${logicalId} (${replicationGroupId}) from a previous delete attempt`);
|
|
28142
|
+
} catch (probeError) {
|
|
28143
|
+
logger.debug(`Final-snapshot reuse probe failed for ${replicationGroupId}: ${errMsg(probeError)}`);
|
|
28345
28144
|
}
|
|
28346
|
-
|
|
28347
|
-
|
|
28348
|
-
|
|
28349
|
-
|
|
28350
|
-
|
|
28351
|
-
|
|
28352
|
-
|
|
28353
|
-
|
|
28354
|
-
|
|
28145
|
+
const reused = snapshotId !== void 0;
|
|
28146
|
+
if (!snapshotId) {
|
|
28147
|
+
const newId = buildFinalSnapshotIdentifier(replicationGroupId, "AWS::ElastiCache::ReplicationGroup");
|
|
28148
|
+
try {
|
|
28149
|
+
await client.send(new CreateSnapshotCommand$1({
|
|
28150
|
+
...snapshotSource,
|
|
28151
|
+
SnapshotName: newId
|
|
28152
|
+
}));
|
|
28153
|
+
snapshotId = newId;
|
|
28154
|
+
} catch (createError) {
|
|
28155
|
+
if (errorNameOrMessageIncludes(createError, "ReplicationGroupNotFoundFault")) {
|
|
28156
|
+
logger.debug(`Replication group ${replicationGroupId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
28157
|
+
return null;
|
|
28158
|
+
}
|
|
28159
|
+
if (errorNameOrMessageIncludes(createError, "InvalidReplicationGroupStateFault")) {
|
|
28160
|
+
logger.info(`Replication group ${replicationGroupId} is not in a snapshottable state (likely already deleting) — skipping the final snapshot for ${logicalId}: ${errMsg(createError)}`);
|
|
28161
|
+
return null;
|
|
28162
|
+
}
|
|
28163
|
+
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");
|
|
28164
|
+
}
|
|
28355
28165
|
}
|
|
28356
|
-
return
|
|
28357
|
-
|
|
28358
|
-
|
|
28359
|
-
|
|
28360
|
-
|
|
28361
|
-
|
|
28362
|
-
|
|
28363
|
-
|
|
28166
|
+
return waitForNamedSnapshot({
|
|
28167
|
+
snapshotId,
|
|
28168
|
+
logicalId,
|
|
28169
|
+
sourceId: replicationGroupId,
|
|
28170
|
+
noun: "replication group",
|
|
28171
|
+
reused,
|
|
28172
|
+
readyStatus: "available",
|
|
28173
|
+
failedStatuses: ["failed"],
|
|
28174
|
+
pollStatus: async () => {
|
|
28175
|
+
return (await client.send(new DescribeSnapshotsCommand$1({ SnapshotName: snapshotId }))).Snapshots?.[0]?.SnapshotStatus;
|
|
28176
|
+
},
|
|
28177
|
+
isTransientNotFound: (e) => errorNameOrMessageIncludes(e, "SnapshotNotFoundFault"),
|
|
28178
|
+
logger
|
|
28179
|
+
});
|
|
28364
28180
|
}
|
|
28365
28181
|
|
|
28366
28182
|
//#endregion
|
|
@@ -28388,1159 +28204,1378 @@ function extractDeploymentEventError(err) {
|
|
|
28388
28204
|
}
|
|
28389
28205
|
|
|
28390
28206
|
//#endregion
|
|
28391
|
-
//#region src/
|
|
28392
|
-
const
|
|
28393
|
-
const
|
|
28394
|
-
|
|
28395
|
-
|
|
28396
|
-
|
|
28207
|
+
//#region src/deployment/rollback-executor/types.ts
|
|
28208
|
+
const NESTED_STACK_RESOURCE_TYPE$1 = "AWS::CloudFormation::Stack";
|
|
28209
|
+
const nonEmptyString = (v) => typeof v === "string" && v.length > 0;
|
|
28210
|
+
|
|
28211
|
+
//#endregion
|
|
28212
|
+
//#region src/deployment/rollback-executor/messages.ts
|
|
28213
|
+
function throwIfDeleteSkipped(result, logicalId, physicalId, duringClause, guardScope) {
|
|
28214
|
+
recordRollbackDeleteGuards(result, logicalId, physicalId, guardScope);
|
|
28215
|
+
const reason = deleteSkipReason(result);
|
|
28216
|
+
if (reason === void 0) return;
|
|
28217
|
+
throw new Error(deleteSkippedMessage(logicalId, physicalId, reason, duringClause));
|
|
28397
28218
|
}
|
|
28398
|
-
function
|
|
28399
|
-
|
|
28219
|
+
function recordRollbackDeleteGuards(result, logicalId, physicalId, scope) {
|
|
28220
|
+
for (const guard of deleteIndeterminateGuards(result)) scope.ctx.recordEvent?.({
|
|
28221
|
+
eventType: "RESOURCE_GUARD_INDETERMINATE",
|
|
28222
|
+
stackName: scope.stackName,
|
|
28223
|
+
operation: "DELETE",
|
|
28224
|
+
logicalId,
|
|
28225
|
+
resourceType: scope.resourceType,
|
|
28226
|
+
...scope.provisionedBy && { provisionedBy: scope.provisionedBy },
|
|
28227
|
+
...physicalId && { physicalId: scope.mask(physicalId) },
|
|
28228
|
+
guard: guard.guard,
|
|
28229
|
+
reason: scope.mask(guard.reason)
|
|
28230
|
+
});
|
|
28400
28231
|
}
|
|
28401
|
-
|
|
28402
|
-
|
|
28232
|
+
const SKIP_FINAL_SNAPSHOT_FLAG = "--skip-final-snapshot";
|
|
28233
|
+
const REPLAYING_STATE_CREATE_CONTEXT = { replayingState: true };
|
|
28234
|
+
function replayingStateCreateContext(secrets) {
|
|
28235
|
+
return {
|
|
28236
|
+
...REPLAYING_STATE_CREATE_CONTEXT,
|
|
28237
|
+
maskSecrets: createSecretMasker(secrets)
|
|
28238
|
+
};
|
|
28403
28239
|
}
|
|
28404
|
-
function
|
|
28405
|
-
|
|
28240
|
+
function maskedRollbackEventError(error, mask) {
|
|
28241
|
+
const extracted = extractDeploymentEventError(error);
|
|
28242
|
+
if (isOwnRemedyError(error)) return {
|
|
28243
|
+
...extracted,
|
|
28244
|
+
ownLines: true
|
|
28245
|
+
};
|
|
28246
|
+
const message = mask(extracted.message);
|
|
28247
|
+
return message === extracted.message ? extracted : {
|
|
28248
|
+
...extracted,
|
|
28249
|
+
message
|
|
28250
|
+
};
|
|
28406
28251
|
}
|
|
28407
|
-
|
|
28408
|
-
|
|
28409
|
-
|
|
28410
|
-
|
|
28411
|
-
|
|
28412
|
-
|
|
28413
|
-
|
|
28414
|
-
|
|
28415
|
-
|
|
28416
|
-
|
|
28417
|
-
|
|
28418
|
-
|
|
28419
|
-
}
|
|
28420
|
-
return swept;
|
|
28252
|
+
function recordRollbackSkip(scope, op, reason) {
|
|
28253
|
+
scope.result.warnings++;
|
|
28254
|
+
scope.result.skipped++;
|
|
28255
|
+
scope.ctx.recordEvent?.({
|
|
28256
|
+
eventType: "ROLLBACK_RESOURCE_SKIPPED",
|
|
28257
|
+
stackName: scope.stackName,
|
|
28258
|
+
operation: op.changeType,
|
|
28259
|
+
logicalId: op.logicalId,
|
|
28260
|
+
resourceType: op.resourceType,
|
|
28261
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy },
|
|
28262
|
+
reason: scope.mask(reason)
|
|
28263
|
+
});
|
|
28421
28264
|
}
|
|
28422
|
-
function
|
|
28423
|
-
|
|
28424
|
-
if (!m) return null;
|
|
28425
|
-
const iso = `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}.${m[7]}Z`;
|
|
28426
|
-
const ms = Date.parse(iso);
|
|
28427
|
-
return Number.isNaN(ms) ? null : ms;
|
|
28265
|
+
function rollbackCannotAddress(record, opResourceType, route, physicalId) {
|
|
28266
|
+
return !(record?.resourceType === "AWS::CloudFormation::Stack" && opResourceType === "AWS::CloudFormation::Stack" && record.provisionedBy !== "cc-api" && route !== "cc-api") && !hasAddressablePhysicalId({ physicalId });
|
|
28428
28267
|
}
|
|
28429
|
-
function
|
|
28430
|
-
if (
|
|
28431
|
-
return
|
|
28268
|
+
function retainedSurvivorId(record, op) {
|
|
28269
|
+
if (hasAddressablePhysicalId(record)) return record.physicalId;
|
|
28270
|
+
if (hasAddressablePhysicalId(op)) return op.physicalId;
|
|
28432
28271
|
}
|
|
28433
|
-
|
|
28434
|
-
|
|
28435
|
-
|
|
28436
|
-
|
|
28437
|
-
|
|
28438
|
-
|
|
28439
|
-
|
|
28440
|
-
cdkdVersion;
|
|
28441
|
-
startedAt;
|
|
28442
|
-
events = [];
|
|
28443
|
-
persistedCount = 0;
|
|
28444
|
-
flushTimer;
|
|
28445
|
-
writeChain = Promise.resolve();
|
|
28446
|
-
warnedOnce = false;
|
|
28447
|
-
finalized = false;
|
|
28448
|
-
constructor(backend, options) {
|
|
28449
|
-
this.backend = backend;
|
|
28450
|
-
this.stackName = options.stackName;
|
|
28451
|
-
this.region = options.region;
|
|
28452
|
-
this.command = options.command;
|
|
28453
|
-
this.runId = options.runId ?? newDeploymentRunId();
|
|
28454
|
-
this.cdkdVersion = options.cdkdVersion ?? getCdkdVersion();
|
|
28455
|
-
this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28456
|
-
}
|
|
28457
|
-
record(event) {
|
|
28458
|
-
try {
|
|
28459
|
-
if (this.finalized) return;
|
|
28460
|
-
this.events.push({
|
|
28461
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28462
|
-
...event
|
|
28463
|
-
});
|
|
28464
|
-
if (this.events.length - this.persistedCount >= FLUSH_EVENT_THRESHOLD) this.scheduleFlush(0);
|
|
28465
|
-
else this.scheduleFlush(FLUSH_INTERVAL_MS);
|
|
28466
|
-
} catch {}
|
|
28272
|
+
const UNADDRESSABLE_SKIP_CAUSE = "Its recorded 'physicalId' is not a non-empty string, so cdkd cannot address the resource in AWS and sent nothing for it; the rollback left it and its state record exactly as they are.";
|
|
28273
|
+
const UNADDRESSABLE_JOURNAL_SKIP_CAUSE = "Its rollback-journal entry's 'physicalId' is not a non-empty string and no state record holds the resource, so cdkd cannot address it in AWS and sent nothing for it; if it was created in AWS, delete it manually.";
|
|
28274
|
+
function skipUnaddressableReplay(scope, logger, op, what, source = "record") {
|
|
28275
|
+
if (source === "journal") {
|
|
28276
|
+
logger.warn(safeMsg` Rollback: Cannot ${what} ${shownLogicalId(op.logicalId)} (${refusalResourceType(op.resourceType)}) ` + "— its rollback journal entry has no non-empty string 'physicalId' and no state record holds it, so cdkd cannot address it in AWS and sent nothing for it. If it was created in AWS, delete it manually.");
|
|
28277
|
+
recordRollbackSkip(scope, op, UNADDRESSABLE_JOURNAL_SKIP_CAUSE);
|
|
28278
|
+
return;
|
|
28467
28279
|
}
|
|
28468
|
-
|
|
28469
|
-
|
|
28470
|
-
|
|
28471
|
-
|
|
28472
|
-
|
|
28473
|
-
|
|
28280
|
+
logger.warn(safeMsg` Rollback: Cannot ${what} ${shownLogicalId(op.logicalId)} (${refusalResourceType(op.resourceType)}) ` + "— its state record has no non-empty string 'physicalId', so cdkd cannot address it in AWS and sent nothing for it. The resource and its state record are left exactly as they are. Repair the record's 'physicalId' (inspect it with `cdkd state show`), then re-converge it with `cdkd deploy`.");
|
|
28281
|
+
recordRollbackSkip(scope, op, UNADDRESSABLE_SKIP_CAUSE);
|
|
28282
|
+
}
|
|
28283
|
+
function effectiveProvisionedBy(record, fallbackProvisionedBy) {
|
|
28284
|
+
return record?.provisionedBy ?? fallbackProvisionedBy;
|
|
28285
|
+
}
|
|
28286
|
+
function safe(value) {
|
|
28287
|
+
return displayIdent(value);
|
|
28288
|
+
}
|
|
28289
|
+
const OWN_REMEDY_ERRORS = /* @__PURE__ */ new WeakSet();
|
|
28290
|
+
function isOwnRemedyError(error) {
|
|
28291
|
+
return error instanceof Error && OWN_REMEDY_ERRORS.has(error);
|
|
28292
|
+
}
|
|
28293
|
+
function maskedFailureText(prefix, error, mask) {
|
|
28294
|
+
const text = rollbackFailureText(error);
|
|
28295
|
+
return prefix + (isOwnRemedyError(error) ? text : mask(text));
|
|
28296
|
+
}
|
|
28297
|
+
function ownRemedyError(error) {
|
|
28298
|
+
OWN_REMEDY_ERRORS.add(error);
|
|
28299
|
+
return error;
|
|
28300
|
+
}
|
|
28301
|
+
function rollbackFailureText(error) {
|
|
28302
|
+
if (error instanceof Error && OWN_REMEDY_ERRORS.has(error)) return error.message.split("\n").map((line) => displaySafe(line)).join("\n");
|
|
28303
|
+
return displaySafe(error instanceof Error ? error.message : String(error));
|
|
28304
|
+
}
|
|
28305
|
+
function collisionText(msg) {
|
|
28306
|
+
return displayAwsMessage(displaySafe(msg).replace(/[\s\p{Cf}\p{Default_Ignorable_Code_Point}\u2800]{2,}/gu, " "));
|
|
28307
|
+
}
|
|
28308
|
+
function collisionLine(maskedMsg) {
|
|
28309
|
+
return JSON.stringify(collisionText(maskedMsg));
|
|
28310
|
+
}
|
|
28311
|
+
const PASTEABLE_LOGICAL_ID = /^[A-Za-z0-9]{1,255}$/;
|
|
28312
|
+
function refusalLogicalId(logicalId) {
|
|
28313
|
+
return typeof logicalId === "string" && PASTEABLE_LOGICAL_ID.test(logicalId) ? logicalId : "a resource whose logical id is not a plain CloudFormation logical id";
|
|
28314
|
+
}
|
|
28315
|
+
function shownLogicalId(logicalId) {
|
|
28316
|
+
return typeof logicalId === "string" ? logicalIdShown(logicalId) : "a logical id that is not a plain identifier";
|
|
28317
|
+
}
|
|
28318
|
+
function shownChangeType(changeType) {
|
|
28319
|
+
return typeof changeType === "string" ? plainOrDescribed(changeType, "change type") : "a change type that is not a plain identifier";
|
|
28320
|
+
}
|
|
28321
|
+
function refusalResourceType(resourceType) {
|
|
28322
|
+
return typeof resourceType === "string" ? resourceTypeShown(resourceType) : "a resource type that is not a plain identifier";
|
|
28323
|
+
}
|
|
28324
|
+
function quotedPlainOr(value, what) {
|
|
28325
|
+
if (typeof value !== "string" || Array.from(value).length > 255) return `a ${what} that is not a plain identifier`;
|
|
28326
|
+
const unindexed = value.replace(/\[\d+\]/g, "");
|
|
28327
|
+
return unindexed !== "" && plainIdentOr(unindexed, "") === unindexed ? `'${value}'` : `a ${what} that is not a plain identifier`;
|
|
28328
|
+
}
|
|
28329
|
+
const DESCRIBED_PHYSICAL_ID = "a physical id that is not a plain identifier";
|
|
28330
|
+
function refusalPhysicalId(maskedPhysicalId) {
|
|
28331
|
+
return physicalIdShownBesideCommand(maskedPhysicalId, { maskToken: "***" }) ?? DESCRIBED_PHYSICAL_ID;
|
|
28332
|
+
}
|
|
28333
|
+
function describedPhysicalIdPointer(...shown) {
|
|
28334
|
+
return shown.includes(DESCRIBED_PHYSICAL_ID) ? " A physical id is left out of the prose above: it is not a plain identifier — read it from the rollback journal or from the state record of the stack." : "";
|
|
28335
|
+
}
|
|
28336
|
+
function orphanRemedy(logicalId, ctx) {
|
|
28337
|
+
if (ctx.nestedChildRevert === true) return {
|
|
28338
|
+
offered: false,
|
|
28339
|
+
clause: ` This op is reverted inside the revert of a nested stack for the rollback of its parent, where cdkd rollback --orphan cannot reach it: resolve the cause and re-run the rollback of the top-level stack, or re-deploy the top-level stack.${typeof logicalId === "string" && PASTEABLE_LOGICAL_ID.test(logicalId) ? "" : " The id is left out of the prose above: it is not a plain CloudFormation logical id — read it from cdkd events."}`,
|
|
28340
|
+
line: ""
|
|
28341
|
+
};
|
|
28342
|
+
const pasteable = typeof logicalId === "string" && PASTEABLE_LOGICAL_ID.test(logicalId);
|
|
28343
|
+
const idClause = pasteable ? "" : " The id is left out of the prose above and of that command: it is not a plain CloudFormation logical id, so a pasted command could be reshaped by the shell or name a different resource — read it from cdkd events and fill the quoted hole.";
|
|
28344
|
+
const target = ctx.nestedChildStack === void 0 ? void 0 : pasteableCommand("cdkd rollback", [{
|
|
28345
|
+
value: ctx.nestedChildStack,
|
|
28346
|
+
hole: "stack",
|
|
28347
|
+
opts: { plainIdent: true }
|
|
28348
|
+
}, {
|
|
28349
|
+
flag: "--stack-region",
|
|
28350
|
+
value: ctx.region,
|
|
28351
|
+
hole: "region",
|
|
28352
|
+
opts: {
|
|
28353
|
+
plainIdent: true,
|
|
28354
|
+
maxCodePoints: 128
|
|
28474
28355
|
}
|
|
28475
|
-
|
|
28476
|
-
|
|
28477
|
-
|
|
28478
|
-
|
|
28479
|
-
|
|
28480
|
-
|
|
28481
|
-
})
|
|
28482
|
-
}
|
|
28483
|
-
|
|
28484
|
-
|
|
28485
|
-
|
|
28486
|
-
|
|
28487
|
-
|
|
28488
|
-
|
|
28489
|
-
|
|
28490
|
-
|
|
28491
|
-
|
|
28492
|
-
|
|
28493
|
-
|
|
28494
|
-
|
|
28495
|
-
|
|
28356
|
+
}]);
|
|
28357
|
+
const stackClause = target === void 0 ? "" : " This is the rollback of the nested stack itself, and only a rollback of the nested stack itself honours --orphan for this op, so the command names it." + withheldTargetClause(target, "stack", "cdkd rollback", "The name of the nested stack");
|
|
28358
|
+
const rollbackVerb = (target ?? pasteableCommand("cdkd rollback")).command;
|
|
28359
|
+
return {
|
|
28360
|
+
offered: true,
|
|
28361
|
+
clause: `${stackClause}${idClause}`,
|
|
28362
|
+
line: `\nTo orphan it: ${rollbackVerb} --orphan ${pasteable ? logicalId : commandHole("id")}`
|
|
28363
|
+
};
|
|
28364
|
+
}
|
|
28365
|
+
function rerunRollbackPhrase(ctx, topLevel) {
|
|
28366
|
+
return ctx.nestedChildStack === void 0 ? topLevel : `the rollback of the nested stack ${quotedOrDescribed(ctx.nestedChildStack, "nested stack name")} itself`;
|
|
28367
|
+
}
|
|
28368
|
+
function rollbackFinalSnapshotId(resourceType, record, fallbackProvisionedBy) {
|
|
28369
|
+
if (record.updateReplacePolicy !== "Snapshot") return void 0;
|
|
28370
|
+
if (!ATOMIC_FINAL_SNAPSHOT_TYPES.has(resourceType)) return void 0;
|
|
28371
|
+
if (effectiveProvisionedBy(record, fallbackProvisionedBy) === "cc-api") return void 0;
|
|
28372
|
+
return buildFinalSnapshotIdentifier(record.physicalId, resourceType);
|
|
28373
|
+
}
|
|
28374
|
+
function rollbackRetainsNewResource(record) {
|
|
28375
|
+
return record?.updateReplacePolicy === "Retain";
|
|
28376
|
+
}
|
|
28377
|
+
function retainedSurvivorMessages(logicalId, resourceType, survivorPhysicalId, stateClause, mask = (text) => text) {
|
|
28378
|
+
const survivorId = mask(survivorPhysicalId);
|
|
28379
|
+
const clause = mask(stateClause);
|
|
28380
|
+
return {
|
|
28381
|
+
warn: ` ⚠ ${safe(logicalId)} (${safe(resourceType)}) has UpdateReplacePolicy: Retain — the replacement's new physical resource (${displaySafe(survivorId)}) is RETAINED by this rollback and is no longer tracked by cdkd: it keeps running and incurring cost, and destroying the stack will not remove it. Delete it yourself once you no longer need it. ${displaySafe(clause)}`,
|
|
28382
|
+
reason: `UpdateReplacePolicy: Retain kept the replacement's new ${resourceType} (${survivorId}); it is live, still billing, and no longer tracked by cdkd. ${clause}`
|
|
28383
|
+
};
|
|
28384
|
+
}
|
|
28385
|
+
|
|
28386
|
+
//#endregion
|
|
28387
|
+
//#region src/deployment/rollback-executor/names.ts
|
|
28388
|
+
function replayPrefixScope(input, logger, mask, restoring) {
|
|
28389
|
+
const choice = replayPrefixChoice(input);
|
|
28390
|
+
if (choice.kind === "not-applicable") return (fn) => fn();
|
|
28391
|
+
const setting = (skip) => skip ? "SKIPPED" : "KEPT";
|
|
28392
|
+
const subject = `${safe(input.logicalId)} (${safe(input.resourceType)})`;
|
|
28393
|
+
const declared = choice.declared;
|
|
28394
|
+
const secretDerived = declared !== void 0 && mask(declared) !== declared;
|
|
28395
|
+
const withheld = secretDerived || choice.kind === "unreproduced" && declared === void 0;
|
|
28396
|
+
const shown = (value) => displayIdent(mask(String(value)));
|
|
28397
|
+
const quoted = (value) => {
|
|
28398
|
+
const masked = mask(value);
|
|
28399
|
+
const rendered = displayIdent(masked);
|
|
28400
|
+
return rendered === masked ? `"${masked}"` : rendered;
|
|
28401
|
+
};
|
|
28402
|
+
if (choice.kind === "reproduced" && choice.skipPrefix !== choice.recorded) logger.info(mask(` Rollback: ${restoring ? "re-creating" : "reverting"} ${subject} under the user-supplied-name prefix setting that created ` + (withheld ? `it (its physical id is withheld, as its name is secret-derived): ` : `${shown(input.physicalId)} (`) + `stack-name prefix ${setting(choice.skipPrefix)}; the failed deploy ran with it ${setting(choice.recorded)}${withheld ? "" : ")"}`));
|
|
28403
|
+
else if (choice.kind === "unreproduced" && restoring) {
|
|
28404
|
+
const idUnreadable = typeof input.physicalId !== "string" || input.physicalId === "";
|
|
28405
|
+
const prop = safe(choice.property);
|
|
28406
|
+
const unreadable = declared === void 0 ? idUnreadable ? `its ${prop} or its physical id` : `its ${prop}` : idUnreadable ? `its physical id` : void 0;
|
|
28407
|
+
logger.warn(mask(` ⚠ ${subject}: cdkd cannot tell which user-supplied-name prefix setting created the old resource (` + (withheld ? `its physical id is withheld, as its name is secret-derived or unreadable` : shown(input.physicalId)) + `) — ` + (choice.names === void 0 ? unreadable !== void 0 ? `cdkd cannot read ${unreadable} to compare` : `cdkd cannot derive the names its ${prop} takes to compare` : secretDerived ? `its ${prop} is secret-derived, and neither name it derives matches` : `its ${prop} derives to ${quoted(choice.names.skipped)} (prefix skipped) or ${quoted(choice.names.kept)} (prefix kept), and it is neither`) + `; re-creating it with the stack-name prefix ${setting(choice.skipPrefix)}, as the failed deploy ran, so it may come back under a different physical name.`));
|
|
28496
28408
|
}
|
|
28497
|
-
|
|
28498
|
-
|
|
28499
|
-
|
|
28500
|
-
|
|
28409
|
+
const skip = choice.skipPrefix;
|
|
28410
|
+
return (fn) => withSkipPrefix(skip, fn);
|
|
28411
|
+
}
|
|
28412
|
+
function createOpMasker(logger, secrets) {
|
|
28413
|
+
const base = createMaskedLogSinks(logger, (text) => maskSecretsInText(text, secrets));
|
|
28414
|
+
const pairs = [];
|
|
28415
|
+
let sinks = base;
|
|
28416
|
+
return {
|
|
28417
|
+
mask: (text) => typeof text === "string" ? sinks.mask(text) : text,
|
|
28418
|
+
addNamed: (record) => {
|
|
28419
|
+
pairs.push(...secretDerivedNamePairs(record));
|
|
28420
|
+
sinks = withDerivedNameMasks(logger, base, pairs);
|
|
28501
28421
|
}
|
|
28502
|
-
|
|
28503
|
-
|
|
28504
|
-
|
|
28505
|
-
|
|
28506
|
-
|
|
28507
|
-
|
|
28508
|
-
|
|
28509
|
-
|
|
28510
|
-
|
|
28511
|
-
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
|
|
28515
|
-
|
|
28516
|
-
|
|
28517
|
-
|
|
28518
|
-
|
|
28519
|
-
|
|
28520
|
-
|
|
28521
|
-
|
|
28522
|
-
|
|
28523
|
-
|
|
28524
|
-
|
|
28525
|
-
|
|
28526
|
-
|
|
28527
|
-
|
|
28528
|
-
|
|
28422
|
+
};
|
|
28423
|
+
}
|
|
28424
|
+
const OTHER_SPELLED_NAME_KEYS = {
|
|
28425
|
+
"AWS::Cognito::UserPoolDomain": ["Domain"],
|
|
28426
|
+
"AWS::Cognito::UserPoolUser": ["Username"],
|
|
28427
|
+
"AWS::ECS::TaskDefinition": ["Family"],
|
|
28428
|
+
"AWS::ElastiCache::ReplicationGroup": ["ReplicationGroupId"],
|
|
28429
|
+
"AWS::ElastiCache::User": ["UserId"],
|
|
28430
|
+
"AWS::ElastiCache::UserGroup": ["UserGroupId"],
|
|
28431
|
+
"AWS::SES::EmailIdentity": ["EmailIdentity"]
|
|
28432
|
+
};
|
|
28433
|
+
const COMPOSITE_SCOPE_LAST = /^(?:REGIONAL|CLOUDFRONT|GLOBAL)$/;
|
|
28434
|
+
const COMPOSITE_POOL_FIRST = /^[a-z]{2}(?:-gov)?-[a-z]+-\d+_[A-Za-z0-9]+$/;
|
|
28435
|
+
function idNameSegments(id) {
|
|
28436
|
+
const segments = /* @__PURE__ */ new Set();
|
|
28437
|
+
const arn = /^arn:[^:]*:([^:]*):[^:]*:[^:]*:(.+)$/.exec(id);
|
|
28438
|
+
if (arn !== null) {
|
|
28439
|
+
const service = arn[1];
|
|
28440
|
+
const resource = arn[2];
|
|
28441
|
+
const lambda = service === "lambda" ? /^(?:function|layer):([^:]+)(?::([^:]+))?/.exec(resource) : null;
|
|
28442
|
+
if (lambda !== null) {
|
|
28443
|
+
segments.add(lambda[1]);
|
|
28444
|
+
const qualifier = lambda[2];
|
|
28445
|
+
if (qualifier !== void 0 && !/^\d+$/.test(qualifier)) segments.add(qualifier);
|
|
28446
|
+
} else {
|
|
28447
|
+
const parts = resource.split(/[/:]/).filter((part) => part !== "");
|
|
28448
|
+
let body = parts.length > 1 ? parts.slice(1) : parts;
|
|
28449
|
+
if (body.length > 1 && /^\d+$/.test(body[body.length - 1])) body = body.slice(0, -1);
|
|
28450
|
+
for (const part of body) {
|
|
28451
|
+
segments.add(part);
|
|
28452
|
+
if (service === "secretsmanager") segments.add(part.replace(/-[A-Za-z0-9]{6}$/, ""));
|
|
28529
28453
|
}
|
|
28530
|
-
} catch (err) {
|
|
28531
|
-
this.logger.debug(`Deployment-events index unreadable, rewriting: ${describeAwsFailure(err).detail}`);
|
|
28532
28454
|
}
|
|
28533
|
-
return [];
|
|
28534
28455
|
}
|
|
28535
|
-
|
|
28536
|
-
const
|
|
28537
|
-
|
|
28538
|
-
|
|
28539
|
-
command: this.command,
|
|
28540
|
-
cdkdVersion: this.cdkdVersion,
|
|
28541
|
-
startedAt: this.startedAt,
|
|
28542
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28543
|
-
result,
|
|
28544
|
-
eventCount: this.persistedCount
|
|
28545
|
-
}, ...existingRuns.filter((r) => r.runId !== this.runId)].slice(0, 20);
|
|
28546
|
-
return {
|
|
28547
|
-
key,
|
|
28548
|
-
file: {
|
|
28549
|
-
indexVersion: 1,
|
|
28550
|
-
stackName: this.stackName,
|
|
28551
|
-
region: this.region,
|
|
28552
|
-
runs,
|
|
28553
|
-
lastModified: Date.now()
|
|
28554
|
-
},
|
|
28555
|
-
runs
|
|
28556
|
-
};
|
|
28456
|
+
if (id.includes("/")) {
|
|
28457
|
+
const afterSlash = id.slice(id.lastIndexOf("/") + 1);
|
|
28458
|
+
segments.add(afterSlash);
|
|
28459
|
+
segments.add(afterSlash.replace(/:\d+$/, ""));
|
|
28557
28460
|
}
|
|
28558
|
-
|
|
28559
|
-
|
|
28560
|
-
|
|
28561
|
-
|
|
28562
|
-
|
|
28563
|
-
|
|
28461
|
+
if (id.includes("|")) {
|
|
28462
|
+
const parts = id.split("|");
|
|
28463
|
+
parts.forEach((part, index) => {
|
|
28464
|
+
if (part === "") return;
|
|
28465
|
+
if (index === parts.length - 1 && parts.length > 1 && COMPOSITE_SCOPE_LAST.test(part)) return;
|
|
28466
|
+
if (index === 0 && parts.length > 1 && COMPOSITE_POOL_FIRST.test(part)) return;
|
|
28467
|
+
segments.add(part);
|
|
28564
28468
|
});
|
|
28565
|
-
if (stale.length === 0) return;
|
|
28566
|
-
await deleteAndPurgeEventObjects(this.backend, stale, dirPrefix);
|
|
28567
|
-
this.logger.debug(`Pruned ${stale.length} superseded deployment-event stream(s) for ${this.stackName} (${this.region})`);
|
|
28568
28469
|
}
|
|
28569
|
-
|
|
28570
|
-
|
|
28571
|
-
|
|
28572
|
-
|
|
28573
|
-
|
|
28574
|
-
|
|
28575
|
-
|
|
28576
|
-
|
|
28577
|
-
|
|
28578
|
-
|
|
28579
|
-
|
|
28580
|
-
|
|
28581
|
-
|
|
28582
|
-
|
|
28583
|
-
|
|
28584
|
-
|
|
28585
|
-
|
|
28586
|
-
|
|
28587
|
-
|
|
28588
|
-
|
|
28589
|
-
|
|
28590
|
-
|
|
28591
|
-
|
|
28592
|
-
|
|
28593
|
-
|
|
28594
|
-
const
|
|
28595
|
-
|
|
28596
|
-
const raw = await this.backend.getRawObject(key);
|
|
28597
|
-
if (raw !== null) {
|
|
28598
|
-
const parsed = JSON.parse(raw);
|
|
28599
|
-
if (Array.isArray(parsed.runs)) return parsed.runs;
|
|
28600
|
-
}
|
|
28601
|
-
} catch {}
|
|
28602
|
-
const dirPrefix = `${this.backend.prefix}/${stackName}/${region}/deployments/`;
|
|
28603
|
-
const runIds = (await this.backend.listRawKeys(dirPrefix)).filter((k) => k.endsWith(".jsonl")).map((k) => k.slice(dirPrefix.length, -6)).sort().reverse();
|
|
28604
|
-
return Promise.all(runIds.map((runId) => this.summarizeRunFromJsonl(stackName, region, runId)));
|
|
28470
|
+
return [...segments].filter((segment) => segment.length >= 4 && segment !== id);
|
|
28471
|
+
}
|
|
28472
|
+
function ownEntry(table, key) {
|
|
28473
|
+
return Object.hasOwn(table, key) ? table[key] : void 0;
|
|
28474
|
+
}
|
|
28475
|
+
function secretDerivedNamePairs(record) {
|
|
28476
|
+
const { resourceType, properties, logicalId } = record;
|
|
28477
|
+
if (typeof resourceType !== "string" || !isRestorableBag(properties)) return [];
|
|
28478
|
+
const rewritten = reverseReplacementRewrittenNameTypes();
|
|
28479
|
+
const named = /* @__PURE__ */ new Set();
|
|
28480
|
+
const primary = Object.hasOwn(rewritten, resourceType) ? rewritten[resourceType].property : explicitNamePropertyFor(resourceType);
|
|
28481
|
+
if (typeof primary === "string") named.add(primary);
|
|
28482
|
+
for (const key of Object.keys(properties)) if (/(?:Name|Identifier)$/.test(key)) named.add(key);
|
|
28483
|
+
for (const key of ownEntry(OTHER_SPELLED_NAME_KEYS, resourceType) ?? []) named.add(key);
|
|
28484
|
+
const idSpellings = record.physicalIds.filter((id) => typeof id === "string" && id !== "").flatMap((id) => [
|
|
28485
|
+
id,
|
|
28486
|
+
...id.toLowerCase() === id || id.length < 4 ? [] : [id.toLowerCase()],
|
|
28487
|
+
finalSnapshotNamePrefix(id, resourceType),
|
|
28488
|
+
...idNameSegments(id)
|
|
28489
|
+
]);
|
|
28490
|
+
const pairs = [];
|
|
28491
|
+
for (const key of named) {
|
|
28492
|
+
if (!Object.hasOwn(properties, key)) continue;
|
|
28493
|
+
const value = properties[key];
|
|
28494
|
+
if (typeof value !== "string" || value === "") continue;
|
|
28495
|
+
const derived = key === primary && !value.includes("{{resolve:") && value !== "***" ? rewrittenNameSpellings(resourceType, value, logicalId) : [];
|
|
28496
|
+
for (const spelling of [...idSpellings, ...derived]) pairs.push([value, spelling]);
|
|
28605
28497
|
}
|
|
28606
|
-
|
|
28607
|
-
|
|
28608
|
-
|
|
28609
|
-
|
|
28610
|
-
|
|
28611
|
-
|
|
28612
|
-
|
|
28613
|
-
|
|
28614
|
-
|
|
28615
|
-
|
|
28616
|
-
|
|
28617
|
-
|
|
28618
|
-
|
|
28619
|
-
|
|
28620
|
-
|
|
28621
|
-
|
|
28622
|
-
|
|
28623
|
-
|
|
28624
|
-
let finished;
|
|
28625
|
-
for (const e of events) if (e.eventType === "RUN_FINISHED") finished = e;
|
|
28626
|
-
return {
|
|
28627
|
-
runId,
|
|
28628
|
-
command: started?.command ?? fallback.command,
|
|
28629
|
-
cdkdVersion: started?.cdkdVersion ?? fallback.cdkdVersion,
|
|
28630
|
-
startedAt: started?.timestamp ?? fallback.startedAt,
|
|
28631
|
-
finishedAt: finished?.timestamp ?? fallback.finishedAt,
|
|
28632
|
-
result: finished?.result ?? "UNKNOWN",
|
|
28633
|
-
eventCount: events.length
|
|
28634
|
-
};
|
|
28498
|
+
return pairs;
|
|
28499
|
+
}
|
|
28500
|
+
function addRecordNames(opMasker, op, current) {
|
|
28501
|
+
const { logicalId } = op;
|
|
28502
|
+
for (const properties of [op.properties, op.attemptedProperties]) opMasker.addNamed({
|
|
28503
|
+
resourceType: op.resourceType,
|
|
28504
|
+
properties,
|
|
28505
|
+
logicalId,
|
|
28506
|
+
physicalIds: [op.physicalId]
|
|
28507
|
+
});
|
|
28508
|
+
for (const record of [op.previousState, current]) {
|
|
28509
|
+
if (record === null || typeof record !== "object") continue;
|
|
28510
|
+
opMasker.addNamed({
|
|
28511
|
+
resourceType: record.resourceType ?? op.resourceType,
|
|
28512
|
+
properties: record.properties,
|
|
28513
|
+
logicalId,
|
|
28514
|
+
physicalIds: [record.physicalId]
|
|
28515
|
+
});
|
|
28635
28516
|
}
|
|
28636
|
-
|
|
28637
|
-
|
|
28638
|
-
|
|
28639
|
-
|
|
28640
|
-
|
|
28641
|
-
|
|
28642
|
-
|
|
28643
|
-
if (!trimmed) continue;
|
|
28644
|
-
try {
|
|
28645
|
-
events.push(JSON.parse(trimmed));
|
|
28646
|
-
} catch {}
|
|
28647
|
-
}
|
|
28648
|
-
return events;
|
|
28517
|
+
}
|
|
28518
|
+
const ABSENT_BASELINE_SKIP_CAUSE = "Its recorded previous state has no `properties` bag, so there is nothing to restore it to; the rollback left the resource exactly as it is.";
|
|
28519
|
+
function requireRestorableBaseline(bag, logger, { logicalId, consequence, remedy, retry }) {
|
|
28520
|
+
if (isRestorableBag(bag)) return true;
|
|
28521
|
+
if (bag === void 0) {
|
|
28522
|
+
logger.warn(` Rollback: Cannot restore ${shownLogicalId(logicalId)} \u2014 its recorded previous state has no \`properties\` bag, so there is nothing to restore it to. An empty desired state would ${consequence}. The resource is therefore left exactly as it is. ${remedy}`);
|
|
28523
|
+
return false;
|
|
28649
28524
|
}
|
|
28650
|
-
|
|
28651
|
-
|
|
28652
|
-
|
|
28653
|
-
|
|
28654
|
-
|
|
28655
|
-
|
|
28656
|
-
|
|
28657
|
-
|
|
28658
|
-
|
|
28659
|
-
|
|
28660
|
-
|
|
28661
|
-
|
|
28662
|
-
|
|
28663
|
-
|
|
28664
|
-
|
|
28665
|
-
}
|
|
28666
|
-
const noGuards = opts.keep === void 0 && opts.olderThanMs === void 0;
|
|
28667
|
-
const keep = opts.keep ?? (noGuards ? 20 : void 0);
|
|
28668
|
-
const protectedByCount = keep === void 0 ? /* @__PURE__ */ new Set() : new Set(runIdsDesc.slice(0, keep));
|
|
28669
|
-
let candidates = runIdsDesc.filter((id) => !protectedByCount.has(id));
|
|
28670
|
-
if (opts.olderThanMs !== void 0) {
|
|
28671
|
-
const cutoff = (opts.now ?? /* @__PURE__ */ new Date()).getTime() - opts.olderThanMs;
|
|
28672
|
-
candidates = candidates.filter((id) => {
|
|
28673
|
-
const t = runIdTimestampMs(id);
|
|
28674
|
-
return t !== null && t < cutoff;
|
|
28525
|
+
const shape = bag === null ? "null" : Array.isArray(bag) ? "an array" : `a ${typeof bag}`;
|
|
28526
|
+
throw new CdkdError(`Cannot roll ${shownLogicalId(logicalId)} back: its recorded previous state has a \`properties\` field that is not a property bag (${shape}), so cdkd cannot tell what to restore it to. Replaying it would do one of two things, and cdkd does neither: send the malformed value to the provider as-is, or send an empty desired state (which would ${consequence}). The rollback JOURNAL record is kept: once that record holds a property bag again, ${retry}. Otherwise fix forward with \`cdkd deploy\`, or remove the stack with \`cdkd destroy\`.`, "ROLLBACK_UNUSABLE_BASELINE");
|
|
28527
|
+
}
|
|
28528
|
+
function isRestorableBag(bag) {
|
|
28529
|
+
return typeof bag === "object" && bag !== null && !Array.isArray(bag);
|
|
28530
|
+
}
|
|
28531
|
+
async function prepareCreateRollbackFinalSnapshot(op, provisionedBy, ctx, mask) {
|
|
28532
|
+
const { logicalId, resourceType } = op;
|
|
28533
|
+
const physicalId = op.physicalId;
|
|
28534
|
+
switch (finalSnapshotMechanism(resourceType, provisionedBy)) {
|
|
28535
|
+
case "atomic-delete-parameter": return buildFinalSnapshotIdentifier(physicalId, resourceType);
|
|
28536
|
+
case "pre-delete-snapshot":
|
|
28537
|
+
await createPreDeleteFinalSnapshot(resourceType, physicalId, logicalId, ctx.finalSnapshotClients ?? getAwsClients(), {
|
|
28538
|
+
info: (message) => ctx.logger.info(mask(message)),
|
|
28539
|
+
debug: (message) => ctx.logger.debug(mask(message))
|
|
28675
28540
|
});
|
|
28676
|
-
}
|
|
28677
|
-
if (candidates.length === 0) return {
|
|
28678
|
-
deletedRunIds: [],
|
|
28679
|
-
remainingRunIds: runIdsDesc,
|
|
28680
|
-
indexDeleted: false
|
|
28681
|
-
};
|
|
28682
|
-
const deletedSet = new Set(candidates);
|
|
28683
|
-
const deleteKeys = candidates.map((id) => deploymentEventsKey(this.backend.prefix, stackName, region, id));
|
|
28684
|
-
const remainingRunIds = runIdsDesc.filter((id) => !deletedSet.has(id));
|
|
28685
|
-
if (remainingRunIds.length === 0) {
|
|
28686
|
-
await deleteAndPurgeEventObjects(this.backend, [...deleteKeys, indexKey], dirPrefix);
|
|
28687
|
-
return {
|
|
28688
|
-
deletedRunIds: candidates,
|
|
28689
|
-
remainingRunIds,
|
|
28690
|
-
indexDeleted: indexExisted
|
|
28691
|
-
};
|
|
28692
|
-
}
|
|
28693
|
-
await this.rewriteIndexAfterPrune(indexKey, stackName, region, deletedSet);
|
|
28694
|
-
await deleteAndPurgeEventObjects(this.backend, deleteKeys, dirPrefix);
|
|
28695
|
-
return {
|
|
28696
|
-
deletedRunIds: candidates,
|
|
28697
|
-
remainingRunIds,
|
|
28698
|
-
indexDeleted: false
|
|
28699
|
-
};
|
|
28700
|
-
}
|
|
28701
|
-
async rewriteIndexAfterPrune(indexKey, stackName, region, deletedRunIds) {
|
|
28702
|
-
let raw;
|
|
28703
|
-
try {
|
|
28704
|
-
raw = await this.backend.getRawObject(indexKey);
|
|
28705
|
-
} catch {
|
|
28706
|
-
return;
|
|
28707
|
-
}
|
|
28708
|
-
if (raw === null) return;
|
|
28709
|
-
let parsed;
|
|
28710
|
-
try {
|
|
28711
|
-
parsed = JSON.parse(raw);
|
|
28712
|
-
} catch {
|
|
28713
28541
|
return;
|
|
28714
|
-
|
|
28715
|
-
|
|
28716
|
-
const remaining = parsed.runs.filter((r) => !deletedRunIds.has(r.runId));
|
|
28717
|
-
if (remaining.length === parsed.runs.length) return;
|
|
28718
|
-
const file = {
|
|
28719
|
-
indexVersion: 1,
|
|
28720
|
-
stackName,
|
|
28721
|
-
region,
|
|
28722
|
-
runs: remaining,
|
|
28723
|
-
lastModified: Date.now()
|
|
28724
|
-
};
|
|
28725
|
-
await this.backend.putRawObject(indexKey, JSON.stringify(file, null, 2));
|
|
28542
|
+
case "refuse-cc-routed": throw ccRoutedFinalSnapshotError(logicalId, resourceType, SKIP_FINAL_SNAPSHOT_FLAG);
|
|
28543
|
+
case "refuse-unsupported-type": throw unsupportedFinalSnapshotError(logicalId, resourceType, SKIP_FINAL_SNAPSHOT_FLAG);
|
|
28726
28544
|
}
|
|
28727
|
-
}
|
|
28545
|
+
}
|
|
28728
28546
|
|
|
28729
28547
|
//#endregion
|
|
28730
|
-
//#region src/
|
|
28731
|
-
|
|
28732
|
-
"
|
|
28733
|
-
|
|
28734
|
-
|
|
28735
|
-
"
|
|
28736
|
-
|
|
28737
|
-
|
|
28738
|
-
|
|
28739
|
-
|
|
28740
|
-
"AWS::Redshift::Cluster",
|
|
28741
|
-
"AWS::ElastiCache::ReplicationGroup"
|
|
28742
|
-
]);
|
|
28743
|
-
function effectiveDeletionPolicy(resourceType, recorded, properties) {
|
|
28744
|
-
if (recorded !== void 0) return recorded;
|
|
28745
|
-
if (resourceType === "AWS::RDS::DBCluster") return "Snapshot";
|
|
28746
|
-
if (resourceType === "AWS::RDS::DBInstance" && properties?.["DBClusterIdentifier"] === void 0) return "Snapshot";
|
|
28747
|
-
}
|
|
28748
|
-
function replacementDeletePolicy(updateReplacePolicy) {
|
|
28749
|
-
return updateReplacePolicy ?? "Delete";
|
|
28750
|
-
}
|
|
28751
|
-
function finalSnapshotMechanism(resourceType, provisionedBy) {
|
|
28752
|
-
if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resourceType)) return provisionedBy === "cc-api" ? "refuse-cc-routed" : "atomic-delete-parameter";
|
|
28753
|
-
if (PRE_DELETE_SNAPSHOT_TYPES.has(resourceType)) return "pre-delete-snapshot";
|
|
28754
|
-
return "refuse-unsupported-type";
|
|
28755
|
-
}
|
|
28756
|
-
function refusesFinalSnapshot(resourceType, provisionedBy) {
|
|
28757
|
-
const mechanism = finalSnapshotMechanism(resourceType, provisionedBy);
|
|
28758
|
-
return mechanism === "refuse-cc-routed" || mechanism === "refuse-unsupported-type";
|
|
28759
|
-
}
|
|
28760
|
-
function sanitizeSnapshotBase(physicalId) {
|
|
28761
|
-
let base = physicalId.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
28762
|
-
if (!/^[a-z]/.test(base)) base = `r${base}`;
|
|
28763
|
-
return base;
|
|
28764
|
-
}
|
|
28765
|
-
function finalSnapshotNamePrefix(physicalId, resourceType) {
|
|
28766
|
-
return finalSnapshotNameCut(physicalId, resourceType).prefix;
|
|
28767
|
-
}
|
|
28768
|
-
function finalSnapshotNameCut(physicalId, resourceType) {
|
|
28769
|
-
const maxLength = resourceType === "AWS::ElastiCache::CacheCluster" || resourceType === "AWS::ElastiCache::ReplicationGroup" ? 50 : 255;
|
|
28770
|
-
const sanitized = sanitizeSnapshotBase(physicalId);
|
|
28771
|
-
const base = sanitized.slice(0, maxLength - 22).replace(/-+$/, "");
|
|
28772
|
-
return {
|
|
28773
|
-
prefix: `${base}-final-`,
|
|
28774
|
-
sanitized,
|
|
28775
|
-
kept: base.length
|
|
28548
|
+
//#region src/deployment/secret-name-needles.ts
|
|
28549
|
+
function secretNameNeedlesOf(logicalId, record, secrets, options) {
|
|
28550
|
+
const physicalId = typeof record?.physicalId === "string" && record.physicalId !== "" ? record.physicalId : void 0;
|
|
28551
|
+
const bag = secrets !== void 0 && hasMaskableValues(secrets) ? secrets : void 0;
|
|
28552
|
+
const mask = (text) => bag === void 0 ? text : maskSecretsInText(text, bag);
|
|
28553
|
+
const unresolved = (value) => value.includes("{{resolve:") || value === "***";
|
|
28554
|
+
const named = {
|
|
28555
|
+
resourceType: record?.resourceType,
|
|
28556
|
+
properties: record?.properties,
|
|
28557
|
+
logicalId
|
|
28776
28558
|
};
|
|
28777
|
-
|
|
28778
|
-
|
|
28779
|
-
|
|
28780
|
-
|
|
28781
|
-
|
|
28782
|
-
|
|
28783
|
-
|
|
28784
|
-
|
|
28785
|
-
const
|
|
28786
|
-
|
|
28787
|
-
|
|
28788
|
-
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
|
|
28792
|
-
|
|
28793
|
-
|
|
28794
|
-
|
|
28795
|
-
|
|
28796
|
-
snapshotId = (await client.send(new DescribeSnapshotsCommand({
|
|
28797
|
-
OwnerIds: ["self"],
|
|
28798
|
-
Filters: [{
|
|
28799
|
-
Name: `tag:${EBS_FINAL_SNAPSHOT_TAG_KEY}`,
|
|
28800
|
-
Values: [volumeId]
|
|
28801
|
-
}, {
|
|
28802
|
-
Name: "status",
|
|
28803
|
-
Values: ["pending", "completed"]
|
|
28804
|
-
}]
|
|
28805
|
-
}))).Snapshots?.[0]?.SnapshotId;
|
|
28806
|
-
if (snapshotId) logger.debug(`Reusing existing final snapshot ${snapshotId} for ${logicalId} (${volumeId}) from a previous destroy attempt`);
|
|
28807
|
-
} catch (probeError) {
|
|
28808
|
-
logger.debug(`Final-snapshot reuse probe failed for ${volumeId}: ${errMsg(probeError)}`);
|
|
28809
|
-
}
|
|
28810
|
-
if (!snapshotId) try {
|
|
28811
|
-
snapshotId = (await client.send(new CreateSnapshotCommand({
|
|
28812
|
-
VolumeId: volumeId,
|
|
28813
|
-
Description: `cdkd final snapshot for ${volumeId} (DeletionPolicy: Snapshot)`,
|
|
28814
|
-
TagSpecifications: [{
|
|
28815
|
-
ResourceType: "snapshot",
|
|
28816
|
-
Tags: [{
|
|
28817
|
-
Key: EBS_FINAL_SNAPSHOT_TAG_KEY,
|
|
28818
|
-
Value: volumeId
|
|
28819
|
-
}]
|
|
28820
|
-
}]
|
|
28821
|
-
}))).SnapshotId;
|
|
28822
|
-
} catch (createError) {
|
|
28823
|
-
if (isVolumeNotFound(createError)) {
|
|
28824
|
-
logger.debug(`Volume ${volumeId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
28825
|
-
return null;
|
|
28559
|
+
const needles = /* @__PURE__ */ new Set();
|
|
28560
|
+
const add = (needle) => {
|
|
28561
|
+
if (typeof needle === "string" && needle !== "") needles.add(needle);
|
|
28562
|
+
};
|
|
28563
|
+
for (const [raw, derived] of secretDerivedNamePairs({
|
|
28564
|
+
...named,
|
|
28565
|
+
physicalIds: []
|
|
28566
|
+
})) if (typeof raw === "string" && !unresolved(raw) && isSecretDerivedValue(raw, mask)) add(derived);
|
|
28567
|
+
for (const [raw, spelling] of secretDerivedNamePairs({
|
|
28568
|
+
...named,
|
|
28569
|
+
physicalIds: [physicalId ?? "cdkd-no-physical-id-yet"]
|
|
28570
|
+
})) {
|
|
28571
|
+
if (!isSecretDerivedValue(raw, mask)) continue;
|
|
28572
|
+
if (unresolved(raw)) {
|
|
28573
|
+
if (physicalId === void 0) continue;
|
|
28574
|
+
add(spelling);
|
|
28575
|
+
} else {
|
|
28576
|
+
add(raw);
|
|
28577
|
+
if (raw.toLowerCase() !== raw && raw.length >= 4) add(raw.toLowerCase());
|
|
28826
28578
|
}
|
|
28827
|
-
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");
|
|
28828
28579
|
}
|
|
28829
|
-
|
|
28830
|
-
|
|
28831
|
-
|
|
28832
|
-
|
|
28833
|
-
|
|
28834
|
-
|
|
28835
|
-
|
|
28836
|
-
|
|
28837
|
-
|
|
28838
|
-
|
|
28839
|
-
|
|
28840
|
-
|
|
28841
|
-
|
|
28842
|
-
|
|
28843
|
-
|
|
28844
|
-
|
|
28580
|
+
const properties = record?.properties;
|
|
28581
|
+
const path = properties !== null && typeof properties === "object" && Object.hasOwn(properties, "Path") ? properties["Path"] : void 0;
|
|
28582
|
+
if (physicalId !== void 0 && isSecretDerivedValue(path, mask)) add(physicalId);
|
|
28583
|
+
const embedded = options === void 0 ? secrets : options.embedded;
|
|
28584
|
+
const embeddedBag = embedded !== void 0 && hasMaskableValues(embedded) ? embedded : void 0;
|
|
28585
|
+
if (embeddedBag !== void 0 && physicalId !== void 0) {
|
|
28586
|
+
for (const needle of printingCorpusOf(embeddedBag).keys()) if (needle !== "" && (needle === physicalId || needle.length >= 4 && physicalId.includes(needle))) add(needle);
|
|
28587
|
+
}
|
|
28588
|
+
if (physicalId !== void 0) for (const spelling of finalSnapshotSpellingsOf(physicalId, record?.resourceType, needles)) add(spelling);
|
|
28589
|
+
return needles.size > 0 ? needles : void 0;
|
|
28590
|
+
}
|
|
28591
|
+
function finalSnapshotSpellingsOf(physicalId, resourceType, needles) {
|
|
28592
|
+
if (typeof resourceType !== "string" || !(ATOMIC_FINAL_SNAPSHOT_TYPES.has(resourceType) || PRE_DELETE_SNAPSHOT_TYPES.has(resourceType))) return [];
|
|
28593
|
+
const { prefix, sanitized, kept } = finalSnapshotNameCut(physicalId, resourceType);
|
|
28594
|
+
const spellings = [];
|
|
28595
|
+
for (const needle of needles) {
|
|
28596
|
+
const folded = needle.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-");
|
|
28597
|
+
if (folded.length < 4) continue;
|
|
28598
|
+
for (let at = sanitized.indexOf(folded); at !== -1; at = sanitized.indexOf(folded, at + 1)) {
|
|
28599
|
+
if (at >= kept) break;
|
|
28600
|
+
if (at + folded.length > kept) spellings.push(prefix);
|
|
28601
|
+
else if (folded !== needle) spellings.push(folded);
|
|
28845
28602
|
}
|
|
28846
|
-
if (state === "error") throw new CdkdError(`Final snapshot ${snapshotId} for ${logicalId} (${volumeId}) entered 'error' state. The volume was NOT deleted.`, "FINAL_SNAPSHOT_FAILED");
|
|
28847
|
-
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");
|
|
28848
|
-
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
28849
28603
|
}
|
|
28604
|
+
return spellings;
|
|
28605
|
+
}
|
|
28606
|
+
function secretNamesReadBy(logicalId, record, resources, judge = () => ({
|
|
28607
|
+
secrets: void 0,
|
|
28608
|
+
embedded: void 0
|
|
28609
|
+
})) {
|
|
28610
|
+
const read = /* @__PURE__ */ new Set();
|
|
28611
|
+
const leaves = [...wholeStringLeavesOf(record?.properties)].filter((leaf) => leaf !== "");
|
|
28612
|
+
if (leaves.length === 0) return read;
|
|
28613
|
+
for (const [otherId, other] of Object.entries(resources)) {
|
|
28614
|
+
if (otherId === logicalId) continue;
|
|
28615
|
+
const bags = judge(otherId);
|
|
28616
|
+
const needles = secretNameNeedlesOf(otherId, other, bags.secrets, { embedded: bags.embedded });
|
|
28617
|
+
if (needles === void 0) continue;
|
|
28618
|
+
for (const needle of needles) if (leaves.some((leaf) => leaf === needle || needle.length >= 4 && leaf.includes(needle))) read.add(needle);
|
|
28619
|
+
}
|
|
28620
|
+
return read;
|
|
28850
28621
|
}
|
|
28851
|
-
function
|
|
28852
|
-
return
|
|
28853
|
-
}
|
|
28854
|
-
function ccRoutedFinalSnapshotError(logicalId, resourceType, skipFlagHint) {
|
|
28855
|
-
return new CdkdError(`${logicalIdShown(logicalId)} (${resourceTypeShown(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");
|
|
28622
|
+
function stateSecretNameNeedles(resources, secrets) {
|
|
28623
|
+
return (logicalId) => secretNameNeedlesOf(logicalId, Object.hasOwn(resources, logicalId) ? resources[logicalId] : void 0, secrets, { embedded: void 0 });
|
|
28856
28624
|
}
|
|
28857
|
-
function
|
|
28858
|
-
|
|
28625
|
+
function secretNamePrintingBag(logicalId, resources) {
|
|
28626
|
+
const bag = /* @__PURE__ */ new Map();
|
|
28627
|
+
const record = Object.hasOwn(resources, logicalId) ? resources[logicalId] : void 0;
|
|
28628
|
+
for (const needle of secretNameNeedlesOf(logicalId, record, void 0) ?? []) recordLogOnlyValue(bag, needle);
|
|
28629
|
+
for (const needle of secretNamesReadBy(logicalId, record, resources)) recordLogOnlyValue(bag, needle);
|
|
28630
|
+
return bag;
|
|
28859
28631
|
}
|
|
28860
|
-
|
|
28861
|
-
|
|
28862
|
-
|
|
28863
|
-
|
|
28864
|
-
|
|
28865
|
-
|
|
28632
|
+
function journaledOrphanPrintingBag(ops, stateResources) {
|
|
28633
|
+
const bag = /* @__PURE__ */ new Map();
|
|
28634
|
+
for (const op of ops) {
|
|
28635
|
+
const record = {
|
|
28636
|
+
resourceType: op.resourceType,
|
|
28637
|
+
physicalId: op.physicalId,
|
|
28638
|
+
properties: op.attemptedProperties
|
|
28639
|
+
};
|
|
28640
|
+
const replaced = Object.hasOwn(stateResources, op.logicalId) ? stateResources[op.logicalId] : void 0;
|
|
28641
|
+
for (const needle of [
|
|
28642
|
+
...secretNameNeedlesOf(op.logicalId, record, void 0) ?? [],
|
|
28643
|
+
...secretNameNeedlesOf(op.logicalId, replaced, void 0) ?? [],
|
|
28644
|
+
...secretNamesReadBy(op.logicalId, record, stateResources)
|
|
28645
|
+
]) recordLogOnlyValue(bag, needle);
|
|
28866
28646
|
}
|
|
28647
|
+
return bag;
|
|
28867
28648
|
}
|
|
28868
|
-
|
|
28869
|
-
|
|
28870
|
-
|
|
28871
|
-
|
|
28872
|
-
|
|
28873
|
-
|
|
28874
|
-
|
|
28875
|
-
|
|
28876
|
-
|
|
28877
|
-
|
|
28878
|
-
|
|
28879
|
-
|
|
28880
|
-
|
|
28881
|
-
|
|
28882
|
-
|
|
28883
|
-
|
|
28884
|
-
|
|
28885
|
-
|
|
28886
|
-
if (
|
|
28887
|
-
|
|
28888
|
-
|
|
28649
|
+
function completedReplayEntries(ops) {
|
|
28650
|
+
return ops.flatMap((op) => [{
|
|
28651
|
+
logicalId: op.logicalId,
|
|
28652
|
+
resourceType: op.resourceType,
|
|
28653
|
+
physicalId: op.physicalId,
|
|
28654
|
+
properties: op.properties,
|
|
28655
|
+
attemptedProperties: op.properties
|
|
28656
|
+
}, ...op.previousState === void 0 ? [] : [{
|
|
28657
|
+
logicalId: op.logicalId,
|
|
28658
|
+
resourceType: op.previousState.resourceType,
|
|
28659
|
+
physicalId: op.previousState.physicalId,
|
|
28660
|
+
properties: op.previousState.properties,
|
|
28661
|
+
attemptedProperties: op.previousState.properties
|
|
28662
|
+
}]]);
|
|
28663
|
+
}
|
|
28664
|
+
function orphanRecordsPrintingBag(records) {
|
|
28665
|
+
const bag = /* @__PURE__ */ new Map();
|
|
28666
|
+
for (const entry of records) {
|
|
28667
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
28668
|
+
const { logicalId, state } = entry;
|
|
28669
|
+
if (typeof logicalId !== "string" || state === null || typeof state !== "object") continue;
|
|
28670
|
+
for (const needle of secretNameNeedlesOf(logicalId, state, void 0) ?? []) recordLogOnlyValue(bag, needle);
|
|
28889
28671
|
}
|
|
28672
|
+
return bag;
|
|
28890
28673
|
}
|
|
28891
|
-
|
|
28892
|
-
|
|
28674
|
+
const ORPHAN_COMMAND_LABEL = "To orphan it: ";
|
|
28675
|
+
function maskOutsideCommandLines(message, mask) {
|
|
28676
|
+
const out = [];
|
|
28677
|
+
let run = [];
|
|
28678
|
+
const flush = () => {
|
|
28679
|
+
if (run.length === 0) return;
|
|
28680
|
+
const masked = mask(run.join("\n")).split("\n").map((line) => mask(line) === "***" ? "***" : line);
|
|
28681
|
+
out.push(masked.join("\n"));
|
|
28682
|
+
run = [];
|
|
28683
|
+
};
|
|
28684
|
+
for (const line of message.split("\n")) if (line.startsWith("To orphan it: ")) {
|
|
28685
|
+
flush();
|
|
28686
|
+
out.push(line);
|
|
28687
|
+
} else run.push(line);
|
|
28688
|
+
flush();
|
|
28689
|
+
return out.join("\n");
|
|
28893
28690
|
}
|
|
28894
|
-
|
|
28895
|
-
|
|
28896
|
-
const
|
|
28897
|
-
|
|
28691
|
+
function maskEventTextWithBoundBags(event) {
|
|
28692
|
+
if (event.reason === void 0 && event.error === void 0) return event;
|
|
28693
|
+
const mask = currentLogLineMasker();
|
|
28694
|
+
if (mask === void 0) return event;
|
|
28695
|
+
const masked = { ...event };
|
|
28696
|
+
if (masked.error?.message) {
|
|
28697
|
+
const message = masked.error.ownLines === true ? maskOutsideCommandLines(masked.error.message, mask) : mask(masked.error.message);
|
|
28698
|
+
masked.error = {
|
|
28699
|
+
...masked.error,
|
|
28700
|
+
message
|
|
28701
|
+
};
|
|
28702
|
+
}
|
|
28703
|
+
if (masked.reason) masked.reason = mask(masked.reason);
|
|
28704
|
+
return masked;
|
|
28898
28705
|
}
|
|
28899
|
-
|
|
28900
|
-
|
|
28901
|
-
|
|
28902
|
-
|
|
28706
|
+
|
|
28707
|
+
//#endregion
|
|
28708
|
+
//#region src/deployment/deploy-engine/options.ts
|
|
28709
|
+
const DEFAULT_RESOURCE_WARN_AFTER_MS = 3e5;
|
|
28710
|
+
const DEFAULT_RESOURCE_TIMEOUT_MS = 18e5;
|
|
28711
|
+
|
|
28712
|
+
//#endregion
|
|
28713
|
+
//#region src/deployment/deploy-engine/name-collision.ts
|
|
28714
|
+
function replacementNameOrigin(logicalId, physicalId) {
|
|
28715
|
+
if (looksLikeCdkdGeneratedName(physicalId, logicalId, getCurrentStackName())) return {
|
|
28716
|
+
descriptor: `The physical name (${physicalId}) was GENERATED by cdkd from the construct's logical id rather than declared in your template, and that derivation has no random component — so the new resource asks for exactly the name the existing one still holds`,
|
|
28717
|
+
remedy: "Either give the resource an explicit physical name in your CDK code, or rename the CONSTRUCT (its id feeds the generated name — note that this replaces the resource rather than renaming it in place)"
|
|
28718
|
+
};
|
|
28719
|
+
return {
|
|
28720
|
+
descriptor: `The resource has a user-supplied physical name (${physicalId})`,
|
|
28721
|
+
remedy: "Either rename the resource in your CDK code (a fresh name lets the safe create-first order proceed)"
|
|
28722
|
+
};
|
|
28723
|
+
}
|
|
28724
|
+
function orphanedNameCollisionAdvice(changeType, logicalId, error) {
|
|
28725
|
+
if (changeType !== "CREATE") return void 0;
|
|
28726
|
+
if (!(error instanceof ProvisioningError)) return void 0;
|
|
28727
|
+
const physicalId = error.physicalId;
|
|
28728
|
+
if (!physicalId) return void 0;
|
|
28729
|
+
const replayed = isReplayedNameCollisionFrom(error, logicalId, (link) => isAuxiliaryMarkOf(link, RETRY_AUXILIARY_OWNER));
|
|
28730
|
+
if (!replayed && !isNameCollisionErrorFrom(error, logicalId)) return void 0;
|
|
28731
|
+
const stackName = getCurrentStackName();
|
|
28732
|
+
if (!looksLikeCdkdGeneratedName(physicalId, logicalId, stackName)) return void 0;
|
|
28733
|
+
if (!stackName) return void 0;
|
|
28734
|
+
const safeId = displaySafe(physicalId, { asciiOnly: true });
|
|
28735
|
+
const safeStack = displaySafe(stackName, { asciiOnly: true });
|
|
28736
|
+
const safeLogicalId = displaySafe(logicalId, { asciiOnly: true });
|
|
28737
|
+
const idShown = isInertUnquoted(safeId);
|
|
28738
|
+
const shownId = idShown ? safeId : "a name that cannot be shown safely here";
|
|
28739
|
+
const shownLogicalId = isInertUnquoted(safeLogicalId) ? safeLogicalId : "A resource whose logical id cannot be shown safely here";
|
|
28740
|
+
const diagnosis = replayed ? `${shownLogicalId}: the name AWS reports as taken (${shownId}) is most likely held by a resource THIS create made. An earlier attempt of it ended without a clear verdict (a server error, a dropped connection or a timeout, or a failure after the create call itself may have succeeded), and the retry then found the name taken. A dropped connection can also happen before the request reached AWS, so the holder may be another resource: confirm it is yours before deleting or importing it. cdkd records a resource only once its create succeeds, so this one is in no state file: no rollback or cdkd destroy will remove it, and re-running collides with it again.` : `${shownLogicalId}: the name AWS reports as taken (${shownId}) is one cdkd DERIVED from the logical id, and that derivation has no random component — so this is most likely a resource an earlier cdkd run left behind. A rollback leaves a resource carrying DeletionPolicy: Retain in AWS and drops it from state, as CloudFormation does; what differs is the name. CloudFormation would generate a fresh one for an unnamed resource and redeploy clean, whereas cdkd asks again for the name the orphan still holds — so re-running does not clear this.`;
|
|
28741
|
+
const deleteArm = `If it is not a resource you want to keep, delete ${idShown ? safeId : "it"} in AWS — after confirming it holds nothing you need${replayed ? "" : ", since Retain is what kept it"} — and re-deploy.`;
|
|
28742
|
+
let canImport;
|
|
28903
28743
|
try {
|
|
28904
|
-
|
|
28905
|
-
|
|
28906
|
-
|
|
28907
|
-
ClusterIdentifier: clusterId,
|
|
28908
|
-
SnapshotType: "manual",
|
|
28909
|
-
...marker !== void 0 && { Marker: marker }
|
|
28910
|
-
}));
|
|
28911
|
-
snapshotId = existing.Snapshots?.find((s) => s.SnapshotIdentifier !== void 0 && matches.test(s.SnapshotIdentifier) && s.Status === RESUMABLE_SNAPSHOT_STATUS)?.SnapshotIdentifier;
|
|
28912
|
-
marker = existing.Marker;
|
|
28913
|
-
if (marker === void 0) break;
|
|
28914
|
-
}
|
|
28915
|
-
if (snapshotId) logger.debug(`Resuming in-flight final snapshot ${snapshotId} for ${logicalId} (${clusterId}) from a previous delete attempt`);
|
|
28916
|
-
} catch (probeError) {
|
|
28917
|
-
logger.debug(`Final-snapshot reuse probe failed for ${clusterId}: ${errMsg(probeError)}`);
|
|
28744
|
+
canImport = typeof this.providerRegistry.getProvider(error.resourceType).import === "function";
|
|
28745
|
+
} catch {
|
|
28746
|
+
canImport = false;
|
|
28918
28747
|
}
|
|
28919
|
-
const
|
|
28920
|
-
|
|
28921
|
-
|
|
28922
|
-
|
|
28923
|
-
|
|
28924
|
-
|
|
28925
|
-
|
|
28926
|
-
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
28930
|
-
|
|
28931
|
-
|
|
28932
|
-
|
|
28933
|
-
|
|
28934
|
-
|
|
28935
|
-
|
|
28936
|
-
|
|
28937
|
-
|
|
28748
|
+
const commandNamesTheRightResource = safeId === physicalId && safeStack === stackName && safeLogicalId === logicalId;
|
|
28749
|
+
const importableTarget = !stackName.includes("~");
|
|
28750
|
+
if (!canImport || !commandNamesTheRightResource || !importableTarget) return `${diagnosis} ${!canImport ? `cdkd cannot adopt ${displaySafe(error.resourceType, { asciiOnly: true })} back into state (its provider implements no import)` : !importableTarget ? `this is a nested-stack child, whose stack name cdkd import cannot resolve` : `cdkd cannot render an import command that provably names this resource`}, so the way forward is to delete it. ${deleteArm}`;
|
|
28751
|
+
const adopt = pasteableCommand("cdkd import", [{
|
|
28752
|
+
value: stackName,
|
|
28753
|
+
hole: "stack"
|
|
28754
|
+
}, {
|
|
28755
|
+
flag: "--resource",
|
|
28756
|
+
value: `${logicalId}=${physicalId}`,
|
|
28757
|
+
hole: "logicalId=physicalId"
|
|
28758
|
+
}]);
|
|
28759
|
+
return `${diagnosis} To recover, adopt it back into state instead of re-creating it, with the command below (a selective import merges into existing state and needs no --force while the resource is absent from it). CONFIRM IT IS YOURS FIRST — a name cdkd derives is predictable, so for a globally-namespaced type it can belong to another account, and the same stack deployed in another region derives the same name. ${deleteArm}${adopt.withheld.length === 0 ? "" : " The command below prints a quoted hole in place of a value cdkd will not name on a command line; replace it whole, quotes included, with your stack name or '<logicalId>=<name>', shell-quoted."}\nAdopt with: ${adopt.command}`;
|
|
28760
|
+
}
|
|
28761
|
+
|
|
28762
|
+
//#endregion
|
|
28763
|
+
//#region src/analyzer/skipped-outputs.ts
|
|
28764
|
+
function canonicalJson$1(value) {
|
|
28765
|
+
return JSON.stringify(value, (_key, v) => {
|
|
28766
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
28767
|
+
const sorted = Object.create(null);
|
|
28768
|
+
for (const k of Object.keys(v).sort()) sorted[k] = v[k];
|
|
28769
|
+
return sorted;
|
|
28938
28770
|
}
|
|
28939
|
-
|
|
28940
|
-
const readySnapshotId = await waitForNamedSnapshot({
|
|
28941
|
-
snapshotId,
|
|
28942
|
-
logicalId,
|
|
28943
|
-
sourceId: clusterId,
|
|
28944
|
-
noun: "cluster",
|
|
28945
|
-
reused,
|
|
28946
|
-
readyStatus: "available",
|
|
28947
|
-
failedStatuses: [
|
|
28948
|
-
"failed",
|
|
28949
|
-
"cancelled",
|
|
28950
|
-
"deleted"
|
|
28951
|
-
],
|
|
28952
|
-
pollStatus: async () => {
|
|
28953
|
-
return (await client.send(new DescribeClusterSnapshotsCommand({ SnapshotIdentifier: snapshotId }))).Snapshots?.[0]?.Status;
|
|
28954
|
-
},
|
|
28955
|
-
isTransientNotFound: (e) => errorNameOrMessageIncludes(e, "ClusterSnapshotNotFoundFault"),
|
|
28956
|
-
logger
|
|
28771
|
+
return v;
|
|
28957
28772
|
});
|
|
28958
|
-
await waitForRedshiftClusterSettled(client, clusterId, logicalId, logger);
|
|
28959
|
-
return readySnapshotId;
|
|
28960
28773
|
}
|
|
28961
|
-
|
|
28962
|
-
|
|
28963
|
-
|
|
28964
|
-
|
|
28965
|
-
|
|
28966
|
-
|
|
28967
|
-
|
|
28968
|
-
|
|
28969
|
-
|
|
28970
|
-
|
|
28971
|
-
}
|
|
28972
|
-
if (status === void 0 || status === "available") {
|
|
28973
|
-
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
28974
|
-
return;
|
|
28975
|
-
}
|
|
28976
|
-
if (Date.now() >= deadline) {
|
|
28977
|
-
logger.info(`Redshift cluster ${clusterId} (${logicalId}) is still '${status}' after the post-snapshot settle wait — proceeding with the delete anyway.`);
|
|
28978
|
-
return;
|
|
28979
|
-
}
|
|
28980
|
-
logger.debug(`Waiting for Redshift cluster ${clusterId} to settle (status: ${status})`);
|
|
28981
|
-
await finalSnapshotDelays.sleep(PRE_DELETE_SNAPSHOT_POLL_INTERVAL_MS);
|
|
28774
|
+
const NO_ECHO_DEFAULT_MASK = "<cdkd:noecho-default>";
|
|
28775
|
+
function withNoEchoDefaultsMasked(parameters) {
|
|
28776
|
+
if (parameters === null || typeof parameters !== "object" || Array.isArray(parameters)) return parameters;
|
|
28777
|
+
const masked = Object.create(null);
|
|
28778
|
+
for (const [name, decl] of Object.entries(parameters)) {
|
|
28779
|
+
const body = decl;
|
|
28780
|
+
masked[name] = typeof body === "object" && body !== null && body["NoEcho"] === true && "Default" in body ? {
|
|
28781
|
+
...body,
|
|
28782
|
+
Default: NO_ECHO_DEFAULT_MASK
|
|
28783
|
+
} : decl;
|
|
28982
28784
|
}
|
|
28785
|
+
return masked;
|
|
28983
28786
|
}
|
|
28984
|
-
|
|
28985
|
-
|
|
28986
|
-
|
|
28987
|
-
|
|
28988
|
-
|
|
28989
|
-
logger.debug(`Replication group ${replicationGroupId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
28990
|
-
return null;
|
|
28991
|
-
}
|
|
28992
|
-
if (group.ClusterEnabled === true) snapshotSource = { ReplicationGroupId: replicationGroupId };
|
|
28993
|
-
else {
|
|
28994
|
-
const member = group.NodeGroups?.flatMap((n) => n.NodeGroupMembers ?? []).find((m) => m.CurrentRole === "primary")?.CacheClusterId ?? group.MemberClusters?.[0];
|
|
28995
|
-
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");
|
|
28996
|
-
snapshotSource = { CacheClusterId: member };
|
|
28997
|
-
}
|
|
28998
|
-
} catch (describeError) {
|
|
28999
|
-
if (describeError instanceof CdkdError) throw describeError;
|
|
29000
|
-
if (errorNameOrMessageIncludes(describeError, "ReplicationGroupNotFoundFault")) {
|
|
29001
|
-
logger.debug(`Replication group ${replicationGroupId} no longer exists — skipping final snapshot for ${logicalId}`);
|
|
29002
|
-
return null;
|
|
29003
|
-
}
|
|
29004
|
-
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);
|
|
28787
|
+
function skippedOutputDigest(template, outputKey) {
|
|
28788
|
+
const sections = Object.create(null);
|
|
28789
|
+
for (const [section, body] of Object.entries(template)) {
|
|
28790
|
+
if (section === "Resources" || section === "Outputs") continue;
|
|
28791
|
+
sections[section] = section === "Parameters" ? withNoEchoDefaultsMasked(body) : body;
|
|
29005
28792
|
}
|
|
29006
|
-
const
|
|
29007
|
-
|
|
29008
|
-
|
|
29009
|
-
|
|
29010
|
-
|
|
29011
|
-
|
|
29012
|
-
|
|
29013
|
-
|
|
29014
|
-
|
|
29015
|
-
|
|
29016
|
-
|
|
29017
|
-
|
|
29018
|
-
}
|
|
29019
|
-
if (snapshotId) logger.debug(`Resuming in-flight final snapshot ${snapshotId} for ${logicalId} (${replicationGroupId}) from a previous delete attempt`);
|
|
29020
|
-
} catch (probeError) {
|
|
29021
|
-
logger.debug(`Final-snapshot reuse probe failed for ${replicationGroupId}: ${errMsg(probeError)}`);
|
|
28793
|
+
const entry = template.Outputs?.[outputKey];
|
|
28794
|
+
return createHash("sha256").update(canonicalJson$1({
|
|
28795
|
+
output: entry,
|
|
28796
|
+
template: sections
|
|
28797
|
+
})).digest("hex");
|
|
28798
|
+
}
|
|
28799
|
+
function collectSkippedOutputs(template, resolvedOutputs) {
|
|
28800
|
+
let record;
|
|
28801
|
+
for (const [outputKey, value] of Object.entries(resolvedOutputs)) {
|
|
28802
|
+
if (value !== void 0) continue;
|
|
28803
|
+
record ??= Object.create(null);
|
|
28804
|
+
record[outputKey] = skippedOutputDigest(template, outputKey);
|
|
29022
28805
|
}
|
|
29023
|
-
|
|
29024
|
-
|
|
29025
|
-
|
|
29026
|
-
|
|
29027
|
-
|
|
29028
|
-
|
|
29029
|
-
|
|
29030
|
-
|
|
29031
|
-
|
|
29032
|
-
|
|
29033
|
-
|
|
29034
|
-
|
|
29035
|
-
|
|
28806
|
+
return record;
|
|
28807
|
+
}
|
|
28808
|
+
function referencedLogicalIds(entry) {
|
|
28809
|
+
const ids = /* @__PURE__ */ new Set();
|
|
28810
|
+
const addName = (name) => {
|
|
28811
|
+
const logicalId = name.split(".")[0];
|
|
28812
|
+
if (logicalId !== void 0 && logicalId !== "" && !logicalId.startsWith("AWS::")) ids.add(logicalId);
|
|
28813
|
+
};
|
|
28814
|
+
const walk = (value) => {
|
|
28815
|
+
if (Array.isArray(value)) {
|
|
28816
|
+
for (const item of value) walk(item);
|
|
28817
|
+
return;
|
|
28818
|
+
}
|
|
28819
|
+
if (value === null || typeof value !== "object") return;
|
|
28820
|
+
const obj = value;
|
|
28821
|
+
if (typeof obj["Ref"] === "string") addName(obj["Ref"]);
|
|
28822
|
+
const getAtt = obj["Fn::GetAtt"];
|
|
28823
|
+
if (typeof getAtt === "string") addName(getAtt);
|
|
28824
|
+
else if (Array.isArray(getAtt) && typeof getAtt[0] === "string") addName(getAtt[0]);
|
|
28825
|
+
for (const [key, nested] of Object.entries(obj)) {
|
|
28826
|
+
if (key === "Fn::Sub") {
|
|
28827
|
+
walkSub(nested);
|
|
28828
|
+
continue;
|
|
29036
28829
|
}
|
|
29037
|
-
|
|
29038
|
-
|
|
29039
|
-
|
|
28830
|
+
walk(nested);
|
|
28831
|
+
}
|
|
28832
|
+
};
|
|
28833
|
+
const walkSub = (sub) => {
|
|
28834
|
+
const isPair = Array.isArray(sub);
|
|
28835
|
+
const subTemplate = typeof sub === "string" ? sub : isPair ? sub[0] : void 0;
|
|
28836
|
+
const variableMap = isPair && sub[1] !== null && typeof sub[1] === "object" ? sub[1] : void 0;
|
|
28837
|
+
if (typeof subTemplate === "string") {
|
|
28838
|
+
const declaredVars = new Set(variableMap === void 0 ? [] : Object.keys(variableMap));
|
|
28839
|
+
for (const [, placeholder] of subTemplate.matchAll(/\$\{([^}]*)\}/g)) {
|
|
28840
|
+
if (placeholder === void 0 || placeholder.startsWith("!")) continue;
|
|
28841
|
+
if (declaredVars.has(placeholder)) continue;
|
|
28842
|
+
addName(placeholder);
|
|
29040
28843
|
}
|
|
29041
|
-
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");
|
|
29042
28844
|
}
|
|
28845
|
+
if (isPair) for (const [index, element] of sub.entries()) {
|
|
28846
|
+
if (index === 1) {
|
|
28847
|
+
if (variableMap !== void 0) for (const declared of Object.values(variableMap)) walk(declared);
|
|
28848
|
+
continue;
|
|
28849
|
+
}
|
|
28850
|
+
walk(element);
|
|
28851
|
+
}
|
|
28852
|
+
else walk(sub);
|
|
28853
|
+
};
|
|
28854
|
+
walk(entry);
|
|
28855
|
+
return ids;
|
|
28856
|
+
}
|
|
28857
|
+
function bindingSkippedOutputs(pristineTemplate, record, changedLogicalIds) {
|
|
28858
|
+
const binding = /* @__PURE__ */ new Set();
|
|
28859
|
+
if (record === null || record === void 0) return binding;
|
|
28860
|
+
const declared = pristineTemplate.Outputs ?? {};
|
|
28861
|
+
for (const [outputKey, digest] of Object.entries(record)) {
|
|
28862
|
+
if (!Object.prototype.hasOwnProperty.call(declared, outputKey)) continue;
|
|
28863
|
+
const entry = declared[outputKey];
|
|
28864
|
+
if (digest !== skippedOutputDigest(pristineTemplate, outputKey)) continue;
|
|
28865
|
+
if (changedLogicalIds !== void 0 && [...referencedLogicalIds(entry)].some((id) => changedLogicalIds.has(id))) continue;
|
|
28866
|
+
binding.add(outputKey);
|
|
29043
28867
|
}
|
|
29044
|
-
return
|
|
29045
|
-
|
|
29046
|
-
|
|
29047
|
-
|
|
29048
|
-
|
|
29049
|
-
|
|
29050
|
-
|
|
29051
|
-
failedStatuses: ["failed"],
|
|
29052
|
-
pollStatus: async () => {
|
|
29053
|
-
return (await client.send(new DescribeSnapshotsCommand$1({ SnapshotName: snapshotId }))).Snapshots?.[0]?.SnapshotStatus;
|
|
29054
|
-
},
|
|
29055
|
-
isTransientNotFound: (e) => errorNameOrMessageIncludes(e, "SnapshotNotFoundFault"),
|
|
29056
|
-
logger
|
|
29057
|
-
});
|
|
28868
|
+
return binding;
|
|
28869
|
+
}
|
|
28870
|
+
function skippedOutputsEqual(a, b) {
|
|
28871
|
+
const aKeys = a === null || a === void 0 ? [] : Object.keys(a);
|
|
28872
|
+
const bKeys = b === null || b === void 0 ? [] : Object.keys(b);
|
|
28873
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
28874
|
+
return aKeys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && a?.[k] === b?.[k]);
|
|
29058
28875
|
}
|
|
29059
28876
|
|
|
29060
28877
|
//#endregion
|
|
29061
|
-
//#region src/deployment/
|
|
29062
|
-
|
|
29063
|
-
|
|
28878
|
+
//#region src/deployment/outputs-export-alias/names.ts
|
|
28879
|
+
function isOutputSuppressedByCondition(output, conditions) {
|
|
28880
|
+
return output.Condition !== void 0 && conditions?.[output.Condition] === false;
|
|
28881
|
+
}
|
|
28882
|
+
function collectPublishedOutputNames(outputs, conditions) {
|
|
28883
|
+
const names = /* @__PURE__ */ new Set();
|
|
28884
|
+
for (const [name, output] of Object.entries(outputs)) if (!isOutputSuppressedByCondition(output, conditions)) names.add(name);
|
|
28885
|
+
return names;
|
|
28886
|
+
}
|
|
28887
|
+
function collectDeclaredOutputNames(outputs) {
|
|
28888
|
+
return new Set(Object.keys(outputs));
|
|
28889
|
+
}
|
|
28890
|
+
function isExportAliasCollision(exportName, outputKey, ownedOutputNames) {
|
|
28891
|
+
return exportName !== outputKey && ownedOutputNames.has(exportName);
|
|
28892
|
+
}
|
|
29064
28893
|
|
|
29065
28894
|
//#endregion
|
|
29066
|
-
//#region src/deployment/
|
|
29067
|
-
|
|
29068
|
-
|
|
29069
|
-
|
|
29070
|
-
|
|
29071
|
-
|
|
28895
|
+
//#region src/deployment/outputs-export-alias/secret-scan.ts
|
|
28896
|
+
const MIN_SECRET_NEEDLE = 4;
|
|
28897
|
+
const SECRET_SCAN_INVISIBLES = /[\p{Cc}\p{Cf}\p{Me}\p{Zl}\p{Zp}\p{Default_Ignorable_Code_Point}]/gu;
|
|
28898
|
+
const SECRET_DETECTION_ONLY = /\p{Mn}/gu;
|
|
28899
|
+
function detectionForms(canonical) {
|
|
28900
|
+
return [canonical.normalize("NFD").replace(SECRET_DETECTION_ONLY, "").normalize("NFC"), canonical.normalize("NFKD").replace(SECRET_DETECTION_ONLY, "").normalize("NFKC")];
|
|
29072
28901
|
}
|
|
29073
|
-
|
|
29074
|
-
|
|
29075
|
-
|
|
29076
|
-
|
|
29077
|
-
|
|
29078
|
-
|
|
29079
|
-
|
|
29080
|
-
|
|
29081
|
-
|
|
29082
|
-
|
|
29083
|
-
|
|
29084
|
-
}
|
|
28902
|
+
const DETECTION_NEEDLES = /* @__PURE__ */ new WeakMap();
|
|
28903
|
+
function detectionNeedlesOf(secrets, plaintext, canonical) {
|
|
28904
|
+
let cache = DETECTION_NEEDLES.get(secrets);
|
|
28905
|
+
if (cache === void 0) {
|
|
28906
|
+
cache = /* @__PURE__ */ new Map();
|
|
28907
|
+
DETECTION_NEEDLES.set(secrets, cache);
|
|
28908
|
+
}
|
|
28909
|
+
let forms = cache.get(plaintext);
|
|
28910
|
+
if (forms === void 0) {
|
|
28911
|
+
forms = detectionForms(canonical);
|
|
28912
|
+
cache.set(plaintext, forms);
|
|
28913
|
+
}
|
|
28914
|
+
return forms;
|
|
28915
|
+
}
|
|
28916
|
+
function canonicalForSecretScan(text) {
|
|
28917
|
+
return secretScanHaystacks(text)[0];
|
|
28918
|
+
}
|
|
28919
|
+
function secretScanHaystacks(text) {
|
|
28920
|
+
const untrimmed = text.replace(SECRET_SCAN_INVISIBLES, "");
|
|
28921
|
+
return [untrimmed.trim(), untrimmed];
|
|
28922
|
+
}
|
|
28923
|
+
function canonicalNeedle(plaintext) {
|
|
28924
|
+
return plaintext.replace(SECRET_SCAN_INVISIBLES, "");
|
|
28925
|
+
}
|
|
28926
|
+
function canonicalNeedles(secrets) {
|
|
28927
|
+
const out = /* @__PURE__ */ new Map();
|
|
28928
|
+
for (const [plaintext, expression] of secrets ?? []) {
|
|
28929
|
+
const needle = canonicalNeedle(plaintext);
|
|
28930
|
+
if (needle.length > 0) out.set(needle, expression);
|
|
28931
|
+
}
|
|
28932
|
+
return out;
|
|
28933
|
+
}
|
|
28934
|
+
function secretsPresentIn(text, secrets) {
|
|
28935
|
+
if (!secrets || secrets.size === 0) return void 0;
|
|
28936
|
+
const haystacks = secretScanHaystacks(text);
|
|
28937
|
+
const [markedUntrimmed, foldedUntrimmed] = detectionForms(haystacks[1]);
|
|
28938
|
+
const [markedTrimmed, foldedTrimmed] = detectionForms(haystacks[0]);
|
|
28939
|
+
const wideHaystacks = [[
|
|
28940
|
+
markedUntrimmed.trim(),
|
|
28941
|
+
markedUntrimmed,
|
|
28942
|
+
markedTrimmed
|
|
28943
|
+
], [
|
|
28944
|
+
foldedUntrimmed.trim(),
|
|
28945
|
+
foldedUntrimmed,
|
|
28946
|
+
foldedTrimmed
|
|
28947
|
+
]];
|
|
28948
|
+
const exposure = /* @__PURE__ */ new Map();
|
|
28949
|
+
for (const [plaintext, expression] of secrets) {
|
|
28950
|
+
const needle = canonicalNeedle(plaintext);
|
|
28951
|
+
const canonicalHit = haystacks.some((haystack) => haystack === needle || needle.length >= 4 && haystack.includes(needle));
|
|
28952
|
+
const [markedNeedle, foldedNeedle] = detectionNeedlesOf(secrets, plaintext, needle);
|
|
28953
|
+
const hitIn = (wides, wideNeedle, floor) => wides.some((wide) => wide === wideNeedle || floor >= 4 && wide.includes(wideNeedle));
|
|
28954
|
+
const wideHit = hitIn(wideHaystacks[0], markedNeedle, markedNeedle.length) || hitIn(wideHaystacks[1], foldedNeedle, Math.min(markedNeedle.length, foldedNeedle.length));
|
|
28955
|
+
const rawHit = plaintext.length >= 4 && text.includes(plaintext);
|
|
28956
|
+
if (canonicalHit || wideHit || rawHit) exposure.set(plaintext, expression);
|
|
28957
|
+
}
|
|
28958
|
+
return exposure.size > 0 ? exposure : void 0;
|
|
28959
|
+
}
|
|
28960
|
+
function exportNameSecretExposure(exportName, substitutedIntoName, recordedThisPass, noEchoParameterValues) {
|
|
28961
|
+
const exposure = new Map(substitutedIntoName);
|
|
28962
|
+
for (const [plaintext, expression] of secretsPresentIn(exportName, recordedThisPass) ?? []) exposure.set(plaintext, expression);
|
|
28963
|
+
for (const corpus of [substitutedIntoName, noEchoParameterValues]) {
|
|
28964
|
+
if (corpus === void 0) continue;
|
|
28965
|
+
for (const [plaintext, expression] of secretsPresentIn(exportName, printingCorpusOf(corpus)) ?? []) if (!exposure.has(plaintext)) exposure.set(plaintext, expression);
|
|
28966
|
+
}
|
|
28967
|
+
return exposure.size > 0 ? exposure : void 0;
|
|
29085
28968
|
}
|
|
29086
|
-
|
|
29087
|
-
const
|
|
29088
|
-
|
|
28969
|
+
function carriedExportAliasExposure(exportName, recordedThisPass, noEchoParameterValues) {
|
|
28970
|
+
const carried = /* @__PURE__ */ new Map();
|
|
28971
|
+
shareLogOnlyValues(carried, recordedThisPass);
|
|
28972
|
+
const exposure = exportNameSecretExposure(exportName, carried, recordedThisPass, noEchoParameterValues);
|
|
28973
|
+
if (exposure === void 0) return void 0;
|
|
29089
28974
|
return {
|
|
29090
|
-
|
|
29091
|
-
|
|
29092
|
-
};
|
|
29093
|
-
}
|
|
29094
|
-
function maskedRollbackEventError(error, mask) {
|
|
29095
|
-
const extracted = extractDeploymentEventError(error);
|
|
29096
|
-
if (isOwnRemedyError(error)) return {
|
|
29097
|
-
...extracted,
|
|
29098
|
-
ownLines: true
|
|
29099
|
-
};
|
|
29100
|
-
const message = mask(extracted.message);
|
|
29101
|
-
return message === extracted.message ? extracted : {
|
|
29102
|
-
...extracted,
|
|
29103
|
-
message
|
|
28975
|
+
exposure,
|
|
28976
|
+
noEchoOnly: isNoEchoOnlyExposure(exposure, carried, recordedThisPass)
|
|
29104
28977
|
};
|
|
29105
28978
|
}
|
|
29106
|
-
function
|
|
29107
|
-
|
|
29108
|
-
|
|
29109
|
-
scope.ctx.recordEvent?.({
|
|
29110
|
-
eventType: "ROLLBACK_RESOURCE_SKIPPED",
|
|
29111
|
-
stackName: scope.stackName,
|
|
29112
|
-
operation: op.changeType,
|
|
29113
|
-
logicalId: op.logicalId,
|
|
29114
|
-
resourceType: op.resourceType,
|
|
29115
|
-
...op.provisionedBy && { provisionedBy: op.provisionedBy },
|
|
29116
|
-
reason: scope.mask(reason)
|
|
29117
|
-
});
|
|
29118
|
-
}
|
|
29119
|
-
function rollbackCannotAddress(record, opResourceType, route, physicalId) {
|
|
29120
|
-
return !(record?.resourceType === "AWS::CloudFormation::Stack" && opResourceType === "AWS::CloudFormation::Stack" && record.provisionedBy !== "cc-api" && route !== "cc-api") && !hasAddressablePhysicalId({ physicalId });
|
|
28979
|
+
function exportNameNoEchoParameters(exportName, sources) {
|
|
28980
|
+
if (sources === void 0 || exportName === null || typeof exportName !== "object") return [];
|
|
28981
|
+
return noEchoParametersReadBy(exportName, sources);
|
|
29121
28982
|
}
|
|
29122
|
-
function
|
|
29123
|
-
if (
|
|
29124
|
-
|
|
28983
|
+
function isNoEchoOnlyExposure(exposure, substitutedIntoName, recordedThisPass) {
|
|
28984
|
+
for (const plaintext of exposure.keys()) if (substitutedIntoName.has(plaintext) || recordedThisPass?.has(plaintext) === true) return false;
|
|
28985
|
+
return exposure.size > 0;
|
|
29125
28986
|
}
|
|
29126
|
-
|
|
29127
|
-
const
|
|
29128
|
-
|
|
29129
|
-
|
|
29130
|
-
|
|
29131
|
-
|
|
29132
|
-
|
|
28987
|
+
function noEchoParameterValueSeed(parameters, values, inherited, userParameters) {
|
|
28988
|
+
const seed = /* @__PURE__ */ new Map();
|
|
28989
|
+
if (values === void 0) return seed;
|
|
28990
|
+
for (const [name, value] of Object.entries(values)) {
|
|
28991
|
+
if (isWholeDynamicReferenceValue(value)) continue;
|
|
28992
|
+
if (parameters?.[name]?.NoEcho === true) {
|
|
28993
|
+
const type = parameters[name]?.Type;
|
|
28994
|
+
const ssmTyped = typeof type === "string" && type.startsWith("AWS::SSM::Parameter::Value<");
|
|
28995
|
+
const spelled = Object.prototype.hasOwnProperty.call(userParameters ?? {}, name) ? userParameters?.[name] : parameters[name]?.Default;
|
|
28996
|
+
if (!(ssmTyped && typeof value === "string" && (typeof spelled === "string" || typeof spelled === "number") && value === String(spelled))) recordLogOnlyParameterValue(seed, value);
|
|
28997
|
+
if (!ssmTyped && (typeof spelled === "string" || typeof spelled === "number") && !isWholeDynamicReferenceValue(spelled)) recordLogOnlyParameterValue(seed, String(spelled));
|
|
28998
|
+
}
|
|
28999
|
+
if (inherited !== void 0) carryLogOnlyValuesCarriedBy(inherited, seed, value);
|
|
29133
29000
|
}
|
|
29134
|
-
|
|
29135
|
-
recordRollbackSkip(scope, op, UNADDRESSABLE_SKIP_CAUSE);
|
|
29136
|
-
}
|
|
29137
|
-
function effectiveProvisionedBy(record, fallbackProvisionedBy) {
|
|
29138
|
-
return record?.provisionedBy ?? fallbackProvisionedBy;
|
|
29001
|
+
return seed;
|
|
29139
29002
|
}
|
|
29140
|
-
function
|
|
29141
|
-
return
|
|
29003
|
+
function isWholeDynamicReferenceValue(value) {
|
|
29004
|
+
if (typeof value === "string") return isSingleDynamicReferenceToken(value);
|
|
29005
|
+
return Array.isArray(value) && value.length > 0 && value.every((element) => typeof element === "string" && isSingleDynamicReferenceToken(element));
|
|
29142
29006
|
}
|
|
29143
|
-
|
|
29144
|
-
|
|
29145
|
-
return error instanceof Error && OWN_REMEDY_ERRORS.has(error);
|
|
29007
|
+
function stateKeySecretExposure(key, secrets) {
|
|
29008
|
+
return secretsPresentIn(key, secrets);
|
|
29146
29009
|
}
|
|
29147
|
-
function
|
|
29148
|
-
|
|
29149
|
-
|
|
29010
|
+
function maskEveryOccurrence(text, exposure) {
|
|
29011
|
+
let out = text;
|
|
29012
|
+
for (const value of Array.from(exposure.keys()).sort((a, b) => b.length - a.length)) out = out.split(value).join("***");
|
|
29013
|
+
return out;
|
|
29150
29014
|
}
|
|
29151
|
-
|
|
29152
|
-
|
|
29153
|
-
|
|
29015
|
+
|
|
29016
|
+
//#endregion
|
|
29017
|
+
//#region src/deployment/outputs-export-alias/warnings.ts
|
|
29018
|
+
function secretBearingExportNameWarning(outputKey, exportName, exposure, secrets, noEchoOnly = false) {
|
|
29019
|
+
const corpus = printingCorpusOf(secrets ?? exposure);
|
|
29020
|
+
const name = secretSafeKeyDisplay(exportName, corpus, exposure);
|
|
29021
|
+
const shown = name.kind === "masked" ? `${maskedLabel(name.text)} ` : "";
|
|
29022
|
+
const ownerForceMask = /* @__PURE__ */ new Map();
|
|
29023
|
+
const ownerHaystacks = secretScanHaystacks(outputKey);
|
|
29024
|
+
for (const [plaintext, expression] of exposure) {
|
|
29025
|
+
const needle = canonicalNeedle(plaintext);
|
|
29026
|
+
if (ownerHaystacks.some((haystack) => haystack === needle) || plaintext.length >= 4) ownerForceMask.set(plaintext, expression);
|
|
29027
|
+
}
|
|
29028
|
+
const owner = displayTextOrWithheld(secretSafeKeyDisplay(outputKey, corpus, ownerForceMask));
|
|
29029
|
+
if (noEchoOnly) return `Output ${owner} has an Export.Name that resolves to a value containing a secret ${shown}— skipping the export alias. The name contains the value of a NoEcho template parameter, or a value derived from one: the whole name at any length, or embedded at 4 or more characters, even where the match is a coincidence. An export name becomes a key in state.json and in the exports index, where the value would be stored in plaintext. An existing Fn::ImportValue of this name stops resolving. Rename the export, or choose a NoEcho value the name does not contain.`;
|
|
29030
|
+
return `Output ${owner} has an Export.Name that resolves to a value containing a secret ${shown}— skipping the export alias. An export name becomes a key in state.json and in the exports index, and redaction rewrites VALUES only, so publishing it would persist the secret in plaintext. Use a non-secret Export.Name.`;
|
|
29154
29031
|
}
|
|
29155
|
-
function
|
|
29156
|
-
|
|
29157
|
-
|
|
29032
|
+
function noEchoParameterExportNameWarning(outputKey, parameterNames, secrets) {
|
|
29033
|
+
const owner = displayTextOrWithheld(secretSafeKeyDisplay(outputKey, printingCorpusOf(secrets)));
|
|
29034
|
+
const names = parameterNames.map((name) => displayIdent(name, { listMember: true })).join(", ");
|
|
29035
|
+
return `Output ${owner} has an Export.Name that reads the NoEcho template ${parameterNames.length === 1 ? "parameter" : "parameters"} ${names} — skipping the export alias. An export name becomes a key in state.json and in the exports index, where the value would be stored in plaintext, so a name built from a NoEcho parameter is refused whatever the value's length. An existing Fn::ImportValue of this name stops resolving. Build the Export.Name without the NoEcho parameter.`;
|
|
29158
29036
|
}
|
|
29159
|
-
function
|
|
29160
|
-
|
|
29037
|
+
function mergedExposure(a, b) {
|
|
29038
|
+
if (a === void 0) return b;
|
|
29039
|
+
if (b === void 0) return a;
|
|
29040
|
+
return new Map([...a, ...b]);
|
|
29161
29041
|
}
|
|
29162
|
-
function
|
|
29163
|
-
|
|
29042
|
+
function secretSafeKeyDisplay(key, secrets, forceMask) {
|
|
29043
|
+
const [shown, untrimmed] = secretScanHaystacks(key);
|
|
29044
|
+
const exposure = mergedExposure(stateKeySecretExposure(key, secrets), forceMask === void 0 ? void 0 : secretsPresentIn(key, forceMask));
|
|
29045
|
+
const mask = canonicalNeedles(forceMask);
|
|
29046
|
+
for (const [plaintext, expression] of exposure ?? []) {
|
|
29047
|
+
const needle = canonicalNeedle(plaintext);
|
|
29048
|
+
if (needle.length > 0) mask.set(needle, expression);
|
|
29049
|
+
}
|
|
29050
|
+
if (mask.size === 0) return {
|
|
29051
|
+
kind: "safe",
|
|
29052
|
+
text: shown
|
|
29053
|
+
};
|
|
29054
|
+
const masked = maskEveryOccurrence(untrimmed, mask);
|
|
29055
|
+
if (masked === untrimmed) return exposure ? { kind: "withheld" } : {
|
|
29056
|
+
kind: "safe",
|
|
29057
|
+
text: shown
|
|
29058
|
+
};
|
|
29059
|
+
if (stateKeySecretExposure(masked, secrets) || forceMask !== void 0 && secretsPresentIn(masked, forceMask)) return { kind: "withheld" };
|
|
29060
|
+
return {
|
|
29061
|
+
kind: "masked",
|
|
29062
|
+
text: canonicalForSecretScan(masked)
|
|
29063
|
+
};
|
|
29164
29064
|
}
|
|
29165
|
-
|
|
29166
|
-
|
|
29167
|
-
return typeof logicalId === "string" && PASTEABLE_LOGICAL_ID.test(logicalId) ? logicalId : "a resource whose logical id is not a plain CloudFormation logical id";
|
|
29065
|
+
function secretBearing(display) {
|
|
29066
|
+
return display.kind !== "safe";
|
|
29168
29067
|
}
|
|
29169
|
-
|
|
29170
|
-
|
|
29068
|
+
const WITHHELD_NAME_DISPLAY = "<name withheld: contains a secret>";
|
|
29069
|
+
function displayTextOrWithheld(display) {
|
|
29070
|
+
return display.kind === "withheld" ? WITHHELD_NAME_DISPLAY : display.text;
|
|
29171
29071
|
}
|
|
29172
|
-
function
|
|
29173
|
-
return
|
|
29072
|
+
function maskedLabel(maskedText) {
|
|
29073
|
+
return /[^ -~]/.test(maskedText) ? "(masked, name withheld: it carries characters this line cannot show as tested)" : `(masked: ${displayIdent(maskedText)})`;
|
|
29174
29074
|
}
|
|
29175
|
-
function
|
|
29176
|
-
|
|
29075
|
+
function secretBearingStateKeyWarning(stackName, display) {
|
|
29076
|
+
const clause = display.kind === "masked" ? `${maskedLabel(display.text)} ` : `(the name is withheld: masking it would leave the secret readable) `;
|
|
29077
|
+
return `State for ${displayStackName(canonicalForSecretScan(stackName))} holds an output KEY that renders a secret ${clause}— cdkd scrub cannot rewrite a key, only a value, because the key IS the export name consumers resolve by. Give that output a non-secret Export.Name and redeploy: the next deploy replaces state.outputs and the exports index entirely. ROTATE the exposed secret.`;
|
|
29177
29078
|
}
|
|
29178
|
-
function
|
|
29179
|
-
|
|
29180
|
-
const
|
|
29181
|
-
|
|
29079
|
+
function exportAliasCollisionWarning(outputKey, exportName, secrets) {
|
|
29080
|
+
const corpus = printingCorpusOf(secrets);
|
|
29081
|
+
const shown = displayTextOrWithheld(secretSafeKeyDisplay(exportName, corpus));
|
|
29082
|
+
const from = displayTextOrWithheld(secretSafeKeyDisplay(outputKey, corpus));
|
|
29083
|
+
return `Output ${from} exports as "${shown}", which is also the name of another output in this stack — skipping the export alias, so output ${shown} keeps its own value and the export is not published. A consumer's Fn::ImportValue on "${shown}" therefore resolves to output ${shown}, NOT to ${from} (CloudFormation would publish both). Rename the export, or the colliding output.`;
|
|
29182
29084
|
}
|
|
29183
|
-
|
|
29184
|
-
|
|
29185
|
-
|
|
29085
|
+
function exportAliasCollisionScrubWarning(outputKey, exportName, secrets) {
|
|
29086
|
+
const corpus = printingCorpusOf(secrets);
|
|
29087
|
+
const nameDisplay = (name) => secretSafeKeyDisplay(name, corpus);
|
|
29088
|
+
const exportDisplay = nameDisplay(exportName);
|
|
29089
|
+
const shown = displayTextOrWithheld(exportDisplay);
|
|
29090
|
+
const storedUnder = exportDisplay.kind === "withheld" ? "the stored value under that name" : `the stored value under "${shown}"`;
|
|
29091
|
+
const ownerDisplay = nameDisplay(outputKey);
|
|
29092
|
+
return `Output ${ownerDisplay.kind === "withheld" && exportDisplay.kind === "withheld" ? "<the owning output, name withheld: contains a secret>" : displayTextOrWithheld(ownerDisplay)} exports as ${exportDisplay.kind === "withheld" ? shown : `"${shown}"`}, which is also the name of another output in this stack — state cannot say which of the two ${storedUnder} came from, so that key is redacted by value match instead of by template position, and two references resolving to the same value could still collapse there. Rename the export, or the colliding output, and redeploy.`;
|
|
29186
29093
|
}
|
|
29187
|
-
|
|
29188
|
-
|
|
29094
|
+
|
|
29095
|
+
//#endregion
|
|
29096
|
+
//#region src/deployment/deploy-engine/outputs.ts
|
|
29097
|
+
var ForwardingSecrets = class extends Map {
|
|
29098
|
+
target;
|
|
29099
|
+
constructor(target) {
|
|
29100
|
+
super();
|
|
29101
|
+
this.target = target;
|
|
29102
|
+
}
|
|
29103
|
+
set(plaintext, expression) {
|
|
29104
|
+
super.set(plaintext, expression);
|
|
29105
|
+
this.target?.set(plaintext, expression);
|
|
29106
|
+
return this;
|
|
29107
|
+
}
|
|
29108
|
+
};
|
|
29109
|
+
function handleOutputResolutionFailure(error, outputKey, outputs, secrets, inheritedSecrets) {
|
|
29110
|
+
const union = unionOfSecretBags([inheritedSecrets, secrets]);
|
|
29111
|
+
const detail = maskSecretsInText(error instanceof Error ? error.message || error.name : String(error), union);
|
|
29112
|
+
if (this.options.strictGetAtt) {
|
|
29113
|
+
const cause = typeof error === "string" ? maskSecretsInText(error, union) : maskSecretsInError(error, union);
|
|
29114
|
+
throw new Error(`Failed to resolve output ${outputKey}: ${detail} (--strict-getatt promotes output resolution failures to deploy errors; drop the flag to skip the output instead)`, cause instanceof Error || typeof cause === "string" ? { cause } : {});
|
|
29115
|
+
}
|
|
29116
|
+
this.logger.warn(`Failed to resolve output ${outputKey}: ${detail}`);
|
|
29117
|
+
outputs[outputKey] = void 0;
|
|
29189
29118
|
}
|
|
29190
|
-
function
|
|
29191
|
-
|
|
29192
|
-
|
|
29193
|
-
|
|
29194
|
-
|
|
29119
|
+
async function resolveOutputs(template, resources, stackName, digestSource, parameterValues, conditions) {
|
|
29120
|
+
this.resolvedExportNames = [];
|
|
29121
|
+
this.carriedExportAliasRefusal = void 0;
|
|
29122
|
+
if (!template.Outputs) {
|
|
29123
|
+
this.carriedExportAliasRefusal = seedOnlyAliasRefusal(noEchoParameterValueSeed(template.Parameters, parameterValues, this.options.inheritedSecrets, this.options.parameters));
|
|
29124
|
+
return {};
|
|
29125
|
+
}
|
|
29126
|
+
const outputs = Object.create(null);
|
|
29127
|
+
const context = this.buildResolverContext({
|
|
29128
|
+
template,
|
|
29129
|
+
resources,
|
|
29130
|
+
...parameterValues && { parameters: parameterValues },
|
|
29131
|
+
...conditions && { conditions }
|
|
29132
|
+
}, stackName);
|
|
29133
|
+
context.printingSecrets = /* @__PURE__ */ new Map();
|
|
29134
|
+
const refuseMaskedOutputReads = (outputKey, ownReads) => {
|
|
29135
|
+
const added = ownReads.filter((read) => read.kind === "ref-state-key");
|
|
29136
|
+
if (added.length === 0) return;
|
|
29137
|
+
throw markNonRetryable(/* @__PURE__ */ new Error(`Cannot resolve ${added.map((read) => read.display).join(", ")} for output ${outputKey}: cdkd's recorded state holds only the redaction mask there, so this output would publish a value cdkd cannot confirm (for most such types the resource's raw physical id) instead of the value CloudFormation's Ref returns — a possibly wrong value that a consuming stack's Fn::ImportValue would accept and send to AWS. The output is not published. ${DeployEngine.maskedRecordRemedyFor(added, context.resources)}`));
|
|
29195
29138
|
};
|
|
29196
|
-
const
|
|
29197
|
-
const
|
|
29198
|
-
|
|
29199
|
-
|
|
29200
|
-
|
|
29201
|
-
|
|
29202
|
-
|
|
29203
|
-
|
|
29204
|
-
|
|
29205
|
-
|
|
29206
|
-
|
|
29207
|
-
|
|
29208
|
-
|
|
29139
|
+
const outputsPassSecrets = context.recordedSecretValues ?? EMPTY_SECRETS;
|
|
29140
|
+
const outputsPassInherited = context.inheritedSecrets ?? EMPTY_SECRETS;
|
|
29141
|
+
if (context.recordedSecretValues) this.outputsPassSecretMaps.push(context.recordedSecretValues);
|
|
29142
|
+
const publishedOutputNames = collectPublishedOutputNames(template.Outputs, conditions);
|
|
29143
|
+
let outputsPassCompleted = false;
|
|
29144
|
+
try {
|
|
29145
|
+
for (const [outputKey, output] of Object.entries(template.Outputs)) {
|
|
29146
|
+
if (isOutputSuppressedByCondition(output, conditions)) {
|
|
29147
|
+
this.logger.debug(`Skipping output ${outputKey} — condition ${output.Condition} is false`);
|
|
29148
|
+
continue;
|
|
29149
|
+
}
|
|
29150
|
+
const ownReads = [];
|
|
29151
|
+
try {
|
|
29152
|
+
const resolved = await this.resolver.resolve(output.Value, {
|
|
29153
|
+
...context,
|
|
29154
|
+
redactedAttributeReads: ownReads
|
|
29155
|
+
});
|
|
29156
|
+
refuseMaskedOutputReads(outputKey, ownReads);
|
|
29157
|
+
if (resolved === void 0 && this.options.strictGetAtt) throw markNonRetryable(/* @__PURE__ */ new Error("the value resolved to nothing"));
|
|
29158
|
+
outputs[outputKey] = resolved;
|
|
29159
|
+
} catch (error) {
|
|
29160
|
+
this.handleOutputResolutionFailure(error, outputKey, outputs, outputsPassSecrets, outputsPassInherited);
|
|
29161
|
+
}
|
|
29209
29162
|
}
|
|
29210
|
-
|
|
29211
|
-
|
|
29212
|
-
|
|
29213
|
-
|
|
29214
|
-
|
|
29215
|
-
|
|
29216
|
-
|
|
29217
|
-
|
|
29218
|
-
|
|
29219
|
-
|
|
29220
|
-
|
|
29221
|
-
|
|
29222
|
-
|
|
29223
|
-
|
|
29224
|
-
|
|
29225
|
-
|
|
29226
|
-
|
|
29163
|
+
const noEchoSeed = noEchoParameterValueSeed(template.Parameters, parameterValues, context.inheritedSecrets, this.options.parameters);
|
|
29164
|
+
const noEchoSources = this.noEchoPositionSources(resources, template, conditions);
|
|
29165
|
+
this.carriedExportAliasRefusal = (outputKey, exportName) => {
|
|
29166
|
+
const verdict = carriedExportAliasExposure(exportName, outputsPassSecrets, noEchoSeed);
|
|
29167
|
+
if (verdict === void 0) return void 0;
|
|
29168
|
+
return secretBearingExportNameWarning(outputKey, exportName, verdict.exposure, context.recordedSecretValues, verdict.noEchoOnly);
|
|
29169
|
+
};
|
|
29170
|
+
const resolvedNames = [];
|
|
29171
|
+
for (const [outputKey, output] of Object.entries(template.Outputs)) {
|
|
29172
|
+
if (isOutputSuppressedByCondition(output, conditions)) continue;
|
|
29173
|
+
if (!output.Export?.Name) continue;
|
|
29174
|
+
const value = outputs[outputKey];
|
|
29175
|
+
if (value === void 0) continue;
|
|
29176
|
+
const nameSecrets = new ForwardingSecrets(context.recordedSecretValues);
|
|
29177
|
+
if (context.recordedSecretValues) shareLogOnlyValues(nameSecrets, context.recordedSecretValues);
|
|
29178
|
+
const nameReads = [];
|
|
29179
|
+
let exportName;
|
|
29180
|
+
try {
|
|
29181
|
+
exportName = typeof output.Export.Name === "string" ? output.Export.Name : await this.resolver.resolve(output.Export.Name, {
|
|
29182
|
+
...context,
|
|
29183
|
+
recordedSecretValues: nameSecrets,
|
|
29184
|
+
redactedAttributeReads: nameReads
|
|
29185
|
+
});
|
|
29186
|
+
refuseMaskedOutputReads(outputKey, nameReads);
|
|
29187
|
+
} catch (error) {
|
|
29188
|
+
this.handleOutputResolutionFailure(error, outputKey, outputs, outputsPassSecrets, outputsPassInherited);
|
|
29189
|
+
continue;
|
|
29190
|
+
}
|
|
29191
|
+
if (typeof exportName !== "string") continue;
|
|
29192
|
+
resolvedNames.push({
|
|
29193
|
+
outputKey,
|
|
29194
|
+
exportName,
|
|
29195
|
+
value,
|
|
29196
|
+
outputValue: output.Value,
|
|
29197
|
+
nameSource: output.Export.Name,
|
|
29198
|
+
nameSecrets
|
|
29199
|
+
});
|
|
29200
|
+
}
|
|
29201
|
+
for (const { outputKey, exportName, value, outputValue, nameSource, nameSecrets } of resolvedNames) {
|
|
29202
|
+
const noEchoParameters = exportNameNoEchoParameters(nameSource, noEchoSources);
|
|
29203
|
+
const exposure = noEchoParameters.length > 0 ? void 0 : exportNameSecretExposure(exportName, nameSecrets, context.recordedSecretValues, noEchoSeed);
|
|
29204
|
+
if (noEchoParameters.length > 0) this.logger.warn(noEchoParameterExportNameWarning(outputKey, noEchoParameters, outputsPassSecrets));
|
|
29205
|
+
else if (exposure) this.logger.warn(secretBearingExportNameWarning(outputKey, exportName, exposure, context.recordedSecretValues, isNoEchoOnlyExposure(exposure, nameSecrets, context.recordedSecretValues)));
|
|
29206
|
+
else if (isExportAliasCollision(exportName, outputKey, publishedOutputNames)) this.logger.warn(exportAliasCollisionWarning(outputKey, exportName, outputsPassSecrets));
|
|
29207
|
+
else {
|
|
29208
|
+
outputs[exportName] = value;
|
|
29209
|
+
if (!this.resolvedExportNames.includes(exportName)) this.resolvedExportNames.push(exportName);
|
|
29210
|
+
this.outputsTemplateSource[exportName] = outputValue;
|
|
29211
|
+
}
|
|
29212
|
+
}
|
|
29213
|
+
outputsPassCompleted = true;
|
|
29214
|
+
} finally {
|
|
29215
|
+
if (!outputsPassCompleted) this.outputsSourceUsable = false;
|
|
29216
|
+
}
|
|
29217
|
+
for (const [outputKey, output] of Object.entries(template.Outputs)) {
|
|
29218
|
+
if (!publishedOutputNames.has(outputKey)) continue;
|
|
29219
|
+
this.outputsTemplateSource[outputKey] = output.Value;
|
|
29220
|
+
}
|
|
29221
|
+
this.skippedOutputs = collectSkippedOutputs(digestSource, outputs);
|
|
29222
|
+
return markSameGenerationBag(outputs);
|
|
29227
29223
|
}
|
|
29228
|
-
function
|
|
29229
|
-
|
|
29224
|
+
function buildDisplayOutputs(template, resolvedOutputs) {
|
|
29225
|
+
const display = {};
|
|
29226
|
+
if (!template.Outputs) return display;
|
|
29227
|
+
for (const key of Object.keys(template.Outputs)) {
|
|
29228
|
+
const v = resolvedOutputs[key];
|
|
29229
|
+
if (v !== void 0) display[key] = v;
|
|
29230
|
+
}
|
|
29231
|
+
return display;
|
|
29230
29232
|
}
|
|
29231
|
-
function
|
|
29232
|
-
|
|
29233
|
-
|
|
29234
|
-
|
|
29235
|
-
|
|
29236
|
-
reason: `UpdateReplacePolicy: Retain kept the replacement's new ${resourceType} (${survivorId}); it is live, still billing, and no longer tracked by cdkd. ${clause}`
|
|
29233
|
+
function seedOnlyAliasRefusal(seed) {
|
|
29234
|
+
return (outputKey, exportName) => {
|
|
29235
|
+
const verdict = carriedExportAliasExposure(exportName, /* @__PURE__ */ new Map(), seed);
|
|
29236
|
+
if (verdict === void 0) return void 0;
|
|
29237
|
+
return secretBearingExportNameWarning(outputKey, exportName, verdict.exposure, EMPTY_SECRETS, verdict.noEchoOnly);
|
|
29237
29238
|
};
|
|
29238
29239
|
}
|
|
29239
29240
|
|
|
29240
29241
|
//#endregion
|
|
29241
|
-
//#region src/deployment
|
|
29242
|
-
|
|
29243
|
-
|
|
29244
|
-
|
|
29245
|
-
|
|
29246
|
-
|
|
29247
|
-
|
|
29248
|
-
|
|
29249
|
-
|
|
29250
|
-
|
|
29251
|
-
|
|
29252
|
-
|
|
29253
|
-
|
|
29254
|
-
|
|
29255
|
-
}
|
|
29256
|
-
|
|
29257
|
-
|
|
29258
|
-
|
|
29259
|
-
|
|
29260
|
-
|
|
29261
|
-
|
|
29242
|
+
//#region src/state/deployment-events-store.ts
|
|
29243
|
+
const DEPLOYMENT_EVENTS_MAX_INDEX_RUNS = 20;
|
|
29244
|
+
const FLUSH_INTERVAL_MS = 2e3;
|
|
29245
|
+
const FLUSH_EVENT_THRESHOLD = 50;
|
|
29246
|
+
function newDeploymentRunId(now = /* @__PURE__ */ new Date()) {
|
|
29247
|
+
return `${now.toISOString().replace(/[-:.]/g, "")}-${randomUUID().slice(0, 8)}`;
|
|
29248
|
+
}
|
|
29249
|
+
function deploymentEventsKey(prefix, stackName, region, runId) {
|
|
29250
|
+
return `${prefix}/${stackName}/${region}/deployments/${runId}.jsonl`;
|
|
29251
|
+
}
|
|
29252
|
+
function deploymentEventsIndexKey(prefix, stackName, region) {
|
|
29253
|
+
return `${prefix}/${stackName}/${region}/deployments/index.json`;
|
|
29254
|
+
}
|
|
29255
|
+
function deploymentsDirPrefix(prefix, stackName, region) {
|
|
29256
|
+
return `${prefix}/${stackName}/${region}/deployments/`;
|
|
29257
|
+
}
|
|
29258
|
+
const DEPLOYMENT_EVENTS_OBJECT_DESCRIPTION = "deployment-event streams and their index, whose recorded error messages can quote values the secret mask does not catch";
|
|
29259
|
+
async function deleteAndPurgeEventObjects(backend, keys, dirPrefix, scope = "deleted-keys") {
|
|
29260
|
+
if (keys.length === 0) return void 0;
|
|
29261
|
+
let swept;
|
|
29262
|
+
try {
|
|
29263
|
+
await backend.deleteRawObjects(keys);
|
|
29264
|
+
} finally {
|
|
29265
|
+
if (scope === "whole-dir") swept = await backend.purgeNoncurrentVersionsUnderPrefix(dirPrefix, { objectDescription: DEPLOYMENT_EVENTS_OBJECT_DESCRIPTION });
|
|
29266
|
+
else await backend.purgeNoncurrentVersions(keys, {
|
|
29267
|
+
...keys.length > 1 && { listPrefix: dirPrefix },
|
|
29268
|
+
objectDescription: DEPLOYMENT_EVENTS_OBJECT_DESCRIPTION
|
|
29269
|
+
});
|
|
29262
29270
|
}
|
|
29263
|
-
|
|
29264
|
-
return (fn) => withSkipPrefix(skip, fn);
|
|
29271
|
+
return swept;
|
|
29265
29272
|
}
|
|
29266
|
-
function
|
|
29267
|
-
const
|
|
29268
|
-
|
|
29269
|
-
|
|
29270
|
-
|
|
29271
|
-
|
|
29272
|
-
addNamed: (record) => {
|
|
29273
|
-
pairs.push(...secretDerivedNamePairs(record));
|
|
29274
|
-
sinks = withDerivedNameMasks(logger, base, pairs);
|
|
29275
|
-
}
|
|
29276
|
-
};
|
|
29273
|
+
function runIdTimestampMs(runId) {
|
|
29274
|
+
const m = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(\d{3})Z/.exec(runId);
|
|
29275
|
+
if (!m) return null;
|
|
29276
|
+
const iso = `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}.${m[7]}Z`;
|
|
29277
|
+
const ms = Date.parse(iso);
|
|
29278
|
+
return Number.isNaN(ms) ? null : ms;
|
|
29277
29279
|
}
|
|
29278
|
-
|
|
29279
|
-
"
|
|
29280
|
-
|
|
29281
|
-
|
|
29282
|
-
|
|
29283
|
-
|
|
29284
|
-
|
|
29285
|
-
|
|
29286
|
-
|
|
29287
|
-
|
|
29288
|
-
|
|
29289
|
-
|
|
29290
|
-
|
|
29291
|
-
|
|
29292
|
-
|
|
29293
|
-
|
|
29294
|
-
|
|
29295
|
-
|
|
29296
|
-
|
|
29297
|
-
|
|
29298
|
-
|
|
29299
|
-
|
|
29300
|
-
|
|
29301
|
-
|
|
29302
|
-
|
|
29303
|
-
|
|
29304
|
-
|
|
29305
|
-
|
|
29306
|
-
|
|
29307
|
-
|
|
29280
|
+
function runIdFromJsonlKey(key, dirPrefix) {
|
|
29281
|
+
if (!key.startsWith(dirPrefix) || !key.endsWith(".jsonl")) return null;
|
|
29282
|
+
return key.slice(dirPrefix.length, -6);
|
|
29283
|
+
}
|
|
29284
|
+
var DeploymentEventsStore = class {
|
|
29285
|
+
logger = getLogger().child("DeploymentEvents");
|
|
29286
|
+
backend;
|
|
29287
|
+
stackName;
|
|
29288
|
+
region;
|
|
29289
|
+
command;
|
|
29290
|
+
runId;
|
|
29291
|
+
cdkdVersion;
|
|
29292
|
+
startedAt;
|
|
29293
|
+
events = [];
|
|
29294
|
+
persistedCount = 0;
|
|
29295
|
+
flushTimer;
|
|
29296
|
+
writeChain = Promise.resolve();
|
|
29297
|
+
warnedOnce = false;
|
|
29298
|
+
finalized = false;
|
|
29299
|
+
constructor(backend, options) {
|
|
29300
|
+
this.backend = backend;
|
|
29301
|
+
this.stackName = options.stackName;
|
|
29302
|
+
this.region = options.region;
|
|
29303
|
+
this.command = options.command;
|
|
29304
|
+
this.runId = options.runId ?? newDeploymentRunId();
|
|
29305
|
+
this.cdkdVersion = options.cdkdVersion ?? getCdkdVersion();
|
|
29306
|
+
this.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
29307
|
+
}
|
|
29308
|
+
record(event) {
|
|
29309
|
+
try {
|
|
29310
|
+
if (this.finalized) return;
|
|
29311
|
+
this.events.push({
|
|
29312
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29313
|
+
...event
|
|
29314
|
+
});
|
|
29315
|
+
if (this.events.length - this.persistedCount >= FLUSH_EVENT_THRESHOLD) this.scheduleFlush(0);
|
|
29316
|
+
else this.scheduleFlush(FLUSH_INTERVAL_MS);
|
|
29317
|
+
} catch {}
|
|
29318
|
+
}
|
|
29319
|
+
async finalize(result) {
|
|
29320
|
+
if (this.finalized) return;
|
|
29321
|
+
this.finalized = true;
|
|
29322
|
+
if (this.flushTimer) {
|
|
29323
|
+
clearTimeout(this.flushTimer);
|
|
29324
|
+
this.flushTimer = void 0;
|
|
29308
29325
|
}
|
|
29326
|
+
if (this.events.length === 0) return;
|
|
29327
|
+
await this.enqueueWrite(async () => {
|
|
29328
|
+
const indexRead = this.readIndexRuns();
|
|
29329
|
+
indexRead.catch(() => {});
|
|
29330
|
+
await this.doFlush();
|
|
29331
|
+
await this.writeIndexAndPrune(result, await indexRead);
|
|
29332
|
+
});
|
|
29309
29333
|
}
|
|
29310
|
-
|
|
29311
|
-
const
|
|
29312
|
-
|
|
29313
|
-
|
|
29334
|
+
async writeIndexAndPrune(result, existingRuns) {
|
|
29335
|
+
const { key, file, runs } = this.buildIndexUpdate(result, existingRuns);
|
|
29336
|
+
const keptRunIds = runs.map((r) => r.runId);
|
|
29337
|
+
const willPrune = keptRunIds.length >= 20;
|
|
29338
|
+
const dirPrefix = deploymentsDirPrefix(this.backend.prefix, this.stackName, this.region);
|
|
29339
|
+
const staleKeysRead = willPrune ? this.backend.listRawKeys(dirPrefix) : void 0;
|
|
29340
|
+
staleKeysRead?.catch(() => {});
|
|
29341
|
+
await this.backend.putRawObject(key, JSON.stringify(file, null, 2));
|
|
29342
|
+
if (!staleKeysRead) return;
|
|
29343
|
+
await this.pruneSupersededRunFiles(keptRunIds, dirPrefix, await staleKeysRead);
|
|
29314
29344
|
}
|
|
29315
|
-
|
|
29316
|
-
|
|
29317
|
-
parts.forEach((part, index) => {
|
|
29318
|
-
if (part === "") return;
|
|
29319
|
-
if (index === parts.length - 1 && parts.length > 1 && COMPOSITE_SCOPE_LAST.test(part)) return;
|
|
29320
|
-
if (index === 0 && parts.length > 1 && COMPOSITE_POOL_FIRST.test(part)) return;
|
|
29321
|
-
segments.add(part);
|
|
29322
|
-
});
|
|
29345
|
+
async drain() {
|
|
29346
|
+
await this.writeChain;
|
|
29323
29347
|
}
|
|
29324
|
-
|
|
29325
|
-
|
|
29326
|
-
|
|
29327
|
-
|
|
29328
|
-
}
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
|
|
29332
|
-
|
|
29333
|
-
|
|
29334
|
-
const primary = Object.hasOwn(rewritten, resourceType) ? rewritten[resourceType].property : explicitNamePropertyFor(resourceType);
|
|
29335
|
-
if (typeof primary === "string") named.add(primary);
|
|
29336
|
-
for (const key of Object.keys(properties)) if (/(?:Name|Identifier)$/.test(key)) named.add(key);
|
|
29337
|
-
for (const key of ownEntry(OTHER_SPELLED_NAME_KEYS, resourceType) ?? []) named.add(key);
|
|
29338
|
-
const idSpellings = record.physicalIds.filter((id) => typeof id === "string" && id !== "").flatMap((id) => [
|
|
29339
|
-
id,
|
|
29340
|
-
...id.toLowerCase() === id || id.length < 4 ? [] : [id.toLowerCase()],
|
|
29341
|
-
finalSnapshotNamePrefix(id, resourceType),
|
|
29342
|
-
...idNameSegments(id)
|
|
29343
|
-
]);
|
|
29344
|
-
const pairs = [];
|
|
29345
|
-
for (const key of named) {
|
|
29346
|
-
if (!Object.hasOwn(properties, key)) continue;
|
|
29347
|
-
const value = properties[key];
|
|
29348
|
-
if (typeof value !== "string" || value === "") continue;
|
|
29349
|
-
const derived = key === primary && !value.includes("{{resolve:") && value !== "***" ? rewrittenNameSpellings(resourceType, value, logicalId) : [];
|
|
29350
|
-
for (const spelling of [...idSpellings, ...derived]) pairs.push([value, spelling]);
|
|
29348
|
+
scheduleFlush(delayMs) {
|
|
29349
|
+
if (this.flushTimer) {
|
|
29350
|
+
if (delayMs > 0) return;
|
|
29351
|
+
clearTimeout(this.flushTimer);
|
|
29352
|
+
}
|
|
29353
|
+
this.flushTimer = setTimeout(() => {
|
|
29354
|
+
this.flushTimer = void 0;
|
|
29355
|
+
this.enqueueWrite(() => this.doFlush());
|
|
29356
|
+
}, delayMs);
|
|
29357
|
+
this.flushTimer.unref?.();
|
|
29351
29358
|
}
|
|
29352
|
-
|
|
29353
|
-
|
|
29354
|
-
|
|
29355
|
-
const { logicalId } = op;
|
|
29356
|
-
for (const properties of [op.properties, op.attemptedProperties]) opMasker.addNamed({
|
|
29357
|
-
resourceType: op.resourceType,
|
|
29358
|
-
properties,
|
|
29359
|
-
logicalId,
|
|
29360
|
-
physicalIds: [op.physicalId]
|
|
29361
|
-
});
|
|
29362
|
-
for (const record of [op.previousState, current]) {
|
|
29363
|
-
if (record === null || typeof record !== "object") continue;
|
|
29364
|
-
opMasker.addNamed({
|
|
29365
|
-
resourceType: record.resourceType ?? op.resourceType,
|
|
29366
|
-
properties: record.properties,
|
|
29367
|
-
logicalId,
|
|
29368
|
-
physicalIds: [record.physicalId]
|
|
29359
|
+
enqueueWrite(op) {
|
|
29360
|
+
const next = this.writeChain.then(op).catch((err) => {
|
|
29361
|
+
this.warnOnce(`Failed to persist deployment events for run ${this.runId}: ${describeAwsFailure(err).detail}`);
|
|
29369
29362
|
});
|
|
29363
|
+
this.writeChain = next;
|
|
29364
|
+
return next;
|
|
29370
29365
|
}
|
|
29371
|
-
|
|
29372
|
-
|
|
29373
|
-
|
|
29374
|
-
|
|
29375
|
-
|
|
29376
|
-
|
|
29377
|
-
return false;
|
|
29366
|
+
async doFlush() {
|
|
29367
|
+
if (this.events.length === 0 || this.events.length === this.persistedCount) return;
|
|
29368
|
+
const snapshotCount = this.events.length;
|
|
29369
|
+
const body = this.events.slice(0, snapshotCount).map((e) => JSON.stringify(e)).join("\n");
|
|
29370
|
+
await this.backend.putRawObject(deploymentEventsKey(this.backend.prefix, this.stackName, this.region, this.runId), body + "\n", "application/x-ndjson");
|
|
29371
|
+
this.persistedCount = snapshotCount;
|
|
29378
29372
|
}
|
|
29379
|
-
|
|
29380
|
-
|
|
29381
|
-
|
|
29382
|
-
|
|
29383
|
-
|
|
29384
|
-
|
|
29385
|
-
|
|
29386
|
-
|
|
29387
|
-
|
|
29388
|
-
|
|
29389
|
-
|
|
29390
|
-
|
|
29391
|
-
await createPreDeleteFinalSnapshot(resourceType, physicalId, logicalId, ctx.finalSnapshotClients ?? getAwsClients(), {
|
|
29392
|
-
info: (message) => ctx.logger.info(mask(message)),
|
|
29393
|
-
debug: (message) => ctx.logger.debug(mask(message))
|
|
29394
|
-
});
|
|
29395
|
-
return;
|
|
29396
|
-
case "refuse-cc-routed": throw ccRoutedFinalSnapshotError(logicalId, resourceType, SKIP_FINAL_SNAPSHOT_FLAG);
|
|
29397
|
-
case "refuse-unsupported-type": throw unsupportedFinalSnapshotError(logicalId, resourceType, SKIP_FINAL_SNAPSHOT_FLAG);
|
|
29373
|
+
async readIndexRuns() {
|
|
29374
|
+
const key = deploymentEventsIndexKey(this.backend.prefix, this.stackName, this.region);
|
|
29375
|
+
try {
|
|
29376
|
+
const raw = await this.backend.getRawObject(key);
|
|
29377
|
+
if (raw !== null) {
|
|
29378
|
+
const parsed = JSON.parse(raw);
|
|
29379
|
+
if (Array.isArray(parsed.runs)) return parsed.runs;
|
|
29380
|
+
}
|
|
29381
|
+
} catch (err) {
|
|
29382
|
+
this.logger.debug(`Deployment-events index unreadable, rewriting: ${describeAwsFailure(err).detail}`);
|
|
29383
|
+
}
|
|
29384
|
+
return [];
|
|
29398
29385
|
}
|
|
29399
|
-
|
|
29400
|
-
|
|
29401
|
-
|
|
29402
|
-
|
|
29403
|
-
|
|
29404
|
-
|
|
29405
|
-
|
|
29406
|
-
|
|
29407
|
-
|
|
29408
|
-
|
|
29409
|
-
|
|
29410
|
-
|
|
29411
|
-
|
|
29412
|
-
|
|
29413
|
-
|
|
29414
|
-
|
|
29415
|
-
|
|
29416
|
-
|
|
29417
|
-
|
|
29418
|
-
|
|
29419
|
-
|
|
29420
|
-
|
|
29421
|
-
|
|
29422
|
-
|
|
29423
|
-
|
|
29424
|
-
|
|
29425
|
-
|
|
29426
|
-
|
|
29427
|
-
|
|
29428
|
-
|
|
29429
|
-
|
|
29430
|
-
|
|
29431
|
-
|
|
29386
|
+
buildIndexUpdate(result, existingRuns) {
|
|
29387
|
+
const key = deploymentEventsIndexKey(this.backend.prefix, this.stackName, this.region);
|
|
29388
|
+
const runs = [{
|
|
29389
|
+
runId: this.runId,
|
|
29390
|
+
command: this.command,
|
|
29391
|
+
cdkdVersion: this.cdkdVersion,
|
|
29392
|
+
startedAt: this.startedAt,
|
|
29393
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29394
|
+
result,
|
|
29395
|
+
eventCount: this.persistedCount
|
|
29396
|
+
}, ...existingRuns.filter((r) => r.runId !== this.runId)].slice(0, 20);
|
|
29397
|
+
return {
|
|
29398
|
+
key,
|
|
29399
|
+
file: {
|
|
29400
|
+
indexVersion: 1,
|
|
29401
|
+
stackName: this.stackName,
|
|
29402
|
+
region: this.region,
|
|
29403
|
+
runs,
|
|
29404
|
+
lastModified: Date.now()
|
|
29405
|
+
},
|
|
29406
|
+
runs
|
|
29407
|
+
};
|
|
29408
|
+
}
|
|
29409
|
+
async pruneSupersededRunFiles(keptRunIds, dirPrefix, keys) {
|
|
29410
|
+
if (keptRunIds.length < 20) return;
|
|
29411
|
+
const cutoff = keptRunIds.reduce((min, id) => id < min ? id : min, keptRunIds[0]);
|
|
29412
|
+
const stale = keys.filter((k) => {
|
|
29413
|
+
const runId = runIdFromJsonlKey(k, dirPrefix);
|
|
29414
|
+
return runId !== null && runId < cutoff;
|
|
29415
|
+
});
|
|
29416
|
+
if (stale.length === 0) return;
|
|
29417
|
+
await deleteAndPurgeEventObjects(this.backend, stale, dirPrefix);
|
|
29418
|
+
this.logger.debug(`Pruned ${stale.length} superseded deployment-event stream(s) for ${this.stackName} (${this.region})`);
|
|
29419
|
+
}
|
|
29420
|
+
warnOnce(message) {
|
|
29421
|
+
if (this.warnedOnce) {
|
|
29422
|
+
this.logger.debug(message);
|
|
29423
|
+
return;
|
|
29432
29424
|
}
|
|
29425
|
+
this.warnedOnce = true;
|
|
29426
|
+
this.logger.warn(`${message} — continuing; deployment events are best-effort and never block the run.`);
|
|
29433
29427
|
}
|
|
29434
|
-
|
|
29435
|
-
|
|
29436
|
-
|
|
29437
|
-
|
|
29438
|
-
|
|
29439
|
-
if (embeddedBag !== void 0 && physicalId !== void 0) {
|
|
29440
|
-
for (const needle of printingCorpusOf(embeddedBag).keys()) if (needle !== "" && (needle === physicalId || needle.length >= 4 && physicalId.includes(needle))) add(needle);
|
|
29428
|
+
};
|
|
29429
|
+
var DeploymentEventsReader = class {
|
|
29430
|
+
backend;
|
|
29431
|
+
constructor(backend) {
|
|
29432
|
+
this.backend = backend;
|
|
29441
29433
|
}
|
|
29442
|
-
|
|
29443
|
-
|
|
29444
|
-
|
|
29445
|
-
|
|
29446
|
-
|
|
29447
|
-
|
|
29448
|
-
|
|
29449
|
-
for (const needle of needles) {
|
|
29450
|
-
const folded = needle.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-");
|
|
29451
|
-
if (folded.length < 4) continue;
|
|
29452
|
-
for (let at = sanitized.indexOf(folded); at !== -1; at = sanitized.indexOf(folded, at + 1)) {
|
|
29453
|
-
if (at >= kept) break;
|
|
29454
|
-
if (at + folded.length > kept) spellings.push(prefix);
|
|
29455
|
-
else if (folded !== needle) spellings.push(folded);
|
|
29434
|
+
async listRegions(stackName) {
|
|
29435
|
+
const prefix = `${this.backend.prefix}/${stackName}/`;
|
|
29436
|
+
const keys = await this.backend.listRawKeys(prefix);
|
|
29437
|
+
const regions = /* @__PURE__ */ new Set();
|
|
29438
|
+
for (const key of keys) {
|
|
29439
|
+
const segments = key.slice(prefix.length).split("/");
|
|
29440
|
+
if (segments.length === 3 && segments[1] === "deployments" && segments[0]) regions.add(segments[0]);
|
|
29456
29441
|
}
|
|
29442
|
+
return [...regions].sort();
|
|
29457
29443
|
}
|
|
29458
|
-
|
|
29459
|
-
|
|
29460
|
-
|
|
29461
|
-
|
|
29462
|
-
|
|
29463
|
-
|
|
29464
|
-
|
|
29465
|
-
|
|
29466
|
-
|
|
29467
|
-
|
|
29468
|
-
|
|
29469
|
-
|
|
29470
|
-
const needles = secretNameNeedlesOf(otherId, other, bags.secrets, { embedded: bags.embedded });
|
|
29471
|
-
if (needles === void 0) continue;
|
|
29472
|
-
for (const needle of needles) if (leaves.some((leaf) => leaf === needle || needle.length >= 4 && leaf.includes(needle))) read.add(needle);
|
|
29444
|
+
async listRuns(stackName, region) {
|
|
29445
|
+
const key = deploymentEventsIndexKey(this.backend.prefix, stackName, region);
|
|
29446
|
+
try {
|
|
29447
|
+
const raw = await this.backend.getRawObject(key);
|
|
29448
|
+
if (raw !== null) {
|
|
29449
|
+
const parsed = JSON.parse(raw);
|
|
29450
|
+
if (Array.isArray(parsed.runs)) return parsed.runs;
|
|
29451
|
+
}
|
|
29452
|
+
} catch {}
|
|
29453
|
+
const dirPrefix = `${this.backend.prefix}/${stackName}/${region}/deployments/`;
|
|
29454
|
+
const runIds = (await this.backend.listRawKeys(dirPrefix)).filter((k) => k.endsWith(".jsonl")).map((k) => k.slice(dirPrefix.length, -6)).sort().reverse();
|
|
29455
|
+
return Promise.all(runIds.map((runId) => this.summarizeRunFromJsonl(stackName, region, runId)));
|
|
29473
29456
|
}
|
|
29474
|
-
|
|
29475
|
-
|
|
29476
|
-
|
|
29477
|
-
|
|
29478
|
-
|
|
29479
|
-
|
|
29480
|
-
|
|
29481
|
-
|
|
29482
|
-
|
|
29483
|
-
|
|
29484
|
-
|
|
29485
|
-
|
|
29486
|
-
|
|
29487
|
-
|
|
29488
|
-
|
|
29489
|
-
|
|
29490
|
-
|
|
29491
|
-
|
|
29492
|
-
|
|
29457
|
+
async summarizeRunFromJsonl(stackName, region, runId) {
|
|
29458
|
+
const fallback = {
|
|
29459
|
+
runId,
|
|
29460
|
+
command: "deploy",
|
|
29461
|
+
cdkdVersion: "unknown",
|
|
29462
|
+
startedAt: "",
|
|
29463
|
+
finishedAt: "",
|
|
29464
|
+
result: "UNKNOWN",
|
|
29465
|
+
eventCount: 0
|
|
29466
|
+
};
|
|
29467
|
+
let events = null;
|
|
29468
|
+
try {
|
|
29469
|
+
events = await this.readRunEvents(stackName, region, runId);
|
|
29470
|
+
} catch {
|
|
29471
|
+
return fallback;
|
|
29472
|
+
}
|
|
29473
|
+
if (events === null || events.length === 0) return fallback;
|
|
29474
|
+
const started = events.find((e) => e.eventType === "RUN_STARTED");
|
|
29475
|
+
let finished;
|
|
29476
|
+
for (const e of events) if (e.eventType === "RUN_FINISHED") finished = e;
|
|
29477
|
+
return {
|
|
29478
|
+
runId,
|
|
29479
|
+
command: started?.command ?? fallback.command,
|
|
29480
|
+
cdkdVersion: started?.cdkdVersion ?? fallback.cdkdVersion,
|
|
29481
|
+
startedAt: started?.timestamp ?? fallback.startedAt,
|
|
29482
|
+
finishedAt: finished?.timestamp ?? fallback.finishedAt,
|
|
29483
|
+
result: finished?.result ?? "UNKNOWN",
|
|
29484
|
+
eventCount: events.length
|
|
29493
29485
|
};
|
|
29494
|
-
const replaced = Object.hasOwn(stateResources, op.logicalId) ? stateResources[op.logicalId] : void 0;
|
|
29495
|
-
for (const needle of [
|
|
29496
|
-
...secretNameNeedlesOf(op.logicalId, record, void 0) ?? [],
|
|
29497
|
-
...secretNameNeedlesOf(op.logicalId, replaced, void 0) ?? [],
|
|
29498
|
-
...secretNamesReadBy(op.logicalId, record, stateResources)
|
|
29499
|
-
]) recordLogOnlyValue(bag, needle);
|
|
29500
29486
|
}
|
|
29501
|
-
|
|
29502
|
-
|
|
29503
|
-
|
|
29504
|
-
|
|
29505
|
-
|
|
29506
|
-
|
|
29507
|
-
|
|
29508
|
-
|
|
29509
|
-
|
|
29510
|
-
|
|
29511
|
-
|
|
29512
|
-
|
|
29513
|
-
|
|
29514
|
-
properties: op.previousState.properties,
|
|
29515
|
-
attemptedProperties: op.previousState.properties
|
|
29516
|
-
}]]);
|
|
29517
|
-
}
|
|
29518
|
-
function orphanRecordsPrintingBag(records) {
|
|
29519
|
-
const bag = /* @__PURE__ */ new Map();
|
|
29520
|
-
for (const entry of records) {
|
|
29521
|
-
if (entry === null || typeof entry !== "object") continue;
|
|
29522
|
-
const { logicalId, state } = entry;
|
|
29523
|
-
if (typeof logicalId !== "string" || state === null || typeof state !== "object") continue;
|
|
29524
|
-
for (const needle of secretNameNeedlesOf(logicalId, state, void 0) ?? []) recordLogOnlyValue(bag, needle);
|
|
29487
|
+
async readRunEvents(stackName, region, runId) {
|
|
29488
|
+
const key = deploymentEventsKey(this.backend.prefix, stackName, region, runId);
|
|
29489
|
+
const raw = await this.backend.getRawObject(key);
|
|
29490
|
+
if (raw === null) return null;
|
|
29491
|
+
const events = [];
|
|
29492
|
+
for (const line of raw.split("\n")) {
|
|
29493
|
+
const trimmed = line.trim();
|
|
29494
|
+
if (!trimmed) continue;
|
|
29495
|
+
try {
|
|
29496
|
+
events.push(JSON.parse(trimmed));
|
|
29497
|
+
} catch {}
|
|
29498
|
+
}
|
|
29499
|
+
return events;
|
|
29525
29500
|
}
|
|
29526
|
-
|
|
29527
|
-
|
|
29528
|
-
const
|
|
29529
|
-
|
|
29530
|
-
|
|
29531
|
-
|
|
29532
|
-
|
|
29533
|
-
|
|
29534
|
-
|
|
29535
|
-
|
|
29536
|
-
|
|
29537
|
-
|
|
29538
|
-
|
|
29501
|
+
async pruneRuns(stackName, region, opts = {}) {
|
|
29502
|
+
const dirPrefix = deploymentsDirPrefix(this.backend.prefix, stackName, region);
|
|
29503
|
+
const keys = await this.backend.listRawKeys(dirPrefix);
|
|
29504
|
+
const runIdsDesc = keys.map((k) => runIdFromJsonlKey(k, dirPrefix)).filter((id) => id !== null).sort().reverse();
|
|
29505
|
+
const indexKey = deploymentEventsIndexKey(this.backend.prefix, stackName, region);
|
|
29506
|
+
const indexExisted = keys.includes(indexKey);
|
|
29507
|
+
if (opts.all === true) {
|
|
29508
|
+
const toDelete = runIdsDesc.map((id) => deploymentEventsKey(this.backend.prefix, stackName, region, id));
|
|
29509
|
+
const earlierVersions = await deleteAndPurgeEventObjects(this.backend, [...toDelete, indexKey], dirPrefix, "whole-dir");
|
|
29510
|
+
return {
|
|
29511
|
+
deletedRunIds: runIdsDesc,
|
|
29512
|
+
remainingRunIds: [],
|
|
29513
|
+
indexDeleted: indexExisted,
|
|
29514
|
+
...earlierVersions !== void 0 && { earlierVersions }
|
|
29515
|
+
};
|
|
29516
|
+
}
|
|
29517
|
+
const noGuards = opts.keep === void 0 && opts.olderThanMs === void 0;
|
|
29518
|
+
const keep = opts.keep ?? (noGuards ? 20 : void 0);
|
|
29519
|
+
const protectedByCount = keep === void 0 ? /* @__PURE__ */ new Set() : new Set(runIdsDesc.slice(0, keep));
|
|
29520
|
+
let candidates = runIdsDesc.filter((id) => !protectedByCount.has(id));
|
|
29521
|
+
if (opts.olderThanMs !== void 0) {
|
|
29522
|
+
const cutoff = (opts.now ?? /* @__PURE__ */ new Date()).getTime() - opts.olderThanMs;
|
|
29523
|
+
candidates = candidates.filter((id) => {
|
|
29524
|
+
const t = runIdTimestampMs(id);
|
|
29525
|
+
return t !== null && t < cutoff;
|
|
29526
|
+
});
|
|
29527
|
+
}
|
|
29528
|
+
if (candidates.length === 0) return {
|
|
29529
|
+
deletedRunIds: [],
|
|
29530
|
+
remainingRunIds: runIdsDesc,
|
|
29531
|
+
indexDeleted: false
|
|
29532
|
+
};
|
|
29533
|
+
const deletedSet = new Set(candidates);
|
|
29534
|
+
const deleteKeys = candidates.map((id) => deploymentEventsKey(this.backend.prefix, stackName, region, id));
|
|
29535
|
+
const remainingRunIds = runIdsDesc.filter((id) => !deletedSet.has(id));
|
|
29536
|
+
if (remainingRunIds.length === 0) {
|
|
29537
|
+
await deleteAndPurgeEventObjects(this.backend, [...deleteKeys, indexKey], dirPrefix);
|
|
29538
|
+
return {
|
|
29539
|
+
deletedRunIds: candidates,
|
|
29540
|
+
remainingRunIds,
|
|
29541
|
+
indexDeleted: indexExisted
|
|
29542
|
+
};
|
|
29543
|
+
}
|
|
29544
|
+
await this.rewriteIndexAfterPrune(indexKey, stackName, region, deletedSet);
|
|
29545
|
+
await deleteAndPurgeEventObjects(this.backend, deleteKeys, dirPrefix);
|
|
29546
|
+
return {
|
|
29547
|
+
deletedRunIds: candidates,
|
|
29548
|
+
remainingRunIds,
|
|
29549
|
+
indexDeleted: false
|
|
29539
29550
|
};
|
|
29540
29551
|
}
|
|
29541
|
-
|
|
29542
|
-
|
|
29543
|
-
|
|
29552
|
+
async rewriteIndexAfterPrune(indexKey, stackName, region, deletedRunIds) {
|
|
29553
|
+
let raw;
|
|
29554
|
+
try {
|
|
29555
|
+
raw = await this.backend.getRawObject(indexKey);
|
|
29556
|
+
} catch {
|
|
29557
|
+
return;
|
|
29558
|
+
}
|
|
29559
|
+
if (raw === null) return;
|
|
29560
|
+
let parsed;
|
|
29561
|
+
try {
|
|
29562
|
+
parsed = JSON.parse(raw);
|
|
29563
|
+
} catch {
|
|
29564
|
+
return;
|
|
29565
|
+
}
|
|
29566
|
+
if (!Array.isArray(parsed.runs)) return;
|
|
29567
|
+
const remaining = parsed.runs.filter((r) => !deletedRunIds.has(r.runId));
|
|
29568
|
+
if (remaining.length === parsed.runs.length) return;
|
|
29569
|
+
const file = {
|
|
29570
|
+
indexVersion: 1,
|
|
29571
|
+
stackName,
|
|
29572
|
+
region,
|
|
29573
|
+
runs: remaining,
|
|
29574
|
+
lastModified: Date.now()
|
|
29575
|
+
};
|
|
29576
|
+
await this.backend.putRawObject(indexKey, JSON.stringify(file, null, 2));
|
|
29577
|
+
}
|
|
29578
|
+
};
|
|
29544
29579
|
|
|
29545
29580
|
//#endregion
|
|
29546
29581
|
//#region src/deployment/update-outcome.ts
|
|
@@ -30209,6 +30244,61 @@ function sortRollbackCreates(createOps, stateResources, logger) {
|
|
|
30209
30244
|
logger?.debug(`Rollback CREATE deletion order: ${sorted.map((op) => op.logicalId).join(" → ")}`);
|
|
30210
30245
|
return sorted;
|
|
30211
30246
|
}
|
|
30247
|
+
const deleteFirstBlockedBy = /* @__PURE__ */ new WeakMap();
|
|
30248
|
+
function markDeleteFirstBlocked(operations, failedOperations = []) {
|
|
30249
|
+
const gone = [];
|
|
30250
|
+
const add = (logicalId, physicalId, attributes) => {
|
|
30251
|
+
if (typeof physicalId !== "string" || physicalId === "") return;
|
|
30252
|
+
gone.push({
|
|
30253
|
+
logicalId,
|
|
30254
|
+
physicalId,
|
|
30255
|
+
needles: [physicalId, ...identityAttributeValues(physicalId, attributes)]
|
|
30256
|
+
});
|
|
30257
|
+
};
|
|
30258
|
+
for (const f of failedOperations) if (seedsDeleteFirstGuard(f)) add(f.logicalId, f.previousState?.physicalId, f.previousState?.attributes);
|
|
30259
|
+
else if (f.changeType === "CREATE" && f.replacedResourceDeleted === true) add(f.logicalId, f.replacedPhysicalId, void 0);
|
|
30260
|
+
for (const op of operations) {
|
|
30261
|
+
const prev = op.previousState?.physicalId;
|
|
30262
|
+
if (typeof prev !== "string" || prev === "") continue;
|
|
30263
|
+
if (op.changeType === "DELETE" || op.changeType === "UPDATE" && op.physicalId !== prev && op.wasReplaced !== false && op.oldResourceRetained !== true) add(op.logicalId, prev, op.previousState?.attributes);
|
|
30264
|
+
}
|
|
30265
|
+
if (gone.length === 0) return;
|
|
30266
|
+
for (const op of operations) {
|
|
30267
|
+
if (op.oldDeletedBeforeCreate !== true) continue;
|
|
30268
|
+
const props = op.previousState?.properties;
|
|
30269
|
+
if (props === void 0) continue;
|
|
30270
|
+
const hit = gone.find((g) => g.logicalId !== op.logicalId && someStringLeaf(props, (leaf) => g.needles.some((n) => namesResource(leaf, n))));
|
|
30271
|
+
if (hit) deleteFirstBlockedBy.set(op, {
|
|
30272
|
+
logicalId: hit.logicalId,
|
|
30273
|
+
physicalId: hit.physicalId
|
|
30274
|
+
});
|
|
30275
|
+
}
|
|
30276
|
+
}
|
|
30277
|
+
function identityAttributeValues(physicalId, attributes) {
|
|
30278
|
+
if (attributes === null || typeof attributes !== "object") return [];
|
|
30279
|
+
const idSegments = new Set(physicalId.split(/[:/|]/).filter((t) => t !== ""));
|
|
30280
|
+
const out = [];
|
|
30281
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
30282
|
+
if (typeof value !== "string" || value === "" || value === physicalId) continue;
|
|
30283
|
+
const arnTail = value.startsWith("arn:") ? value.split(/[:/]/).at(-1) : void 0;
|
|
30284
|
+
if (physicalId.length >= 8 && value.includes(physicalId) || arnTail !== void 0 && arnTail !== "" && idSegments.has(arnTail) || key === "DNSName") out.push(value);
|
|
30285
|
+
}
|
|
30286
|
+
return out;
|
|
30287
|
+
}
|
|
30288
|
+
function namesResource(leaf, needle) {
|
|
30289
|
+
return leaf === needle || leaf.split(/[:/]/).includes(needle) || needle.includes("/") && (leaf.includes(`:${needle}:`) || leaf.endsWith(`:${needle}`)) || needle.length >= 16 && leaf.includes(needle);
|
|
30290
|
+
}
|
|
30291
|
+
function seedsDeleteFirstGuard(op) {
|
|
30292
|
+
return op.changeType === "UPDATE" && (op.oldDeletedBeforeCreate === true || op.replacementOrphaned === "delete-first");
|
|
30293
|
+
}
|
|
30294
|
+
function deleteFirstBlocker(op) {
|
|
30295
|
+
return deleteFirstBlockedBy.get(op);
|
|
30296
|
+
}
|
|
30297
|
+
function someStringLeaf(value, test, depth = 0) {
|
|
30298
|
+
if (typeof value === "string") return test(value);
|
|
30299
|
+
if (depth > 64 || value === null || typeof value !== "object") return false;
|
|
30300
|
+
return (Array.isArray(value) ? value : Object.values(value)).some((child) => someStringLeaf(child, test, depth + 1));
|
|
30301
|
+
}
|
|
30212
30302
|
|
|
30213
30303
|
//#endregion
|
|
30214
30304
|
//#region src/deployment/rollback-executor/replay-noecho.ts
|
|
@@ -30796,7 +30886,10 @@ async function replayReverseReplacement(s) {
|
|
|
30796
30886
|
physicalIds: [prev.physicalId]
|
|
30797
30887
|
});
|
|
30798
30888
|
recordNestedStackParameterExpressions(secrets, oldType, resolvedPrevProps, prev.properties, STATE_DERIVED_RULES);
|
|
30799
|
-
|
|
30889
|
+
const deleteFirstBlocked = op.oldDeletedBeforeCreate === true && !rollbackRetainsNewResource(current) ? deleteFirstBlocker(op) : void 0;
|
|
30890
|
+
if (deleteFirstBlocked !== void 0) logger.warn(mask(safeMsg` Rollback: not deleting the new ${shownLogicalId(op.logicalId)} first: its old properties ` + safeMsg`name the resource ${shownLogicalId(deleteFirstBlocked.logicalId)} had before the same deploy ` + "replaced or deleted it, which this rollback may not be able to restore under that id — re-creating the old resource first, which keeps the new one if that fails"));
|
|
30891
|
+
const reverseDeleteFirst = op.oldDeletedBeforeCreate === true && !rollbackRetainsNewResource(current) && deleteFirstBlocked === void 0;
|
|
30892
|
+
logger.info(` Rollback: Reversing replacement of ${safe(op.logicalId)} (${typeChanged ? `${safe(op.resourceType)} -> ${safe(oldType)}` : safe(op.resourceType)}) — ` + (reverseDeleteFirst ? `deleting the new resource and re-creating the old one` : `re-creating the old resource and deleting the new one`));
|
|
30800
30893
|
if (STATEFUL_TYPES.has(oldType)) logger.warn(` ⚠ ${safe(op.logicalId)} (${safe(oldType)}) is a stateful type — the old physical resource's data was destroyed by the replacement and is NOT recovered by this rollback; the re-created resource starts empty.`);
|
|
30801
30894
|
const { provider: createProvider, provisionedBy: createProvisionedBy } = ctx.providerRegistry.getProviderFor({
|
|
30802
30895
|
resourceType: oldType,
|
|
@@ -30822,7 +30915,6 @@ async function replayReverseReplacement(s) {
|
|
|
30822
30915
|
mask
|
|
30823
30916
|
});
|
|
30824
30917
|
const newCopyClaimed = inlinePolicyWriters.claimedFor(op.resourceType, op.logicalId, stateResources);
|
|
30825
|
-
let deletedNewFirst = false;
|
|
30826
30918
|
const recreateCleanup = {
|
|
30827
30919
|
op,
|
|
30828
30920
|
oldType,
|
|
@@ -30834,8 +30926,40 @@ async function replayReverseReplacement(s) {
|
|
|
30834
30926
|
logger,
|
|
30835
30927
|
mask
|
|
30836
30928
|
};
|
|
30929
|
+
const deleteNewResourceFirst = async (purpose) => {
|
|
30930
|
+
const finalSnapshotIdentifier = rollbackFinalSnapshotId(op.resourceType, current, op.provisionedBy);
|
|
30931
|
+
const deleteNewFirstRoute = resolveNewDeleteRoute();
|
|
30932
|
+
const deleteNewFirst = await deleteNewFirstRoute.provider.delete(op.logicalId, current.physicalId, op.resourceType, current.properties, {
|
|
30933
|
+
expectedRegion: ctx.region,
|
|
30934
|
+
...newCopyClaimed && { inlinePolicyClaimed: newCopyClaimed },
|
|
30935
|
+
...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier },
|
|
30936
|
+
deletionPolicy: replacementDeletePolicy(current.updateReplacePolicy),
|
|
30937
|
+
recordedAttributes: current.attributes,
|
|
30938
|
+
recordedNoEchoLeaves: current.noEchoLeaves
|
|
30939
|
+
});
|
|
30940
|
+
throwIfDeleteSkipped(deleteNewFirst, op.logicalId, current.physicalId, purpose, newDeleteGuardScope(deleteNewFirstRoute.provisionedBy));
|
|
30941
|
+
delete stateResources[op.logicalId];
|
|
30942
|
+
await afterOp?.(op.logicalId);
|
|
30943
|
+
};
|
|
30944
|
+
const recreateAfterNewDeleted = async () => {
|
|
30945
|
+
try {
|
|
30946
|
+
return await createWithRollbackRetry(createProvider, () => inOriginalPrefix(() => withCurrentResourceSecrets(secrets, () => createProvider.create(op.logicalId, oldType, replayCreateProps(), replayingStateCreateContext(secrets)))), op.logicalId, logger, isInterrupted, mask, {
|
|
30947
|
+
isRetryable: isRecreateRetryableError,
|
|
30948
|
+
interruptedMessage: "Rollback interrupted while waiting for the new resource to release its name or slot"
|
|
30949
|
+
});
|
|
30950
|
+
} catch (recreateError) {
|
|
30951
|
+
await deleteMarkedRecreate(recreateError, recreateCleanup);
|
|
30952
|
+
throw new Error(mask(`Failed to re-create the old ${safe(op.logicalId)} after the new resource (${mask(current.physicalId)}) was already deleted: ${displaySafe(recreateError instanceof Error ? recreateError.message : String(recreateError))}. The resource is now absent — fix forward by re-deploying the stack.`), { cause: maskSecretsInError(recreateError instanceof Error ? recreateError : void 0, secrets) });
|
|
30953
|
+
}
|
|
30954
|
+
};
|
|
30955
|
+
let deletedNewFirst = false;
|
|
30837
30956
|
let createResult;
|
|
30838
|
-
|
|
30957
|
+
if (reverseDeleteFirst) {
|
|
30958
|
+
logger.info(` Rollback: the replacement deleted the old resource before creating the new one — ` + safeMsg`deleting the new resource (${mask(current.physicalId)}) first...`);
|
|
30959
|
+
await deleteNewResourceFirst("while clearing the new resource before re-creating the old one (the replacement deleted the old one first)");
|
|
30960
|
+
deletedNewFirst = true;
|
|
30961
|
+
createResult = await recreateAfterNewDeleted();
|
|
30962
|
+
} else try {
|
|
30839
30963
|
createResult = await createWithRollbackRetry(createProvider, () => inOriginalPrefix(() => withCurrentResourceSecrets(secrets, () => createProvider.create(op.logicalId, oldType, replayCreateProps(), replayingStateCreateContext(secrets)))), op.logicalId, logger, isInterrupted, mask, {
|
|
30840
30964
|
isRetryable: isNameCooldownError,
|
|
30841
30965
|
interruptedMessage: "Rollback interrupted while waiting out the name cooldown"
|
|
@@ -30867,31 +30991,16 @@ async function replayReverseReplacement(s) {
|
|
|
30867
30991
|
const newShown = refusalPhysicalId(mask(current.physicalId));
|
|
30868
30992
|
throw ownRemedyError(markNonRetryable(new CdkdError(mask(`Cannot reverse the replacement of ${refusalLogicalId(op.logicalId)} (${refusalResourceType(op.resourceType)}): the re-create of the old resource (${oldShown}) collided with the name still held by the new one (${newShown}), and UpdateReplacePolicy: Retain pins that new resource in place, so cdkd will not delete it to free the name. Delete the new resource yourself, or remove UpdateReplacePolicy: Retain, then re-run `) + rerunRollbackPhrase(ctx, "cdkd rollback") + mask(` — the journal is kept, so the revert resumes from here.` + (remedy.offered ? " To leave THIS resource alone and let the rest of the rollback proceed, re-run with the command below: one op failure stops the segment loop, so a single pinned resource otherwise halts every OLDER segment too." : "") + `${remedy.clause}${describedPhysicalIdPointer(oldShown, newShown)}\nUnderlying collision: ${collisionLine(mask(msg))}`) + remedy.line, "NAMED_REPLACEMENT_COLLISION", maskSecretsInError(createError instanceof Error ? createError : void 0, secrets))));
|
|
30869
30993
|
}
|
|
30870
|
-
|
|
30871
|
-
|
|
30872
|
-
const
|
|
30873
|
-
const
|
|
30874
|
-
|
|
30875
|
-
expectedRegion: ctx.region,
|
|
30876
|
-
...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier },
|
|
30877
|
-
deletionPolicy: replacementDeletePolicy(current.updateReplacePolicy),
|
|
30878
|
-
recordedAttributes: current.attributes,
|
|
30879
|
-
recordedNoEchoLeaves: current.noEchoLeaves
|
|
30880
|
-
});
|
|
30881
|
-
throwIfDeleteSkipped(deleteNewFirst, op.logicalId, current.physicalId, "while clearing the new resource so the old one could be re-created", newDeleteGuardScope(deleteNewFirstRoute.provisionedBy));
|
|
30994
|
+
if (deleteFirstBlocked !== void 0) {
|
|
30995
|
+
const remedy = orphanRemedy(op.logicalId, ctx);
|
|
30996
|
+
const oldShown = refusalPhysicalId(mask(prev.physicalId));
|
|
30997
|
+
const newShown = refusalPhysicalId(mask(current.physicalId));
|
|
30998
|
+
throw ownRemedyError(markNonRetryable(new CdkdError(mask(`Cannot reverse the replacement of ${refusalLogicalId(op.logicalId)} (${refusalResourceType(op.resourceType)}): the re-create of the old resource (${oldShown}) collided with the name still held by the new one (${newShown}), and the old resource's properties name what ${refusalLogicalId(deleteFirstBlocked.logicalId)} was before the same deploy replaced or deleted it, which this rollback may not be able to restore, so cdkd will not delete the new resource to free the name. Nothing was deleted. Re-deploy to fix forward, or re-run `) + rerunRollbackPhrase(ctx, "cdkd rollback") + mask(` once the old resource can be re-created — the journal is kept.` + (remedy.offered ? " To leave THIS resource alone and let the rest of the rollback proceed, re-run with the command below." : "") + `${remedy.clause}${describedPhysicalIdPointer(oldShown, newShown)}\nUnderlying collision: ${collisionLine(mask(msg))}`) + remedy.line, "NAMED_REPLACEMENT_COLLISION", maskSecretsInError(createError instanceof Error ? createError : void 0, secrets))));
|
|
30882
30999
|
}
|
|
31000
|
+
logger.info(` Rollback: re-create collided with the new resource's name — deleting the new resource (${displaySafe(mask(current.physicalId))}) first...` + (typeChanged ? ` (this op changed the resource's Type, ${safe(op.resourceType)} -> ${safe(oldType)}, which share a name space)` : ""));
|
|
31001
|
+
await deleteNewResourceFirst("while clearing the new resource so the old one could be re-created");
|
|
30883
31002
|
deletedNewFirst = true;
|
|
30884
|
-
|
|
30885
|
-
await afterOp?.(op.logicalId);
|
|
30886
|
-
try {
|
|
30887
|
-
createResult = await createWithRollbackRetry(createProvider, () => inOriginalPrefix(() => withCurrentResourceSecrets(secrets, () => createProvider.create(op.logicalId, oldType, replayCreateProps(), replayingStateCreateContext(secrets)))), op.logicalId, logger, isInterrupted, mask, {
|
|
30888
|
-
isRetryable: isRecreateRetryableError,
|
|
30889
|
-
interruptedMessage: "Rollback interrupted while waiting for the old name to release"
|
|
30890
|
-
});
|
|
30891
|
-
} catch (recreateError) {
|
|
30892
|
-
await deleteMarkedRecreate(recreateError, recreateCleanup);
|
|
30893
|
-
throw new Error(mask(`Failed to re-create the old ${safe(op.logicalId)} after the new resource (${mask(current.physicalId)}) was already deleted: ${displaySafe(recreateError instanceof Error ? recreateError.message : String(recreateError))}. The resource is now absent — fix forward by re-deploying the stack.`), { cause: maskSecretsInError(recreateError instanceof Error ? recreateError : void 0, secrets) });
|
|
30894
|
-
}
|
|
31003
|
+
createResult = await recreateAfterNewDeleted();
|
|
30895
31004
|
}
|
|
30896
31005
|
opMasker.addNamed({
|
|
30897
31006
|
resourceType: oldType,
|
|
@@ -31095,6 +31204,7 @@ async function replayRollbackUnbound(operations, stateResources, stackName, ctx,
|
|
|
31095
31204
|
});
|
|
31096
31205
|
const resolver = new ReplayResolvers(ctx.region);
|
|
31097
31206
|
inlinePolicyWriters.notePending(operations);
|
|
31207
|
+
markDeleteFirstBlocked(operations, options.failedOperations);
|
|
31098
31208
|
const { createOps, otherOps } = partitionOps(operations);
|
|
31099
31209
|
for (let i = otherOps.length - 1; i >= 0; i--) {
|
|
31100
31210
|
if (options.isInterrupted?.()) {
|
|
@@ -32268,7 +32378,8 @@ async function revertNestedChildFromJournal(args) {
|
|
|
32268
32378
|
const replayed = await withPrintingSecrets(journaledOrphanPrintingBag(completedReplayEntries(split.replay), stateResources), () => replayRollback(split.replay, stateResources, childStackName, execCtx, {
|
|
32269
32379
|
afterOp: save,
|
|
32270
32380
|
onOrphan: (record) => mintedOrphans.push(record),
|
|
32271
|
-
inlinePolicyWriters
|
|
32381
|
+
inlinePolicyWriters,
|
|
32382
|
+
failedOperations: segment.failedOperations
|
|
32272
32383
|
}));
|
|
32273
32384
|
for (const [id, below] of inner.settled) settledBelow.set(id, below);
|
|
32274
32385
|
return {
|
|
@@ -32537,7 +32648,10 @@ async function performRollback(completedOperations, stateResources, stackName, p
|
|
|
32537
32648
|
emitEnvelope: completedOperations.length === 0,
|
|
32538
32649
|
inlinePolicyWriters
|
|
32539
32650
|
})) : void 0,
|
|
32540
|
-
result: await withPrintingSecrets(replayPrintingBag(this, completedReplayEntries(completedOperations), stateResources), () => replayRollback(completedOperations, stateResources, stackName, ctx, {
|
|
32651
|
+
result: await withPrintingSecrets(replayPrintingBag(this, completedReplayEntries(completedOperations), stateResources), () => replayRollback(completedOperations, stateResources, stackName, ctx, {
|
|
32652
|
+
inlinePolicyWriters,
|
|
32653
|
+
failedOperations
|
|
32654
|
+
})),
|
|
32541
32655
|
run: scope
|
|
32542
32656
|
};
|
|
32543
32657
|
});
|
|
@@ -33921,6 +34035,7 @@ function priorAttemptsInJournal(journal, logicalId, resourceType) {
|
|
|
33921
34035
|
for (const op of segment.failedOperations ?? []) {
|
|
33922
34036
|
if (op.logicalId !== logicalId || op.resourceType !== resourceType) continue;
|
|
33923
34037
|
if (op.changeType === "DELETE" || !isBag(op.attemptedProperties)) continue;
|
|
34038
|
+
if (op.changeType === "UPDATE" && op.replacementOrphaned !== void 0) continue;
|
|
33924
34039
|
if (recordedAsNewResource(op)) continue;
|
|
33925
34040
|
bags.push(op.attemptedProperties);
|
|
33926
34041
|
}
|
|
@@ -39112,6 +39227,7 @@ async function executeDeployment(template, currentState, changes, dag, execution
|
|
|
39112
39227
|
physicalIdRecoveredFromError: true,
|
|
39113
39228
|
deletionPolicy: journaledOrphanPolicy(this.extractTemplateAttributes(template, logicalId).deletionPolicy)
|
|
39114
39229
|
},
|
|
39230
|
+
...change.changeType === "UPDATE" && this.oldDeletedBeforeCreate.has(logicalId) && { oldDeletedBeforeCreate: true },
|
|
39115
39231
|
...orphanedBy !== void 0 && { replacementOrphaned: this.oldDeletedBeforeCreate.has(logicalId) ? "delete-first" : "create-first" },
|
|
39116
39232
|
...!refused && { attemptedProperties: this.attemptedResolvedProps.get(logicalId) }
|
|
39117
39233
|
};
|
|
@@ -39156,7 +39272,10 @@ async function executeDeployment(template, currentState, changes, dag, execution
|
|
|
39156
39272
|
previousState,
|
|
39157
39273
|
physicalId: newResources[logicalId]?.physicalId,
|
|
39158
39274
|
properties: newResources[logicalId]?.properties,
|
|
39159
|
-
...change.changeType === "UPDATE" && {
|
|
39275
|
+
...change.changeType === "UPDATE" && {
|
|
39276
|
+
oldResourceRetained: this.retainedOldOnReplacement.has(logicalId),
|
|
39277
|
+
oldDeletedBeforeCreate: this.oldDeletedBeforeCreate.has(logicalId)
|
|
39278
|
+
},
|
|
39160
39279
|
...change.changeType === "UPDATE" && this.updateWasReplaced.has(logicalId) && { wasReplaced: this.updateWasReplaced.get(logicalId) },
|
|
39161
39280
|
...change.changeType === "UPDATE" && previousState !== void 0 && { previousResourceType: previousState.resourceType }
|
|
39162
39281
|
});
|
|
@@ -40716,15 +40835,19 @@ var DeployEngine = class {
|
|
|
40716
40835
|
} catch {}
|
|
40717
40836
|
}
|
|
40718
40837
|
maskSecretsInEvent(event) {
|
|
40719
|
-
const
|
|
40720
|
-
if (
|
|
40721
|
-
const
|
|
40722
|
-
|
|
40723
|
-
|
|
40724
|
-
|
|
40838
|
+
const own = event.logicalId ? this.printingSecretsFor(event.logicalId) : void 0;
|
|
40839
|
+
if (own === void 0 || !hasMaskableValues(own)) return maskEventTextWithBoundBags(event);
|
|
40840
|
+
const masked = withPrintingSecrets(own, () => maskEventTextWithBoundBags(event));
|
|
40841
|
+
const message = masked.error?.message;
|
|
40842
|
+
if (masked.error?.ownLines !== true || !message) return masked;
|
|
40843
|
+
const commandLines = message.split("\n").map((line) => line.startsWith("To orphan it: ") ? maskSecretsInText(line, own) : line).join("\n");
|
|
40844
|
+
return {
|
|
40845
|
+
...masked,
|
|
40846
|
+
error: {
|
|
40847
|
+
...masked.error,
|
|
40848
|
+
message: commandLines
|
|
40849
|
+
}
|
|
40725
40850
|
};
|
|
40726
|
-
if (next.reason) next.reason = maskSecretsInText(next.reason, secrets);
|
|
40727
|
-
return next;
|
|
40728
40851
|
}
|
|
40729
40852
|
async withRetry(operation, logicalId, maxRetries, initialDelayMs, provider) {
|
|
40730
40853
|
if (provider?.disableOuterRetry) return operation();
|
|
@@ -40832,5 +40955,5 @@ DeployEngine.prototype.replacementNameOrigin = replacementNameOrigin;
|
|
|
40832
40955
|
DeployEngine.prototype.orphanedNameCollisionAdvice = orphanedNameCollisionAdvice;
|
|
40833
40956
|
|
|
40834
40957
|
//#endregion
|
|
40835
|
-
export { gray as $, templateResourceTypes as $a, refuseMalformedOutputsForDestroy as $i, definedAttributes as $n, STATE_SOURCED_BASELINE_RULES as $o, isImpossibleEmptyStoredAttribute as $r, redactDockerArgvValues as $s, updatePartialMessage as $t, perType as A, hasNoCloudControlHandlers as Aa, expectedOwnerParam as Ac, malformedLocalResourcesWarning as Ai, collectDeclaredOutputNames as An, createSecretMasker as Ao, classifyReplaySecretRegion as Ar, AssetModeResolver as As, awaitInFlightSibling as At, INDEX_SETTLE_POLL_INTERVAL_MS as B, toCfnInteger as Ba, nullPrototypeRecord as Bc, malformedResourcePropertiesWarning as Bi, RollbackInlinePolicyWriters as Bn, pathCrossesDottedKey as Bo, applyCallerIdentityCredentials as Br, buildDenyExternalAccessPolicy as Bs, planRollback as Bt, noEchoComparisonForTemplate as C, unreadableResourceEntries as Ca, CFN_TEMPLATE_BODY_LIMIT as Cc, isReadableOrphanRecord as Ci, secretBearingStateKeyWarning as Cn, hasMaskableValues as Co, observeThenDisableInstanceApiTermination as Cr, isSensitiveAttributeName as Cs, isHandledOrphan as Ct, recordedProtectionNote as D, unwrittenCreateOnlyReplacement as Da, uploadCfnTemplate as Dc, malformedExportSourceWarning as Di, exportNameSecretExposure as Dn, recordLogOnlyParameterValue as Do, INTRINSIC_KEYS as Dr, createAssetRedirectResolver as Ds, sameJournaledOrphans as Dt, recordedProtectionEvidence as E, findSilentDropProperties as Ea, findLargeInlineResources as Ec, malformedExportNamesWarning as Ei, exportNameNoEchoParameters as En, printingCorpusOf as Eo, DiffCalculator as Er, buildAssetRedirectMap as Es, makeForeignHolderScan as Et, compensateRemovedDeletionProtection as F, isStatefulRecreateTargetSync as Fa, StageLoadError as Fc, malformedOrphansWarning as Fi, DEFAULT_RESOURCE_WARN_AFTER_MS as Fn, redactSecretsForState as Fo, assertSendableRoleArn as Fr, isCrossRegionRedirect as Fs, recordAfterRollbackUpdate as Ft, hasCdkAutoDeleteTag as G, configIntegerRefusal as Ga, renderAssemblyPathEscape as Gc, refuseDivergentRecordRegionForDestroy as Gi, acquireIdempotencyToken as Gn, carriesSecretMask as Go, s3BucketDomainName as Gr, describeDockerFailure as Gs, orphanDeleteNeedsIdentity as Gt, waitForIndexesSettled as H, coerceCfnBoolean as Ha, absoluteAssemblyPathEscape as Hc, malformedScrubResourceEntriesRefusalMessage as Hi, ledgerForStack as Hn, inheritNestedStackParameterAssociations as Ho, envCredentialsAreAssumedRole as Hr, resolveDockerContextDirectory as Hs, recordUnderIdIsNotOwn as Ht, deleteBudgetKey as I, renderStatefulReason as Ia, processStackMessages as Ic, malformedOutputsRefusalMessage as Ii, maskingRetryLogger as In, scrubResourceRecord as Io, explicitRoleArnOrThrow as Ir, parseBootstrapMarker as Is, demoteSupersededOrphans as It, markRefusedBeforeApplying as J, replayWarn as Ja, resolveBucketRegion as Jc, refuseMalformedOrphanRecordsForDestroy as Ji, planTagDiff as Jn, recordMaskOnlyValuesIn as Jo, s3BucketWebsiteUrl as Jr, formatDockerLoginError as Js, inheritProducerRegions as Jt, isTruthyCfnBoolean as K, configStringRefusal as Ka, resolveAssemblyPath as Kc, refuseMalformedNestedChildOutputs as Ki, DBPROXY_TAGS_WHAT as Kn, provenPublicValue as Ko, s3BucketDualStackDomainName as Kr, dockerSpawnEnvWithSensitive as Ks, findNestedStackTypeChanges as Kt, resolveDynamoDbDeleteBudgetClock as L, WARM_THROUGHPUT_MEMBERS as La, defineOwnKey as Lc, malformedOutputsWarning as Li, constructPathOf as Ln, DYNAMIC_REFERENCE_TOKEN_SCAN as Lo, isIamRoleArn as Lr, readBootstrapMarkerBody as Ls, isJournaledOrphan as Lt, DynamoDBGlobalTableProvider as M, shownType as Ma, canonicalizeRegion as Mc, malformedOrphanRecordsWarning as Mi, isExportAliasCollision as Mn, maskSecretsInError as Mo, regionLessSecretName as Mr, assertAssetBucketRegion as Ms, getStackRecords as Mt, DYNAMODB_DELETE_MIN_RESOURCE_TIMEOUT_MS as N, MULTI_REGION_RECREATE_BLOCKED_TYPES as Na, derivePartitionAndUrlSuffix as Nc, malformedOrphanRowsKeptWarning as Ni, bindingSkippedOutputs as Nn, maskSecretsInText as No, applyRoleArnIfSet as Nr, ensureAssetStorage as Ns, withStackRecords as Nt, PROTECTION_PROPERTY_BY_TYPE as O, withoutSilentDropProperties as Oa, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Oc, malformedLocalOutputsWarning as Oi, isWholeDynamicReferenceValue as On, recordLogOnlyValue as Oo, IntrinsicFunctionResolver as Or, loadPublishableAssetManifest as Os, replayFailedOperations as Ot, beginDeleteWait as P, isStatefulRecreateTargetForReplace as Pa, AssemblyReader as Pc, malformedOrphansRefusalMessage as Pi, DEFAULT_RESOURCE_TIMEOUT_MS as Pn, markSameGenerationBag as Po, assertFlagRoleArn as Pr, getBootstrapMarkerKey as Ps, removeProtectionTypeList as Pt, cyan as Q, prefetchCreateOnlyPropertyPaths as Qa, refuseMalformedOutputs as Qi, tagPlanWarning as Qn, wholeStringLeavesOf as Qo, getAccountInfo as Qr, partitionSensitiveEnv as Qs, redactedDeleteAddressSkip as Qt, resolveDynamoDbDeleteBudgetMs as R, coerceWarmThroughput as Ra, hasOwnKey as Rc, malformedRenderedContainersWarning as Ri, findDestructiveChanges as Rn, dynamicReferenceTokens as Ro, sendableAssumeRoleCommand as Rr, validateAssetBucketName as Rs, isReplacementOrphan as Rt, noEchoAttributeNamesOf as S, unreadableOrphanRecords as Sa, ssmResolvedValueType as Sc, hasReadableResources as Si, secretBearing as Sn, createUnionSecretMasker as So, isTerminationProtectionPropagationError as Sr, AssetPublisher as Ss, dropFailedHint as St, noEchoParameterNamesOf as T, findActionableSilentDrops as Ta, MIGRATE_TMP_PREFIX as Tc, malformedDriftResourcePropertiesWarning as Ti, carriedExportAliasExposure as Tn, logOnlyValueCount as To, reEnableInstanceApiTermination as Tr, WorkGraph as Ts, loadJournaledOrphans as Tt, ECR_AUTO_DELETE_IMAGES_TAG as U, coerceCfnInteger as Ua, describeFileReadFailure as Uc, malformedStateRefusalMessage as Ui, reserveStackCreateToken as Un, recordNestedStackParameterExpressions as Uo, isSettledInstanceState as Ur, describeDockerCapturedOutput as Us, replacementNeverSwapped as Ut, deleteTableWithIndexBusyRetry as V, toFiniteNumber as Va, ownValue as Vc, malformedResourcesWarning as Vi, earliestDefined as Vn, identityKeyFor as Vo, callerEnvCredentials as Vr, buildDockerImage as Vs, recheckFailedPlan as Vt, S3_AUTO_DELETE_OBJECTS_TAG as W, configBooleanRefusal as Wa, displayAssemblyPath as Wc, previewableOrphanRecords as Wi, withCreateTokenLedger as Wn, recoverMaskedOutput as Wo, s3BucketArn as Wr, describeDockerExecFailure as Ws, resolveReplacementOldType as Wt, createReadOnlyAttributeHealerFactory as X, requireConfigObject as Xa, refuseMalformedOrphansForDestroy as Xi, refuseMalformedDesiredTags as Xn, recordNoEchoParameterFreshValue as Xo, isUnboundTemplateParameter as Xr, isDockerClientEnvKey as Xs, isRedactedRecordedValue as Xt, formatResourceLine as Y, requireConfigArray as Ya, refuseMalformedOrphans as Yi, readTagList as Yn, recordNoEchoAttributeValues as Yo, coerceParameterTypedValue as Yr, getDockerCmd as Ys, REDACTED_DELETE_ADDRESS_SKIP_REASON as Yt, bold as Z, requireConfigString as Za, refuseMalformedOrphansForOrphan as Zi, tagMapAsList as Zn, recordProvenPublicExpression as Zo, parameterTypeMayLoseSecretIdentity as Zr, isMalformedEnvKey as Zs, redactedDeleteAddressFields as Zt, makeCanonicalizePropertiesFn as _, repairMalformedOutputsForReadOnly as _a, resolveStateBucketWithDefault as _c, accountArgs as _i, DeploymentEventsStore as _n, readbackPathFor as _o, withIndeterminateGuard as _r, importableOutputs as _s, revertedNestedRowIds as _t, findNestedTemplateTreeDefect as a, refuseMalformedResourceEntriesForImportSave as aa, stripControlChars as ac, cfnRefValueFromPhysicalId as ai, secretNamePrintingBag as an, DagBuilder as ao, wouldReturnToSdkProvider as ar, LockManager as as, reachableDivergentParameters as at, serviceTokenBlockingReason as b, repairMalformedResourcesForReadOnly as ba, stateBucketExistenceConfirmed as bc, hasReadableOrphans as bi, displayTextOrWithheld as bn, liveMatchesUnresolvedTokenFrame as bo, disableInstanceApiTermination as br, orphansCarriedFrom as bs, withNestedStackContext as bt, renderNestedTemplateTreeDefect as c, refuseMalformedResourcePropertiesForDrift as ca, Synthesizer as cc, parseWebACLArn as ci, PRE_DELETE_SNAPSHOT_TYPES as cn, isMarkedCoordinate as co, isCustomResourceType as cr, rebuildClientForBucketRegion as cs, planOrphanAdoption as ct, conditionInputsFrom as d, refuseMalformedResourcesForDeploy as da, getLegacyStateBucketName as dc, passedParameterClassesOf as di, createPreDeleteFinalSnapshot as dn, maskWholeValue as do, CloudControlProvider as dr, describeRegionValueKind as ds, displacedPhysicalIdShown as dt, refuseMalformedResourceAttributesForOrphan as ea, redactedDockerCause as ec, carriesDynamicReference as ei, updatePartialReason as en, describeTypeWithThrottleRetry as eo, stringifyIfAssigned as er, STATE_SOURCED_CROSS_GENERATION_RULES as es, green as et, parentSuppliedValues as f, refuseMalformedResourcesForDestroy as fa, loadCdkJson as fc, JSON_LISTING_HOLE_VALUE as fi, effectiveDeletionPolicy as fn, maskedLeafCoordinatesOf as fo, slowCcOperationTimeoutMs as fr, exportNamesCarriedFrom as fs, dropSettledNestedJournals as ft, mergeNoChangeOutputs as g, repairMalformedOrphansForReadOnly as ga, resolveSkipPrefix as gc, UNREADABLE_RESOURCES_MAP_ROW as gi, DeploymentEventsReader as gn, passedNoEchoParametersOf as go, deleteSkipReason as gr, importableOutputKeys as gs, revertNestedChildFromJournal as gt, keptWholeReasonText as h, repairMalformedOrphanRecordsForReadOnly as ha, resolveCaptureObservedState as hc, UNREADABLE_ORPHANS_CONTAINER_ROW as hi, unsupportedFinalSnapshotError as hn, noEchoOutputsComparison as ho, deleteIndeterminateGuards as hr, hasUnverifiableParameterRefusal as hs, recordDisplacedSkips as ht, childTemplateLoader as i, refuseMalformedResourceEntriesForImport as ia, escapeRegExp$3 as ic, AWS_NO_VALUE as ii, orphanRecordsPrintingBag as in, sameLambdaFunctionAddress as io, isCustomResource as ir, isSingleDynamicReferenceToken as is, namesAnyDeclaredParameter as it, readProtection as j, findReplacedReadersOfRecreateTargets as ja, PARTITION_TABLE as jc, malformedOrphanRecordsRefusalMessage as ji, collectPublishedOutputNames as jn, errorCauseChain as jo, producerRegionsFromState as jr, BOOTSTRAP_MARKER_PREFIX as js, destroyStackRecordsView as jt, isProtectionValueActive as k, NO_CC_HANDLERS_REASON as ka, decodeListingKey as kc, malformedLocalResourceEntriesWarning as ki, noEchoParameterValueSeed as kn, shareLogOnlyValues as ko, poisonRenderedSpellingsCollidingIn as kr, rewriteTemplateAssetReferences as ks, replayRollback as kt, templateIdentity as l, refuseMalformedResourcePropertiesForExport as la, synthesisStatusMessage as lc, withSharedDrainBudget as li, buildFinalSnapshotIdentifier as ln, maskAtCoordinates as lo, getCurrentResourceSecrets as lr, splitImportedOps as ls, NESTED_PENDING_PARENT_REASON as lt, isSecretBearingReferenceString as m, refuseMalformedState as ma, resolveAutoAssetStorage as mc, STATE_RESOURCES_MALFORMED as mi, refusesFinalSnapshot as mn, noEchoLeavesOf as mo, UNSPECIFIED_SKIP_REASON as mr, hasReasonlessBaselineRefusal as ms, nestedChildStackName as mt, IMPLICIT_DELETE_DEPENDENCIES as n, refuseMalformedResourceEntriesForDeploy as na, runDockerStreaming as nc, isNamelessDynamicReferenceError as ni, journaledOrphanPrintingBag as nn, canonicalLambdaFunctionName as no, ProviderRegistry as nr, TEMPLATE_SOURCED_RULES as ns, yellow as nt, isAbsoluteAssetPath as o, refuseMalformedResourceEntriesForOrphan as oa, AssetManifestLoader as oc, isStalePlaceholderArnAttribute as oi, stateSecretNameNeedles as on, TemplateParser as oo, TemplateNoEchoReresolver as or, isLockInfoExpired as os, resourcesNamingDeclaredParameter as ot, readRecordedConditionVerdicts as p, refuseMalformedResourcesForExport as pa, resolveApp as pc, STATE_REGION_DIVERGED as pi, isFinalSnapshotError as pn, noEchoCoordinatesOf as po, unchangedBehindSecretReference as pr, hasReadableExportSet as ps, getNestedRevertRun as pt, getPriorAttempts as q, readConfigString as qa, clearBucketRegionCache as qc, refuseMalformedOrphanRecords as qi, DBPROXY_TAG_OPTIONS as qn, recordMaskOnlyValue as qo, s3BucketRegionalDomainName as qr, finchSecretArgvRefusal as qs, getCurrentProducerRegions as qt, computeImplicitDeleteEdges as r, refuseMalformedResourceEntriesForDestroy as ra, warnFinchArgvExposure as rc, keepsSecretReferenceToken as ri, maskEventTextWithBoundBags as rn, lambdaFunctionNameForMask as ro, ccBrokenReason as rr, WHOLE_DYNAMIC_REFERENCE_PATTERN as rs, computeParameterTaint as rt, listNestedTemplateRows as s, refuseMalformedResourcePropertiesForDestroy as sa, getDockerImageBySourceHash as sc, refStateLookupFromResource as si, ATOMIC_FINAL_SNAPSHOT_TYPES as sn, canonicalCoordinates as so, readsNoEchoDeleteValues as sr, S3StateBackend as ss, makeSiblingClaimReader as st, DeployEngine as t, refuseMalformedResourceEntries as ta, runDockerForeground as tc, carriesFetchableDynamicReference as ti, completedReplayEntries as tn, withRetry as to, CC_BROKEN_REASON as tr, STATE_SOURCED_READBACK_RULES as ts, red as tt, conditionFingerprint as u, refuseMalformedResourcePropertiesForOrphan as ua, getDefaultStateBucketName as uc, parameterInputsFor as ui, ccRoutedFinalSnapshotError as un, maskReadbackAtCoordinates as uo, withPrintingSecrets as ur, acceptedCreateOnlyDropsOf as us, displacedOpClause as ut, deferredServiceTokenWarning as v, repairMalformedResourceEntriesForReadOnly as va, resolveStateBucketWithDefaultAndSource as vc, deployRefusesOrphanRowsReason as vi, extractDeploymentEventError as vn, readsNoEchoSource as vo, isWaitAbandonedError as vr, isReadableBag as vs, withNestedRevertRun as vt, noEchoExactEchoLeavesOf as w, withheldAccountClause as wa, CFN_TEMPLATE_URL_LIMIT as wc, isReadableResourceEntry as wi, secretSafeKeyDisplay as wn, literalSplitDelimitersOf as wo, reDisableInstanceApiTermination as wr, stringifyValue as ws, journaledOrphanLines as wt, withResourceDeadline as x, unpreviewableOrphanRecords as xa, warnDeprecatedNoPrefixCliFlag as xc, hasReadableOutputs as xi, exportAliasCollisionScrubWarning as xn, carryLogOnlyValues as xo, ec2InstanceProtectionSite as xr, shouldRetainResource as xs, deleteJournaledOrphans as xt, findServiceTokenRefusals as y, repairMalformedResourcePropertiesForReadOnly as ya, resolveUseCdkBootstrapAssets as yc, displayLogicalId as yi, WITHHELD_NAME_DISPLAY as yn, valueAtCoordinate as yo, markWaitAbandoned as yr, orphansAfterRollback as ys, getCurrentNestedStackContext as yt, DELETE_INDEX_WAIT_PROCEED_NOTE as z, isWarmThroughputDecrease as za, hasPlainPrototype$1 as zc, malformedResourceEntriesWarning as zi, formatDestructiveChange as zn, isUncertifiedBaselineMaskPosition as zo, AWS_CREDENTIAL_ENV_KEYS as zr, validateContainerRepoName as zs, planFailedOps as zt };
|
|
40836
|
-
//# sourceMappingURL=deploy-engine-
|
|
40958
|
+
export { gray as $, prefetchCreateOnlyPropertyPaths as $a, refuseMalformedOutputs as $i, tagPlanWarning as $n, wholeStringLeavesOf as $o, getAccountInfo as $r, partitionSensitiveEnv as $s, redactedDeleteAddressSkip as $t, perType as A, NO_CC_HANDLERS_REASON as Aa, decodeListingKey as Ac, malformedLocalResourceEntriesWarning as Ai, buildFinalSnapshotIdentifier as An, shareLogOnlyValues as Ao, poisonRenderedSpellingsCollidingIn as Ar, rewriteTemplateAssetReferences as As, awaitInFlightSibling as At, INDEX_SETTLE_POLL_INTERVAL_MS as B, isWarmThroughputDecrease as Ba, hasPlainPrototype$1 as Bc, malformedResourceEntriesWarning as Bi, formatDestructiveChange as Bn, isUncertifiedBaselineMaskPosition as Bo, AWS_CREDENTIAL_ENV_KEYS as Br, validateContainerRepoName as Bs, planRollback as Bt, noEchoComparisonForTemplate as C, unreadableOrphanRecords as Ca, ssmResolvedValueType as Cc, hasReadableResources as Ci, maskEventTextWithBoundBags as Cn, createUnionSecretMasker as Co, isTerminationProtectionPropagationError as Cr, AssetPublisher as Cs, isHandledOrphan as Ct, recordedProtectionNote as D, findSilentDropProperties as Da, findLargeInlineResources as Dc, malformedExportNamesWarning as Di, extractDeploymentEventError as Dn, printingCorpusOf as Do, DiffCalculator as Dr, buildAssetRedirectMap as Ds, sameJournaledOrphans as Dt, recordedProtectionEvidence as E, findActionableSilentDrops as Ea, MIGRATE_TMP_PREFIX as Ec, malformedDriftResourcePropertiesWarning as Ei, stateSecretNameNeedles as En, logOnlyValueCount as Eo, reEnableInstanceApiTermination as Er, WorkGraph as Es, makeForeignHolderScan as Et, compensateRemovedDeletionProtection as F, isStatefulRecreateTargetForReplace as Fa, AssemblyReader as Fc, malformedOrphansRefusalMessage as Fi, refusesFinalSnapshot as Fn, markSameGenerationBag as Fo, assertFlagRoleArn as Fr, getBootstrapMarkerKey as Fs, recordAfterRollbackUpdate as Ft, hasCdkAutoDeleteTag as G, configBooleanRefusal as Ga, displayAssemblyPath as Gc, previewableOrphanRecords as Gi, withCreateTokenLedger as Gn, recoverMaskedOutput as Go, s3BucketArn as Gr, describeDockerExecFailure as Gs, seedsDeleteFirstGuard as Gt, waitForIndexesSettled as H, toFiniteNumber as Ha, ownValue as Hc, malformedResourcesWarning as Hi, earliestDefined as Hn, identityKeyFor as Ho, callerEnvCredentials as Hr, buildDockerImage as Hs, recordUnderIdIsNotOwn as Ht, deleteBudgetKey as I, isStatefulRecreateTargetSync as Ia, StageLoadError as Ic, malformedOrphansWarning as Ii, unsupportedFinalSnapshotError as In, redactSecretsForState as Io, assertSendableRoleArn as Ir, isCrossRegionRedirect as Is, demoteSupersededOrphans as It, markRefusedBeforeApplying as J, readConfigString as Ja, clearBucketRegionCache as Jc, refuseMalformedOrphanRecords as Ji, DBPROXY_TAG_OPTIONS as Jn, recordMaskOnlyValue as Jo, s3BucketRegionalDomainName as Jr, finchSecretArgvRefusal as Js, getCurrentProducerRegions as Jt, isTruthyCfnBoolean as K, configIntegerRefusal as Ka, renderAssemblyPathEscape as Kc, refuseDivergentRecordRegionForDestroy as Ki, acquireIdempotencyToken as Kn, carriesSecretMask as Ko, s3BucketDomainName as Kr, describeDockerFailure as Ks, orphanDeleteNeedsIdentity as Kt, resolveDynamoDbDeleteBudgetClock as L, renderStatefulReason as La, processStackMessages as Lc, malformedOutputsRefusalMessage as Li, maskingRetryLogger as Ln, scrubResourceRecord as Lo, explicitRoleArnOrThrow as Lr, parseBootstrapMarker as Ls, isJournaledOrphan as Lt, DynamoDBGlobalTableProvider as M, findReplacedReadersOfRecreateTargets as Ma, PARTITION_TABLE as Mc, malformedOrphanRecordsRefusalMessage as Mi, createPreDeleteFinalSnapshot as Mn, errorCauseChain as Mo, producerRegionsFromState as Mr, BOOTSTRAP_MARKER_PREFIX as Ms, getStackRecords as Mt, DYNAMODB_DELETE_MIN_RESOURCE_TIMEOUT_MS as N, shownType as Na, canonicalizeRegion as Nc, malformedOrphanRecordsWarning as Ni, effectiveDeletionPolicy as Nn, maskSecretsInError as No, regionLessSecretName as Nr, assertAssetBucketRegion as Ns, withStackRecords as Nt, PROTECTION_PROPERTY_BY_TYPE as O, unwrittenCreateOnlyReplacement as Oa, uploadCfnTemplate as Oc, malformedExportSourceWarning as Oi, ATOMIC_FINAL_SNAPSHOT_TYPES as On, recordLogOnlyParameterValue as Oo, INTRINSIC_KEYS as Or, createAssetRedirectResolver as Os, replayFailedOperations as Ot, beginDeleteWait as P, MULTI_REGION_RECREATE_BLOCKED_TYPES as Pa, derivePartitionAndUrlSuffix as Pc, malformedOrphanRowsKeptWarning as Pi, isFinalSnapshotError as Pn, maskSecretsInText as Po, applyRoleArnIfSet as Pr, ensureAssetStorage as Ps, removeProtectionTypeList as Pt, cyan as Q, requireConfigString as Qa, refuseMalformedOrphansForOrphan as Qi, tagMapAsList as Qn, recordProvenPublicExpression as Qo, parameterTypeMayLoseSecretIdentity as Qr, isMalformedEnvKey as Qs, redactedDeleteAddressFields as Qt, resolveDynamoDbDeleteBudgetMs as R, WARM_THROUGHPUT_MEMBERS as Ra, defineOwnKey as Rc, malformedOutputsWarning as Ri, constructPathOf as Rn, DYNAMIC_REFERENCE_TOKEN_SCAN as Ro, isIamRoleArn as Rr, readBootstrapMarkerBody as Rs, isReplacementOrphan as Rt, noEchoAttributeNamesOf as S, unpreviewableOrphanRecords as Sa, warnDeprecatedNoPrefixCliFlag as Sc, hasReadableOutputs as Si, journaledOrphanPrintingBag as Sn, carryLogOnlyValues as So, ec2InstanceProtectionSite as Sr, shouldRetainResource as Ss, dropFailedHint as St, noEchoParameterNamesOf as T, withheldAccountClause as Ta, CFN_TEMPLATE_URL_LIMIT as Tc, isReadableResourceEntry as Ti, secretNamePrintingBag as Tn, literalSplitDelimitersOf as To, reDisableInstanceApiTermination as Tr, stringifyValue as Ts, loadJournaledOrphans as Tt, ECR_AUTO_DELETE_IMAGES_TAG as U, coerceCfnBoolean as Ua, absoluteAssemblyPathEscape as Uc, malformedScrubResourceEntriesRefusalMessage as Ui, ledgerForStack as Un, inheritNestedStackParameterAssociations as Uo, envCredentialsAreAssumedRole as Ur, resolveDockerContextDirectory as Us, replacementNeverSwapped as Ut, deleteTableWithIndexBusyRetry as V, toCfnInteger as Va, nullPrototypeRecord as Vc, malformedResourcePropertiesWarning as Vi, RollbackInlinePolicyWriters as Vn, pathCrossesDottedKey as Vo, applyCallerIdentityCredentials as Vr, buildDenyExternalAccessPolicy as Vs, recheckFailedPlan as Vt, S3_AUTO_DELETE_OBJECTS_TAG as W, coerceCfnInteger as Wa, describeFileReadFailure as Wc, malformedStateRefusalMessage as Wi, reserveStackCreateToken as Wn, recordNestedStackParameterExpressions as Wo, isSettledInstanceState as Wr, describeDockerCapturedOutput as Ws, resolveReplacementOldType as Wt, createReadOnlyAttributeHealerFactory as X, requireConfigArray as Xa, refuseMalformedOrphans as Xi, readTagList as Xn, recordNoEchoAttributeValues as Xo, coerceParameterTypedValue as Xr, getDockerCmd as Xs, REDACTED_DELETE_ADDRESS_SKIP_REASON as Xt, formatResourceLine as Y, replayWarn as Ya, resolveBucketRegion as Yc, refuseMalformedOrphanRecordsForDestroy as Yi, planTagDiff as Yn, recordMaskOnlyValuesIn as Yo, s3BucketWebsiteUrl as Yr, formatDockerLoginError as Ys, inheritProducerRegions as Yt, bold as Z, requireConfigObject as Za, refuseMalformedOrphansForDestroy as Zi, refuseMalformedDesiredTags as Zn, recordNoEchoParameterFreshValue as Zo, isUnboundTemplateParameter as Zr, isDockerClientEnvKey as Zs, isRedactedRecordedValue as Zt, makeCanonicalizePropertiesFn as _, repairMalformedOrphansForReadOnly as _a, resolveSkipPrefix as _c, UNREADABLE_RESOURCES_MAP_ROW as _i, isExportAliasCollision as _n, passedNoEchoParametersOf as _o, deleteSkipReason as _r, importableOutputKeys as _s, revertedNestedRowIds as _t, findNestedTemplateTreeDefect as a, refuseMalformedResourceEntriesForImport as aa, escapeRegExp$3 as ac, AWS_NO_VALUE as ai, displayTextOrWithheld as an, sameLambdaFunctionAddress as ao, isCustomResource as ar, isSingleDynamicReferenceToken as as, reachableDivergentParameters as at, serviceTokenBlockingReason as b, repairMalformedResourcePropertiesForReadOnly as ba, resolveUseCdkBootstrapAssets as bc, displayLogicalId as bi, DEFAULT_RESOURCE_WARN_AFTER_MS as bn, valueAtCoordinate as bo, markWaitAbandoned as br, orphansAfterRollback as bs, withNestedStackContext as bt, renderNestedTemplateTreeDefect as c, refuseMalformedResourcePropertiesForDestroy as ca, getDockerImageBySourceHash as cc, refStateLookupFromResource as ci, secretBearingStateKeyWarning as cn, canonicalCoordinates as co, readsNoEchoDeleteValues as cr, S3StateBackend as cs, planOrphanAdoption as ct, conditionInputsFrom as d, refuseMalformedResourcePropertiesForOrphan as da, getDefaultStateBucketName as dc, parameterInputsFor as di, exportNameNoEchoParameters as dn, maskReadbackAtCoordinates as do, withPrintingSecrets as dr, acceptedCreateOnlyDropsOf as ds, displacedPhysicalIdShown as dt, refuseMalformedOutputsForDestroy as ea, redactDockerArgvValues as ec, isImpossibleEmptyStoredAttribute as ei, updatePartialMessage as en, templateResourceTypes as eo, definedAttributes as er, STATE_SOURCED_BASELINE_RULES as es, green as et, parentSuppliedValues as f, refuseMalformedResourcesForDeploy as fa, getLegacyStateBucketName as fc, passedParameterClassesOf as fi, exportNameSecretExposure as fn, maskWholeValue as fo, CloudControlProvider as fr, describeRegionValueKind as fs, dropSettledNestedJournals as ft, mergeNoChangeOutputs as g, repairMalformedOrphanRecordsForReadOnly as ga, resolveCaptureObservedState as gc, UNREADABLE_ORPHANS_CONTAINER_ROW as gi, collectPublishedOutputNames as gn, noEchoOutputsComparison as go, deleteIndeterminateGuards as gr, hasUnverifiableParameterRefusal as gs, revertNestedChildFromJournal as gt, keptWholeReasonText as h, refuseMalformedState as ha, resolveAutoAssetStorage as hc, STATE_RESOURCES_MALFORMED as hi, collectDeclaredOutputNames as hn, noEchoLeavesOf as ho, UNSPECIFIED_SKIP_REASON as hr, hasReasonlessBaselineRefusal as hs, recordDisplacedSkips as ht, childTemplateLoader as i, refuseMalformedResourceEntriesForDestroy as ia, warnFinchArgvExposure as ic, keepsSecretReferenceToken as ii, WITHHELD_NAME_DISPLAY as in, lambdaFunctionNameForMask as io, ccBrokenReason as ir, WHOLE_DYNAMIC_REFERENCE_PATTERN as is, namesAnyDeclaredParameter as it, readProtection as j, hasNoCloudControlHandlers as ja, expectedOwnerParam as jc, malformedLocalResourcesWarning as ji, ccRoutedFinalSnapshotError as jn, createSecretMasker as jo, classifyReplaySecretRegion as jr, AssetModeResolver as js, destroyStackRecordsView as jt, isProtectionValueActive as k, withoutSilentDropProperties as ka, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as kc, malformedLocalOutputsWarning as ki, PRE_DELETE_SNAPSHOT_TYPES as kn, recordLogOnlyValue as ko, IntrinsicFunctionResolver as kr, loadPublishableAssetManifest as ks, replayRollback as kt, templateIdentity as l, refuseMalformedResourcePropertiesForDrift as la, Synthesizer as lc, parseWebACLArn as li, secretSafeKeyDisplay as ln, isMarkedCoordinate as lo, isCustomResourceType as lr, rebuildClientForBucketRegion as ls, NESTED_PENDING_PARENT_REASON as lt, isSecretBearingReferenceString as m, refuseMalformedResourcesForExport as ma, resolveApp as mc, STATE_REGION_DIVERGED as mi, noEchoParameterValueSeed as mn, noEchoCoordinatesOf as mo, unchangedBehindSecretReference as mr, hasReadableExportSet as ms, nestedChildStackName as mt, IMPLICIT_DELETE_DEPENDENCIES as n, refuseMalformedResourceEntries as na, runDockerForeground as nc, carriesFetchableDynamicReference as ni, DeploymentEventsReader as nn, withRetry as no, CC_BROKEN_REASON as nr, STATE_SOURCED_READBACK_RULES as ns, yellow as nt, isAbsoluteAssetPath as o, refuseMalformedResourceEntriesForImportSave as oa, stripControlChars as oc, cfnRefValueFromPhysicalId as oi, exportAliasCollisionScrubWarning as on, DagBuilder as oo, wouldReturnToSdkProvider as or, LockManager as os, resourcesNamingDeclaredParameter as ot, readRecordedConditionVerdicts as p, refuseMalformedResourcesForDestroy as pa, loadCdkJson as pc, JSON_LISTING_HOLE_VALUE as pi, isWholeDynamicReferenceValue as pn, maskedLeafCoordinatesOf as po, slowCcOperationTimeoutMs as pr, exportNamesCarriedFrom as ps, getNestedRevertRun as pt, getPriorAttempts as q, configStringRefusal as qa, resolveAssemblyPath as qc, refuseMalformedNestedChildOutputs as qi, DBPROXY_TAGS_WHAT as qn, provenPublicValue as qo, s3BucketDualStackDomainName as qr, dockerSpawnEnvWithSensitive as qs, findNestedStackTypeChanges as qt, computeImplicitDeleteEdges as r, refuseMalformedResourceEntriesForDeploy as ra, runDockerStreaming as rc, isNamelessDynamicReferenceError as ri, DeploymentEventsStore as rn, canonicalLambdaFunctionName as ro, ProviderRegistry as rr, TEMPLATE_SOURCED_RULES as rs, computeParameterTaint as rt, listNestedTemplateRows as s, refuseMalformedResourceEntriesForOrphan as sa, AssetManifestLoader as sc, isStalePlaceholderArnAttribute as si, secretBearing as sn, TemplateParser as so, TemplateNoEchoReresolver as sr, isLockInfoExpired as ss, makeSiblingClaimReader as st, DeployEngine as t, refuseMalformedResourceAttributesForOrphan as ta, redactedDockerCause as tc, carriesDynamicReference as ti, updatePartialReason as tn, describeTypeWithThrottleRetry as to, stringifyIfAssigned as tr, STATE_SOURCED_CROSS_GENERATION_RULES as ts, red as tt, conditionFingerprint as u, refuseMalformedResourcePropertiesForExport as ua, synthesisStatusMessage as uc, withSharedDrainBudget as ui, carriedExportAliasExposure as un, maskAtCoordinates as uo, getCurrentResourceSecrets as ur, splitImportedOps as us, displacedOpClause as ut, deferredServiceTokenWarning as v, repairMalformedOutputsForReadOnly as va, resolveStateBucketWithDefault as vc, accountArgs as vi, bindingSkippedOutputs as vn, readbackPathFor as vo, withIndeterminateGuard as vr, importableOutputs as vs, withNestedRevertRun as vt, noEchoExactEchoLeavesOf as w, unreadableResourceEntries as wa, CFN_TEMPLATE_BODY_LIMIT as wc, isReadableOrphanRecord as wi, orphanRecordsPrintingBag as wn, hasMaskableValues as wo, observeThenDisableInstanceApiTermination as wr, isSensitiveAttributeName as ws, journaledOrphanLines as wt, withResourceDeadline as x, repairMalformedResourcesForReadOnly as xa, stateBucketExistenceConfirmed as xc, hasReadableOrphans as xi, completedReplayEntries as xn, liveMatchesUnresolvedTokenFrame as xo, disableInstanceApiTermination as xr, orphansCarriedFrom as xs, deleteJournaledOrphans as xt, findServiceTokenRefusals as y, repairMalformedResourceEntriesForReadOnly as ya, resolveStateBucketWithDefaultAndSource as yc, deployRefusesOrphanRowsReason as yi, DEFAULT_RESOURCE_TIMEOUT_MS as yn, readsNoEchoSource as yo, isWaitAbandonedError as yr, isReadableBag as ys, getCurrentNestedStackContext as yt, DELETE_INDEX_WAIT_PROCEED_NOTE as z, coerceWarmThroughput as za, hasOwnKey as zc, malformedRenderedContainersWarning as zi, findDestructiveChanges as zn, dynamicReferenceTokens as zo, sendableAssumeRoleCommand as zr, validateAssetBucketName as zs, planFailedOps as zt };
|
|
40959
|
+
//# sourceMappingURL=deploy-engine-D4FdwPCi.js.map
|