@go-to-k/cdkd 0.288.1 → 0.288.3
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-4QzW_6q5.js → asg-provider-CQrHElrl.js} +16 -12
- package/dist/{asg-provider-4QzW_6q5.js.map → asg-provider-CQrHElrl.js.map} +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-ZPkBIilF.js → deploy-engine-CfxcC3q3.js} +798 -110
- package/dist/deploy-engine-CfxcC3q3.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-BPeZLD0q.js → program-BXH3FeeV.js} +1436 -552
- package/dist/{program-BPeZLD0q.js.map → program-BXH3FeeV.js.map} +1 -1
- package/dist/{version-CGFRodgE.js → version-CEyZvTcB.js} +2 -2
- package/dist/{version-CGFRodgE.js.map → version-CEyZvTcB.js.map} +1 -1
- package/package.json +11 -21
- package/dist/deploy-engine-ZPkBIilF.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as ProviderRegistry, $n as redactDockerArgvValues, $r as ResourceUpdateNotSupportedError, $t as STATE_SOURCED_READBACK_RULES, A as renderStatefulReason, An as createAssetRedirectResolver, Ar as AssemblyReader, At as configStringRefusal, B as red, Bn as readBootstrapMarkerBody, Bt as s3BucketDualStackDomainName, C as refusesFinalSnapshot, Cn as importableOutputKeys, Cr as uploadCfnTemplate, Ct as refStateLookupFromResource, D as MULTI_REGION_RECREATE_BLOCKED_TYPES, Dn as stringifyValue, Dr as PARTITION_TABLE, Dt as assertRegionMatch, E as extractDeploymentEventError, En as AssetPublisher, Er as expectedOwnerParam, Et as resolveExplicitPhysicalId, F as formatResourceLine, Fn as assertAssetBucketRegion, Fr as getAwsClients, Ft as requireConfigString, G as isExportAliasCollision, Gn as buildDockerImage, Gr as IntrinsicResolutionRefusalError, Gt as INTRINSIC_KEYS, H as collectDeclaredOutputNames, Hn as validateContainerRepoName, Ht as s3BucketWebsiteUrl, I as bold, In as ensureAssetStorage, Ir as resetAwsClients, It as classifyReplaySecretRegion, J as stateKeySecretExposure, Jn as describeDockerFailure, Jt as describeTypeWithThrottleRetry, K as secretBearingStateKeyWarning, Kn as describeDockerCapturedOutput, Kr as LocalInvokeBuildError$1, Kt as findActionableSilentDrops, L as cyan, Ln as getBootstrapMarkerKey, Lr as setAwsClients, Lt as producerRegionsFromState, M as coerceWarmThroughput$1, Mn as rewriteTemplateAssetReferences, Mt as replayWarn, N as isWarmThroughputDecrease, Nn as AssetModeResolver, Nr as resolveBucketRegion, Nt as requireConfigArray, O as isStatefulRecreateTargetForReplace, On as WorkGraph, Or as canonicalizeRegion, Ot as coerceCfnBoolean, P as toFiniteNumber, Pn as BOOTSTRAP_MARKER_PREFIX, Pr as AwsClients, Pt as requireConfigObject, Q as clearOnUpdateRemoval, Qn as partitionSensitiveEnv, Qr as ResourceTimeoutError, Qt as STATE_SOURCED_CROSS_GENERATION_RULES, R as gray, Rn as isCrossRegionRedirect, Rt as s3BucketArn, S as isFinalSnapshotError, Sn as exportNamesCarriedFrom, Sr as findLargeInlineResources, St as parameterTypeMayLoseSecretIdentity, T as makeCanonicalizePropertiesFn, Tn as shouldRetainResource, Tr as displaySafe, Tt as normalizeAwsTagsToCfn, U as collectPublishedOutputNames, Un as buildDenyExternalAccessPolicy, Ur as DeployCancelledError, Ut as applyRoleArnIfSet, V as yellow, Vn as validateAssetBucketName, Vr as CrossAccountSecretRefusalError, Vt as s3BucketRegionalDomainName, W as exportAliasCollisionScrubWarning, Wn as describeAwsFailure, Wr as DynamicReferenceRegionAmbiguousError, Wt as DiffCalculator, X as IAMRoleProvider, Xn as formatDockerLoginError, Xr as PartialFailureError, Xt as DagBuilder, Y as getCurrentResourceSecrets, Yn as dockerSpawnEnvWithSensitive, Yr as NestedStackChildDirectDestroyError, Yt as withRetry, Z as collectInlinePolicyNamesManagedBySiblings, Zn as getDockerCmd, Zr as ProvisioningError, Zt as TemplateParser, _ as ATOMIC_FINAL_SNAPSHOT_TYPES, _n as buildLockContentionMessage, _r as stateBucketExistenceConfirmed, _t as carriesDynamicReference, a as DeploymentEventsStore, an as isSingleDynamicReferenceToken, ar as getDockerImageBySourceHash, at as endCommandInterruptScope, b as ccRoutedFinalSnapshotError, bn as CUSTOM_RESOURCE_RESPONSE_PREFIX, br as CFN_TEMPLATE_URL_LIMIT, bt as getAccountInfo, c as replayFailedOperations, ci as isMarkedNonRetryable, cn as recordMaskOnlyValue, cr as getDefaultStateBucketName, ct as startInterruptWatch, d as updatePartialReason, di as isTransientServerError, dn as scrubResourceRecord, dr as resolveAutoAssetStorage, dt as UNSPECIFIED_SKIP_REASON, ei as StackHasActiveImportsError, en as TEMPLATE_SOURCED_RULES, er as runDockerForeground, et as wouldReturnToSdkProvider, f as withResourceDeadline, fi as markNonRetryable, fn as LockManager, fr as resolveCaptureObservedState, ft as deleteIndeterminateGuards, g as bindingSkippedOutputs, gn as buildForceUnlockCommand, gr as resolveUseCdkBootstrapAssets, gt as IntrinsicFunctionResolver, h as computeImplicitDeleteEdges, hn as UNRENDERABLE, hr as resolveStateBucketWithDefaultAndSource, ht as isTerminationProtectionPropagationError, i as DeploymentEventsReader, in as errorCauseChain, ir as AssetManifestLoader, it as beginCommandInterruptScope, j as WARM_THROUGHPUT_MEMBERS, jn as loadPublishableAssetManifest, jr as processStackMessages, jt as readConfigString, k as isStatefulRecreateTargetSync, kn as buildAssetRedirectMap, kr as derivePartitionAndUrlSuffix, kt as configBooleanRefusal, l as replayRollback, li as isRetryableTransientError, ln as recoverMaskedOutput, lr as getLegacyStateBucketName, lt as CloudControlProvider, m as IMPLICIT_DELETE_DEPENDENCIES, mi as retryClassificationText, mn as rebuildClientForBucketRegion, mr as resolveStateBucketWithDefault, mt as disableInstanceApiTermination, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as createSecretMasker, nr as escapeRegExp, nt as maskDeep, o as planFailedOps, oi as normalizeAwsError, on as maskSecretsInError, or as Synthesizer, ot as interruptWatchListenerCount, p as maskingRetryLogger, pi as markRedactedCause, pn as S3StateBackend, pr as resolveSkipPrefix, pt as deleteSkipReason, q as secretSafeKeyDisplay, qn as describeDockerExecFailure, qr as LocalStartServiceError, qt as findSilentDropProperties, r as DeployEngine, rn as dynamicReferenceTokens, rr as stripControlChars, rt as maskerOrIdentity, s as planRollback, si as withErrorHandling, sn as maskSecretsInText, sr as synthesisStatusMessage, st as isInterruptedWaitError, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as StackTerminationProtectionError, tn as carriesSecretMask, tr as runDockerStreaming, tt as createMaskedRetryLogger, u as updatePartialMessage, ui as isThrottlingError, un as redactSecretsForState, ur as resolveApp, ut as slowCcOperationTimeoutMs, v as PRE_DELETE_SNAPSHOT_TYPES, vn as forceQuitRecoveryClause, vr as warnDeprecatedNoPrefixCliFlag, vt as cfnRefValueFromPhysicalId, w as unsupportedFinalSnapshotError, wn as importableOutputs, wr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, wt as WAFv2WebACLProvider, x as createPreDeleteFinalSnapshot, xn as DEFAULT_STATE_PREFIX, xr as MIGRATE_TMP_PREFIX, xt as isUnboundTemplateParameter, y as buildFinalSnapshotIdentifier, yn as shellQuote, yr as CFN_TEMPLATE_BODY_LIMIT, yt as coerceParameterTypedValue, z as green, zn as parseBootstrapMarker, zr as CdkdError, zt as s3BucketDomainName } from "./deploy-engine-CfxcC3q3.js";
|
|
2
2
|
import { _ as withStackName, c as PATTERN_B_NAME_OPTIONS, f as generateResourceName, g as withSkipPrefix, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as getCurrentStackName, n as getLogger, o as runStackBuffered, p as generateResourceNameWithFallback, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES } from "./logger-Dw-3y48G.js";
|
|
3
3
|
import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
|
|
4
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
-
import { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as getCdkdVersion } from "./version-CEyZvTcB.js";
|
|
5
|
+
import { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-CQrHElrl.js";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
8
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, NotFound, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -1780,15 +1780,16 @@ async function bootstrapCommand(options) {
|
|
|
1780
1780
|
});
|
|
1781
1781
|
}
|
|
1782
1782
|
let skipStateBucketConfig = false;
|
|
1783
|
-
if (bucketExists)
|
|
1784
|
-
if (!options.
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1783
|
+
if (bucketExists) {
|
|
1784
|
+
if (!options.force) {
|
|
1785
|
+
if (!options.assets) {
|
|
1786
|
+
logger.warn(`Bucket ${bucketName} already exists. Use --force to reconfigure (this will not delete existing state)`);
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
logger.info(`State bucket ${bucketName} already exists — skipping reconfiguration (use --force to reconfigure)`);
|
|
1790
|
+
skipStateBucketConfig = true;
|
|
1791
|
+
} else logger.info("--force specified, continuing with existing bucket");
|
|
1792
|
+
} else {
|
|
1792
1793
|
logger.info(`Creating S3 bucket: ${bucketName} in region ${region}`);
|
|
1793
1794
|
const createBucketParams = { Bucket: bucketName };
|
|
1794
1795
|
if (region !== "us-east-1") createBucketParams.CreateBucketConfiguration = { LocationConstraint: region };
|
|
@@ -3469,6 +3470,67 @@ function mapsEqual(a, b) {
|
|
|
3469
3470
|
}
|
|
3470
3471
|
return true;
|
|
3471
3472
|
}
|
|
3473
|
+
/**
|
|
3474
|
+
* Describe a `JSON.parse` failure WITHOUT echoing the body (issue
|
|
3475
|
+
* [#2667](https://github.com/go-to-k/cdkd/issues/2667) review).
|
|
3476
|
+
*
|
|
3477
|
+
* V8 embeds a window of the INPUT in its parse error, so interpolating
|
|
3478
|
+
* `err.message` republishes whatever bytes sat near the syntax error. Measured
|
|
3479
|
+
* on node v24.19.0:
|
|
3480
|
+
*
|
|
3481
|
+
* ```
|
|
3482
|
+
* JSON.parse('{"exports":{"K":{"value":hunter2SECRET}}}')
|
|
3483
|
+
* -> Unexpected token 'h', ..."K":{"value":hunter2SEC"... is not valid JSON
|
|
3484
|
+
* ```
|
|
3485
|
+
*
|
|
3486
|
+
* This index holds resolved Output VALUES, and for the unscrubbed state
|
|
3487
|
+
* `cdkd scrub` targets those are exactly the plaintext — which then reaches a
|
|
3488
|
+
* `logger.error` and the command's failure message, i.e. `--dry-run --fail` CI
|
|
3489
|
+
* logs. Same class as the `import.ts` parse-snippet leak issue
|
|
3490
|
+
* [#2829](https://github.com/go-to-k/cdkd/issues/2829) closed.
|
|
3491
|
+
*
|
|
3492
|
+
* What survives is the SIZE, plus the OFFSET WHEN V8 STATES ONE — neither is a
|
|
3493
|
+
* function of the body's content, and both help an operator holding the object
|
|
3494
|
+
* locate the damage. The offset is extracted by pattern rather than passed
|
|
3495
|
+
* through, so a message worded differently yields no offset instead of leaking
|
|
3496
|
+
* the rest of the sentence. The two numbers are in DIFFERENT UNITS and the
|
|
3497
|
+
* message says so: V8 counts `at position` in UTF-16 code units while the size
|
|
3498
|
+
* is UTF-8 bytes, so `'{ n\u00f6t json'` reports position 2 and 11 bytes. Measured on node v24.19.0: the `Expected ...`
|
|
3499
|
+
* family carries `at position N` and the `Unexpected token 'X', ...snippet...`
|
|
3500
|
+
* family — the one that embeds the body, i.e. the case this exists for — does
|
|
3501
|
+
* NOT, so the size is what is always present and the offset is a bonus. Both
|
|
3502
|
+
* arms are driven by tests.
|
|
3503
|
+
*/
|
|
3504
|
+
function describeParseFailure(err, bytes) {
|
|
3505
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
3506
|
+
const at = /at position (\d+)/.exec(raw);
|
|
3507
|
+
return `invalid JSON${at ? ` at code-unit position ${at[1]}` : ""}; ${bytes} byte(s) read`;
|
|
3508
|
+
}
|
|
3509
|
+
/**
|
|
3510
|
+
* UTF-8 byte length of a decoded body, for {@link describeParseFailure}.
|
|
3511
|
+
*
|
|
3512
|
+
* `body.length` counts UTF-16 CODE UNITS, so reporting it as "byte(s)" is
|
|
3513
|
+
* wrong for any non-ASCII index — an export name outside the BMP counts 2
|
|
3514
|
+
* where the object holds 4 (issue #2667 review). The number is meant to help
|
|
3515
|
+
* an operator match the message against the object S3 holds, so it has to be
|
|
3516
|
+
* the same unit S3 reports.
|
|
3517
|
+
*/
|
|
3518
|
+
function utf8ByteLength(body) {
|
|
3519
|
+
return Buffer.byteLength(body, "utf8");
|
|
3520
|
+
}
|
|
3521
|
+
/**
|
|
3522
|
+
* Describe an `indexVersion` that is not a usable number, WITHOUT rendering it.
|
|
3523
|
+
*
|
|
3524
|
+
* `String(x)` renders a body-controlled value verbatim for two of the three
|
|
3525
|
+
* non-number shapes — measured on node v24.19.0: a JSON string yields itself
|
|
3526
|
+
* and a one-element array yields its element, while only an object degrades to
|
|
3527
|
+
* `[object Object]`. The field is attacker-controlled in the same sense the
|
|
3528
|
+
* rest of the body is, so the message reports its TYPE and, for a number, its
|
|
3529
|
+
* value — the only shape whose value is safe because it is a number.
|
|
3530
|
+
*/
|
|
3531
|
+
function describeIndexVersion(value) {
|
|
3532
|
+
return typeof value === "number" ? String(value) : `a non-numeric value (${typeof value})`;
|
|
3533
|
+
}
|
|
3472
3534
|
const DEFAULT_OPTIONS = {
|
|
3473
3535
|
maxWriteRetries: 5,
|
|
3474
3536
|
initialBackoffMs: 100,
|
|
@@ -3596,12 +3658,57 @@ var ExportIndexStore = class {
|
|
|
3596
3658
|
await this.enqueueWrite("update", () => this.applyStackUpdate(stackName, producerRegion, outputs));
|
|
3597
3659
|
}
|
|
3598
3660
|
/**
|
|
3661
|
+
* A snapshot of the persisted index, read WITHOUT the rebuild that
|
|
3662
|
+
* {@link lookup} triggers on a missing or corrupt object.
|
|
3663
|
+
*
|
|
3664
|
+
* Every S3 call THIS METHOD makes is a GET, so a caller that must perform no
|
|
3665
|
+
* S3 write (`cdkd scrub --dry-run`) can read the entries here — with ONE
|
|
3666
|
+
* documented exception, which such a caller has to know about: when another
|
|
3667
|
+
* caller on the same instance already has a `doLoad` in flight, the first
|
|
3668
|
+
* line below awaits IT, and `doLoad` rebuilds (a PUT) on a missing or
|
|
3669
|
+
* corrupt object. Unreachable from `cdkd scrub`, which never calls `lookup`
|
|
3670
|
+
* and drives one store per region serially — but the guarantee is the thing
|
|
3671
|
+
* a future no-write caller would rely on, so it is stated with its bound
|
|
3672
|
+
* rather than unconditionally.
|
|
3673
|
+
*
|
|
3674
|
+
* THREE OUTCOMES, and only ONE of them is `undefined`. A MISSING object
|
|
3675
|
+
* returns `undefined`: there is nothing to audit and nothing to repair. A
|
|
3676
|
+
* body that does not PARSE, and an `indexVersion` this binary cannot
|
|
3677
|
+
* interpret, both THROW — because the caller's question is "what does this
|
|
3678
|
+
* index hold", and for those two the honest answer is "this call could not
|
|
3679
|
+
* find out". Collapsing the corrupt case into `undefined` reads as an empty
|
|
3680
|
+
* index, so a caller reports nothing over bytes it never interpreted, which
|
|
3681
|
+
* for `cdkd scrub` is a green gate over a file that may still hold the
|
|
3682
|
+
* plaintext (issue [#2667](https://github.com/go-to-k/cdkd/issues/2667)) —
|
|
3683
|
+
* the exact shape that issue exists to close, one level down. `doLoad`'s own
|
|
3684
|
+
* corrupt arm is unaffected: it REBUILDS, which is a write, and is why this
|
|
3685
|
+
* read cannot share it.
|
|
3686
|
+
*
|
|
3687
|
+
* The returned map is a COPY, and a `patchEntry` on this same instance
|
|
3688
|
+
* afterwards writes under the etag this read cached rather than re-reading.
|
|
3689
|
+
*/
|
|
3690
|
+
async readPersistedEntries() {
|
|
3691
|
+
if (this.loadState.kind === "loading") await this.loadState.promise;
|
|
3692
|
+
if (this.loadState.kind === "loaded") return new Map(this.loadState.entries);
|
|
3693
|
+
const outcome = await this.loadPersisted();
|
|
3694
|
+
if (outcome.kind === "missing") return void 0;
|
|
3695
|
+
if (outcome.kind === "corrupt") throw new Error(`Exports index at ${this.indexKey()} could not be parsed (${describeParseFailure(outcome.error, outcome.bytes)}). Its contents were NOT examined.`);
|
|
3696
|
+
return new Map(outcome.entries);
|
|
3697
|
+
}
|
|
3698
|
+
/**
|
|
3599
3699
|
* Patch a single entry into the index after a `lookup` miss fell back
|
|
3600
3700
|
* to a state.json scan and found the value. Lightweight write that
|
|
3601
3701
|
* does NOT require a full rebuild.
|
|
3702
|
+
*
|
|
3703
|
+
* Returns whether the index now holds `entry` — `false` when the write
|
|
3704
|
+
* was abandoned (a non-retryable S3 failure, or an exhausted If-Match
|
|
3705
|
+
* retry budget), which `runWithRetry` logs and does not throw. A caller
|
|
3706
|
+
* for which an unwritten entry is the outcome it must report — `cdkd
|
|
3707
|
+
* scrub`, where the entry keeps whatever value it held — reads this
|
|
3708
|
+
* return; the deploy path treats the index as a perf hint and ignores it.
|
|
3602
3709
|
*/
|
|
3603
|
-
async patchEntry(exportName, entry) {
|
|
3604
|
-
|
|
3710
|
+
async patchEntry(exportName, entry, opts = {}) {
|
|
3711
|
+
return this.enqueueWrite("patch", () => this.applyPatch(exportName, entry, opts.requireOwner));
|
|
3605
3712
|
}
|
|
3606
3713
|
/**
|
|
3607
3714
|
* Drop all entries owned by `(stackName, producerRegion)`. Used after
|
|
@@ -3622,7 +3729,7 @@ var ExportIndexStore = class {
|
|
|
3622
3729
|
*/
|
|
3623
3730
|
async enqueueWrite(label, op) {
|
|
3624
3731
|
const next = this.writeChain.then(() => this.runWithRetry(label, op));
|
|
3625
|
-
this.writeChain = next.
|
|
3732
|
+
this.writeChain = next.then(() => {}, () => {});
|
|
3626
3733
|
return next;
|
|
3627
3734
|
}
|
|
3628
3735
|
/**
|
|
@@ -3657,31 +3764,54 @@ var ExportIndexStore = class {
|
|
|
3657
3764
|
};
|
|
3658
3765
|
await promise;
|
|
3659
3766
|
}
|
|
3767
|
+
/**
|
|
3768
|
+
* GET the index object and, when it parses, install it as `loadState`.
|
|
3769
|
+
* Performs no PUT: the missing / corrupt REBUILD lives one level up in
|
|
3770
|
+
* {@link doLoad}, so {@link readPersistedEntries} can share this parse
|
|
3771
|
+
* without writing. A `indexVersion` above this binary's still throws
|
|
3772
|
+
* here rather than reporting `corrupt` — a rebuild would overwrite an
|
|
3773
|
+
* index a newer binary wrote with this binary's view of it.
|
|
3774
|
+
*/
|
|
3775
|
+
async loadPersisted() {
|
|
3776
|
+
const raw = await this.readIndexRaw();
|
|
3777
|
+
if (raw === null) return { kind: "missing" };
|
|
3778
|
+
const { body, etag } = raw;
|
|
3779
|
+
let parsed;
|
|
3780
|
+
try {
|
|
3781
|
+
parsed = JSON.parse(body);
|
|
3782
|
+
} catch (err) {
|
|
3783
|
+
return {
|
|
3784
|
+
kind: "corrupt",
|
|
3785
|
+
error: err,
|
|
3786
|
+
bytes: utf8ByteLength(body)
|
|
3787
|
+
};
|
|
3788
|
+
}
|
|
3789
|
+
if (typeof parsed.indexVersion !== "number" || parsed.indexVersion > 1) throw new Error(`Exports index uses indexVersion ${describeIndexVersion(parsed.indexVersion)} which is newer than this cdkd binary supports (max ${1}). Upgrade cdkd.`);
|
|
3790
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3791
|
+
for (const [name, entry] of Object.entries(parsed.exports ?? {})) entries.set(name, entry);
|
|
3792
|
+
this.loadState = {
|
|
3793
|
+
kind: "loaded",
|
|
3794
|
+
etag,
|
|
3795
|
+
entries
|
|
3796
|
+
};
|
|
3797
|
+
return {
|
|
3798
|
+
kind: "loaded",
|
|
3799
|
+
entries
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3660
3802
|
async doLoad() {
|
|
3661
3803
|
try {
|
|
3662
|
-
const
|
|
3663
|
-
if (
|
|
3804
|
+
const outcome = await this.loadPersisted();
|
|
3805
|
+
if (outcome.kind === "missing") {
|
|
3664
3806
|
this.logger.info("Exports index missing; rebuilding from state.json files");
|
|
3665
3807
|
await this.rebuild();
|
|
3666
3808
|
return;
|
|
3667
3809
|
}
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
try {
|
|
3671
|
-
parsed = JSON.parse(body);
|
|
3672
|
-
} catch (err) {
|
|
3673
|
-
this.logger.warn(`Exports index corrupt (${err instanceof Error ? err.message : String(err)}); rebuilding from state.json files`);
|
|
3810
|
+
if (outcome.kind === "corrupt") {
|
|
3811
|
+
this.logger.warn(`Exports index corrupt (${outcome.error instanceof Error ? outcome.error.message : String(outcome.error)}); rebuilding from state.json files`);
|
|
3674
3812
|
await this.rebuild();
|
|
3675
3813
|
return;
|
|
3676
3814
|
}
|
|
3677
|
-
if (typeof parsed.indexVersion !== "number" || parsed.indexVersion > 1) throw new Error(`Exports index uses indexVersion ${String(parsed.indexVersion)} which is newer than this cdkd binary supports (max ${1}). Upgrade cdkd.`);
|
|
3678
|
-
const entries = /* @__PURE__ */ new Map();
|
|
3679
|
-
for (const [name, entry] of Object.entries(parsed.exports ?? {})) entries.set(name, entry);
|
|
3680
|
-
this.loadState = {
|
|
3681
|
-
kind: "loaded",
|
|
3682
|
-
etag,
|
|
3683
|
-
entries
|
|
3684
|
-
};
|
|
3685
3815
|
} catch (err) {
|
|
3686
3816
|
this.loadState = { kind: "unloaded" };
|
|
3687
3817
|
throw err;
|
|
@@ -3763,7 +3893,7 @@ var ExportIndexStore = class {
|
|
|
3763
3893
|
}
|
|
3764
3894
|
async applyStackUpdate(stackName, producerRegion, outputs) {
|
|
3765
3895
|
await this.ensureLoaded();
|
|
3766
|
-
if (this.loadState.kind !== "loaded") return;
|
|
3896
|
+
if (this.loadState.kind !== "loaded") return false;
|
|
3767
3897
|
const next = new Map(this.loadState.entries);
|
|
3768
3898
|
for (const [name, entry] of next) if (entry.producerStack === stackName && entry.producerRegion === producerRegion) next.delete(name);
|
|
3769
3899
|
for (const [name, value] of Object.entries(outputs)) {
|
|
@@ -3774,8 +3904,8 @@ var ExportIndexStore = class {
|
|
|
3774
3904
|
producerRegion
|
|
3775
3905
|
});
|
|
3776
3906
|
}
|
|
3777
|
-
if (mapsEqual(this.loadState.entries, next)) return;
|
|
3778
|
-
|
|
3907
|
+
if (mapsEqual(this.loadState.entries, next)) return true;
|
|
3908
|
+
return this.persist(next);
|
|
3779
3909
|
}
|
|
3780
3910
|
/**
|
|
3781
3911
|
* Two stacks publishing one export name (issue #2193). CloudFormation
|
|
@@ -3792,27 +3922,34 @@ var ExportIndexStore = class {
|
|
|
3792
3922
|
const safeName = displaySafe(exportName);
|
|
3793
3923
|
this.logger.warn(`Export '${safeName}' is published by both '${existing.producerStack}' (${existing.producerRegion}) and '${stackName}' (${producerRegion}); the exports index keeps the latest writer, so an Fn::ImportValue on it binds to whichever deployed last. CloudFormation refuses a second producer of the same export name — rename one of the two exports. (If '${existing.producerStack}' predates cdkd's state schema v9, this is its stale entry for a plain output of that name, and its next deploy clears it.)`);
|
|
3794
3924
|
}
|
|
3795
|
-
async applyPatch(exportName, entry) {
|
|
3925
|
+
async applyPatch(exportName, entry, requireOwner) {
|
|
3796
3926
|
await this.ensureLoaded();
|
|
3797
|
-
if (this.loadState.kind !== "loaded") return;
|
|
3927
|
+
if (this.loadState.kind !== "loaded") return false;
|
|
3928
|
+
if (requireOwner) {
|
|
3929
|
+
const current = this.loadState.entries.get(exportName);
|
|
3930
|
+
if (!current || current.producerStack !== requireOwner.producerStack || current.producerRegion !== requireOwner.producerRegion) {
|
|
3931
|
+
this.logger.warn(`Exports index entry ownership changed under a patch; refusing to write it. Expected producer '${displaySafe(requireOwner.producerStack)}' (${displaySafe(requireOwner.producerRegion)}), found ${current ? `'${displaySafe(current.producerStack)}' (${displaySafe(current.producerRegion)})` : "no entry"}.`);
|
|
3932
|
+
return false;
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3798
3935
|
const next = new Map(this.loadState.entries);
|
|
3799
3936
|
next.set(exportName, entry);
|
|
3800
|
-
|
|
3937
|
+
return this.persist(next);
|
|
3801
3938
|
}
|
|
3802
3939
|
async applyRemoveStack(stackName, producerRegion) {
|
|
3803
3940
|
await this.ensureLoaded();
|
|
3804
|
-
if (this.loadState.kind !== "loaded") return;
|
|
3941
|
+
if (this.loadState.kind !== "loaded") return false;
|
|
3805
3942
|
const next = new Map(this.loadState.entries);
|
|
3806
3943
|
let changed = false;
|
|
3807
3944
|
for (const [name, entry] of next) if (entry.producerStack === stackName && entry.producerRegion === producerRegion) {
|
|
3808
3945
|
next.delete(name);
|
|
3809
3946
|
changed = true;
|
|
3810
3947
|
}
|
|
3811
|
-
if (!changed) return;
|
|
3812
|
-
|
|
3948
|
+
if (!changed) return true;
|
|
3949
|
+
return this.persist(next);
|
|
3813
3950
|
}
|
|
3814
3951
|
async persist(entries) {
|
|
3815
|
-
if (this.loadState.kind !== "loaded") return;
|
|
3952
|
+
if (this.loadState.kind !== "loaded") return false;
|
|
3816
3953
|
const file = {
|
|
3817
3954
|
indexVersion: 1,
|
|
3818
3955
|
region: this.region,
|
|
@@ -3825,12 +3962,20 @@ var ExportIndexStore = class {
|
|
|
3825
3962
|
etag,
|
|
3826
3963
|
entries
|
|
3827
3964
|
};
|
|
3965
|
+
return true;
|
|
3828
3966
|
}
|
|
3967
|
+
/**
|
|
3968
|
+
* Run `op` under the If-Match retry budget. `true` means `op` reported the
|
|
3969
|
+
* index now holds what it was asked to write (a PUT, or a no-op skip whose
|
|
3970
|
+
* result was already there); `false` means this store abandoned the write
|
|
3971
|
+
* and logged the warn below. It does NOT throw on either arm — `state.json`
|
|
3972
|
+
* is the canonical record for the deploy path, and a caller that needs the
|
|
3973
|
+
* failure surfaced reads the return value (see {@link patchEntry}).
|
|
3974
|
+
*/
|
|
3829
3975
|
async runWithRetry(label, op) {
|
|
3830
3976
|
let lastErr;
|
|
3831
3977
|
for (let attempt = 0; attempt < this.opts.maxWriteRetries; attempt++) try {
|
|
3832
|
-
await op();
|
|
3833
|
-
return;
|
|
3978
|
+
return await op();
|
|
3834
3979
|
} catch (err) {
|
|
3835
3980
|
lastErr = err;
|
|
3836
3981
|
if (this.isPreconditionFailed(err)) {
|
|
@@ -3840,9 +3985,10 @@ var ExportIndexStore = class {
|
|
|
3840
3985
|
continue;
|
|
3841
3986
|
}
|
|
3842
3987
|
this.logger.warn(`Exports index ${label} failed (non-retryable): ${err instanceof Error ? err.message : String(err)}; continuing without index update`);
|
|
3843
|
-
return;
|
|
3988
|
+
return false;
|
|
3844
3989
|
}
|
|
3845
3990
|
this.logger.warn(`Exports index ${label} exhausted ${this.opts.maxWriteRetries} retries due to concurrent writers; continuing without index update. Last error: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
|
|
3991
|
+
return false;
|
|
3846
3992
|
}
|
|
3847
3993
|
isNoSuchKey(err) {
|
|
3848
3994
|
if (!err || typeof err !== "object") return false;
|
|
@@ -4173,7 +4319,8 @@ var IAMPolicyProvider = class {
|
|
|
4173
4319
|
};
|
|
4174
4320
|
}
|
|
4175
4321
|
const onNotFound = async (target) => {
|
|
4176
|
-
|
|
4322
|
+
const clientRegion = await this.iamClient.config.region();
|
|
4323
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, `${physicalId} (${target})`);
|
|
4177
4324
|
};
|
|
4178
4325
|
try {
|
|
4179
4326
|
if (legacyRoleFromPhysicalId) {
|
|
@@ -4493,7 +4640,8 @@ var IAMManagedPolicyProvider = class {
|
|
|
4493
4640
|
await this.iamClient.send(new GetPolicyCommand({ PolicyArn: physicalId }));
|
|
4494
4641
|
} catch (error) {
|
|
4495
4642
|
if (error instanceof NoSuchEntityException) {
|
|
4496
|
-
|
|
4643
|
+
const clientRegion = await this.iamClient.config.region();
|
|
4644
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
4497
4645
|
this.logger.debug(`Managed policy ${physicalId} does not exist, skipping deletion`);
|
|
4498
4646
|
return;
|
|
4499
4647
|
}
|
|
@@ -4970,7 +5118,8 @@ var IAMInstanceProfileProvider = class {
|
|
|
4970
5118
|
roles = (await this.iamClient.send(new GetInstanceProfileCommand({ InstanceProfileName: physicalId }))).InstanceProfile?.Roles?.map((r) => r.RoleName).filter((name) => !!name) || [];
|
|
4971
5119
|
} catch (error) {
|
|
4972
5120
|
if (error instanceof NoSuchEntityException) {
|
|
4973
|
-
|
|
5121
|
+
const clientRegion = await this.iamClient.config.region();
|
|
5122
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
4974
5123
|
this.logger.debug(`Instance profile ${physicalId} does not exist, skipping deletion`);
|
|
4975
5124
|
return;
|
|
4976
5125
|
}
|
|
@@ -4989,7 +5138,8 @@ var IAMInstanceProfileProvider = class {
|
|
|
4989
5138
|
this.logger.debug(`Successfully deleted IAM instance profile ${logicalId}`);
|
|
4990
5139
|
} catch (error) {
|
|
4991
5140
|
if (error instanceof NoSuchEntityException) {
|
|
4992
|
-
|
|
5141
|
+
const clientRegion = await this.iamClient.config.region();
|
|
5142
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
4993
5143
|
this.logger.debug(`Instance profile ${physicalId} does not exist, skipping deletion`);
|
|
4994
5144
|
return;
|
|
4995
5145
|
}
|
|
@@ -5390,7 +5540,8 @@ var IAMAccessKeyProvider = class {
|
|
|
5390
5540
|
this.logger.debug(`Successfully deleted IAM access key ${logicalId}`);
|
|
5391
5541
|
} catch (error) {
|
|
5392
5542
|
if (error instanceof NoSuchEntityException) {
|
|
5393
|
-
|
|
5543
|
+
const clientRegion = await this.iamClient.config.region();
|
|
5544
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
5394
5545
|
this.logger.debug(`Access key ${physicalId} does not exist, skipping deletion`);
|
|
5395
5546
|
return;
|
|
5396
5547
|
}
|
|
@@ -5719,7 +5870,8 @@ var IAMUserGroupProvider = class {
|
|
|
5719
5870
|
await this.iamClient.send(new GetUserCommand({ UserName: physicalId }));
|
|
5720
5871
|
} catch (error) {
|
|
5721
5872
|
if (error instanceof NoSuchEntityException) {
|
|
5722
|
-
|
|
5873
|
+
const clientRegion = await this.iamClient.config.region();
|
|
5874
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
5723
5875
|
this.logger.debug(`User ${physicalId} does not exist, skipping deletion`);
|
|
5724
5876
|
return;
|
|
5725
5877
|
}
|
|
@@ -5983,7 +6135,8 @@ var IAMUserGroupProvider = class {
|
|
|
5983
6135
|
this.logger.debug(`Successfully deleted IAM group ${logicalId}`);
|
|
5984
6136
|
} catch (error) {
|
|
5985
6137
|
if (error instanceof NoSuchEntityException) {
|
|
5986
|
-
|
|
6138
|
+
const clientRegion = await this.iamClient.config.region();
|
|
6139
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
5987
6140
|
this.logger.debug(`Group ${physicalId} does not exist, skipping deletion`);
|
|
5988
6141
|
return;
|
|
5989
6142
|
}
|
|
@@ -6829,7 +6982,8 @@ const EVENTBRIDGE_DEFAULT_ENABLED = true;
|
|
|
6829
6982
|
* correct once every declared-but-unusable value has been refused.
|
|
6830
6983
|
*/
|
|
6831
6984
|
function eventBridgeEnabledOf(eb) {
|
|
6832
|
-
|
|
6985
|
+
const declared = isPlainObject$1(eb) ? eb["EventBridgeEnabled"] : void 0;
|
|
6986
|
+
return coerceCfnBoolean(declared) ?? EVENTBRIDGE_DEFAULT_ENABLED;
|
|
6833
6987
|
}
|
|
6834
6988
|
/** The notification families whose empty declaration is never SENT (issue #1759). */
|
|
6835
6989
|
const NOTIFICATION_LIST_KEYS = [
|
|
@@ -9424,15 +9578,18 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
9424
9578
|
};
|
|
9425
9579
|
const replayOnUnusable = replayWarn(this.logger, context).onUnusable;
|
|
9426
9580
|
const notifConfig = properties["NotificationConfiguration"];
|
|
9427
|
-
if (notifConfig)
|
|
9428
|
-
|
|
9581
|
+
if (notifConfig) {
|
|
9582
|
+
if (!await this.applyNotificationConfiguration(bucketName, notifConfig, replayOnUnusable)) overrides.set("NotificationConfiguration", void 0);
|
|
9583
|
+
else recordEffectiveFold("NotificationConfiguration", effectiveNotificationConfiguration(notifConfig));
|
|
9584
|
+
}
|
|
9429
9585
|
const corsConfig = properties["CorsConfiguration"];
|
|
9430
9586
|
if (corsConfig?.CorsRules && Array.isArray(corsConfig.CorsRules) && corsConfig.CorsRules.length > 0) await this.applyCorsConfiguration(bucketName, corsConfig);
|
|
9431
9587
|
else if (corsConfig != null) this.announceCreateEmptyCollectionSkip("CorsConfiguration", "CorsConfiguration.CorsRules");
|
|
9432
9588
|
const lifecycleConfig = properties["LifecycleConfiguration"];
|
|
9433
|
-
if (lifecycleConfig?.Rules && Array.isArray(lifecycleConfig.Rules) && lifecycleConfig.Rules.length > 0)
|
|
9434
|
-
|
|
9435
|
-
|
|
9589
|
+
if (lifecycleConfig?.Rules && Array.isArray(lifecycleConfig.Rules) && lifecycleConfig.Rules.length > 0) {
|
|
9590
|
+
if (!await this.applyLifecycleConfiguration(bucketName, lifecycleConfig, replayOnUnusable)) overrides.set("LifecycleConfiguration", void 0);
|
|
9591
|
+
else recordEffectiveFold("LifecycleConfiguration", effectiveLifecycleConfiguration(lifecycleConfig));
|
|
9592
|
+
} else if (lifecycleConfig != null) this.announceCreateEmptyCollectionSkip("LifecycleConfiguration", "LifecycleConfiguration.Rules");
|
|
9436
9593
|
const loggingConfig = properties["LoggingConfiguration"];
|
|
9437
9594
|
const loggingRefusal = replayOnUnusable ? configStringRefusal(loggingConfig, "DestinationBucketName", "", "AWS::S3::Bucket LoggingConfiguration") : void 0;
|
|
9438
9595
|
if (loggingRefusal !== void 0) {
|
|
@@ -9839,7 +9996,8 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
9839
9996
|
await this.deleteBucketWithEmptyRetry(logicalId, physicalId, allowAutoEmpty);
|
|
9840
9997
|
} catch (error) {
|
|
9841
9998
|
if (error instanceof NoSuchBucket) {
|
|
9842
|
-
|
|
9999
|
+
const clientRegion = await this.s3Client.config.region();
|
|
10000
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
9843
10001
|
this.logger.debug(`Bucket ${physicalId} does not exist, skipping deletion`);
|
|
9844
10002
|
return;
|
|
9845
10003
|
}
|
|
@@ -10081,7 +10239,8 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
10081
10239
|
}
|
|
10082
10240
|
async readTags(bucket) {
|
|
10083
10241
|
try {
|
|
10084
|
-
|
|
10242
|
+
const resp = await this.s3Client.send(new GetBucketTaggingCommand({ Bucket: bucket }));
|
|
10243
|
+
return normalizeAwsTagsToCfn(resp.TagSet);
|
|
10085
10244
|
} catch (err) {
|
|
10086
10245
|
if (err.name === "NoSuchTagSet") return [];
|
|
10087
10246
|
throw err;
|
|
@@ -10773,12 +10932,14 @@ var S3BucketPolicyProvider = class {
|
|
|
10773
10932
|
this.logger.debug(`Successfully deleted S3 bucket policy ${logicalId}`);
|
|
10774
10933
|
} catch (error) {
|
|
10775
10934
|
if (error instanceof NoSuchBucket) {
|
|
10776
|
-
|
|
10935
|
+
const clientRegion = await this.s3Client.config.region();
|
|
10936
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
10777
10937
|
this.logger.debug(`Bucket ${physicalId} does not exist, skipping policy deletion`);
|
|
10778
10938
|
return;
|
|
10779
10939
|
}
|
|
10780
10940
|
if (error instanceof Error && (error.name === "NoSuchBucketPolicy" || error.message.includes("does not have"))) {
|
|
10781
|
-
|
|
10941
|
+
const clientRegion = await this.s3Client.config.region();
|
|
10942
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
10782
10943
|
this.logger.debug(`Bucket policy for ${physicalId} does not exist, skipping`);
|
|
10783
10944
|
return;
|
|
10784
10945
|
}
|
|
@@ -11252,7 +11413,8 @@ var SQSQueueProvider = class {
|
|
|
11252
11413
|
this.logger.debug(`Successfully deleted SQS queue ${logicalId}`);
|
|
11253
11414
|
} catch (error) {
|
|
11254
11415
|
if (error instanceof QueueDoesNotExist) {
|
|
11255
|
-
|
|
11416
|
+
const clientRegion = await this.sqsClient.config.region();
|
|
11417
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
11256
11418
|
this.logger.debug(`SQS queue ${physicalId} does not exist, skipping deletion`);
|
|
11257
11419
|
return;
|
|
11258
11420
|
}
|
|
@@ -11416,7 +11578,8 @@ var SQSQueueProvider = class {
|
|
|
11416
11578
|
result["RedriveAllowPolicy"] = attributes["RedriveAllowPolicy"];
|
|
11417
11579
|
}
|
|
11418
11580
|
try {
|
|
11419
|
-
|
|
11581
|
+
const tagsResp = await this.sqsClient.send(new ListQueueTagsCommand({ QueueUrl: physicalId }));
|
|
11582
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
11420
11583
|
} catch (err) {
|
|
11421
11584
|
if (err instanceof QueueDoesNotExist) return void 0;
|
|
11422
11585
|
throw err;
|
|
@@ -11548,7 +11711,8 @@ var SQSQueuePolicyProvider = class {
|
|
|
11548
11711
|
this.logger.debug(`Successfully deleted SQS queue policy ${logicalId}`);
|
|
11549
11712
|
} catch (error) {
|
|
11550
11713
|
if (error instanceof Error && (error.name === "QueueDoesNotExist" || error.message.includes("does not exist"))) {
|
|
11551
|
-
|
|
11714
|
+
const clientRegion = await this.sqsClient.config.region();
|
|
11715
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
11552
11716
|
this.logger.debug(`Queue ${physicalId} does not exist, skipping policy deletion`);
|
|
11553
11717
|
return;
|
|
11554
11718
|
}
|
|
@@ -11997,7 +12161,8 @@ var SNSTopicProvider = class {
|
|
|
11997
12161
|
this.logger.debug(`Successfully deleted SNS topic ${logicalId}`);
|
|
11998
12162
|
} catch (error) {
|
|
11999
12163
|
if (error instanceof NotFoundException) {
|
|
12000
|
-
|
|
12164
|
+
const clientRegion = await this.snsClient.config.region();
|
|
12165
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
12001
12166
|
this.logger.debug(`SNS topic ${physicalId} does not exist, skipping deletion`);
|
|
12002
12167
|
return;
|
|
12003
12168
|
}
|
|
@@ -12098,7 +12263,8 @@ var SNSTopicProvider = class {
|
|
|
12098
12263
|
}
|
|
12099
12264
|
result["DeliveryStatusLogging"] = mapDeliveryStatusLogging(attrs, stateProtocolCaseMap(properties?.["DeliveryStatusLogging"]));
|
|
12100
12265
|
try {
|
|
12101
|
-
|
|
12266
|
+
const tagsResp = await this.snsClient.send(new ListTagsForResourceCommand({ ResourceArn: physicalId }));
|
|
12267
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
12102
12268
|
} catch (err) {
|
|
12103
12269
|
if (err instanceof NotFoundException) return void 0;
|
|
12104
12270
|
throw err;
|
|
@@ -12628,7 +12794,8 @@ var SNSSubscriptionProvider = class {
|
|
|
12628
12794
|
this.logger.debug(`Successfully deleted SNS subscription ${logicalId}`);
|
|
12629
12795
|
} catch (error) {
|
|
12630
12796
|
if (error instanceof NotFoundException) {
|
|
12631
|
-
|
|
12797
|
+
const clientRegion = await this.snsClient.config.region();
|
|
12798
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
12632
12799
|
this.logger.debug(`Subscription ${physicalId} does not exist, skipping deletion`);
|
|
12633
12800
|
return;
|
|
12634
12801
|
}
|
|
@@ -12820,7 +12987,8 @@ var SNSTopicPolicyProvider = class {
|
|
|
12820
12987
|
this.logger.debug(`Removed policy from topic ${topicArn}`);
|
|
12821
12988
|
} catch (error) {
|
|
12822
12989
|
if (error instanceof Error && (error.name === "NotFoundException" || error.name === "NotFound" || error.message.includes("not found") || error.message.includes("does not exist") || error.message.includes("Invalid parameter"))) {
|
|
12823
|
-
|
|
12990
|
+
const clientRegion = await getAwsClients().sns.config.region();
|
|
12991
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, topicArn);
|
|
12824
12992
|
this.logger.debug(`Topic ${topicArn} not found or policy already removed, skipping`);
|
|
12825
12993
|
continue;
|
|
12826
12994
|
}
|
|
@@ -13047,12 +13215,12 @@ var LambdaFunctionProvider = class {
|
|
|
13047
13215
|
*/
|
|
13048
13216
|
unhandledByDesign = /* @__PURE__ */ new Map([["AWS::Lambda::Function", /* @__PURE__ */ new Map([["PublishToLatestPublished", "CloudFormation-only version-publishing directive with no AWS SDK equivalent"]])]]);
|
|
13049
13217
|
cleanupDeleteRetryDelayMs = 2e3;
|
|
13050
|
-
eniWaitTimeoutMs =
|
|
13218
|
+
eniWaitTimeoutMs = 6e5;
|
|
13051
13219
|
eniWaitInitialDelayMs = 1e4;
|
|
13052
13220
|
eniWaitMaxDelayMs = 1e4;
|
|
13053
13221
|
functionUpdateMaxWaitSeconds = 600;
|
|
13054
13222
|
eniInitialSleepMs = 1e4;
|
|
13055
|
-
eniDeleteRetryBudgetMs =
|
|
13223
|
+
eniDeleteRetryBudgetMs = 18e5;
|
|
13056
13224
|
eniDeleteRetryIntervalMs = 15e3;
|
|
13057
13225
|
constructor() {
|
|
13058
13226
|
const awsClients = getAwsClients();
|
|
@@ -13189,7 +13357,7 @@ var LambdaFunctionProvider = class {
|
|
|
13189
13357
|
const deleteMessage = deleteError instanceof Error ? deleteError.message : String(deleteError);
|
|
13190
13358
|
cleanupFailure = deleteMessage;
|
|
13191
13359
|
if (!/currently in the following state|ResourceConflict/i.test(deleteMessage)) break;
|
|
13192
|
-
if (attempt <
|
|
13360
|
+
if (attempt < 2) await new Promise((resolve) => setTimeout(resolve, this.cleanupDeleteRetryDelayMs * (attempt + 1)));
|
|
13193
13361
|
}
|
|
13194
13362
|
if (cleanupFailure !== void 0) this.logger.error(`Cleanup DeleteFunction failed for ${ctx.logicalId} after ${ctx.apiName} failure — function may be orphaned: ${cleanupFailure}`);
|
|
13195
13363
|
throw new ProvisioningError(`Failed to set ${ctx.propertyName} on Lambda function ${ctx.logicalId} (function was deleted to maintain atomicity): ${message}`, ctx.resourceType, ctx.logicalId, ctx.functionName, error instanceof Error ? error : void 0);
|
|
@@ -13306,15 +13474,17 @@ var LambdaFunctionProvider = class {
|
|
|
13306
13474
|
this.logger.debug(`Updated RecursiveLoop for Lambda function ${physicalId} to '${newRecursiveLoop}'`);
|
|
13307
13475
|
}
|
|
13308
13476
|
const newReservedConcurrentExecutions = properties["ReservedConcurrentExecutions"];
|
|
13309
|
-
if (newReservedConcurrentExecutions !== previousProperties["ReservedConcurrentExecutions"])
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13477
|
+
if (newReservedConcurrentExecutions !== previousProperties["ReservedConcurrentExecutions"]) {
|
|
13478
|
+
if (newReservedConcurrentExecutions === void 0) {
|
|
13479
|
+
await this.lambdaClient.send(new DeleteFunctionConcurrencyCommand({ FunctionName: physicalId }));
|
|
13480
|
+
this.logger.debug(`Cleared ReservedConcurrentExecutions for Lambda function ${physicalId} (template removed the property)`);
|
|
13481
|
+
} else {
|
|
13482
|
+
await this.lambdaClient.send(new PutFunctionConcurrencyCommand({
|
|
13483
|
+
FunctionName: physicalId,
|
|
13484
|
+
ReservedConcurrentExecutions: newReservedConcurrentExecutions
|
|
13485
|
+
}));
|
|
13486
|
+
this.logger.debug(`Updated ReservedConcurrentExecutions for Lambda function ${physicalId} to ${newReservedConcurrentExecutions}`);
|
|
13487
|
+
}
|
|
13318
13488
|
}
|
|
13319
13489
|
if (codeSigningChanged && newCodeSigningConfigArn !== void 0) {
|
|
13320
13490
|
await this.lambdaClient.send(new PutFunctionCodeSigningConfigCommand({
|
|
@@ -13373,7 +13543,8 @@ var LambdaFunctionProvider = class {
|
|
|
13373
13543
|
this.logger.debug(`Detached VPC config from Lambda ${physicalId} before deletion`);
|
|
13374
13544
|
} catch (error) {
|
|
13375
13545
|
if (error instanceof ResourceNotFoundException) {
|
|
13376
|
-
|
|
13546
|
+
const clientRegion = await this.lambdaClient.config.region();
|
|
13547
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
13377
13548
|
return;
|
|
13378
13549
|
}
|
|
13379
13550
|
this.logger.warn(`Pre-delete VPC detach failed for ${physicalId}: ${error instanceof Error ? error.message : String(error)} — continuing with delete`);
|
|
@@ -13385,7 +13556,8 @@ var LambdaFunctionProvider = class {
|
|
|
13385
13556
|
this.logger.debug(`Successfully deleted Lambda function ${logicalId}`);
|
|
13386
13557
|
} catch (error) {
|
|
13387
13558
|
if (error instanceof ResourceNotFoundException) {
|
|
13388
|
-
|
|
13559
|
+
const clientRegion = await this.lambdaClient.config.region();
|
|
13560
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
13389
13561
|
this.logger.debug(`Lambda function ${physicalId} does not exist, skipping deletion`);
|
|
13390
13562
|
return;
|
|
13391
13563
|
}
|
|
@@ -14119,7 +14291,8 @@ var LambdaPermissionProvider = class {
|
|
|
14119
14291
|
this.logger.debug(`Successfully deleted Lambda permission ${logicalId}`);
|
|
14120
14292
|
} catch (error) {
|
|
14121
14293
|
if (error instanceof ResourceNotFoundException) {
|
|
14122
|
-
|
|
14294
|
+
const clientRegion = await this.lambdaClient.config.region();
|
|
14295
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
14123
14296
|
this.logger.debug(`Lambda permission ${physicalId} does not exist, skipping deletion`);
|
|
14124
14297
|
return;
|
|
14125
14298
|
}
|
|
@@ -14345,7 +14518,8 @@ var LambdaUrlProvider = class {
|
|
|
14345
14518
|
this.logger.debug(`Successfully deleted Lambda URL ${logicalId}`);
|
|
14346
14519
|
} catch (error) {
|
|
14347
14520
|
if (error instanceof ResourceNotFoundException) {
|
|
14348
|
-
|
|
14521
|
+
const clientRegion = await this.lambdaClient.config.region();
|
|
14522
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
14349
14523
|
this.logger.debug(`Lambda URL ${physicalId} does not exist, skipping deletion`);
|
|
14350
14524
|
return;
|
|
14351
14525
|
}
|
|
@@ -14839,7 +15013,8 @@ var LambdaEventSourceMappingProvider = class {
|
|
|
14839
15013
|
await this.lambdaClient.send(new GetEventSourceMappingCommand({ UUID: physicalId }));
|
|
14840
15014
|
} catch (error) {
|
|
14841
15015
|
if (error instanceof ResourceNotFoundException) {
|
|
14842
|
-
|
|
15016
|
+
const clientRegion = await this.lambdaClient.config.region();
|
|
15017
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
14843
15018
|
this.logger.debug(`Event source mapping ${physicalId} does not exist, skipping deletion`);
|
|
14844
15019
|
return;
|
|
14845
15020
|
}
|
|
@@ -14849,7 +15024,8 @@ var LambdaEventSourceMappingProvider = class {
|
|
|
14849
15024
|
this.logger.debug(`Successfully deleted event source mapping ${logicalId}`);
|
|
14850
15025
|
} catch (error) {
|
|
14851
15026
|
if (error instanceof ResourceNotFoundException) {
|
|
14852
|
-
|
|
15027
|
+
const clientRegion = await this.lambdaClient.config.region();
|
|
15028
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
14853
15029
|
this.logger.debug(`Event source mapping ${physicalId} does not exist, skipping deletion`);
|
|
14854
15030
|
return;
|
|
14855
15031
|
}
|
|
@@ -15112,7 +15288,8 @@ var LambdaLayerVersionProvider = class {
|
|
|
15112
15288
|
this.logger.debug(`Successfully deleted Lambda layer version ${logicalId}`);
|
|
15113
15289
|
} catch (error) {
|
|
15114
15290
|
if (error instanceof ResourceNotFoundException) {
|
|
15115
|
-
|
|
15291
|
+
const clientRegion = await this.lambdaClient.config.region();
|
|
15292
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
15116
15293
|
this.logger.debug(`Lambda layer version ${physicalId} does not exist, skipping deletion`);
|
|
15117
15294
|
return;
|
|
15118
15295
|
}
|
|
@@ -15312,8 +15489,10 @@ function compositeIdSeparatorRefusal(resourceType, logicalId, segments, maskSecr
|
|
|
15312
15489
|
*/
|
|
15313
15490
|
function packCompositeId(resourceType, logicalId, segments, options) {
|
|
15314
15491
|
const refusal = compositeIdSeparatorRefusal(resourceType, logicalId, segments, options?.maskSecrets);
|
|
15315
|
-
if (refusal !== void 0)
|
|
15316
|
-
|
|
15492
|
+
if (refusal !== void 0) {
|
|
15493
|
+
if (options?.onRefusal) options.onRefusal(`${refusal} Continuing with the ambiguous id because the value comes from a cdkd state record, which no template edit can repair; the same value is REFUSED on a template-path create.`);
|
|
15494
|
+
else throw new ProvisioningError(refusal, resourceType, logicalId);
|
|
15495
|
+
}
|
|
15317
15496
|
return segments.map((segment) => String(segment.value)).join("|");
|
|
15318
15497
|
}
|
|
15319
15498
|
/** Render the expected id shape, e.g. `<databaseName>|<tableName>`. */
|
|
@@ -15556,7 +15735,8 @@ var LambdaEventInvokeConfigProvider = class {
|
|
|
15556
15735
|
this.logger.debug(`Successfully deleted Lambda EventInvokeConfig ${logicalId}`);
|
|
15557
15736
|
} catch (error) {
|
|
15558
15737
|
if (error instanceof ResourceNotFoundException) {
|
|
15559
|
-
|
|
15738
|
+
const clientRegion = await this.lambdaClient.config.region();
|
|
15739
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
15560
15740
|
this.logger.debug(`Lambda EventInvokeConfig ${physicalId} does not exist, skipping deletion`);
|
|
15561
15741
|
return;
|
|
15562
15742
|
}
|
|
@@ -15799,7 +15979,8 @@ var LambdaMicrovmImageProvider = class {
|
|
|
15799
15979
|
await this.client.send(new DeleteMicrovmImageCommand({ imageIdentifier: physicalId }));
|
|
15800
15980
|
} catch (error) {
|
|
15801
15981
|
if (error instanceof ResourceNotFoundException$8) {
|
|
15802
|
-
|
|
15982
|
+
const clientRegion = await this.client.config.region();
|
|
15983
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
15803
15984
|
this.logger.debug(`MicroVM image ${physicalId} does not exist, skipping deletion`);
|
|
15804
15985
|
return;
|
|
15805
15986
|
}
|
|
@@ -15874,7 +16055,8 @@ var LambdaMicrovmImageProvider = class {
|
|
|
15874
16055
|
}
|
|
15875
16056
|
const result = {};
|
|
15876
16057
|
if (name !== void 0) result["Name"] = name;
|
|
15877
|
-
|
|
16058
|
+
const tagsResp = await this.client.send(new ListTagsCommand$1({ Resource: physicalId }));
|
|
16059
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
15878
16060
|
return result;
|
|
15879
16061
|
}
|
|
15880
16062
|
/**
|
|
@@ -16726,7 +16908,7 @@ const DYNAMODB_DELETE_POLL_COST_MS = INDEX_SETTLE_POLL_INTERVAL_MS + 200;
|
|
|
16726
16908
|
* having spent different amounts; this is the ceiling on the total, and a total
|
|
16727
16909
|
* does not need to know which terms produced it.
|
|
16728
16910
|
*/
|
|
16729
|
-
const DYNAMODB_DELETE_BUDGET_MS =
|
|
16911
|
+
const DYNAMODB_DELETE_BUDGET_MS = 156e4;
|
|
16730
16912
|
/**
|
|
16731
16913
|
* What both DynamoDB providers self-report through `getMinResourceTimeoutMs()`.
|
|
16732
16914
|
*
|
|
@@ -16911,7 +17093,7 @@ function autoScalingRetriesWithinDeleteBudget(budget, defaultMaxRetries) {
|
|
|
16911
17093
|
* to ~6 min of retry backoff, which at ~8s a retry is ~45 retries spread across
|
|
16912
17094
|
* however many calls the table's index and replica count produce.
|
|
16913
17095
|
*/
|
|
16914
|
-
const AUTO_SCALING_TEARDOWN_RESERVE_MS =
|
|
17096
|
+
const AUTO_SCALING_TEARDOWN_RESERVE_MS = 12e5;
|
|
16915
17097
|
/** `withRetry`'s capped backoff step (`maxDelayMs`), the cost of one retry. */
|
|
16916
17098
|
const AUTO_SCALING_RETRY_STEP_MS = 8e3;
|
|
16917
17099
|
/**
|
|
@@ -17882,7 +18064,10 @@ var DynamoDBTableProvider = class {
|
|
|
17882
18064
|
} });
|
|
17883
18065
|
const billingMode = billingModeUnusable ? prevBillingMode : requestedBillingMode;
|
|
17884
18066
|
const billingOrThroughputChanged = billingMode !== prevBillingMode || JSON.stringify(properties["ProvisionedThroughput"]) !== JSON.stringify(previousProperties["ProvisionedThroughput"]);
|
|
17885
|
-
if (billingOrThroughputChanged && billingMode === "PROVISIONED" && (prevBillingMode === "PAY_PER_REQUEST" || liveBillingMode === "PAY_PER_REQUEST") && properties["OnDemandThroughput"] !== void 0)
|
|
18067
|
+
if (billingOrThroughputChanged && billingMode === "PROVISIONED" && (prevBillingMode === "PAY_PER_REQUEST" || liveBillingMode === "PAY_PER_REQUEST") && properties["OnDemandThroughput"] !== void 0) {
|
|
18068
|
+
const absentNote = properties["BillingMode"] === void 0 ? " (BillingMode is absent from the template, which CloudFormation resets to PROVISIONED)" : "";
|
|
18069
|
+
throw new ProvisioningError(`AWS::DynamoDB::Table ${logicalId}: the template flips BillingMode to PROVISIONED${absentNote} while still declaring OnDemandThroughput, which AWS accepts only on a PAY_PER_REQUEST table. Nothing was applied. Remove OnDemandThroughput, or keep BillingMode: PAY_PER_REQUEST.`, logicalId, resourceType);
|
|
18070
|
+
}
|
|
17886
18071
|
const liveIndexes = table?.GlobalSecondaryIndexes ?? [];
|
|
17887
18072
|
const desiredIndexByName = /* @__PURE__ */ new Map();
|
|
17888
18073
|
for (const entry of Array.isArray(properties["GlobalSecondaryIndexes"]) ? properties["GlobalSecondaryIndexes"] : []) {
|
|
@@ -18172,7 +18357,8 @@ var DynamoDBTableProvider = class {
|
|
|
18172
18357
|
this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
|
|
18173
18358
|
} catch (error) {
|
|
18174
18359
|
if (error instanceof ResourceNotFoundException$1) {
|
|
18175
|
-
|
|
18360
|
+
const clientRegion = await this.dynamoDBClient.config.region();
|
|
18361
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
18176
18362
|
this.logger.debug(`DynamoDB table ${physicalId} does not exist, skipping deletion`);
|
|
18177
18363
|
this.deleteBudgets.release(deleteBudgetKey(physicalId, context?.expectedRegion));
|
|
18178
18364
|
this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
|
|
@@ -19146,7 +19332,8 @@ var DynamoDBTableProvider = class {
|
|
|
19146
19332
|
if (table.DeletionProtectionEnabled !== void 0) result["DeletionProtectionEnabled"] = table.DeletionProtectionEnabled;
|
|
19147
19333
|
if (table.TableClassSummary?.TableClass) result["TableClass"] = table.TableClassSummary.TableClass;
|
|
19148
19334
|
if (table.TableArn) try {
|
|
19149
|
-
|
|
19335
|
+
const tagsResp = await this.dynamoDBClient.send(new ListTagsOfResourceCommand({ ResourceArn: table.TableArn }));
|
|
19336
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
19150
19337
|
} catch (err) {
|
|
19151
19338
|
if (err instanceof ResourceNotFoundException$1) return void 0;
|
|
19152
19339
|
throw err;
|
|
@@ -20219,10 +20406,14 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
20219
20406
|
const onDemand = { ...gsi.OnDemandThroughput };
|
|
20220
20407
|
const unresolvedMembers = [];
|
|
20221
20408
|
if (!billingFlipped && previousOnDemand) {
|
|
20222
|
-
if (previousOnDemand.MaxReadRequestUnits !== void 0 && onDemand.MaxReadRequestUnits === void 0)
|
|
20223
|
-
|
|
20224
|
-
|
|
20225
|
-
|
|
20409
|
+
if (previousOnDemand.MaxReadRequestUnits !== void 0 && onDemand.MaxReadRequestUnits === void 0) {
|
|
20410
|
+
if (declared?.readDeclared) unresolvedMembers.push("MaxReadRequestUnits");
|
|
20411
|
+
else onDemand.MaxReadRequestUnits = ON_DEMAND_LIMIT_RESET;
|
|
20412
|
+
}
|
|
20413
|
+
if (previousOnDemand.MaxWriteRequestUnits !== void 0 && onDemand.MaxWriteRequestUnits === void 0) {
|
|
20414
|
+
if (declared?.writeDeclared) unresolvedMembers.push("MaxWriteRequestUnits");
|
|
20415
|
+
else onDemand.MaxWriteRequestUnits = ON_DEMAND_LIMIT_RESET;
|
|
20416
|
+
}
|
|
20226
20417
|
}
|
|
20227
20418
|
if (Object.keys(onDemand).length > 0) update.OnDemandThroughput = onDemand;
|
|
20228
20419
|
if (!billingFlipped && gsi.ProvisionedThroughput) update.ProvisionedThroughput = gsi.ProvisionedThroughput;
|
|
@@ -20811,7 +21002,8 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
20811
21002
|
this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
|
|
20812
21003
|
} catch (error) {
|
|
20813
21004
|
if (error instanceof ResourceNotFoundException$1) {
|
|
20814
|
-
|
|
21005
|
+
const clientRegion = await this.dynamoDBClient.config.region();
|
|
21006
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
20815
21007
|
this.logger.debug(`DynamoDB GlobalTable ${physicalId} does not exist, skipping`);
|
|
20816
21008
|
this.deleteBudgets.release(deleteBudgetKey(physicalId, context?.expectedRegion));
|
|
20817
21009
|
this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
|
|
@@ -20922,7 +21114,8 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
20922
21114
|
const subs = await this.readReplicaSubSpecs(client, tableNameForSubs, regionLabel);
|
|
20923
21115
|
Object.assign(entry, subs);
|
|
20924
21116
|
if (replicaArn) try {
|
|
20925
|
-
|
|
21117
|
+
const tagsResp = await client.send(new ListTagsOfResourceCommand({ ResourceArn: replicaArn }));
|
|
21118
|
+
entry["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
20926
21119
|
} catch (tagErr) {
|
|
20927
21120
|
if (tagErr instanceof ResourceNotFoundException$1) {
|
|
20928
21121
|
if (isLocal) throw tagErr;
|
|
@@ -23152,7 +23345,8 @@ var LogsLogGroupProvider = class {
|
|
|
23152
23345
|
const nextClass = normalizeClass(properties["LogGroupClass"]);
|
|
23153
23346
|
if (prevClass !== nextClass) {
|
|
23154
23347
|
const replaceFlags = "--replace --force-stateful-recreation";
|
|
23155
|
-
|
|
23348
|
+
const remedy = isTruthyCfnBoolean(previousProperties["DeletionProtectionEnabled"]) ? `cdkd's recorded properties for this log group carry DeletionProtectionEnabled, so ${replaceFlags} alone will NOT succeed while AWS still has it on: the replacement normally deletes the log group, AWS refuses that delete while protection is on, and cdkd deploy has no --remove-protection flag to clear it (only cdkd destroy and cdkd state destroy act on one). Read "Deletion protection blocks a replacement" in docs/cli-deploy-safety.md BEFORE you disable anything: whether disabling helps at all, and what the flag ends up as, depend on your UpdateReplacePolicy and on whether the deploy completes — neither of which this refusal can see. Then disable deletion protection — \`aws logs put-log-group-deletion-protection --log-group-identifier '${physicalId}' --no-deletion-protection-enabled\`, or via the console — and re-deploy with ${replaceFlags} to delete + recreate the log group under the new class (its stored log events are lost). Setting DeletionProtectionEnabled: false in the template does NOT clear it in the same deploy: this refusal fires before that property is applied, so that route needs its own deploy with the LogGroupClass change reverted. Or revert the LogGroupClass change and keep the current class.` : `Re-deploy with ${replaceFlags} to delete + recreate the log group under the new class (its stored log events are lost), or revert the LogGroupClass change.`;
|
|
23349
|
+
throw new ResourceUpdateNotSupportedError("AWS::Logs::LogGroup", logicalId, `the LogGroupClass ('${prevClass}' -> '${nextClass}') cannot be changed after creation. ${remedy} Note --force-stateful-recreation has NO per-resource granularity: it clears the data guard for every replacement target in the run`);
|
|
23156
23350
|
}
|
|
23157
23351
|
const rawRetention = properties["RetentionInDays"];
|
|
23158
23352
|
const rawPreviousRetention = previousProperties["RetentionInDays"];
|
|
@@ -23170,18 +23364,22 @@ var LogsLogGroupProvider = class {
|
|
|
23170
23364
|
else await this.logsClient.send(new DisassociateKmsKeyCommand({ logGroupName: physicalId }));
|
|
23171
23365
|
this.logger.debug(`Updated KMS key association for log group ${physicalId}`);
|
|
23172
23366
|
}
|
|
23173
|
-
if (retentionInDays !== oldRetentionInDays || !sameRawRetention && Boolean(rawPreviousRetention) && oldRetentionInDays === void 0)
|
|
23174
|
-
|
|
23175
|
-
|
|
23176
|
-
|
|
23177
|
-
else await this.logsClient.send(new DeleteRetentionPolicyCommand({ logGroupName: physicalId }));
|
|
23178
|
-
if (JSON.stringify(properties["DataProtectionPolicy"]) !== JSON.stringify(previousProperties["DataProtectionPolicy"])) if (properties["DataProtectionPolicy"]) {
|
|
23179
|
-
const policyDocument = typeof properties["DataProtectionPolicy"] === "string" ? properties["DataProtectionPolicy"] : JSON.stringify(properties["DataProtectionPolicy"]);
|
|
23180
|
-
await this.logsClient.send(new PutDataProtectionPolicyCommand({
|
|
23181
|
-
logGroupIdentifier: physicalId,
|
|
23182
|
-
policyDocument
|
|
23367
|
+
if (retentionInDays !== oldRetentionInDays || !sameRawRetention && Boolean(rawPreviousRetention) && oldRetentionInDays === void 0) {
|
|
23368
|
+
if (retentionInDays !== void 0 && retentionInDays > 0) await this.logsClient.send(new PutRetentionPolicyCommand({
|
|
23369
|
+
logGroupName: physicalId,
|
|
23370
|
+
retentionInDays
|
|
23183
23371
|
}));
|
|
23184
|
-
|
|
23372
|
+
else await this.logsClient.send(new DeleteRetentionPolicyCommand({ logGroupName: physicalId }));
|
|
23373
|
+
}
|
|
23374
|
+
if (JSON.stringify(properties["DataProtectionPolicy"]) !== JSON.stringify(previousProperties["DataProtectionPolicy"])) {
|
|
23375
|
+
if (properties["DataProtectionPolicy"]) {
|
|
23376
|
+
const policyDocument = typeof properties["DataProtectionPolicy"] === "string" ? properties["DataProtectionPolicy"] : JSON.stringify(properties["DataProtectionPolicy"]);
|
|
23377
|
+
await this.logsClient.send(new PutDataProtectionPolicyCommand({
|
|
23378
|
+
logGroupIdentifier: physicalId,
|
|
23379
|
+
policyDocument
|
|
23380
|
+
}));
|
|
23381
|
+
} else await this.logsClient.send(new DeleteDataProtectionPolicyCommand({ logGroupIdentifier: physicalId }));
|
|
23382
|
+
}
|
|
23185
23383
|
if (properties["DeletionProtectionEnabled"] !== previousProperties["DeletionProtectionEnabled"]) {
|
|
23186
23384
|
const next = properties["DeletionProtectionEnabled"];
|
|
23187
23385
|
if (next !== void 0) await this.logsClient.send(new PutLogGroupDeletionProtectionCommand({
|
|
@@ -23206,18 +23404,20 @@ var LogsLogGroupProvider = class {
|
|
|
23206
23404
|
}
|
|
23207
23405
|
const newFieldIndex = properties["FieldIndexPolicies"];
|
|
23208
23406
|
const oldFieldIndex = previousProperties["FieldIndexPolicies"];
|
|
23209
|
-
if (JSON.stringify(newFieldIndex) !== JSON.stringify(oldFieldIndex))
|
|
23210
|
-
if (newFieldIndex.length >
|
|
23211
|
-
|
|
23212
|
-
|
|
23213
|
-
|
|
23214
|
-
|
|
23215
|
-
|
|
23216
|
-
|
|
23217
|
-
|
|
23218
|
-
|
|
23219
|
-
|
|
23220
|
-
|
|
23407
|
+
if (JSON.stringify(newFieldIndex) !== JSON.stringify(oldFieldIndex)) {
|
|
23408
|
+
if (newFieldIndex && newFieldIndex.length > 0) {
|
|
23409
|
+
if (newFieldIndex.length > 1) this.logger.debug(`Log group ${physicalId} declares ${newFieldIndex.length} FieldIndexPolicies; AWS only supports one log-group-level field index policy. Applying the first.`);
|
|
23410
|
+
const first = newFieldIndex[0];
|
|
23411
|
+
const policyDocument = typeof first === "string" ? first : JSON.stringify(first);
|
|
23412
|
+
await this.logsClient.send(new PutIndexPolicyCommand({
|
|
23413
|
+
logGroupIdentifier: physicalId,
|
|
23414
|
+
policyDocument
|
|
23415
|
+
}));
|
|
23416
|
+
} else try {
|
|
23417
|
+
await this.logsClient.send(new DeleteIndexPolicyCommand({ logGroupIdentifier: physicalId }));
|
|
23418
|
+
} catch (err) {
|
|
23419
|
+
if (!(err instanceof ResourceNotFoundException$5)) throw err;
|
|
23420
|
+
}
|
|
23221
23421
|
}
|
|
23222
23422
|
const newTags = properties["Tags"];
|
|
23223
23423
|
const oldTags = previousProperties["Tags"];
|
|
@@ -23264,7 +23464,8 @@ var LogsLogGroupProvider = class {
|
|
|
23264
23464
|
this.logger.debug(`Successfully deleted log group ${logicalId}`);
|
|
23265
23465
|
} catch (error) {
|
|
23266
23466
|
if (error instanceof ResourceNotFoundException$5) {
|
|
23267
|
-
|
|
23467
|
+
const clientRegion = await this.logsClient.config.region();
|
|
23468
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
23268
23469
|
this.logger.debug(`Log group ${physicalId} does not exist, skipping deletion`);
|
|
23269
23470
|
return;
|
|
23270
23471
|
}
|
|
@@ -23363,7 +23564,8 @@ var LogsLogGroupProvider = class {
|
|
|
23363
23564
|
if (found.arn) {
|
|
23364
23565
|
const arnForTags = found.arn.replace(/:\*$/, "");
|
|
23365
23566
|
try {
|
|
23366
|
-
|
|
23567
|
+
const tagsResp = await this.logsClient.send(new ListTagsForResourceCommand$5({ resourceArn: arnForTags }));
|
|
23568
|
+
tags = normalizeAwsTagsToCfn(tagsResp.tags);
|
|
23367
23569
|
} catch (err) {
|
|
23368
23570
|
if (err instanceof ResourceNotFoundException$5) return void 0;
|
|
23369
23571
|
throw err;
|
|
@@ -23527,7 +23729,8 @@ var CloudWatchAlarmProvider = class {
|
|
|
23527
23729
|
this.logger.debug(`Successfully deleted CloudWatch alarm ${logicalId}`);
|
|
23528
23730
|
} catch (error) {
|
|
23529
23731
|
if (error instanceof Error && error.name === "ResourceNotFound") {
|
|
23530
|
-
|
|
23732
|
+
const clientRegion = await this.cloudWatchClient.config.region();
|
|
23733
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
23531
23734
|
this.logger.debug(`Alarm ${physicalId} does not exist, skipping deletion`);
|
|
23532
23735
|
return;
|
|
23533
23736
|
}
|
|
@@ -23718,7 +23921,8 @@ var CloudWatchAlarmProvider = class {
|
|
|
23718
23921
|
if (alarm.EvaluateLowSampleCountPercentile !== void 0) result["EvaluateLowSampleCountPercentile"] = alarm.EvaluateLowSampleCountPercentile;
|
|
23719
23922
|
if (alarm.ThresholdMetricId !== void 0) result["ThresholdMetricId"] = alarm.ThresholdMetricId;
|
|
23720
23923
|
if (alarm.AlarmArn) try {
|
|
23721
|
-
|
|
23924
|
+
const tagsResp = await this.cloudWatchClient.send(new ListTagsForResourceCommand$4({ ResourceARN: alarm.AlarmArn }));
|
|
23925
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
23722
23926
|
} catch (err) {
|
|
23723
23927
|
this.logger.debug(`CloudWatch ListTagsForResource(${alarm.AlarmArn}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
23724
23928
|
}
|
|
@@ -23856,7 +24060,8 @@ var CloudWatchAnomalyDetectorProvider = class {
|
|
|
23856
24060
|
this.logger.debug(`Successfully deleted CloudWatch anomaly detector ${logicalId}`);
|
|
23857
24061
|
} catch (error) {
|
|
23858
24062
|
if (error instanceof ResourceNotFoundException$4) {
|
|
23859
|
-
|
|
24063
|
+
const clientRegion = await this.cloudWatchClient.config.region();
|
|
24064
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
23860
24065
|
this.logger.debug(`Anomaly detector ${physicalId} does not exist, skipping deletion`);
|
|
23861
24066
|
return;
|
|
23862
24067
|
}
|
|
@@ -24136,7 +24341,8 @@ var SecretsManagerSecretProvider = class {
|
|
|
24136
24341
|
this.logger.debug(`Successfully deleted secret ${logicalId}`);
|
|
24137
24342
|
} catch (error) {
|
|
24138
24343
|
if (error instanceof ResourceNotFoundException$3) {
|
|
24139
|
-
|
|
24344
|
+
const clientRegion = await this.smClient.config.region();
|
|
24345
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
24140
24346
|
this.logger.debug(`Secret ${physicalId} does not exist, skipping deletion`);
|
|
24141
24347
|
return;
|
|
24142
24348
|
}
|
|
@@ -24530,7 +24736,8 @@ var SSMParameterProvider = class {
|
|
|
24530
24736
|
* `import()` now refuses it — and not here. Do not re-add it.
|
|
24531
24737
|
*/
|
|
24532
24738
|
async buildParameterArnUnguarded(name, mask) {
|
|
24533
|
-
const
|
|
24739
|
+
const region = await this.ssmClient.config.region();
|
|
24740
|
+
const accountInfo = await getAccountInfo(region);
|
|
24534
24741
|
if (accountInfo.fabricated) {
|
|
24535
24742
|
this.logger.warn(mask(`Cannot determine the AWS account (STS is unreachable, and the resolved account id is a placeholder), so the Arn attribute for SSM parameter ${name} would be fabricated and is NOT recorded. An Fn::GetAtt on it will fail until a later deploy resolves the account.`));
|
|
24536
24743
|
return;
|
|
@@ -24664,7 +24871,8 @@ var SSMParameterProvider = class {
|
|
|
24664
24871
|
this.logger.debug(`Successfully deleted SSM parameter ${logicalId}`);
|
|
24665
24872
|
} catch (error) {
|
|
24666
24873
|
if (error instanceof ParameterNotFound) {
|
|
24667
|
-
|
|
24874
|
+
const clientRegion = await this.ssmClient.config.region();
|
|
24875
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
24668
24876
|
this.logger.debug(`Parameter ${physicalId} does not exist, skipping deletion`);
|
|
24669
24877
|
return;
|
|
24670
24878
|
}
|
|
@@ -24748,10 +24956,11 @@ var SSMParameterProvider = class {
|
|
|
24748
24956
|
} catch {}
|
|
24749
24957
|
if (!policiesEmitted) result["Policies"] = [];
|
|
24750
24958
|
try {
|
|
24751
|
-
const
|
|
24959
|
+
const tagsResp = await this.ssmClient.send(new ListTagsForResourceCommand$2({
|
|
24752
24960
|
ResourceType: "Parameter",
|
|
24753
24961
|
ResourceId: physicalId
|
|
24754
|
-
}))
|
|
24962
|
+
}));
|
|
24963
|
+
const tagArr = normalizeAwsTagsToCfn(tagsResp.TagList);
|
|
24755
24964
|
result["Tags"] = Object.fromEntries(tagArr.map((t) => [t.Key, t.Value]));
|
|
24756
24965
|
} catch {}
|
|
24757
24966
|
return result;
|
|
@@ -25113,7 +25322,8 @@ var EventBridgeRuleProvider = class {
|
|
|
25113
25322
|
}))).Targets || []).map((t) => t.Id).filter((id) => id !== void 0);
|
|
25114
25323
|
} catch (error) {
|
|
25115
25324
|
if (error instanceof ResourceNotFoundException$2) {
|
|
25116
|
-
|
|
25325
|
+
const clientRegion = await this.eventBridgeClient.config.region();
|
|
25326
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
25117
25327
|
this.logger.debug(`Rule ${ruleName} does not exist, skipping deletion`);
|
|
25118
25328
|
return;
|
|
25119
25329
|
}
|
|
@@ -25134,7 +25344,8 @@ var EventBridgeRuleProvider = class {
|
|
|
25134
25344
|
this.logger.debug(`Successfully deleted EventBridge rule ${logicalId}`);
|
|
25135
25345
|
} catch (error) {
|
|
25136
25346
|
if (error instanceof ResourceNotFoundException$2) {
|
|
25137
|
-
|
|
25347
|
+
const clientRegion = await this.eventBridgeClient.config.region();
|
|
25348
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
25138
25349
|
this.logger.debug(`Rule ${ruleName} does not exist, skipping deletion`);
|
|
25139
25350
|
return;
|
|
25140
25351
|
}
|
|
@@ -25206,7 +25417,8 @@ var EventBridgeRuleProvider = class {
|
|
|
25206
25417
|
if (!(err instanceof ResourceNotFoundException$2)) throw err;
|
|
25207
25418
|
}
|
|
25208
25419
|
try {
|
|
25209
|
-
|
|
25420
|
+
const tagsResp = await this.eventBridgeClient.send(new ListTagsForResourceCommand$1({ ResourceARN: physicalId }));
|
|
25421
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
25210
25422
|
} catch (err) {
|
|
25211
25423
|
if (!(err instanceof ResourceNotFoundException$2)) throw err;
|
|
25212
25424
|
}
|
|
@@ -25443,13 +25655,15 @@ var EventBridgeBusProvider = class {
|
|
|
25443
25655
|
this.logger.debug(`Successfully deleted EventBus ${logicalId}`);
|
|
25444
25656
|
} catch (error) {
|
|
25445
25657
|
if (error instanceof ResourceNotFoundException$2) {
|
|
25446
|
-
|
|
25658
|
+
const clientRegion = await this.eventBridgeClient.config.region();
|
|
25659
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
25447
25660
|
this.logger.debug(`EventBus ${physicalId} does not exist, skipping`);
|
|
25448
25661
|
return;
|
|
25449
25662
|
}
|
|
25450
25663
|
const msg = error instanceof Error ? error.message : String(error);
|
|
25451
25664
|
if (msg.includes("does not exist")) {
|
|
25452
|
-
|
|
25665
|
+
const clientRegion = await this.eventBridgeClient.config.region();
|
|
25666
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
25453
25667
|
this.logger.debug(`EventBus ${physicalId} does not exist, skipping`);
|
|
25454
25668
|
return;
|
|
25455
25669
|
}
|
|
@@ -25534,7 +25748,8 @@ var EventBridgeBusProvider = class {
|
|
|
25534
25748
|
result["Policy"] = resp.Policy;
|
|
25535
25749
|
}
|
|
25536
25750
|
if (resp.Arn) try {
|
|
25537
|
-
|
|
25751
|
+
const tagsResp = await this.eventBridgeClient.send(new ListTagsForResourceCommand$1({ ResourceARN: resp.Arn }));
|
|
25752
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
25538
25753
|
} catch (err) {
|
|
25539
25754
|
if (err instanceof ResourceNotFoundException$2) return void 0;
|
|
25540
25755
|
throw err;
|
|
@@ -26297,7 +26512,8 @@ var EC2Provider = class {
|
|
|
26297
26512
|
return;
|
|
26298
26513
|
} catch (error) {
|
|
26299
26514
|
if (this.isNotFoundError(error)) {
|
|
26300
|
-
|
|
26515
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
26516
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
26301
26517
|
this.logger.debug(`VPC ${physicalId} does not exist, skipping deletion`);
|
|
26302
26518
|
return;
|
|
26303
26519
|
}
|
|
@@ -26470,7 +26686,8 @@ var EC2Provider = class {
|
|
|
26470
26686
|
return;
|
|
26471
26687
|
} catch (error) {
|
|
26472
26688
|
if (this.isNotFoundError(error)) {
|
|
26473
|
-
|
|
26689
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
26690
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
26474
26691
|
this.logger.debug(`Subnet ${physicalId} does not exist, skipping deletion`);
|
|
26475
26692
|
return;
|
|
26476
26693
|
}
|
|
@@ -26556,7 +26773,8 @@ var EC2Provider = class {
|
|
|
26556
26773
|
this.logger.debug(`Successfully deleted InternetGateway ${logicalId}`);
|
|
26557
26774
|
} catch (error) {
|
|
26558
26775
|
if (this.isNotFoundError(error)) {
|
|
26559
|
-
|
|
26776
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
26777
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
26560
26778
|
this.logger.debug(`InternetGateway ${physicalId} does not exist, skipping deletion`);
|
|
26561
26779
|
return;
|
|
26562
26780
|
}
|
|
@@ -26608,13 +26826,15 @@ var EC2Provider = class {
|
|
|
26608
26826
|
publicIp = response.PublicIp;
|
|
26609
26827
|
await this.applyTags(allocationId, properties, logicalId);
|
|
26610
26828
|
const instanceId = properties["InstanceId"];
|
|
26611
|
-
if (instanceId)
|
|
26612
|
-
|
|
26613
|
-
|
|
26614
|
-
|
|
26615
|
-
|
|
26616
|
-
|
|
26617
|
-
|
|
26829
|
+
if (instanceId) {
|
|
26830
|
+
if (process.env["CDKD_NO_WAIT"] === "true") this.logger.warn(`EIP ${logicalId} (${allocationId}) was NOT associated with ${instanceId}: --no-wait skips the instance running-state wait, and AssociateAddress rejects an instance that is not yet running. Verify and associate once the instance is running: aws ec2 describe-instances --instance-ids ${instanceId} --query 'Reservations[].Instances[].State.Name' && aws ec2 associate-address --allocation-id ${allocationId} --instance-id ${instanceId}`);
|
|
26831
|
+
else {
|
|
26832
|
+
await this.ec2Client.send(new AssociateAddressCommand({
|
|
26833
|
+
AllocationId: allocationId,
|
|
26834
|
+
InstanceId: instanceId
|
|
26835
|
+
}));
|
|
26836
|
+
this.logger.debug(`Associated EIP ${logicalId} (${allocationId}) to ${instanceId}`);
|
|
26837
|
+
}
|
|
26618
26838
|
}
|
|
26619
26839
|
this.logger.debug(`Successfully created EIP ${logicalId}: ${allocationId} (${publicIp})`);
|
|
26620
26840
|
} catch (error) {
|
|
@@ -26639,19 +26859,21 @@ var EC2Provider = class {
|
|
|
26639
26859
|
await this.applyTagDiff(allocationId, previousProperties["Tags"], properties["Tags"]);
|
|
26640
26860
|
const oldInstanceId = previousProperties["InstanceId"];
|
|
26641
26861
|
const newInstanceId = properties["InstanceId"];
|
|
26642
|
-
if (oldInstanceId !== newInstanceId)
|
|
26643
|
-
|
|
26644
|
-
|
|
26645
|
-
|
|
26646
|
-
|
|
26647
|
-
|
|
26648
|
-
|
|
26649
|
-
|
|
26650
|
-
|
|
26651
|
-
|
|
26652
|
-
|
|
26653
|
-
|
|
26654
|
-
|
|
26862
|
+
if (oldInstanceId !== newInstanceId) {
|
|
26863
|
+
if (newInstanceId) try {
|
|
26864
|
+
await this.ec2Client.send(new AssociateAddressCommand({
|
|
26865
|
+
AllocationId: allocationId,
|
|
26866
|
+
InstanceId: newInstanceId,
|
|
26867
|
+
AllowReassociation: true
|
|
26868
|
+
}));
|
|
26869
|
+
} catch (error) {
|
|
26870
|
+
if (process.env["CDKD_NO_WAIT"] === "true" && this.isIncorrectInstanceStateError(error)) this.logger.warn(`EIP ${logicalId} (${allocationId}) was NOT associated with ${newInstanceId}: --no-wait skips the instance running-state wait, and AssociateAddress rejects an instance that is not yet running. Verify and associate once the instance is running: aws ec2 describe-instances --instance-ids ${newInstanceId} --query 'Reservations[].Instances[].State.Name' && aws ec2 associate-address --allocation-id ${allocationId} --instance-id ${newInstanceId} --allow-reassociation`);
|
|
26871
|
+
else throw error;
|
|
26872
|
+
}
|
|
26873
|
+
else {
|
|
26874
|
+
const associationId = (await this.ec2Client.send(new DescribeAddressesCommand({ AllocationIds: [allocationId] }))).Addresses?.[0]?.AssociationId;
|
|
26875
|
+
if (associationId) await this.ec2Client.send(new DisassociateAddressCommand({ AssociationId: associationId }));
|
|
26876
|
+
}
|
|
26655
26877
|
}
|
|
26656
26878
|
}
|
|
26657
26879
|
return {
|
|
@@ -26675,7 +26897,8 @@ var EC2Provider = class {
|
|
|
26675
26897
|
this.logger.debug(`Successfully deleted EIP ${logicalId}`);
|
|
26676
26898
|
} catch (error) {
|
|
26677
26899
|
if (this.isNotFoundError(error)) {
|
|
26678
|
-
|
|
26900
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
26901
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
26679
26902
|
this.logger.debug(`EIP ${physicalId} does not exist, skipping deletion`);
|
|
26680
26903
|
return;
|
|
26681
26904
|
}
|
|
@@ -26742,7 +26965,8 @@ var EC2Provider = class {
|
|
|
26742
26965
|
this.logger.debug(`Successfully deleted VPCGatewayAttachment ${logicalId}`);
|
|
26743
26966
|
} catch (error) {
|
|
26744
26967
|
if (this.isNotFoundError(error)) {
|
|
26745
|
-
|
|
26968
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
26969
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
26746
26970
|
this.logger.debug(`VPCGatewayAttachment ${physicalId} does not exist, skipping deletion`);
|
|
26747
26971
|
return;
|
|
26748
26972
|
}
|
|
@@ -26800,7 +27024,8 @@ var EC2Provider = class {
|
|
|
26800
27024
|
await this.ec2Client.send(new DeleteNatGatewayCommand({ NatGatewayId: physicalId }));
|
|
26801
27025
|
} catch (error) {
|
|
26802
27026
|
if (this.isNotFoundError(error)) {
|
|
26803
|
-
|
|
27027
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
27028
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
26804
27029
|
this.logger.debug(`NatGateway ${physicalId} does not exist, skipping deletion`);
|
|
26805
27030
|
return;
|
|
26806
27031
|
}
|
|
@@ -26855,7 +27080,8 @@ var EC2Provider = class {
|
|
|
26855
27080
|
this.logger.debug(`Successfully deleted RouteTable ${logicalId}`);
|
|
26856
27081
|
} catch (error) {
|
|
26857
27082
|
if (this.isNotFoundError(error)) {
|
|
26858
|
-
|
|
27083
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
27084
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
26859
27085
|
this.logger.debug(`RouteTable ${physicalId} does not exist, skipping deletion`);
|
|
26860
27086
|
return;
|
|
26861
27087
|
}
|
|
@@ -26953,7 +27179,8 @@ var EC2Provider = class {
|
|
|
26953
27179
|
this.logger.debug(`Successfully deleted Route ${logicalId}`);
|
|
26954
27180
|
} catch (error) {
|
|
26955
27181
|
if (this.isNotFoundError(error)) {
|
|
26956
|
-
|
|
27182
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
27183
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
26957
27184
|
this.logger.debug(`Route ${physicalId} does not exist, skipping deletion`);
|
|
26958
27185
|
return;
|
|
26959
27186
|
}
|
|
@@ -26991,7 +27218,8 @@ var EC2Provider = class {
|
|
|
26991
27218
|
this.logger.debug(`Successfully deleted SubnetRouteTableAssociation ${logicalId}`);
|
|
26992
27219
|
} catch (error) {
|
|
26993
27220
|
if (this.isNotFoundError(error)) {
|
|
26994
|
-
|
|
27221
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
27222
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
26995
27223
|
this.logger.debug(`SubnetRouteTableAssociation ${physicalId} does not exist, skipping deletion`);
|
|
26996
27224
|
return;
|
|
26997
27225
|
}
|
|
@@ -27142,7 +27370,8 @@ var EC2Provider = class {
|
|
|
27142
27370
|
return;
|
|
27143
27371
|
} catch (error) {
|
|
27144
27372
|
if (this.isNotFoundError(error)) {
|
|
27145
|
-
|
|
27373
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
27374
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
27146
27375
|
this.logger.debug(`SecurityGroup ${physicalId} does not exist, skipping deletion`);
|
|
27147
27376
|
return;
|
|
27148
27377
|
}
|
|
@@ -27372,7 +27601,8 @@ var EC2Provider = class {
|
|
|
27372
27601
|
this.logger.debug(`Successfully deleted SecurityGroupIngress ${logicalId}`);
|
|
27373
27602
|
} catch (error) {
|
|
27374
27603
|
if (this.isNotFoundError(error)) {
|
|
27375
|
-
|
|
27604
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
27605
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
27376
27606
|
this.logger.debug(`SecurityGroupIngress ${physicalId} does not exist, skipping deletion`);
|
|
27377
27607
|
return;
|
|
27378
27608
|
}
|
|
@@ -27431,10 +27661,12 @@ var EC2Provider = class {
|
|
|
27431
27661
|
maxDelay: 5
|
|
27432
27662
|
}, { InstanceIds: [instanceId] });
|
|
27433
27663
|
} else this.logger.debug(`Instance ${instanceId} launched (skipping running-state wait per --no-wait)`);
|
|
27434
|
-
if (iamInstanceProfile)
|
|
27435
|
-
|
|
27436
|
-
|
|
27437
|
-
|
|
27664
|
+
if (iamInstanceProfile) {
|
|
27665
|
+
if (process.env["CDKD_NO_WAIT"] === "true") {
|
|
27666
|
+
const profileRef = iamInstanceProfile.arn ?? iamInstanceProfile.name;
|
|
27667
|
+
this.logger.warn(`Skipped the IAM instance profile association check for ${logicalId} (${instanceId}) because --no-wait leaves the instance in 'pending', where AssociateIamInstanceProfile is rejected. RunInstances associates the profile asynchronously and can complete with NO profile attached when the profile was created moments earlier. Verify with: aws ec2 describe-iam-instance-profile-associations --filters Name=instance-id,Values=${instanceId} — and if it is missing, re-associate with: aws ec2 associate-iam-instance-profile --instance-id ${instanceId} --iam-instance-profile ${iamInstanceProfile.arn ? `Arn=${profileRef}` : `Name=${profileRef}`}`);
|
|
27668
|
+
} else await this.ensureIamInstanceProfileAssociated(instanceId, iamInstanceProfile.arn, iamInstanceProfile.name, logicalId);
|
|
27669
|
+
}
|
|
27438
27670
|
const runningInstance = (await this.ec2Client.send(new DescribeInstancesCommand({ InstanceIds: [instanceId] }))).Reservations?.[0]?.Instances?.[0];
|
|
27439
27671
|
const attributes = {
|
|
27440
27672
|
InstanceId: instanceId,
|
|
@@ -27644,8 +27876,10 @@ var EC2Provider = class {
|
|
|
27644
27876
|
}));
|
|
27645
27877
|
const newMonitoring = this.coerceBool(properties["Monitoring"]);
|
|
27646
27878
|
const oldMonitoring = this.coerceBool(previousProperties["Monitoring"]);
|
|
27647
|
-
if (newMonitoring !== void 0 && newMonitoring !== oldMonitoring)
|
|
27648
|
-
|
|
27879
|
+
if (newMonitoring !== void 0 && newMonitoring !== oldMonitoring) {
|
|
27880
|
+
if (newMonitoring) await this.ec2Client.send(new MonitorInstancesCommand({ InstanceIds: [physicalId] }));
|
|
27881
|
+
else await this.ec2Client.send(new UnmonitorInstancesCommand({ InstanceIds: [physicalId] }));
|
|
27882
|
+
}
|
|
27649
27883
|
const newMetadata = this.buildMetadataOptions(properties);
|
|
27650
27884
|
const oldMetadata = this.buildMetadataOptions(previousProperties);
|
|
27651
27885
|
if (newMetadata !== void 0 && !this.shallowEqual(newMetadata, oldMetadata)) await this.ec2Client.send(new ModifyInstanceMetadataOptionsCommand({
|
|
@@ -27691,7 +27925,8 @@ var EC2Provider = class {
|
|
|
27691
27925
|
return;
|
|
27692
27926
|
} catch (error) {
|
|
27693
27927
|
if (this.isNotFoundError(error)) {
|
|
27694
|
-
|
|
27928
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
27929
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
27695
27930
|
this.logger.debug(`EC2 Instance ${physicalId} already terminated (not found), treating as success`);
|
|
27696
27931
|
return;
|
|
27697
27932
|
}
|
|
@@ -27996,7 +28231,8 @@ var EC2Provider = class {
|
|
|
27996
28231
|
this.logger.debug(`Successfully deleted NetworkAcl ${logicalId}`);
|
|
27997
28232
|
} catch (error) {
|
|
27998
28233
|
if (this.isNotFoundError(error)) {
|
|
27999
|
-
|
|
28234
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
28235
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
28000
28236
|
this.logger.debug(`NetworkAcl ${physicalId} does not exist, skipping deletion`);
|
|
28001
28237
|
return;
|
|
28002
28238
|
}
|
|
@@ -28080,7 +28316,8 @@ var EC2Provider = class {
|
|
|
28080
28316
|
this.logger.debug(`Successfully deleted NetworkAclEntry ${logicalId}`);
|
|
28081
28317
|
} catch (error) {
|
|
28082
28318
|
if (this.isNotFoundError(error)) {
|
|
28083
|
-
|
|
28319
|
+
const clientRegion = await this.ec2Client.config.region();
|
|
28320
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
28084
28321
|
this.logger.debug(`NetworkAclEntry ${physicalId} does not exist, skipping deletion`);
|
|
28085
28322
|
return;
|
|
28086
28323
|
}
|
|
@@ -28722,8 +28959,10 @@ var EC2Provider = class {
|
|
|
28722
28959
|
if (route.DestinationCidrBlock !== void 0) result["DestinationCidrBlock"] = route.DestinationCidrBlock;
|
|
28723
28960
|
else if (route.DestinationIpv6CidrBlock !== void 0) result["DestinationIpv6CidrBlock"] = route.DestinationIpv6CidrBlock;
|
|
28724
28961
|
else if (route.DestinationPrefixListId !== void 0) result["DestinationPrefixListId"] = route.DestinationPrefixListId;
|
|
28725
|
-
if (route.GatewayId !== void 0)
|
|
28726
|
-
|
|
28962
|
+
if (route.GatewayId !== void 0) {
|
|
28963
|
+
if (route.GatewayId.startsWith("vpce-")) result["VpcEndpointId"] = route.GatewayId;
|
|
28964
|
+
else result["GatewayId"] = route.GatewayId;
|
|
28965
|
+
}
|
|
28727
28966
|
if (route.NatGatewayId !== void 0) result["NatGatewayId"] = route.NatGatewayId;
|
|
28728
28967
|
if (route.InstanceId !== void 0) result["InstanceId"] = route.InstanceId;
|
|
28729
28968
|
if (route.NetworkInterfaceId !== void 0) result["NetworkInterfaceId"] = route.NetworkInterfaceId;
|
|
@@ -29577,7 +29816,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
|
|
|
29577
29816
|
this.logger.debug(`Successfully deleted API Gateway Authorizer ${logicalId}`);
|
|
29578
29817
|
} catch (error) {
|
|
29579
29818
|
if (error instanceof NotFoundException$1) {
|
|
29580
|
-
|
|
29819
|
+
const clientRegion = await this.apiGatewayClient.config.region();
|
|
29820
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
29581
29821
|
this.logger.debug(`API Gateway Authorizer ${physicalId} does not exist, skipping deletion`);
|
|
29582
29822
|
return;
|
|
29583
29823
|
}
|
|
@@ -29669,7 +29909,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
|
|
|
29669
29909
|
this.logger.debug(`Successfully deleted API Gateway Resource ${logicalId}`);
|
|
29670
29910
|
} catch (error) {
|
|
29671
29911
|
if (error instanceof NotFoundException$1) {
|
|
29672
|
-
|
|
29912
|
+
const clientRegion = await this.apiGatewayClient.config.region();
|
|
29913
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
29673
29914
|
this.logger.debug(`API Gateway Resource ${physicalId} does not exist, skipping deletion`);
|
|
29674
29915
|
return;
|
|
29675
29916
|
}
|
|
@@ -29731,7 +29972,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
|
|
|
29731
29972
|
this.logger.debug(`Successfully deleted API Gateway Deployment ${logicalId}`);
|
|
29732
29973
|
} catch (error) {
|
|
29733
29974
|
if (error instanceof NotFoundException$1) {
|
|
29734
|
-
|
|
29975
|
+
const clientRegion = await this.apiGatewayClient.config.region();
|
|
29976
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
29735
29977
|
this.logger.debug(`API Gateway Deployment ${physicalId} does not exist, skipping deletion`);
|
|
29736
29978
|
return;
|
|
29737
29979
|
}
|
|
@@ -29982,7 +30224,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
|
|
|
29982
30224
|
this.logger.debug(`Successfully deleted API Gateway Stage ${logicalId}`);
|
|
29983
30225
|
} catch (error) {
|
|
29984
30226
|
if (error instanceof NotFoundException$1) {
|
|
29985
|
-
|
|
30227
|
+
const clientRegion = await this.apiGatewayClient.config.region();
|
|
30228
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
29986
30229
|
this.logger.debug(`API Gateway Stage ${physicalId} does not exist, skipping deletion`);
|
|
29987
30230
|
return;
|
|
29988
30231
|
}
|
|
@@ -30291,7 +30534,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
|
|
|
30291
30534
|
this.logger.debug(`Successfully deleted API Gateway Method ${logicalId}`);
|
|
30292
30535
|
} catch (error) {
|
|
30293
30536
|
if (error instanceof NotFoundException$1) {
|
|
30294
|
-
|
|
30537
|
+
const clientRegion = await this.apiGatewayClient.config.region();
|
|
30538
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
30295
30539
|
this.logger.debug(`API Gateway Method ${physicalId} does not exist, skipping deletion`);
|
|
30296
30540
|
return;
|
|
30297
30541
|
}
|
|
@@ -30990,11 +31234,13 @@ var ApiGatewayV2Provider = class {
|
|
|
30990
31234
|
const apiId = response.ApiId;
|
|
30991
31235
|
const apiEndpoint = response.ApiEndpoint;
|
|
30992
31236
|
this.logger.debug(`Successfully created API Gateway V2 Api ${logicalId}: ${apiId}`);
|
|
31237
|
+
const executeApiArn = await this.buildExecuteApiArn(apiId);
|
|
30993
31238
|
return {
|
|
30994
31239
|
physicalId: apiId,
|
|
30995
31240
|
attributes: {
|
|
30996
31241
|
ApiId: apiId,
|
|
30997
|
-
ApiEndpoint: apiEndpoint
|
|
31242
|
+
ApiEndpoint: apiEndpoint,
|
|
31243
|
+
...executeApiArn !== void 0 ? { ExecuteApiArn: executeApiArn } : {}
|
|
30998
31244
|
}
|
|
30999
31245
|
};
|
|
31000
31246
|
} catch (error) {
|
|
@@ -31009,7 +31255,8 @@ var ApiGatewayV2Provider = class {
|
|
|
31009
31255
|
this.logger.debug(`Successfully deleted API Gateway V2 Api ${logicalId}`);
|
|
31010
31256
|
} catch (error) {
|
|
31011
31257
|
if (error instanceof NotFoundException$3) {
|
|
31012
|
-
|
|
31258
|
+
const clientRegion = await this.getClient().config.region();
|
|
31259
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
31013
31260
|
this.logger.debug(`API Gateway V2 Api ${physicalId} does not exist, skipping deletion`);
|
|
31014
31261
|
return;
|
|
31015
31262
|
}
|
|
@@ -31017,6 +31264,52 @@ var ApiGatewayV2Provider = class {
|
|
|
31017
31264
|
throw new ProvisioningError(`Failed to delete API Gateway V2 Api ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
31018
31265
|
}
|
|
31019
31266
|
}
|
|
31267
|
+
/**
|
|
31268
|
+
* `arn:{partition}:execute-api:{region}:{account}:{apiId}` — the ARN AWS
|
|
31269
|
+
* documents for an API Gateway v2 API, which no API call returns.
|
|
31270
|
+
*
|
|
31271
|
+
* Refuses to record a FABRICATED account, exactly as
|
|
31272
|
+
* `SSMParameterProvider.buildParameterArn` does: an ARN naming a placeholder
|
|
31273
|
+
* account is worse than an absent one, because it is persisted into
|
|
31274
|
+
* `state.json` and outlives the deploy that produced it. The partition is
|
|
31275
|
+
* DERIVED through the same closed region mapping `${AWS::Partition}` uses,
|
|
31276
|
+
* and the region segment is canonicalized — defence in depth rather than the
|
|
31277
|
+
* only fold, since `getAccountInfo` already canonicalizes what it returns
|
|
31278
|
+
* (the issue #1795 / #1814 class: an unfolded `US-EAST-1` matches no IAM
|
|
31279
|
+
* policy and is persisted).
|
|
31280
|
+
*
|
|
31281
|
+
* **NEVER THROWS, and callers depend on it.** Every call site is INSIDE the
|
|
31282
|
+
* `try` of an operation that has already created or found the API, so an
|
|
31283
|
+
* escape here would report a failed create over a resource that exists —
|
|
31284
|
+
* an orphan. A failure means the attribute is absent, which is the state
|
|
31285
|
+
* every deploy before this change was in.
|
|
31286
|
+
*
|
|
31287
|
+
* Takes NO `SecretMaskingContext`, where its SSM twin takes one, and the
|
|
31288
|
+
* difference is not an oversight: every interpolated value here is
|
|
31289
|
+
* non-secret BY CONSTRUCTION — an AWS-minted api id (or, on import, the
|
|
31290
|
+
* CLI's own `--resource` argument), a derived partition, a region, an
|
|
31291
|
+
* account id — and the catch names the error CLASS rather than its message.
|
|
31292
|
+
* `SSMParameterProvider.buildParameterArn` needs a masker because it
|
|
31293
|
+
* interpolates `error.message`, which quotes a resolved parameter value
|
|
31294
|
+
* back. **Widening this catch to `error.message` therefore requires
|
|
31295
|
+
* threading the masker in the same change**; the invariant is what makes
|
|
31296
|
+
* the omission safe, not the current absence of a caller.
|
|
31297
|
+
*/
|
|
31298
|
+
async buildExecuteApiArn(apiId) {
|
|
31299
|
+
try {
|
|
31300
|
+
const region = await this.getClient().config.region();
|
|
31301
|
+
const accountInfo = await getAccountInfo(region);
|
|
31302
|
+
if (accountInfo.fabricated) {
|
|
31303
|
+
this.logger.warn(`Cannot determine the AWS account (STS is unreachable, and the resolved account id is a placeholder), so the ExecuteApiArn attribute for API ${apiId} would be fabricated and is NOT recorded. An Fn::GetAtt on it will fail until a later deploy resolves the account.`);
|
|
31304
|
+
return;
|
|
31305
|
+
}
|
|
31306
|
+
const { partition } = derivePartitionAndUrlSuffix(accountInfo.region);
|
|
31307
|
+
return `arn:${partition}:execute-api:${canonicalizeRegion(accountInfo.region)}:${accountInfo.accountId}:${apiId}`;
|
|
31308
|
+
} catch (error) {
|
|
31309
|
+
this.logger.warn(`Could not build the ExecuteApiArn attribute for API ${apiId} (${error instanceof Error ? error.name : typeof error}), so it is NOT recorded. An Fn::GetAtt on it will fail until a later deploy records it.`);
|
|
31310
|
+
return;
|
|
31311
|
+
}
|
|
31312
|
+
}
|
|
31020
31313
|
getApiAttribute(physicalId, attributeName) {
|
|
31021
31314
|
if (attributeName === "ApiId") return physicalId;
|
|
31022
31315
|
}
|
|
@@ -31061,7 +31354,8 @@ var ApiGatewayV2Provider = class {
|
|
|
31061
31354
|
this.logger.debug(`Successfully deleted API Gateway V2 Stage ${logicalId}`);
|
|
31062
31355
|
} catch (error) {
|
|
31063
31356
|
if (error instanceof NotFoundException$3) {
|
|
31064
|
-
|
|
31357
|
+
const clientRegion = await this.getClient().config.region();
|
|
31358
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
31065
31359
|
this.logger.debug(`API Gateway V2 Stage ${physicalId} does not exist, skipping deletion`);
|
|
31066
31360
|
return;
|
|
31067
31361
|
}
|
|
@@ -31121,7 +31415,8 @@ var ApiGatewayV2Provider = class {
|
|
|
31121
31415
|
this.logger.debug(`Successfully deleted API Gateway V2 Integration ${logicalId}`);
|
|
31122
31416
|
} catch (error) {
|
|
31123
31417
|
if (error instanceof NotFoundException$3) {
|
|
31124
|
-
|
|
31418
|
+
const clientRegion = await this.getClient().config.region();
|
|
31419
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
31125
31420
|
this.logger.debug(`API Gateway V2 Integration ${physicalId} does not exist, skipping deletion`);
|
|
31126
31421
|
return;
|
|
31127
31422
|
}
|
|
@@ -31174,7 +31469,8 @@ var ApiGatewayV2Provider = class {
|
|
|
31174
31469
|
this.logger.debug(`Successfully deleted API Gateway V2 Route ${logicalId}`);
|
|
31175
31470
|
} catch (error) {
|
|
31176
31471
|
if (error instanceof NotFoundException$3) {
|
|
31177
|
-
|
|
31472
|
+
const clientRegion = await this.getClient().config.region();
|
|
31473
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
31178
31474
|
this.logger.debug(`API Gateway V2 Route ${physicalId} does not exist, skipping deletion`);
|
|
31179
31475
|
return;
|
|
31180
31476
|
}
|
|
@@ -31227,7 +31523,8 @@ var ApiGatewayV2Provider = class {
|
|
|
31227
31523
|
this.logger.debug(`Successfully deleted API Gateway V2 Authorizer ${logicalId}`);
|
|
31228
31524
|
} catch (error) {
|
|
31229
31525
|
if (error instanceof NotFoundException$3) {
|
|
31230
|
-
|
|
31526
|
+
const clientRegion = await this.getClient().config.region();
|
|
31527
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
31231
31528
|
this.logger.debug(`API Gateway V2 Authorizer ${physicalId} does not exist, skipping deletion`);
|
|
31232
31529
|
return;
|
|
31233
31530
|
}
|
|
@@ -31437,10 +31734,15 @@ var ApiGatewayV2Provider = class {
|
|
|
31437
31734
|
}
|
|
31438
31735
|
const explicit = resolveExplicitPhysicalId(input, null);
|
|
31439
31736
|
if (explicit) try {
|
|
31440
|
-
await this.getClient().send(new GetApiCommand({ ApiId: explicit }));
|
|
31737
|
+
const got = await this.getClient().send(new GetApiCommand({ ApiId: explicit }));
|
|
31738
|
+
const executeApiArn = await this.buildExecuteApiArn(explicit);
|
|
31441
31739
|
return {
|
|
31442
31740
|
physicalId: explicit,
|
|
31443
|
-
attributes: {
|
|
31741
|
+
attributes: {
|
|
31742
|
+
ApiId: explicit,
|
|
31743
|
+
...got.ApiEndpoint !== void 0 ? { ApiEndpoint: got.ApiEndpoint } : {},
|
|
31744
|
+
...executeApiArn !== void 0 ? { ExecuteApiArn: executeApiArn } : {}
|
|
31745
|
+
}
|
|
31444
31746
|
};
|
|
31445
31747
|
} catch (err) {
|
|
31446
31748
|
if (err instanceof NotFoundException$3) return null;
|
|
@@ -31519,11 +31821,25 @@ var ApiGatewayV2Provider = class {
|
|
|
31519
31821
|
}
|
|
31520
31822
|
this.logger.debug(`Updating API Gateway V2 Api ${logicalId}: ${physicalId}`);
|
|
31521
31823
|
try {
|
|
31522
|
-
|
|
31824
|
+
let apiEndpoint;
|
|
31825
|
+
if (changed) apiEndpoint = (await this.getClient().send(new UpdateApiCommand(input))).ApiEndpoint;
|
|
31523
31826
|
if (corsRemoved) {
|
|
31524
31827
|
this.logger.debug(`Clearing CORS configuration for Api ${logicalId}: ${physicalId}`);
|
|
31525
31828
|
await this.getClient().send(new DeleteCorsConfigurationCommand({ ApiId: physicalId }));
|
|
31526
31829
|
}
|
|
31830
|
+
if (apiEndpoint !== void 0) {
|
|
31831
|
+
const executeApiArn = await this.buildExecuteApiArn(physicalId);
|
|
31832
|
+
if (executeApiArn !== void 0) return {
|
|
31833
|
+
physicalId,
|
|
31834
|
+
wasReplaced: false,
|
|
31835
|
+
attributes: {
|
|
31836
|
+
ApiId: physicalId,
|
|
31837
|
+
ApiEndpoint: apiEndpoint,
|
|
31838
|
+
ExecuteApiArn: executeApiArn
|
|
31839
|
+
}
|
|
31840
|
+
};
|
|
31841
|
+
this.logger.debug(`Skipping the ExecuteApiArn heal for API ${physicalId}: the ARN could not be built. The previously recorded attributes are kept.`);
|
|
31842
|
+
}
|
|
31527
31843
|
return {
|
|
31528
31844
|
physicalId,
|
|
31529
31845
|
wasReplaced: false
|
|
@@ -32368,7 +32684,8 @@ var CloudFrontOAIProvider = class {
|
|
|
32368
32684
|
etag = (await this.cloudFrontClient.send(new GetCloudFrontOriginAccessIdentityCommand({ Id: physicalId }))).ETag;
|
|
32369
32685
|
} catch (error) {
|
|
32370
32686
|
if (error instanceof NoSuchCloudFrontOriginAccessIdentity) {
|
|
32371
|
-
|
|
32687
|
+
const clientRegion = await this.cloudFrontClient.config.region();
|
|
32688
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
32372
32689
|
this.logger.debug(`OAI ${physicalId} does not exist, skipping deletion`);
|
|
32373
32690
|
return;
|
|
32374
32691
|
}
|
|
@@ -32381,7 +32698,8 @@ var CloudFrontOAIProvider = class {
|
|
|
32381
32698
|
this.logger.debug(`Successfully deleted CloudFront OAI ${logicalId}`);
|
|
32382
32699
|
} catch (error) {
|
|
32383
32700
|
if (error instanceof NoSuchCloudFrontOriginAccessIdentity) {
|
|
32384
|
-
|
|
32701
|
+
const clientRegion = await this.cloudFrontClient.config.region();
|
|
32702
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
32385
32703
|
this.logger.debug(`OAI ${physicalId} does not exist, skipping deletion`);
|
|
32386
32704
|
return;
|
|
32387
32705
|
}
|
|
@@ -32705,7 +33023,8 @@ var CloudFrontOACProvider = class {
|
|
|
32705
33023
|
* region matches the region the resource was recorded in.
|
|
32706
33024
|
*/
|
|
32707
33025
|
async assertDeleteRegion(resourceType, logicalId, physicalId, context) {
|
|
32708
|
-
|
|
33026
|
+
const clientRegion = await this.cloudFrontClient.config.region();
|
|
33027
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
32709
33028
|
}
|
|
32710
33029
|
};
|
|
32711
33030
|
|
|
@@ -33012,7 +33331,8 @@ var CloudFrontDistributionProvider = class {
|
|
|
33012
33331
|
config = getConfigResponse.DistributionConfig;
|
|
33013
33332
|
} catch (error) {
|
|
33014
33333
|
if (error instanceof NoSuchDistribution) {
|
|
33015
|
-
|
|
33334
|
+
const clientRegion = await this.cloudFrontClient.config.region();
|
|
33335
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
33016
33336
|
this.logger.debug(`Distribution ${physicalId} does not exist, skipping deletion`);
|
|
33017
33337
|
return;
|
|
33018
33338
|
}
|
|
@@ -33039,7 +33359,8 @@ var CloudFrontDistributionProvider = class {
|
|
|
33039
33359
|
this.logger.debug(`Successfully deleted CloudFront Distribution ${logicalId}`);
|
|
33040
33360
|
} catch (error) {
|
|
33041
33361
|
if (error instanceof NoSuchDistribution) {
|
|
33042
|
-
|
|
33362
|
+
const clientRegion = await this.cloudFrontClient.config.region();
|
|
33363
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
33043
33364
|
this.logger.debug(`Distribution ${physicalId} does not exist, skipping deletion`);
|
|
33044
33365
|
return;
|
|
33045
33366
|
}
|
|
@@ -33178,7 +33499,7 @@ var CloudFrontDistributionProvider = class {
|
|
|
33178
33499
|
* warning applies), `false` on budget exhaustion.
|
|
33179
33500
|
*/
|
|
33180
33501
|
async waitForDistributionStable(distributionId, expectedEnabled) {
|
|
33181
|
-
const budgetMs = Math.max(
|
|
33502
|
+
const budgetMs = Math.max(12e5, resolvedResourceTimeoutMs("AWS::CloudFront::Distribution") ?? 0);
|
|
33182
33503
|
const deadline = Date.now() + budgetMs;
|
|
33183
33504
|
let delay = 5e3;
|
|
33184
33505
|
const maxDelay = 1e4;
|
|
@@ -33943,7 +34264,8 @@ var AgentCoreRuntimeProvider = class {
|
|
|
33943
34264
|
this.logger.debug(`Successfully deleted BedrockAgentCore Runtime ${logicalId}`);
|
|
33944
34265
|
} catch (error) {
|
|
33945
34266
|
if (error instanceof ResourceNotFoundException$6) {
|
|
33946
|
-
|
|
34267
|
+
const clientRegion = await this.client.config.region();
|
|
34268
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
33947
34269
|
this.logger.debug(`Runtime ${physicalId} does not exist, skipping deletion`);
|
|
33948
34270
|
return;
|
|
33949
34271
|
}
|
|
@@ -34513,7 +34835,8 @@ var AgentCoreEvaluatorProvider = class {
|
|
|
34513
34835
|
this.logger.debug(`Successfully deleted BedrockAgentCore Evaluator ${logicalId}`);
|
|
34514
34836
|
} catch (error) {
|
|
34515
34837
|
if (error instanceof ResourceNotFoundException$6) {
|
|
34516
|
-
|
|
34838
|
+
const clientRegion = await this.client.config.region();
|
|
34839
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
34517
34840
|
this.logger.debug(`Evaluator ${physicalId} does not exist, skipping deletion`);
|
|
34518
34841
|
return;
|
|
34519
34842
|
}
|
|
@@ -34728,7 +35051,8 @@ var StepFunctionsProvider = class {
|
|
|
34728
35051
|
this.logger.debug(`Successfully deleted Step Functions state machine ${logicalId}`);
|
|
34729
35052
|
} catch (error) {
|
|
34730
35053
|
if (error instanceof StateMachineDoesNotExist) {
|
|
34731
|
-
|
|
35054
|
+
const clientRegion = await this.getClient().config.region();
|
|
35055
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
34732
35056
|
this.logger.debug(`Step Functions state machine ${physicalId} does not exist, skipping deletion`);
|
|
34733
35057
|
return;
|
|
34734
35058
|
}
|
|
@@ -34797,7 +35121,8 @@ var StepFunctionsProvider = class {
|
|
|
34797
35121
|
result["EncryptionConfiguration"] = ec;
|
|
34798
35122
|
}
|
|
34799
35123
|
try {
|
|
34800
|
-
|
|
35124
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$11({ resourceArn: physicalId }));
|
|
35125
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.tags);
|
|
34801
35126
|
} catch (err) {
|
|
34802
35127
|
if (!(err instanceof StateMachineDoesNotExist)) throw err;
|
|
34803
35128
|
}
|
|
@@ -35376,7 +35701,8 @@ var ECSProvider = class {
|
|
|
35376
35701
|
this.logger.debug(`Successfully deleted ECS cluster ${logicalId}`);
|
|
35377
35702
|
} catch (error) {
|
|
35378
35703
|
if (this.isClusterNotFoundException(error)) {
|
|
35379
|
-
|
|
35704
|
+
const clientRegion = await client.config.region();
|
|
35705
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
35380
35706
|
this.logger.debug(`ECS cluster ${physicalId} not found, skipping deletion`);
|
|
35381
35707
|
return;
|
|
35382
35708
|
}
|
|
@@ -35437,7 +35763,8 @@ var ECSProvider = class {
|
|
|
35437
35763
|
this.logger.debug(`Successfully deregistered ECS task definition ${logicalId}`);
|
|
35438
35764
|
} catch (error) {
|
|
35439
35765
|
if (this.isNotFoundException(error)) {
|
|
35440
|
-
|
|
35766
|
+
const clientRegion = await client.config.region();
|
|
35767
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
35441
35768
|
this.logger.debug(`ECS task definition ${physicalId} not found, skipping deletion`);
|
|
35442
35769
|
return;
|
|
35443
35770
|
}
|
|
@@ -35659,7 +35986,8 @@ var ECSProvider = class {
|
|
|
35659
35986
|
this.logger.debug(`Scaled down ECS service ${physicalId} to 0`);
|
|
35660
35987
|
} catch (error) {
|
|
35661
35988
|
if (this.isServiceNotFoundException(error)) {
|
|
35662
|
-
|
|
35989
|
+
const clientRegion = await client.config.region();
|
|
35990
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
35663
35991
|
this.logger.debug(`ECS service ${physicalId} not found during scale down, skipping deletion`);
|
|
35664
35992
|
return;
|
|
35665
35993
|
}
|
|
@@ -35673,7 +36001,8 @@ var ECSProvider = class {
|
|
|
35673
36001
|
this.logger.debug(`Successfully deleted ECS service ${logicalId}`);
|
|
35674
36002
|
} catch (error) {
|
|
35675
36003
|
if (this.isServiceNotFoundException(error)) {
|
|
35676
|
-
|
|
36004
|
+
const clientRegion = await client.config.region();
|
|
36005
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
35677
36006
|
this.logger.debug(`ECS service ${physicalId} not found, skipping deletion`);
|
|
35678
36007
|
return;
|
|
35679
36008
|
}
|
|
@@ -37267,15 +37596,18 @@ var ELBv2Provider = class {
|
|
|
37267
37596
|
for (const [k, v] of Object.entries(p)) if (!handledKeys.has(k)) out[k] = v;
|
|
37268
37597
|
return out;
|
|
37269
37598
|
};
|
|
37270
|
-
if (JSON.stringify(stripHandled(properties)) !== JSON.stringify(stripHandled(previousProperties)))
|
|
37271
|
-
|
|
37272
|
-
|
|
37273
|
-
|
|
37274
|
-
|
|
37275
|
-
|
|
37276
|
-
|
|
37277
|
-
|
|
37278
|
-
|
|
37599
|
+
if (JSON.stringify(stripHandled(properties)) !== JSON.stringify(stripHandled(previousProperties))) {
|
|
37600
|
+
const remedy = this.normalizeAttributes(previousProperties["LoadBalancerAttributes"]).some((a) => a.Key === "deletion_protection.enabled" && isTruthyCfnBoolean(a.Value)) ? protectedReplacementAdvice({
|
|
37601
|
+
evidence: "cdkd's recorded properties for this load balancer carry LoadBalancerAttributes deletion_protection.enabled=true",
|
|
37602
|
+
replaceFlags: "cdkd deploy --replace",
|
|
37603
|
+
disable: {
|
|
37604
|
+
before: "aws elbv2 modify-load-balancer-attributes --load-balancer-arn",
|
|
37605
|
+
identifier: physicalId,
|
|
37606
|
+
after: "--attributes Key=deletion_protection.enabled,Value=false"
|
|
37607
|
+
}
|
|
37608
|
+
}) : "For Name / Type / Scheme re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.";
|
|
37609
|
+
throw new ResourceUpdateNotSupportedError("AWS::ElasticLoadBalancingV2::LoadBalancer", logicalId, "ELBv2 LoadBalancer Name / Type / Scheme are immutable on AWS — none of the ELBv2 Modify* / Set* APIs accept these fields; they are fixed at creation. cdkd handles LoadBalancerAttributes / Subnets / SubnetMappings / SecurityGroups / IpAddressType / Tags in-place. " + remedy);
|
|
37610
|
+
}
|
|
37279
37611
|
const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["LoadBalancerAttributes"]), this.normalizeAttributes(previousProperties["LoadBalancerAttributes"]), this.attributeRemovalResolver(LOAD_BALANCER_ATTRIBUTE_DEFAULTS));
|
|
37280
37612
|
if (submittedAttrs.length > 0) {
|
|
37281
37613
|
await this.getClient().send(new ModifyLoadBalancerAttributesCommand({
|
|
@@ -37409,7 +37741,8 @@ var ELBv2Provider = class {
|
|
|
37409
37741
|
this.logger.debug(`Successfully deleted LoadBalancer ${logicalId}`);
|
|
37410
37742
|
} catch (error) {
|
|
37411
37743
|
if (this.isNotFoundError(error)) {
|
|
37412
|
-
|
|
37744
|
+
const clientRegion = await this.getClient().config.region();
|
|
37745
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
37413
37746
|
this.logger.debug(`LoadBalancer ${physicalId} does not exist, skipping deletion`);
|
|
37414
37747
|
return;
|
|
37415
37748
|
}
|
|
@@ -37574,7 +37907,8 @@ var ELBv2Provider = class {
|
|
|
37574
37907
|
this.logger.debug(`Successfully deleted TargetGroup ${logicalId}`);
|
|
37575
37908
|
} catch (error) {
|
|
37576
37909
|
if (this.isNotFoundError(error)) {
|
|
37577
|
-
|
|
37910
|
+
const clientRegion = await this.getClient().config.region();
|
|
37911
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
37578
37912
|
this.logger.debug(`TargetGroup ${physicalId} does not exist, skipping deletion`);
|
|
37579
37913
|
return;
|
|
37580
37914
|
}
|
|
@@ -37694,7 +38028,8 @@ var ELBv2Provider = class {
|
|
|
37694
38028
|
this.logger.debug(`Successfully deleted Listener ${logicalId}`);
|
|
37695
38029
|
} catch (error) {
|
|
37696
38030
|
if (this.isNotFoundError(error)) {
|
|
37697
|
-
|
|
38031
|
+
const clientRegion = await this.getClient().config.region();
|
|
38032
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
37698
38033
|
this.logger.debug(`Listener ${physicalId} does not exist, skipping deletion`);
|
|
37699
38034
|
return;
|
|
37700
38035
|
}
|
|
@@ -38424,7 +38759,8 @@ var RDSProvider = class {
|
|
|
38424
38759
|
this.logger.debug(`Successfully deleted DBSubnetGroup ${logicalId}`);
|
|
38425
38760
|
} catch (error) {
|
|
38426
38761
|
if (this.isNotFoundError(error, "DBSubnetGroupNotFoundFault")) {
|
|
38427
|
-
|
|
38762
|
+
const clientRegion = await this.getClient().config.region();
|
|
38763
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
38428
38764
|
this.logger.debug(`DBSubnetGroup ${physicalId} does not exist, skipping deletion`);
|
|
38429
38765
|
return;
|
|
38430
38766
|
}
|
|
@@ -38582,7 +38918,8 @@ var RDSProvider = class {
|
|
|
38582
38918
|
await this.waitForClusterDeleted(physicalId);
|
|
38583
38919
|
} catch (error) {
|
|
38584
38920
|
if (this.isNotFoundError(error, "DBClusterNotFoundFault")) {
|
|
38585
|
-
|
|
38921
|
+
const clientRegion = await this.getClient().config.region();
|
|
38922
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
38586
38923
|
this.logger.debug(`DBCluster ${physicalId} does not exist, skipping deletion`);
|
|
38587
38924
|
return;
|
|
38588
38925
|
}
|
|
@@ -38709,7 +39046,8 @@ var RDSProvider = class {
|
|
|
38709
39046
|
await this.waitForInstanceDeleted(physicalId);
|
|
38710
39047
|
} catch (error) {
|
|
38711
39048
|
if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) {
|
|
38712
|
-
|
|
39049
|
+
const clientRegion = await this.getClient().config.region();
|
|
39050
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
38713
39051
|
this.logger.debug(`DBInstance ${physicalId} does not exist, skipping deletion`);
|
|
38714
39052
|
return;
|
|
38715
39053
|
}
|
|
@@ -38980,7 +39318,8 @@ var RDSProvider = class {
|
|
|
38980
39318
|
*/
|
|
38981
39319
|
async attachTags(result, arn) {
|
|
38982
39320
|
try {
|
|
38983
|
-
|
|
39321
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$10({ ResourceName: arn }));
|
|
39322
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
|
|
38984
39323
|
} catch (err) {
|
|
38985
39324
|
this.logger.debug(`RDS ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
38986
39325
|
}
|
|
@@ -39032,7 +39371,7 @@ var RDSProvider = class {
|
|
|
39032
39371
|
//#endregion
|
|
39033
39372
|
//#region src/provisioning/providers/rds-dbproxy-provider.ts
|
|
39034
39373
|
const POLL_INTERVAL_MS$1 = 5e3;
|
|
39035
|
-
const POLL_TIMEOUT_MS$1 =
|
|
39374
|
+
const POLL_TIMEOUT_MS$1 = 18e5;
|
|
39036
39375
|
/**
|
|
39037
39376
|
* AWS RDS DBProxy Provider
|
|
39038
39377
|
*
|
|
@@ -39195,7 +39534,8 @@ var RDSDBProxyProvider = class {
|
|
|
39195
39534
|
await client.send(new DeleteDBProxyCommand({ DBProxyName: physicalId }));
|
|
39196
39535
|
} catch (error) {
|
|
39197
39536
|
if (error instanceof DBProxyNotFoundFault) {
|
|
39198
|
-
|
|
39537
|
+
const clientRegion = await client.config.region();
|
|
39538
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
39199
39539
|
this.logger.debug(`DBProxy ${physicalId} already gone, treating as success`);
|
|
39200
39540
|
return;
|
|
39201
39541
|
}
|
|
@@ -39278,7 +39618,8 @@ var RDSDBProxyProvider = class {
|
|
|
39278
39618
|
}))
|
|
39279
39619
|
};
|
|
39280
39620
|
if (p.DBProxyArn) try {
|
|
39281
|
-
|
|
39621
|
+
const tagResp = await client.send(new ListTagsForResourceCommand$10({ ResourceName: p.DBProxyArn }));
|
|
39622
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagResp.TagList ?? []);
|
|
39282
39623
|
} catch (error) {
|
|
39283
39624
|
this.logger.debug(`ListTagsForResource failed for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
39284
39625
|
result["Tags"] = [];
|
|
@@ -39370,7 +39711,7 @@ var RDSDBProxyProvider = class {
|
|
|
39370
39711
|
//#endregion
|
|
39371
39712
|
//#region src/provisioning/providers/rds-dbproxy-endpoint-provider.ts
|
|
39372
39713
|
const POLL_INTERVAL_MS = 5e3;
|
|
39373
|
-
const POLL_TIMEOUT_MS =
|
|
39714
|
+
const POLL_TIMEOUT_MS = 18e5;
|
|
39374
39715
|
/**
|
|
39375
39716
|
* AWS RDS DBProxyEndpoint Provider
|
|
39376
39717
|
*
|
|
@@ -39518,7 +39859,8 @@ var RDSDBProxyEndpointProvider = class {
|
|
|
39518
39859
|
await client.send(new DeleteDBProxyEndpointCommand({ DBProxyEndpointName: physicalId }));
|
|
39519
39860
|
} catch (error) {
|
|
39520
39861
|
if (error instanceof DBProxyEndpointNotFoundFault || error instanceof DBProxyNotFoundFault) {
|
|
39521
|
-
|
|
39862
|
+
const clientRegion = await client.config.region();
|
|
39863
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
39522
39864
|
this.logger.debug(`DBProxyEndpoint ${physicalId} or parent already gone, treating as success`);
|
|
39523
39865
|
return;
|
|
39524
39866
|
}
|
|
@@ -39587,7 +39929,8 @@ var RDSDBProxyEndpointProvider = class {
|
|
|
39587
39929
|
TargetRole: e.TargetRole ?? "READ_WRITE"
|
|
39588
39930
|
};
|
|
39589
39931
|
if (e.DBProxyEndpointArn) try {
|
|
39590
|
-
|
|
39932
|
+
const tagResp = await client.send(new ListTagsForResourceCommand$10({ ResourceName: e.DBProxyEndpointArn }));
|
|
39933
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagResp.TagList ?? []);
|
|
39591
39934
|
} catch (error) {
|
|
39592
39935
|
this.logger.debug(`ListTagsForResource failed for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
39593
39936
|
result["Tags"] = [];
|
|
@@ -39880,7 +40223,8 @@ var RDSDBProxyTargetGroupProvider = class {
|
|
|
39880
40223
|
}));
|
|
39881
40224
|
} catch (error) {
|
|
39882
40225
|
if (error instanceof DBProxyNotFoundFault || error instanceof DBProxyTargetGroupNotFoundFault || error instanceof DBProxyTargetNotFoundFault) {
|
|
39883
|
-
|
|
40226
|
+
const clientRegion = await this.getClient().config.region();
|
|
40227
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
39884
40228
|
this.logger.debug(`${dbProxyName}/${targetGroupName} or its target is already gone, treating as success`);
|
|
39885
40229
|
return;
|
|
39886
40230
|
}
|
|
@@ -40129,7 +40473,8 @@ var DocDBProvider = class {
|
|
|
40129
40473
|
this.logger.debug(`Successfully deleted DocDB DBSubnetGroup ${logicalId}`);
|
|
40130
40474
|
} catch (error) {
|
|
40131
40475
|
if (this.isNotFoundError(error, "DBSubnetGroupNotFoundFault")) {
|
|
40132
|
-
|
|
40476
|
+
const clientRegion = await this.getClient().config.region();
|
|
40477
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
40133
40478
|
this.logger.debug(`DocDB DBSubnetGroup ${physicalId} does not exist, skipping deletion`);
|
|
40134
40479
|
return;
|
|
40135
40480
|
}
|
|
@@ -40245,7 +40590,8 @@ var DocDBProvider = class {
|
|
|
40245
40590
|
await this.waitForClusterDeleted(physicalId);
|
|
40246
40591
|
} catch (error) {
|
|
40247
40592
|
if (this.isNotFoundError(error, "DBClusterNotFoundFault")) {
|
|
40248
|
-
|
|
40593
|
+
const clientRegion = await this.getClient().config.region();
|
|
40594
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
40249
40595
|
this.logger.debug(`DocDB DBCluster ${physicalId} does not exist, skipping deletion`);
|
|
40250
40596
|
return;
|
|
40251
40597
|
}
|
|
@@ -40323,7 +40669,8 @@ var DocDBProvider = class {
|
|
|
40323
40669
|
await this.waitForInstanceDeleted(physicalId);
|
|
40324
40670
|
} catch (error) {
|
|
40325
40671
|
if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) {
|
|
40326
|
-
|
|
40672
|
+
const clientRegion = await this.getClient().config.region();
|
|
40673
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
40327
40674
|
this.logger.debug(`DocDB DBInstance ${physicalId} does not exist, skipping deletion`);
|
|
40328
40675
|
return;
|
|
40329
40676
|
}
|
|
@@ -40564,7 +40911,8 @@ var DocDBProvider = class {
|
|
|
40564
40911
|
*/
|
|
40565
40912
|
async attachTags(result, arn) {
|
|
40566
40913
|
try {
|
|
40567
|
-
|
|
40914
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$12({ ResourceName: arn }));
|
|
40915
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
|
|
40568
40916
|
} catch (err) {
|
|
40569
40917
|
this.logger.debug(`DocDB ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
40570
40918
|
}
|
|
@@ -40765,7 +41113,8 @@ var NeptuneProvider = class {
|
|
|
40765
41113
|
this.logger.debug(`Successfully deleted Neptune DBSubnetGroup ${logicalId}`);
|
|
40766
41114
|
} catch (error) {
|
|
40767
41115
|
if (this.isNotFoundError(error, "DBSubnetGroupNotFoundFault")) {
|
|
40768
|
-
|
|
41116
|
+
const clientRegion = await this.getClient().config.region();
|
|
41117
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
40769
41118
|
this.logger.debug(`Neptune DBSubnetGroup ${physicalId} does not exist, skipping deletion`);
|
|
40770
41119
|
return;
|
|
40771
41120
|
}
|
|
@@ -40878,7 +41227,8 @@ var NeptuneProvider = class {
|
|
|
40878
41227
|
await this.waitForClusterDeleted(physicalId);
|
|
40879
41228
|
} catch (error) {
|
|
40880
41229
|
if (this.isNotFoundError(error, "DBClusterNotFoundFault")) {
|
|
40881
|
-
|
|
41230
|
+
const clientRegion = await this.getClient().config.region();
|
|
41231
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
40882
41232
|
this.logger.debug(`Neptune DBCluster ${physicalId} does not exist, skipping deletion`);
|
|
40883
41233
|
return;
|
|
40884
41234
|
}
|
|
@@ -40972,7 +41322,8 @@ var NeptuneProvider = class {
|
|
|
40972
41322
|
await this.waitForInstanceDeleted(physicalId);
|
|
40973
41323
|
} catch (error) {
|
|
40974
41324
|
if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) {
|
|
40975
|
-
|
|
41325
|
+
const clientRegion = await this.getClient().config.region();
|
|
41326
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
40976
41327
|
this.logger.debug(`Neptune DBInstance ${physicalId} does not exist, skipping deletion`);
|
|
40977
41328
|
return;
|
|
40978
41329
|
}
|
|
@@ -41203,7 +41554,8 @@ var NeptuneProvider = class {
|
|
|
41203
41554
|
*/
|
|
41204
41555
|
async attachTags(result, arn) {
|
|
41205
41556
|
try {
|
|
41206
|
-
|
|
41557
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$13({ ResourceName: arn }));
|
|
41558
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
|
|
41207
41559
|
} catch (err) {
|
|
41208
41560
|
this.logger.debug(`Neptune ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
41209
41561
|
}
|
|
@@ -41666,7 +42018,8 @@ var Route53Provider = class {
|
|
|
41666
42018
|
this.logger.debug(`Successfully deleted hosted zone ${logicalId}`);
|
|
41667
42019
|
} catch (error) {
|
|
41668
42020
|
if (error instanceof Error && error.name === "NoSuchHostedZone") {
|
|
41669
|
-
|
|
42021
|
+
const clientRegion = await this.getClient().config.region();
|
|
42022
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
41670
42023
|
this.logger.debug(`Hosted zone ${physicalId} does not exist, skipping deletion`);
|
|
41671
42024
|
return;
|
|
41672
42025
|
}
|
|
@@ -41863,7 +42216,8 @@ var Route53Provider = class {
|
|
|
41863
42216
|
hostedZoneId = await this.resolveHostedZoneId(properties, logicalId, resourceType, physicalId, { requireUnambiguousZoneName: true });
|
|
41864
42217
|
} catch (error) {
|
|
41865
42218
|
if (error instanceof HostedZoneNameNotFoundError) {
|
|
41866
|
-
|
|
42219
|
+
const clientRegion = await this.getClient().config.region();
|
|
42220
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
41867
42221
|
this.logger.warn(`Hosted zone "${String(properties?.["HostedZoneName"])}" for record set ${logicalId} (${physicalId}) no longer exists; treating the record as already deleted and dropping it from state.`);
|
|
41868
42222
|
return;
|
|
41869
42223
|
}
|
|
@@ -41889,7 +42243,8 @@ var Route53Provider = class {
|
|
|
41889
42243
|
this.logger.debug(`Successfully deleted record set ${logicalId}`);
|
|
41890
42244
|
} catch (error) {
|
|
41891
42245
|
if (error instanceof Error && (error.name === "InvalidChangeBatch" || error.message.includes("it was not found"))) {
|
|
41892
|
-
|
|
42246
|
+
const clientRegion = await this.getClient().config.region();
|
|
42247
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
41893
42248
|
this.logger.debug(`Record set ${physicalId} does not exist, skipping deletion`);
|
|
41894
42249
|
return;
|
|
41895
42250
|
}
|
|
@@ -42362,10 +42717,11 @@ var Route53Provider = class {
|
|
|
42362
42717
|
});
|
|
42363
42718
|
const idTail = physicalId.replace(/^\/hostedzone\//, "");
|
|
42364
42719
|
try {
|
|
42365
|
-
|
|
42720
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$8({
|
|
42366
42721
|
ResourceType: "hostedzone",
|
|
42367
42722
|
ResourceId: idTail
|
|
42368
|
-
}))
|
|
42723
|
+
}));
|
|
42724
|
+
result["HostedZoneTags"] = normalizeAwsTagsToCfn(tagsResp.ResourceTagSet?.Tags);
|
|
42369
42725
|
} catch (err) {
|
|
42370
42726
|
this.logger.debug(`Route53 ListTagsForResource(${idTail}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
42371
42727
|
}
|
|
@@ -44028,7 +44384,8 @@ var CognitoUserPoolProvider = class {
|
|
|
44028
44384
|
needsFlip = (await this.getClient().send(new DescribeUserPoolCommand({ UserPoolId: physicalId }))).UserPool?.DeletionProtection === "ACTIVE";
|
|
44029
44385
|
} catch (descError) {
|
|
44030
44386
|
if (descError instanceof ResourceNotFoundException$9) {
|
|
44031
|
-
|
|
44387
|
+
const clientRegion = await this.getClient().config.region();
|
|
44388
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
44032
44389
|
this.logger.debug(`Cognito User Pool ${physicalId} does not exist, skipping deletion`);
|
|
44033
44390
|
return;
|
|
44034
44391
|
}
|
|
@@ -44051,7 +44408,8 @@ var CognitoUserPoolProvider = class {
|
|
|
44051
44408
|
this.logger.debug(`Successfully deleted Cognito User Pool ${logicalId}`);
|
|
44052
44409
|
} catch (error) {
|
|
44053
44410
|
if (error instanceof ResourceNotFoundException$9) {
|
|
44054
|
-
|
|
44411
|
+
const clientRegion = await this.getClient().config.region();
|
|
44412
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
44055
44413
|
this.logger.debug(`Cognito User Pool ${physicalId} does not exist, skipping deletion`);
|
|
44056
44414
|
return;
|
|
44057
44415
|
}
|
|
@@ -44302,7 +44660,8 @@ var ElastiCacheProvider = class {
|
|
|
44302
44660
|
this.logger.debug(`Successfully deleted CacheSubnetGroup ${logicalId}`);
|
|
44303
44661
|
} catch (error) {
|
|
44304
44662
|
if (this.isNotFoundError(error, "CacheSubnetGroupNotFoundFault")) {
|
|
44305
|
-
|
|
44663
|
+
const clientRegion = await this.getClient().config.region();
|
|
44664
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
44306
44665
|
this.logger.debug(`CacheSubnetGroup ${physicalId} does not exist, skipping deletion`);
|
|
44307
44666
|
return;
|
|
44308
44667
|
}
|
|
@@ -44428,7 +44787,8 @@ var ElastiCacheProvider = class {
|
|
|
44428
44787
|
await this.waitForClusterDeleted(physicalId);
|
|
44429
44788
|
} catch (error) {
|
|
44430
44789
|
if (this.isNotFoundError(error, "CacheClusterNotFoundFault")) {
|
|
44431
|
-
|
|
44790
|
+
const clientRegion = await this.getClient().config.region();
|
|
44791
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
44432
44792
|
this.logger.debug(`CacheCluster ${physicalId} does not exist, skipping deletion`);
|
|
44433
44793
|
return;
|
|
44434
44794
|
}
|
|
@@ -44635,7 +44995,8 @@ var ElastiCacheProvider = class {
|
|
|
44635
44995
|
/** Best-effort tag fetch — failures omit the key without breaking the read. */
|
|
44636
44996
|
async attachTags(result, arn) {
|
|
44637
44997
|
try {
|
|
44638
|
-
|
|
44998
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$7({ ResourceName: arn }));
|
|
44999
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
|
|
44639
45000
|
} catch (err) {
|
|
44640
45001
|
this.logger.debug(`ElastiCache ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
44641
45002
|
}
|
|
@@ -44969,7 +45330,8 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
44969
45330
|
this.logger.debug(`Successfully deleted Cloud Map namespace ${logicalId}`);
|
|
44970
45331
|
} catch (error) {
|
|
44971
45332
|
if (error instanceof NamespaceNotFound) {
|
|
44972
|
-
|
|
45333
|
+
const clientRegion = await this.getClient().config.region();
|
|
45334
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
44973
45335
|
this.logger.debug(`Namespace ${physicalId} does not exist, skipping deletion`);
|
|
44974
45336
|
return;
|
|
44975
45337
|
}
|
|
@@ -45299,7 +45661,8 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
45299
45661
|
this.logger.debug(`Successfully deleted service discovery service ${logicalId}`);
|
|
45300
45662
|
} catch (error) {
|
|
45301
45663
|
if (error instanceof ServiceNotFound) {
|
|
45302
|
-
|
|
45664
|
+
const clientRegion = await this.getClient().config.region();
|
|
45665
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
45303
45666
|
this.logger.debug(`Service ${physicalId} does not exist, skipping deletion`);
|
|
45304
45667
|
return;
|
|
45305
45668
|
}
|
|
@@ -45430,7 +45793,10 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
45430
45793
|
if (targets) return targets["NAMESPACE"] || targets["SERVICE"] || operationId;
|
|
45431
45794
|
return operationId;
|
|
45432
45795
|
}
|
|
45433
|
-
if (status === "FAIL")
|
|
45796
|
+
if (status === "FAIL") {
|
|
45797
|
+
const errorMessage = maskerOrIdentity(maskSecrets)(result.Operation?.ErrorMessage || "Unknown error");
|
|
45798
|
+
throw new ProvisioningError(`Operation failed for ${logicalId}: ${errorMessage}`, resourceType, logicalId);
|
|
45799
|
+
}
|
|
45434
45800
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
45435
45801
|
delay = Math.min(delay * 2, 1e4);
|
|
45436
45802
|
}
|
|
@@ -45596,7 +45962,8 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
45596
45962
|
/** Best-effort tag fetch via `ListTagsForResource(ResourceARN)`. */
|
|
45597
45963
|
async attachTags(result, arn) {
|
|
45598
45964
|
try {
|
|
45599
|
-
|
|
45965
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$14({ ResourceARN: arn }));
|
|
45966
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
45600
45967
|
} catch (err) {
|
|
45601
45968
|
this.logger.debug(`ServiceDiscovery ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
45602
45969
|
}
|
|
@@ -45844,7 +46211,8 @@ var AppSyncProvider = class {
|
|
|
45844
46211
|
* at most one `GetCallerIdentity` per deploy.
|
|
45845
46212
|
*/
|
|
45846
46213
|
async buildAppSyncArn(suffix, regionOverride) {
|
|
45847
|
-
const
|
|
46214
|
+
const region = regionOverride ?? this.providerRegion ?? await this.getClient().config.region();
|
|
46215
|
+
const accountInfo = await getAccountInfo(region);
|
|
45848
46216
|
if (accountInfo.fabricated) throw new Error(`cannot determine the AWS account (STS is unreachable, and the resolved account id is a placeholder), so an ARN for ${suffix} would be fabricated`);
|
|
45849
46217
|
const { partition } = derivePartitionAndUrlSuffix(accountInfo.region);
|
|
45850
46218
|
return `arn:${partition}:appsync:${canonicalizeRegion(accountInfo.region)}:${accountInfo.accountId}:${suffix}`;
|
|
@@ -46880,7 +47248,8 @@ var AppSyncProvider = class {
|
|
|
46880
47248
|
this.logger.debug(`Successfully deleted GraphQL API ${logicalId}`);
|
|
46881
47249
|
} catch (error) {
|
|
46882
47250
|
if (this.isNotFoundError(error)) {
|
|
46883
|
-
|
|
47251
|
+
const clientRegion = await this.getClient().config.region();
|
|
47252
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
46884
47253
|
this.logger.debug(`GraphQL API ${physicalId} does not exist, skipping deletion`);
|
|
46885
47254
|
return;
|
|
46886
47255
|
}
|
|
@@ -46972,7 +47341,8 @@ var AppSyncProvider = class {
|
|
|
46972
47341
|
this.logger.debug(`Successfully deleted DataSource ${logicalId}`);
|
|
46973
47342
|
} catch (error) {
|
|
46974
47343
|
if (this.isNotFoundError(error)) {
|
|
46975
|
-
|
|
47344
|
+
const clientRegion = await this.getClient().config.region();
|
|
47345
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
46976
47346
|
this.logger.debug(`DataSource ${physicalId} does not exist, skipping deletion`);
|
|
46977
47347
|
return;
|
|
46978
47348
|
}
|
|
@@ -47040,7 +47410,8 @@ var AppSyncProvider = class {
|
|
|
47040
47410
|
this.logger.debug(`Successfully deleted Resolver ${logicalId}`);
|
|
47041
47411
|
} catch (error) {
|
|
47042
47412
|
if (this.isNotFoundError(error)) {
|
|
47043
|
-
|
|
47413
|
+
const clientRegion = await this.getClient().config.region();
|
|
47414
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
47044
47415
|
this.logger.debug(`Resolver ${physicalId} does not exist, skipping deletion`);
|
|
47045
47416
|
return;
|
|
47046
47417
|
}
|
|
@@ -47097,7 +47468,8 @@ var AppSyncProvider = class {
|
|
|
47097
47468
|
this.logger.debug(`Successfully deleted ApiKey ${logicalId}`);
|
|
47098
47469
|
} catch (error) {
|
|
47099
47470
|
if (this.isNotFoundError(error)) {
|
|
47100
|
-
|
|
47471
|
+
const clientRegion = await this.getClient().config.region();
|
|
47472
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
47101
47473
|
this.logger.debug(`ApiKey ${physicalId} does not exist, skipping deletion`);
|
|
47102
47474
|
return;
|
|
47103
47475
|
}
|
|
@@ -47756,7 +48128,8 @@ var GlueProvider = class {
|
|
|
47756
48128
|
this.logger.debug(`Successfully deleted Glue Database ${logicalId}`);
|
|
47757
48129
|
} catch (error) {
|
|
47758
48130
|
if (error instanceof EntityNotFoundException) {
|
|
47759
|
-
|
|
48131
|
+
const clientRegion = await this.getClient().config.region();
|
|
48132
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
47760
48133
|
logCatalogScopedDeleteSkip(this.logger, target, "Database", logicalId, physicalId);
|
|
47761
48134
|
return;
|
|
47762
48135
|
}
|
|
@@ -47859,7 +48232,8 @@ var GlueProvider = class {
|
|
|
47859
48232
|
this.logger.debug(`Successfully deleted Glue Table ${logicalId}`);
|
|
47860
48233
|
} catch (error) {
|
|
47861
48234
|
if (error instanceof EntityNotFoundException) {
|
|
47862
|
-
|
|
48235
|
+
const clientRegion = await this.getClient().config.region();
|
|
48236
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
47863
48237
|
logCatalogScopedDeleteSkip(this.logger, target, "Table", logicalId, physicalId);
|
|
47864
48238
|
return;
|
|
47865
48239
|
}
|
|
@@ -48470,10 +48844,7 @@ var GlueProvider = class {
|
|
|
48470
48844
|
case "pipe-in-name":
|
|
48471
48845
|
this.logger.warn(`AWS::Glue::Table ${input.logicalId}: cannot be imported because a name contains '|', which cdkd currently uses as the separator in this type's physical id (<databaseName>|<tableName>). Adopting it would record an id cdkd cannot decode back to the same table. CloudFormation manages such a table fine; this is a cdkd limitation tracked in https://github.com/go-to-k/cdkd/issues/1672. Until it is lifted, rename the table or database to omit '|', or manage this resource outside cdkd.`);
|
|
48472
48846
|
break;
|
|
48473
|
-
case "unpairable":
|
|
48474
|
-
this.logger.warn(`AWS::Glue::Table ${input.logicalId}: cannot resolve a database for physical id '${input.knownPhysicalId}'. ` + (input.knownPhysicalId === "" ? `The supplied physical id is empty. ` : "The template's DatabaseName is absent or is an unresolved intrinsic, so the bare table name cannot be paired. ") + `Pass the composite form instead: --resource '${input.logicalId}=<databaseName>|<tableName>' (quote it — '|' is a shell pipe).`);
|
|
48475
|
-
break;
|
|
48476
|
-
case "unidentified": break;
|
|
48847
|
+
case "unpairable": this.logger.warn(`AWS::Glue::Table ${input.logicalId}: cannot resolve a database for physical id '${input.knownPhysicalId}'. ` + (input.knownPhysicalId === "" ? `The supplied physical id is empty. ` : "The template's DatabaseName is absent or is an unresolved intrinsic, so the bare table name cannot be paired. ") + `Pass the composite form instead: --resource '${input.logicalId}=<databaseName>|<tableName>' (quote it — '|' is a shell pipe).`);
|
|
48477
48848
|
}
|
|
48478
48849
|
return null;
|
|
48479
48850
|
}
|
|
@@ -48578,7 +48949,8 @@ var GlueWorkflowProvider = class {
|
|
|
48578
48949
|
this.logger.debug(`Successfully deleted Glue Workflow ${logicalId}`);
|
|
48579
48950
|
} catch (error) {
|
|
48580
48951
|
if (error instanceof EntityNotFoundException) {
|
|
48581
|
-
|
|
48952
|
+
const clientRegion = await this.getClient().config.region();
|
|
48953
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
48582
48954
|
this.logger.debug(`Glue Workflow ${physicalId} does not exist, skipping deletion`);
|
|
48583
48955
|
return;
|
|
48584
48956
|
}
|
|
@@ -48622,7 +48994,8 @@ var GlueWorkflowProvider = class {
|
|
|
48622
48994
|
const arn = await this.buildWorkflowArn(physicalId);
|
|
48623
48995
|
let tags = [];
|
|
48624
48996
|
try {
|
|
48625
|
-
|
|
48997
|
+
const tagResp = await this.getClient().send(new GetTagsCommand({ ResourceArn: arn }));
|
|
48998
|
+
tags = normalizeAwsTagsToCfn(tagResp.Tags);
|
|
48626
48999
|
} catch (err) {
|
|
48627
49000
|
this.logger.debug(`GetTags failed for Glue Workflow ${physicalId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
48628
49001
|
}
|
|
@@ -48716,7 +49089,8 @@ var GlueSecurityConfigurationProvider = class {
|
|
|
48716
49089
|
this.logger.debug(`Successfully deleted Glue SecurityConfiguration ${logicalId}`);
|
|
48717
49090
|
} catch (error) {
|
|
48718
49091
|
if (error instanceof EntityNotFoundException) {
|
|
48719
|
-
|
|
49092
|
+
const clientRegion = await this.getClient().config.region();
|
|
49093
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
48720
49094
|
this.logger.debug(`Glue SecurityConfiguration ${physicalId} does not exist, skipping deletion`);
|
|
48721
49095
|
return;
|
|
48722
49096
|
}
|
|
@@ -48987,7 +49361,8 @@ async function resolveAccountId(stsClient) {
|
|
|
48987
49361
|
async function fetchGlueTags(client, stsClient, resource, name, accountId, logger) {
|
|
48988
49362
|
try {
|
|
48989
49363
|
const arn = await buildGlueResourceArn(client, stsClient, resource, name, accountId);
|
|
48990
|
-
|
|
49364
|
+
const resp = await client.send(new GetTagsCommand({ ResourceArn: arn }));
|
|
49365
|
+
return normalizeAwsTagsToCfn(resp.Tags);
|
|
48991
49366
|
} catch (err) {
|
|
48992
49367
|
logger.debug(`GetTags failed for ${resource}/${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
48993
49368
|
return [];
|
|
@@ -49111,7 +49486,8 @@ var GlueJobProvider = class {
|
|
|
49111
49486
|
await this.getClient().send(new DeleteJobCommand({ JobName: physicalId }));
|
|
49112
49487
|
} catch (error) {
|
|
49113
49488
|
if (error instanceof EntityNotFoundException) {
|
|
49114
|
-
|
|
49489
|
+
const clientRegion = await this.getClient().config.region();
|
|
49490
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
49115
49491
|
this.logger.debug(`Glue Job ${physicalId} does not exist, skipping deletion`);
|
|
49116
49492
|
return;
|
|
49117
49493
|
}
|
|
@@ -49495,7 +49871,8 @@ var GlueCrawlerProvider = class {
|
|
|
49495
49871
|
await this.getClient().send(new DeleteCrawlerCommand({ Name: physicalId }));
|
|
49496
49872
|
} catch (error) {
|
|
49497
49873
|
if (error instanceof EntityNotFoundException) {
|
|
49498
|
-
|
|
49874
|
+
const clientRegion = await this.getClient().config.region();
|
|
49875
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
49499
49876
|
this.logger.debug(`Glue Crawler ${physicalId} does not exist, skipping deletion`);
|
|
49500
49877
|
return;
|
|
49501
49878
|
}
|
|
@@ -49792,7 +50169,8 @@ var GlueConnectionProvider = class {
|
|
|
49792
50169
|
}));
|
|
49793
50170
|
} catch (error) {
|
|
49794
50171
|
if (error instanceof EntityNotFoundException) {
|
|
49795
|
-
|
|
50172
|
+
const clientRegion = await this.getClient().config.region();
|
|
50173
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
49796
50174
|
logCatalogScopedDeleteSkip(this.logger, target, "Connection", logicalId, physicalId);
|
|
49797
50175
|
return;
|
|
49798
50176
|
}
|
|
@@ -50022,7 +50400,8 @@ var GlueTriggerProvider = class {
|
|
|
50022
50400
|
await this.getClient().send(new DeleteTriggerCommand({ Name: physicalId }));
|
|
50023
50401
|
} catch (error) {
|
|
50024
50402
|
if (error instanceof EntityNotFoundException) {
|
|
50025
|
-
|
|
50403
|
+
const clientRegion = await this.getClient().config.region();
|
|
50404
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
50026
50405
|
this.logger.debug(`Glue Trigger ${physicalId} does not exist, skipping deletion`);
|
|
50027
50406
|
return;
|
|
50028
50407
|
}
|
|
@@ -50214,24 +50593,28 @@ var KMSProvider = class {
|
|
|
50214
50593
|
}));
|
|
50215
50594
|
}
|
|
50216
50595
|
const newEnableKeyRotation = properties["EnableKeyRotation"];
|
|
50217
|
-
if (newEnableKeyRotation !== previousProperties["EnableKeyRotation"])
|
|
50218
|
-
|
|
50219
|
-
|
|
50220
|
-
|
|
50221
|
-
|
|
50222
|
-
|
|
50223
|
-
|
|
50224
|
-
|
|
50225
|
-
|
|
50226
|
-
|
|
50596
|
+
if (newEnableKeyRotation !== previousProperties["EnableKeyRotation"]) {
|
|
50597
|
+
if (newEnableKeyRotation) {
|
|
50598
|
+
const rotationPeriodInDays = properties["RotationPeriodInDays"];
|
|
50599
|
+
this.logger.debug(`Enabling key rotation for KMS Key ${logicalId}`);
|
|
50600
|
+
await this.getClient().send(new EnableKeyRotationCommand({
|
|
50601
|
+
KeyId: physicalId,
|
|
50602
|
+
...rotationPeriodInDays !== void 0 && { RotationPeriodInDays: rotationPeriodInDays }
|
|
50603
|
+
}));
|
|
50604
|
+
} else {
|
|
50605
|
+
this.logger.debug(`Disabling key rotation for KMS Key ${logicalId}`);
|
|
50606
|
+
await this.getClient().send(new DisableKeyRotationCommand({ KeyId: physicalId }));
|
|
50607
|
+
}
|
|
50227
50608
|
}
|
|
50228
50609
|
const newEnabled = properties["Enabled"];
|
|
50229
|
-
if (newEnabled !== previousProperties["Enabled"])
|
|
50230
|
-
|
|
50231
|
-
|
|
50232
|
-
|
|
50233
|
-
|
|
50234
|
-
|
|
50610
|
+
if (newEnabled !== previousProperties["Enabled"]) {
|
|
50611
|
+
if (newEnabled === false) {
|
|
50612
|
+
this.logger.debug(`Disabling KMS Key ${logicalId}`);
|
|
50613
|
+
await this.getClient().send(new DisableKeyCommand({ KeyId: physicalId }));
|
|
50614
|
+
} else {
|
|
50615
|
+
this.logger.debug(`Enabling KMS Key ${logicalId}`);
|
|
50616
|
+
await this.getClient().send(new EnableKeyCommand({ KeyId: physicalId }));
|
|
50617
|
+
}
|
|
50235
50618
|
}
|
|
50236
50619
|
await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
|
|
50237
50620
|
const newKeyPolicy = properties["KeyPolicy"];
|
|
@@ -50266,7 +50649,8 @@ var KMSProvider = class {
|
|
|
50266
50649
|
this.logger.debug(`Successfully scheduled deletion for KMS Key ${logicalId}`);
|
|
50267
50650
|
} catch (error) {
|
|
50268
50651
|
if (error instanceof NotFoundException$2) {
|
|
50269
|
-
|
|
50652
|
+
const clientRegion = await this.getClient().config.region();
|
|
50653
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
50270
50654
|
this.logger.debug(`KMS Key ${physicalId} does not exist, skipping deletion`);
|
|
50271
50655
|
return;
|
|
50272
50656
|
}
|
|
@@ -50356,7 +50740,8 @@ var KMSProvider = class {
|
|
|
50356
50740
|
this.logger.debug(`Successfully deleted KMS Alias ${logicalId}`);
|
|
50357
50741
|
} catch (error) {
|
|
50358
50742
|
if (error instanceof NotFoundException$2) {
|
|
50359
|
-
|
|
50743
|
+
const clientRegion = await this.getClient().config.region();
|
|
50744
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
50360
50745
|
this.logger.debug(`KMS Alias ${physicalId} does not exist, skipping deletion`);
|
|
50361
50746
|
return;
|
|
50362
50747
|
}
|
|
@@ -50433,7 +50818,8 @@ var KMSProvider = class {
|
|
|
50433
50818
|
if (err instanceof NotFoundException$2) return void 0;
|
|
50434
50819
|
}
|
|
50435
50820
|
try {
|
|
50436
|
-
|
|
50821
|
+
const tagsResp = await this.getClient().send(new ListResourceTagsCommand({ KeyId: md.KeyId }));
|
|
50822
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
50437
50823
|
} catch (err) {
|
|
50438
50824
|
if (err instanceof NotFoundException$2) return void 0;
|
|
50439
50825
|
}
|
|
@@ -50619,7 +51005,8 @@ var BudgetsBudgetProvider = class {
|
|
|
50619
51005
|
* unchanged byte for byte.
|
|
50620
51006
|
*/
|
|
50621
51007
|
async budgetArn(accountId, budgetName) {
|
|
50622
|
-
const
|
|
51008
|
+
const region = await this.getClient().config.region();
|
|
51009
|
+
const { partition } = derivePartitionAndUrlSuffix(region ?? "");
|
|
50623
51010
|
return `arn:${partition}:budgets::${accountId}:budget/${budgetName}`;
|
|
50624
51011
|
}
|
|
50625
51012
|
/**
|
|
@@ -50953,7 +51340,8 @@ var BudgetsBudgetProvider = class {
|
|
|
50953
51340
|
this.logger.debug(`Successfully deleted budget ${logicalId}`);
|
|
50954
51341
|
} catch (error) {
|
|
50955
51342
|
if (error instanceof NotFoundException$5) {
|
|
50956
|
-
|
|
51343
|
+
const clientRegion = await this.getClient().config.region();
|
|
51344
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
50957
51345
|
this.logger.debug(`Budget ${physicalId} does not exist, skipping deletion`);
|
|
50958
51346
|
return;
|
|
50959
51347
|
}
|
|
@@ -51196,11 +51584,15 @@ var KinesisStreamProvider = class {
|
|
|
51196
51584
|
this.logger.debug(`Creating Kinesis stream ${logicalId}`);
|
|
51197
51585
|
const streamName = properties["Name"] || generateResourceName(logicalId, { maxLength: 128 });
|
|
51198
51586
|
const desiredRead = readShardLevelMetrics(properties["DesiredShardLevelMetrics"], mask);
|
|
51199
|
-
if (desiredRead.kind === "unusable")
|
|
51200
|
-
|
|
51587
|
+
if (desiredRead.kind === "unusable") {
|
|
51588
|
+
if (context?.replayingState === true) warn(`${desiredRead.reason}. Replaying a state record, so the shard-level metrics are left unset on ${streamName} rather than refusing the restore`);
|
|
51589
|
+
else throw new ProvisioningError(desiredRead.reason, resourceType, logicalId, streamName);
|
|
51590
|
+
}
|
|
51201
51591
|
const maxRecordRead = readMaxRecordSize(properties["MaxRecordSizeInKiB"], mask);
|
|
51202
|
-
if (maxRecordRead.kind === "unusable")
|
|
51203
|
-
|
|
51592
|
+
if (maxRecordRead.kind === "unusable") {
|
|
51593
|
+
if (context?.replayingState === true) warn(`${maxRecordRead.reason}. Replaying a state record, so ${streamName} is created with AWS's default maximum record size rather than refusing the restore`);
|
|
51594
|
+
else throw new ProvisioningError(maxRecordRead.reason, resourceType, logicalId, streamName);
|
|
51595
|
+
}
|
|
51204
51596
|
try {
|
|
51205
51597
|
const streamModeDetails = properties["StreamModeDetails"];
|
|
51206
51598
|
const streamMode = readConfigString(streamModeDetails, "StreamMode", "PROVISIONED", "AWS::Kinesis::Stream StreamModeDetails");
|
|
@@ -51411,7 +51803,8 @@ var KinesisStreamProvider = class {
|
|
|
51411
51803
|
this.logger.debug(`Successfully deleted Kinesis stream ${logicalId}`);
|
|
51412
51804
|
} catch (error) {
|
|
51413
51805
|
if (error instanceof ResourceNotFoundException$10) {
|
|
51414
|
-
|
|
51806
|
+
const clientRegion = await this.getClient().config.region();
|
|
51807
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
51415
51808
|
this.logger.debug(`Kinesis stream ${physicalId} does not exist, skipping deletion`);
|
|
51416
51809
|
return;
|
|
51417
51810
|
}
|
|
@@ -51557,7 +51950,8 @@ var KinesisStreamProvider = class {
|
|
|
51557
51950
|
throw err;
|
|
51558
51951
|
}
|
|
51559
51952
|
try {
|
|
51560
|
-
|
|
51953
|
+
const tagsResp = await this.getClient().send(new ListTagsForStreamCommand({ StreamName: physicalId }));
|
|
51954
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
51561
51955
|
} catch (err) {
|
|
51562
51956
|
if (err instanceof ResourceNotFoundException$10) return void 0;
|
|
51563
51957
|
this.logger.debug(`Kinesis ListTagsForStream(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -51765,7 +52159,8 @@ var KinesisStreamConsumerProvider = class {
|
|
|
51765
52159
|
this.logger.debug(`Successfully deregistered Kinesis stream consumer ${logicalId}`);
|
|
51766
52160
|
} catch (error) {
|
|
51767
52161
|
if (error instanceof ResourceNotFoundException$10) {
|
|
51768
|
-
|
|
52162
|
+
const clientRegion = await this.getClient().config.region();
|
|
52163
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
51769
52164
|
this.logger.debug(`Kinesis stream consumer ${physicalId} not found, skipping`);
|
|
51770
52165
|
return;
|
|
51771
52166
|
}
|
|
@@ -51818,7 +52213,8 @@ var KinesisStreamConsumerProvider = class {
|
|
|
51818
52213
|
if (desc.ConsumerName !== void 0) result["ConsumerName"] = desc.ConsumerName;
|
|
51819
52214
|
if (desc.StreamARN !== void 0) result["StreamARN"] = desc.StreamARN;
|
|
51820
52215
|
try {
|
|
51821
|
-
|
|
52216
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$15({ ResourceARN: physicalId }));
|
|
52217
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
51822
52218
|
} catch (err) {
|
|
51823
52219
|
if (err instanceof ResourceNotFoundException$10) return void 0;
|
|
51824
52220
|
this.logger.debug(`ListTagsForResource(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -52114,7 +52510,8 @@ var SchedulerScheduleProvider = class {
|
|
|
52114
52510
|
this.logger.debug(`Deleted Schedule ${logicalId}`);
|
|
52115
52511
|
} catch (error) {
|
|
52116
52512
|
if (error instanceof ResourceNotFoundException$11) {
|
|
52117
|
-
|
|
52513
|
+
const clientRegion = await this.getClient().config.region();
|
|
52514
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
52118
52515
|
this.logger.debug(`Schedule ${logicalId} already deleted (not found), treating as success`);
|
|
52119
52516
|
return;
|
|
52120
52517
|
}
|
|
@@ -52508,7 +52905,8 @@ var EFSProvider = class {
|
|
|
52508
52905
|
this.logger.debug(`Successfully deleted EFS FileSystem ${logicalId}`);
|
|
52509
52906
|
} catch (error) {
|
|
52510
52907
|
if (error instanceof FileSystemNotFound) {
|
|
52511
|
-
|
|
52908
|
+
const clientRegion = await this.getClient().config.region();
|
|
52909
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
52512
52910
|
this.logger.debug(`EFS FileSystem ${physicalId} does not exist, skipping deletion`);
|
|
52513
52911
|
return;
|
|
52514
52912
|
}
|
|
@@ -52574,7 +52972,8 @@ var EFSProvider = class {
|
|
|
52574
52972
|
this.logger.debug(`Successfully deleted EFS MountTarget ${logicalId}`);
|
|
52575
52973
|
} catch (error) {
|
|
52576
52974
|
if (error instanceof MountTargetNotFound) {
|
|
52577
|
-
|
|
52975
|
+
const clientRegion = await this.getClient().config.region();
|
|
52976
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
52578
52977
|
this.logger.debug(`EFS MountTarget ${physicalId} does not exist, skipping deletion`);
|
|
52579
52978
|
return;
|
|
52580
52979
|
}
|
|
@@ -52746,7 +53145,8 @@ var EFSProvider = class {
|
|
|
52746
53145
|
this.logger.debug(`Successfully deleted EFS AccessPoint ${logicalId}`);
|
|
52747
53146
|
} catch (error) {
|
|
52748
53147
|
if (error instanceof AccessPointNotFound) {
|
|
52749
|
-
|
|
53148
|
+
const clientRegion = await this.getClient().config.region();
|
|
53149
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
52750
53150
|
this.logger.debug(`EFS AccessPoint ${physicalId} does not exist, skipping deletion`);
|
|
52751
53151
|
return;
|
|
52752
53152
|
}
|
|
@@ -52948,7 +53348,7 @@ var EFSProvider = class {
|
|
|
52948
53348
|
* storage-capacity growth and deletes can take substantially longer —
|
|
52949
53349
|
* mirror the Custom Resource provider's 1-hour ceiling.
|
|
52950
53350
|
*/
|
|
52951
|
-
const DEFAULT_MAX_WAIT_MS$3 =
|
|
53351
|
+
const DEFAULT_MAX_WAIT_MS$3 = 36e5;
|
|
52952
53352
|
const DEFAULT_POLL_INTERVAL_MS$3 = 15e3;
|
|
52953
53353
|
/**
|
|
52954
53354
|
* Lustre sub-properties that `UpdateFileSystem` accepts (the mutable
|
|
@@ -53672,7 +54072,8 @@ var FSxFileSystemProvider = class {
|
|
|
53672
54072
|
await this.getClient().send(new DeleteFileSystemCommand$1({ FileSystemId: physicalId }));
|
|
53673
54073
|
} catch (error) {
|
|
53674
54074
|
if (error instanceof FileSystemNotFound$1) {
|
|
53675
|
-
|
|
54075
|
+
const clientRegion = await this.getClient().config.region();
|
|
54076
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
53676
54077
|
this.logger.debug(`FSx FileSystem ${physicalId} does not exist, skipping deletion`);
|
|
53677
54078
|
return;
|
|
53678
54079
|
}
|
|
@@ -53714,7 +54115,10 @@ var FSxFileSystemProvider = class {
|
|
|
53714
54115
|
const fs = await this.describeForPoll(fileSystemId, transientState);
|
|
53715
54116
|
const lifecycle = fs?.Lifecycle;
|
|
53716
54117
|
if (fs && lifecycle === "AVAILABLE") return fs;
|
|
53717
|
-
if (lifecycle === "FAILED" || lifecycle === "MISCONFIGURED")
|
|
54118
|
+
if (lifecycle === "FAILED" || lifecycle === "MISCONFIGURED") {
|
|
54119
|
+
const detail = fs?.FailureDetails?.Message ?? "no failure details reported";
|
|
54120
|
+
throw new ProvisioningError(`FSx FileSystem ${fileSystemId} entered lifecycle state ${lifecycle}: ${detail}`, resourceType, logicalId, fileSystemId);
|
|
54121
|
+
}
|
|
53718
54122
|
this.logger.debug(`FSx FileSystem ${fileSystemId} state: ${lifecycle ?? "unknown"}, waiting...`);
|
|
53719
54123
|
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
53720
54124
|
}
|
|
@@ -53753,7 +54157,10 @@ var FSxFileSystemProvider = class {
|
|
|
53753
54157
|
const updateActions = (fs.AdministrativeActions ?? []).filter((a) => a.AdministrativeActionType === "FILE_SYSTEM_UPDATE" && (a.RequestTime === void 0 || a.RequestTime.getTime() >= actionThresholdMs));
|
|
53754
54158
|
if (updateActions.length > 0) seenTrackedAction = true;
|
|
53755
54159
|
const failed = updateActions.find((a) => a.Status === "FAILED");
|
|
53756
|
-
if (failed)
|
|
54160
|
+
if (failed) {
|
|
54161
|
+
const detail = failed.FailureDetails?.Message ?? "no failure details reported";
|
|
54162
|
+
throw new ProvisioningError(`FSx FileSystem ${fileSystemId} update failed (FILE_SYSTEM_UPDATE administrative action): ${detail}`, resourceType, logicalId, fileSystemId);
|
|
54163
|
+
}
|
|
53757
54164
|
if (!updateActions.some((a) => a.Status === "PENDING" || a.Status === "IN_PROGRESS") && (!requireActionObservation || seenTrackedAction) && fs.Lifecycle === "AVAILABLE") return fs;
|
|
53758
54165
|
this.logger.debug(`FSx FileSystem ${fileSystemId} update in progress (lifecycle: ${fs.Lifecycle ?? "unknown"}), waiting...`);
|
|
53759
54166
|
}
|
|
@@ -53780,7 +54187,10 @@ var FSxFileSystemProvider = class {
|
|
|
53780
54187
|
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
53781
54188
|
continue;
|
|
53782
54189
|
}
|
|
53783
|
-
if (fs.Lifecycle === "FAILED")
|
|
54190
|
+
if (fs.Lifecycle === "FAILED") {
|
|
54191
|
+
const detail = fs.FailureDetails?.Message ?? "no failure details reported";
|
|
54192
|
+
throw new ProvisioningError(`FSx FileSystem ${fileSystemId} entered lifecycle state FAILED during deletion: ${detail}`, resourceType, logicalId, fileSystemId);
|
|
54193
|
+
}
|
|
53784
54194
|
this.logger.debug(`FSx FileSystem ${fileSystemId} state: ${fs.Lifecycle ?? "unknown"}, waiting for deletion...`);
|
|
53785
54195
|
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
53786
54196
|
}
|
|
@@ -54041,9 +54451,7 @@ var FSxFileSystemProvider = class {
|
|
|
54041
54451
|
case "OpenZFSConfiguration":
|
|
54042
54452
|
result[variantKey] = this.readOpenZFSConfiguration(fs.OpenZFSConfiguration ?? {});
|
|
54043
54453
|
break;
|
|
54044
|
-
default:
|
|
54045
|
-
this.logger.warn(`FSx FileSystem ${physicalId} has FileSystemType '${fs.FileSystemType ?? "(unset)"}', which cdkd does not recognize (expected one of ${Object.keys(VARIANT_CONFIG_KEY).join(" / ")}). Its variant configuration is NOT included in the drift snapshot, so drift in that block cannot be detected. Please report this at https://github.com/go-to-k/cdkd/issues`);
|
|
54046
|
-
break;
|
|
54454
|
+
default: this.logger.warn(`FSx FileSystem ${physicalId} has FileSystemType '${fs.FileSystemType ?? "(unset)"}', which cdkd does not recognize (expected one of ${Object.keys(VARIANT_CONFIG_KEY).join(" / ")}). Its variant configuration is NOT included in the drift snapshot, so drift in that block cannot be detected. Please report this at https://github.com/go-to-k/cdkd/issues`);
|
|
54047
54455
|
}
|
|
54048
54456
|
return result;
|
|
54049
54457
|
}
|
|
@@ -54159,7 +54567,7 @@ function toSdkInstanceTypeConfigs(value) {
|
|
|
54159
54567
|
* FSx providers' 1-hour ceiling so the slowest realistic create/terminate
|
|
54160
54568
|
* still fits inside the per-resource deadline.
|
|
54161
54569
|
*/
|
|
54162
|
-
const DEFAULT_MAX_WAIT_MS$2 =
|
|
54570
|
+
const DEFAULT_MAX_WAIT_MS$2 = 36e5;
|
|
54163
54571
|
const DEFAULT_POLL_INTERVAL_MS$2 = 15e3;
|
|
54164
54572
|
/** Cluster states that mean "create succeeded, the cluster is up". */
|
|
54165
54573
|
const CREATE_READY_STATES = /* @__PURE__ */ new Set(["WAITING", "RUNNING"]);
|
|
@@ -54577,7 +54985,8 @@ var EMRClusterProvider = class {
|
|
|
54577
54985
|
current = (await this.getClient().send(new DescribeClusterCommand({ ClusterId: physicalId }))).Cluster;
|
|
54578
54986
|
} catch (error) {
|
|
54579
54987
|
if (error instanceof InvalidRequestException) {
|
|
54580
|
-
|
|
54988
|
+
const clientRegion = await this.getClient().config.region();
|
|
54989
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
54581
54990
|
this.logger.debug(`EMR Cluster ${physicalId} does not exist, skipping deletion`);
|
|
54582
54991
|
return;
|
|
54583
54992
|
}
|
|
@@ -54636,7 +55045,10 @@ var EMRClusterProvider = class {
|
|
|
54636
55045
|
const cluster = await this.describeForPoll(clusterId, transientState);
|
|
54637
55046
|
const state = cluster?.Status?.State;
|
|
54638
55047
|
if (cluster && state && CREATE_READY_STATES.has(state)) return cluster;
|
|
54639
|
-
if (state && TERMINAL_STATES.has(state))
|
|
55048
|
+
if (state && TERMINAL_STATES.has(state)) {
|
|
55049
|
+
const reason = cluster?.Status?.StateChangeReason?.Message ?? "no state-change reason reported";
|
|
55050
|
+
throw new ProvisioningError(`EMR Cluster ${clusterId} entered terminal state ${state} during creation: ${reason}`, resourceType, logicalId, clusterId);
|
|
55051
|
+
}
|
|
54640
55052
|
this.logger.debug(`EMR Cluster ${clusterId} state: ${state ?? "unknown"}, waiting...`);
|
|
54641
55053
|
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
54642
55054
|
}
|
|
@@ -54888,9 +55300,7 @@ var EMRClusterProvider = class {
|
|
|
54888
55300
|
case "CORE":
|
|
54889
55301
|
instances["CoreInstanceGroup"] = cfn;
|
|
54890
55302
|
break;
|
|
54891
|
-
case "TASK":
|
|
54892
|
-
taskGroups.push(cfn);
|
|
54893
|
-
break;
|
|
55303
|
+
case "TASK": taskGroups.push(cfn);
|
|
54894
55304
|
}
|
|
54895
55305
|
}
|
|
54896
55306
|
if (taskGroups.length > 0) instances["TaskInstanceGroups"] = taskGroups;
|
|
@@ -54904,9 +55314,7 @@ var EMRClusterProvider = class {
|
|
|
54904
55314
|
case "CORE":
|
|
54905
55315
|
instances["CoreInstanceFleet"] = cfn;
|
|
54906
55316
|
break;
|
|
54907
|
-
case "TASK":
|
|
54908
|
-
taskFleets.push(cfn);
|
|
54909
|
-
break;
|
|
55317
|
+
case "TASK": taskFleets.push(cfn);
|
|
54910
55318
|
}
|
|
54911
55319
|
}
|
|
54912
55320
|
if (taskFleets.length > 0) instances["TaskInstanceFleets"] = taskFleets;
|
|
@@ -54951,7 +55359,7 @@ var EMRClusterProvider = class {
|
|
|
54951
55359
|
* EMR Cluster provider's 1-hour ceiling so the slowest realistic add still
|
|
54952
55360
|
* fits inside the per-resource deadline.
|
|
54953
55361
|
*/
|
|
54954
|
-
const DEFAULT_MAX_WAIT_MS$1 =
|
|
55362
|
+
const DEFAULT_MAX_WAIT_MS$1 = 36e5;
|
|
54955
55363
|
const DEFAULT_POLL_INTERVAL_MS$1 = 15e3;
|
|
54956
55364
|
/** Instance-group state that means "the group is up and idle/serving". */
|
|
54957
55365
|
const READY_STATES$1 = /* @__PURE__ */ new Set(["RUNNING"]);
|
|
@@ -55165,7 +55573,8 @@ var EMRInstanceGroupConfigProvider = class {
|
|
|
55165
55573
|
this.logger.debug(`Scaled EMR TASK instance group ${physicalId} to 0 to release its instances (no standalone delete API exists)`);
|
|
55166
55574
|
} catch (error) {
|
|
55167
55575
|
if (error instanceof InvalidRequestException) {
|
|
55168
|
-
|
|
55576
|
+
const clientRegion = await this.getClient().config.region();
|
|
55577
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
55169
55578
|
this.logger.debug(`EMR instance group ${physicalId} scale-to-0 skipped (cluster/group gone): ${error.message}`);
|
|
55170
55579
|
} else this.logger.warn(`Best-effort scale-to-0 of EMR instance group ${physicalId} failed: ${error instanceof Error ? error.message : String(error)} — its instances are released when the parent cluster terminates`);
|
|
55171
55580
|
}
|
|
@@ -55219,7 +55628,10 @@ var EMRInstanceGroupConfigProvider = class {
|
|
|
55219
55628
|
}
|
|
55220
55629
|
if (state && READY_STATES$1.has(state) && group?.RunningInstanceCount === expectedCount) return;
|
|
55221
55630
|
const enforceFailedStates = !toleratesStaleFailedState || hasLeftInitialState;
|
|
55222
|
-
if (state && enforceFailedStates && FAILED_STATES$1.has(state))
|
|
55631
|
+
if (state && enforceFailedStates && FAILED_STATES$1.has(state)) {
|
|
55632
|
+
const reason = group?.Status?.StateChangeReason?.Message ?? "no state-change reason reported";
|
|
55633
|
+
throw new ProvisioningError(`EMR instance group ${groupId} entered failed state ${state}: ${reason}`, resourceType, logicalId, groupId);
|
|
55634
|
+
}
|
|
55223
55635
|
if (state && !enforceFailedStates && FAILED_STATES$1.has(state)) this.logger.debug(`EMR instance group ${groupId} still reads ${state} from before the resize; waiting for it to leave that state before treating it as failed`);
|
|
55224
55636
|
this.logger.debug(`EMR instance group ${groupId} state: ${state ?? "unknown"}, running ${group?.RunningInstanceCount ?? "?"}/${expectedCount}, waiting...`);
|
|
55225
55637
|
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
@@ -55271,7 +55683,7 @@ var EMRInstanceGroupConfigProvider = class {
|
|
|
55271
55683
|
* EMR Cluster provider's 1-hour ceiling so the slowest realistic add still
|
|
55272
55684
|
* fits inside the per-resource deadline.
|
|
55273
55685
|
*/
|
|
55274
|
-
const DEFAULT_MAX_WAIT_MS =
|
|
55686
|
+
const DEFAULT_MAX_WAIT_MS = 36e5;
|
|
55275
55687
|
const DEFAULT_POLL_INTERVAL_MS = 15e3;
|
|
55276
55688
|
/** Instance-fleet state that means "the fleet is up and idle/serving". */
|
|
55277
55689
|
const READY_STATES = /* @__PURE__ */ new Set(["RUNNING"]);
|
|
@@ -55475,7 +55887,8 @@ var EMRInstanceFleetConfigProvider = class {
|
|
|
55475
55887
|
this.logger.debug(`Scaled EMR TASK instance fleet ${physicalId} to 0 to release its instances (no standalone delete API exists)`);
|
|
55476
55888
|
} catch (error) {
|
|
55477
55889
|
if (error instanceof InvalidRequestException) {
|
|
55478
|
-
|
|
55890
|
+
const clientRegion = await this.getClient().config.region();
|
|
55891
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
55479
55892
|
this.logger.debug(`EMR instance fleet ${physicalId} scale-to-0 skipped (cluster/fleet gone): ${error.message}`);
|
|
55480
55893
|
} else this.logger.warn(`Best-effort scale-to-0 of EMR instance fleet ${physicalId} failed: ${error instanceof Error ? error.message : String(error)} — its instances are released when the parent cluster terminates`);
|
|
55481
55894
|
}
|
|
@@ -55569,7 +55982,10 @@ var EMRInstanceFleetConfigProvider = class {
|
|
|
55569
55982
|
}
|
|
55570
55983
|
if (state && READY_STATES.has(state) && capacityReady) return;
|
|
55571
55984
|
const enforceFailedStates = !toleratesStaleFailedState || hasLeftInitialState;
|
|
55572
|
-
if (state && enforceFailedStates && FAILED_STATES.has(state))
|
|
55985
|
+
if (state && enforceFailedStates && FAILED_STATES.has(state)) {
|
|
55986
|
+
const reason = fleet?.Status?.StateChangeReason?.Message ?? "no state-change reason reported";
|
|
55987
|
+
throw new ProvisioningError(`EMR instance fleet ${fleetId} entered failed state ${state}: ${reason}`, resourceType, logicalId, fleetId);
|
|
55988
|
+
}
|
|
55573
55989
|
if (state && !enforceFailedStates && FAILED_STATES.has(state)) this.logger.debug(`EMR instance fleet ${fleetId} still reads ${state} from before the resize; waiting for it to leave that state before treating it as failed`);
|
|
55574
55990
|
this.logger.debug(`EMR instance fleet ${fleetId} state: ${state ?? "unknown"}, provisioned ${onDemand}/${target.onDemand} on-demand + ${spot}/${target.spot} spot (${provisioned} total), waiting...`);
|
|
55575
55991
|
await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
|
|
@@ -56085,7 +56501,8 @@ var FirehoseProvider = class {
|
|
|
56085
56501
|
this.logger.debug(`Successfully deleted Firehose delivery stream ${logicalId}`);
|
|
56086
56502
|
} catch (error) {
|
|
56087
56503
|
if (error instanceof ResourceNotFoundException$12) {
|
|
56088
|
-
|
|
56504
|
+
const clientRegion = await this.getClient().config.region();
|
|
56505
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
56089
56506
|
this.logger.debug(`Firehose delivery stream ${physicalId} does not exist, skipping deletion`);
|
|
56090
56507
|
return;
|
|
56091
56508
|
}
|
|
@@ -56650,7 +57067,8 @@ var FirehoseProvider = class {
|
|
|
56650
57067
|
if (enc?.KeyType !== void 0) encOut["KeyType"] = enc.KeyType;
|
|
56651
57068
|
result["DeliveryStreamEncryptionConfigurationInput"] = encOut;
|
|
56652
57069
|
try {
|
|
56653
|
-
|
|
57070
|
+
const tagsResp = await this.getClient().send(new ListTagsForDeliveryStreamCommand({ DeliveryStreamName: physicalId }));
|
|
57071
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
56654
57072
|
} catch (err) {
|
|
56655
57073
|
if (err instanceof ResourceNotFoundException$12) return void 0;
|
|
56656
57074
|
this.logger.debug(`Firehose ListTagsForDeliveryStream(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -57145,12 +57563,14 @@ var CloudTrailProvider = class {
|
|
|
57145
57563
|
}));
|
|
57146
57564
|
}
|
|
57147
57565
|
const oldIsLogging = previousProperties["IsLogging"];
|
|
57148
|
-
if (isLogging !== void 0 && isLogging !== oldIsLogging)
|
|
57149
|
-
|
|
57150
|
-
|
|
57151
|
-
|
|
57152
|
-
|
|
57153
|
-
|
|
57566
|
+
if (isLogging !== void 0 && isLogging !== oldIsLogging) {
|
|
57567
|
+
if (isLogging === false) {
|
|
57568
|
+
this.logger.debug(`Stopping logging for CloudTrail Trail ${logicalId}`);
|
|
57569
|
+
await this.getClient().send(new StopLoggingCommand({ Name: physicalId }));
|
|
57570
|
+
} else {
|
|
57571
|
+
this.logger.debug(`Starting logging for CloudTrail Trail ${logicalId}`);
|
|
57572
|
+
await this.getClient().send(new StartLoggingCommand({ Name: physicalId }));
|
|
57573
|
+
}
|
|
57154
57574
|
}
|
|
57155
57575
|
await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
|
|
57156
57576
|
this.logger.debug(`Successfully updated CloudTrail Trail ${logicalId}`);
|
|
@@ -57173,7 +57593,8 @@ var CloudTrailProvider = class {
|
|
|
57173
57593
|
this.logger.debug(`Successfully deleted CloudTrail Trail ${logicalId}`);
|
|
57174
57594
|
} catch (error) {
|
|
57175
57595
|
if (error instanceof TrailNotFoundException) {
|
|
57176
|
-
|
|
57596
|
+
const clientRegion = await this.getClient().config.region();
|
|
57597
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
57177
57598
|
this.logger.debug(`CloudTrail Trail ${physicalId} does not exist, skipping deletion`);
|
|
57178
57599
|
return;
|
|
57179
57600
|
}
|
|
@@ -57293,7 +57714,8 @@ var CloudTrailProvider = class {
|
|
|
57293
57714
|
result["InsightSelectors"] = insightSelectors;
|
|
57294
57715
|
let tags = [];
|
|
57295
57716
|
if (trail.TrailARN) try {
|
|
57296
|
-
|
|
57717
|
+
const tagsResp = await this.getClient().send(new ListTagsCommand$2({ ResourceIdList: [trail.TrailARN] }));
|
|
57718
|
+
tags = normalizeAwsTagsToCfn(tagsResp.ResourceTagList?.[0]?.TagsList);
|
|
57297
57719
|
} catch (err) {
|
|
57298
57720
|
this.logger.debug(`CloudTrail ListTags(${trail.TrailARN}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
57299
57721
|
}
|
|
@@ -57583,7 +58005,8 @@ var CodeBuildProvider = class {
|
|
|
57583
58005
|
this.logger.debug(`Successfully deleted CodeBuild Project ${logicalId}`);
|
|
57584
58006
|
} catch (error) {
|
|
57585
58007
|
if (error instanceof ResourceNotFoundException$13) {
|
|
57586
|
-
|
|
58008
|
+
const clientRegion = await this.getClient().config.region();
|
|
58009
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
57587
58010
|
this.logger.debug(`CodeBuild Project ${physicalId} does not exist, skipping deletion`);
|
|
57588
58011
|
return;
|
|
57589
58012
|
}
|
|
@@ -58109,7 +58532,8 @@ var CodeCommitRepositoryProvider = class {
|
|
|
58109
58532
|
deletedRepositoryId = (await this.getClient().send(new DeleteRepositoryCommand$1({ repositoryName: physicalId }))).repositoryId;
|
|
58110
58533
|
} catch (error) {
|
|
58111
58534
|
if (error instanceof RepositoryDoesNotExistException) {
|
|
58112
|
-
|
|
58535
|
+
const clientRegion = await this.getClient().config.region();
|
|
58536
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
58113
58537
|
this.logger.debug(`CodeCommit Repository ${physicalId} does not exist, skipping deletion`);
|
|
58114
58538
|
return;
|
|
58115
58539
|
}
|
|
@@ -58117,7 +58541,8 @@ var CodeCommitRepositoryProvider = class {
|
|
|
58117
58541
|
throw new ProvisioningError(`Failed to delete CodeCommit Repository ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
58118
58542
|
}
|
|
58119
58543
|
if (!deletedRepositoryId) {
|
|
58120
|
-
|
|
58544
|
+
const clientRegion = await this.getClient().config.region();
|
|
58545
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
58121
58546
|
this.logger.debug(`CodeCommit Repository ${physicalId} does not exist, skipping deletion`);
|
|
58122
58547
|
return;
|
|
58123
58548
|
}
|
|
@@ -58191,7 +58616,8 @@ var CodeCommitRepositoryProvider = class {
|
|
|
58191
58616
|
if (!metadata) return void 0;
|
|
58192
58617
|
let tags = [];
|
|
58193
58618
|
if (metadata.Arn) try {
|
|
58194
|
-
|
|
58619
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$16({ resourceArn: metadata.Arn }));
|
|
58620
|
+
tags = normalizeAwsTagsToCfn(tagsResp.tags);
|
|
58195
58621
|
} catch (err) {
|
|
58196
58622
|
if (err instanceof RepositoryDoesNotExistException) return void 0;
|
|
58197
58623
|
throw err;
|
|
@@ -58543,7 +58969,8 @@ var DLMLifecyclePolicyProvider = class {
|
|
|
58543
58969
|
this.logger.debug(`Successfully deleted DLM Lifecycle Policy ${logicalId}`);
|
|
58544
58970
|
} catch (error) {
|
|
58545
58971
|
if (error instanceof ResourceNotFoundException$14) {
|
|
58546
|
-
|
|
58972
|
+
const clientRegion = await this.getClient().config.region();
|
|
58973
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
58547
58974
|
this.logger.debug(`DLM Lifecycle Policy ${physicalId} does not exist, skipping deletion`);
|
|
58548
58975
|
return;
|
|
58549
58976
|
}
|
|
@@ -58782,7 +59209,8 @@ var S3VectorsProvider = class {
|
|
|
58782
59209
|
this.logger.debug(`Successfully deleted S3 VectorBucket ${logicalId}`);
|
|
58783
59210
|
} catch (error) {
|
|
58784
59211
|
if (this.isNotFoundError(error)) {
|
|
58785
|
-
|
|
59212
|
+
const clientRegion = await this.getClient().config.region();
|
|
59213
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
58786
59214
|
this.logger.debug(`S3 VectorBucket ${physicalId} does not exist, skipping deletion`);
|
|
58787
59215
|
return;
|
|
58788
59216
|
}
|
|
@@ -58844,7 +59272,8 @@ var S3VectorsProvider = class {
|
|
|
58844
59272
|
if (Object.keys(enc).length > 0) result["EncryptionConfiguration"] = enc;
|
|
58845
59273
|
}
|
|
58846
59274
|
if (bucket?.vectorBucketArn) try {
|
|
58847
|
-
|
|
59275
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$17({ resourceArn: bucket.vectorBucketArn }));
|
|
59276
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.tags);
|
|
58848
59277
|
} catch (err) {
|
|
58849
59278
|
this.logger.debug(`S3Vectors ListTagsForResource(${bucket.vectorBucketArn}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
58850
59279
|
result["Tags"] = [];
|
|
@@ -59101,7 +59530,8 @@ var S3DirectoryBucketProvider = class {
|
|
|
59101
59530
|
await this.deleteBucketWithEmptyRetry(logicalId, physicalId, allowAutoEmpty);
|
|
59102
59531
|
} catch (error) {
|
|
59103
59532
|
if (error instanceof Error && (error.name === "NoSuchBucket" || error.name === "BucketNotFound")) {
|
|
59104
|
-
|
|
59533
|
+
const clientRegion = await this.s3Client.config.region();
|
|
59534
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
59105
59535
|
this.logger.debug(`Bucket ${physicalId} does not exist, skipping deletion`);
|
|
59106
59536
|
return;
|
|
59107
59537
|
}
|
|
@@ -59205,10 +59635,11 @@ var S3DirectoryBucketProvider = class {
|
|
|
59205
59635
|
async readTags(bucketName) {
|
|
59206
59636
|
const accountId = await this.getAccountId();
|
|
59207
59637
|
const resourceArn = await this.buildBucketArn(bucketName, accountId);
|
|
59208
|
-
|
|
59638
|
+
const resp = await this.getS3ControlClient().send(new ListTagsForResourceCommand$18({
|
|
59209
59639
|
AccountId: accountId,
|
|
59210
59640
|
ResourceArn: resourceArn
|
|
59211
|
-
}))
|
|
59641
|
+
}));
|
|
59642
|
+
return normalizeAwsTagsToCfn(resp.Tags);
|
|
59212
59643
|
}
|
|
59213
59644
|
/**
|
|
59214
59645
|
* Adopt an existing S3 Express Directory Bucket into cdkd state.
|
|
@@ -59407,7 +59838,8 @@ var S3TablesProvider = class {
|
|
|
59407
59838
|
this.logger.debug(`Successfully deleted S3 Table Bucket ${logicalId}`);
|
|
59408
59839
|
} catch (error) {
|
|
59409
59840
|
if (error instanceof NotFoundException$6) {
|
|
59410
|
-
|
|
59841
|
+
const clientRegion = await this.getClient().config.region();
|
|
59842
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
59411
59843
|
this.logger.debug(`S3 Table Bucket ${physicalId} does not exist, skipping deletion`);
|
|
59412
59844
|
return;
|
|
59413
59845
|
}
|
|
@@ -59511,7 +59943,8 @@ var S3TablesProvider = class {
|
|
|
59511
59943
|
this.logger.debug(`Successfully deleted S3 Tables Namespace ${logicalId}`);
|
|
59512
59944
|
} catch (error) {
|
|
59513
59945
|
if (error instanceof NotFoundException$6) {
|
|
59514
|
-
|
|
59946
|
+
const clientRegion = await this.getClient().config.region();
|
|
59947
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
59515
59948
|
this.logger.debug(`S3 Tables Namespace ${physicalId} does not exist, skipping deletion`);
|
|
59516
59949
|
return;
|
|
59517
59950
|
}
|
|
@@ -59915,7 +60348,8 @@ var S3TablesProvider = class {
|
|
|
59915
60348
|
throw new ProvisioningError(`Failed to resolve S3 Tables Table ${logicalId} (${physicalId}): ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
59916
60349
|
}
|
|
59917
60350
|
if (resolved === "not-found") {
|
|
59918
|
-
|
|
60351
|
+
const clientRegion = await this.getClient().config.region();
|
|
60352
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
59919
60353
|
this.logger.debug(`S3 Tables Table ${physicalId} does not exist, skipping deletion`);
|
|
59920
60354
|
return;
|
|
59921
60355
|
}
|
|
@@ -59930,7 +60364,8 @@ var S3TablesProvider = class {
|
|
|
59930
60364
|
this.logger.debug(`Successfully deleted S3 Tables Table ${logicalId}`);
|
|
59931
60365
|
} catch (error) {
|
|
59932
60366
|
if (error instanceof NotFoundException$6) {
|
|
59933
|
-
|
|
60367
|
+
const clientRegion = await this.getClient().config.region();
|
|
60368
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
59934
60369
|
this.logger.debug(`S3 Tables Table ${physicalId} does not exist, skipping deletion`);
|
|
59935
60370
|
return;
|
|
59936
60371
|
}
|
|
@@ -60375,7 +60810,8 @@ var ECRProvider = class {
|
|
|
60375
60810
|
} catch (error) {
|
|
60376
60811
|
if (error instanceof RepositoryNotEmptyException) throw new ProvisioningError(`Failed to delete ECR Repository ${logicalId}: repository ${physicalId} still contains images. Matching CloudFormation, cdkd does not force-delete an image-carrying repository unless it opted in via EmptyOnDelete: true (CDK's emptyOnDelete) or the '${ECR_AUTO_DELETE_IMAGES_TAG}' tag (CDK's autoDeleteImages). Either delete the images first (aws ecr batch-delete-image), or redeploy with emptyOnDelete: true and destroy again.`, resourceType, logicalId, physicalId, error);
|
|
60377
60812
|
if (error instanceof RepositoryNotFoundException) {
|
|
60378
|
-
|
|
60813
|
+
const clientRegion = await this.getClient().config.region();
|
|
60814
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
60379
60815
|
this.logger.debug(`ECR Repository ${physicalId} does not exist, skipping deletion`);
|
|
60380
60816
|
return;
|
|
60381
60817
|
}
|
|
@@ -60442,7 +60878,8 @@ var ECRProvider = class {
|
|
|
60442
60878
|
if (!(err instanceof LifecyclePolicyNotFoundException)) throw err;
|
|
60443
60879
|
}
|
|
60444
60880
|
if (r.repositoryArn) try {
|
|
60445
|
-
|
|
60881
|
+
const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$9({ resourceArn: r.repositoryArn }));
|
|
60882
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.tags);
|
|
60446
60883
|
} catch (err) {
|
|
60447
60884
|
if (!(err instanceof RepositoryNotFoundException)) throw err;
|
|
60448
60885
|
}
|
|
@@ -60919,10 +61356,12 @@ async function runDestroyForStack(stackName, state, ctx) {
|
|
|
60919
61356
|
});
|
|
60920
61357
|
try {
|
|
60921
61358
|
let finalSnapshotIdentifier;
|
|
60922
|
-
if (resource.deletionPolicy === "Snapshot" && ctx.skipFinalSnapshot !== true)
|
|
60923
|
-
|
|
60924
|
-
|
|
60925
|
-
|
|
61359
|
+
if (resource.deletionPolicy === "Snapshot" && ctx.skipFinalSnapshot !== true) {
|
|
61360
|
+
if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resource.resourceType) && resource.provisionedBy !== "cc-api") finalSnapshotIdentifier = buildFinalSnapshotIdentifier(resource.physicalId, resource.resourceType);
|
|
61361
|
+
else if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resource.resourceType)) throw ccRoutedFinalSnapshotError(logicalId, resource.resourceType, "--skip-final-snapshot");
|
|
61362
|
+
else if (PRE_DELETE_SNAPSHOT_TYPES.has(resource.resourceType)) await createPreDeleteFinalSnapshot(resource.resourceType, resource.physicalId, logicalId, destroyAwsClients ?? ctx.baseAwsClients, logger);
|
|
61363
|
+
else throw unsupportedFinalSnapshotError(logicalId, resource.resourceType, "--skip-final-snapshot");
|
|
61364
|
+
}
|
|
60926
61365
|
const provider = destroyProviderRegistry.getProviderFor({
|
|
60927
61366
|
resourceType: resource.resourceType,
|
|
60928
61367
|
provisionedBy: resource.provisionedBy
|
|
@@ -61377,12 +61816,13 @@ var NestedStackProvider = class {
|
|
|
61377
61816
|
}
|
|
61378
61817
|
const resourceCount = Object.keys(childStateData.state.resources).length;
|
|
61379
61818
|
this.logger.info(`Destroying nested stack ${childStackName} (logicalId=${logicalId}, ${resourceCount} resource(s))`);
|
|
61380
|
-
const
|
|
61819
|
+
const childCtx = {
|
|
61381
61820
|
...ctx,
|
|
61382
61821
|
parentStackName: childStackName,
|
|
61383
61822
|
parentRegion: childRegion,
|
|
61384
61823
|
nestedTemplates: void 0
|
|
61385
|
-
}
|
|
61824
|
+
};
|
|
61825
|
+
const childResult = await withNestedStackContext(childCtx, () => runDestroyForStack(childStackName, childStateData.state, {
|
|
61386
61826
|
stateBackend: ctx.stateBackend,
|
|
61387
61827
|
lockManager: ctx.lockManager,
|
|
61388
61828
|
providerRegistry: ctx.providerRegistry,
|
|
@@ -61429,12 +61869,13 @@ var NestedStackProvider = class {
|
|
|
61429
61869
|
parentRegion: parentCtx.parentRegion
|
|
61430
61870
|
}
|
|
61431
61871
|
}, childRegion, parentCtx.exportIndexStore);
|
|
61432
|
-
|
|
61872
|
+
const childCtx = {
|
|
61433
61873
|
...parentCtx,
|
|
61434
61874
|
parentStackName: childStackName,
|
|
61435
61875
|
parentRegion: childRegion,
|
|
61436
61876
|
nestedTemplates: grandchildTemplates
|
|
61437
|
-
}
|
|
61877
|
+
};
|
|
61878
|
+
await withNestedStackContext(childCtx, () => childEngine.deploy(childStackName, childTemplate));
|
|
61438
61879
|
}
|
|
61439
61880
|
/**
|
|
61440
61881
|
* The child's outputs, as `Outputs.<Key>` attributes for the parent's
|
|
@@ -61828,7 +62269,8 @@ var ACMCertificateProvider = class {
|
|
|
61828
62269
|
throw new ProvisioningError(`${error.message} ${survivorNote}`, resourceType, logicalId, requestedArn, error);
|
|
61829
62270
|
}
|
|
61830
62271
|
const cause = error instanceof Error ? error : void 0;
|
|
61831
|
-
|
|
62272
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
62273
|
+
throw new ProvisioningError(`Failed to create ACM certificate ${logicalId}: ${detail}` + (survivorNote === void 0 ? "" : ` ${survivorNote}`), resourceType, logicalId, requestedArn, cause);
|
|
61832
62274
|
}
|
|
61833
62275
|
}
|
|
61834
62276
|
/**
|
|
@@ -61976,7 +62418,8 @@ var ACMCertificateProvider = class {
|
|
|
61976
62418
|
await this.acmClient.send(new DeleteCertificateCommand({ CertificateArn: physicalId }));
|
|
61977
62419
|
} catch (error) {
|
|
61978
62420
|
if (error instanceof ResourceNotFoundException$7) {
|
|
61979
|
-
|
|
62421
|
+
const clientRegion = await this.acmClient.config.region();
|
|
62422
|
+
assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
61980
62423
|
this.logger.debug(`Certificate ${physicalId} does not exist, skipping deletion`);
|
|
61981
62424
|
return;
|
|
61982
62425
|
}
|
|
@@ -62025,7 +62468,8 @@ var ACMCertificateProvider = class {
|
|
|
62025
62468
|
if (cert.Options?.CertificateTransparencyLoggingPreference !== void 0) result["CertificateTransparencyLoggingPreference"] = cert.Options.CertificateTransparencyLoggingPreference;
|
|
62026
62469
|
if (cert.Options?.Export !== void 0) result["CertificateExport"] = cert.Options.Export;
|
|
62027
62470
|
try {
|
|
62028
|
-
|
|
62471
|
+
const tagsResp = await this.acmClient.send(new ListTagsForCertificateCommand({ CertificateArn: physicalId }));
|
|
62472
|
+
result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
|
|
62029
62473
|
} catch (err) {
|
|
62030
62474
|
if (!(err instanceof ResourceNotFoundException$7)) throw err;
|
|
62031
62475
|
}
|
|
@@ -62735,23 +63179,24 @@ async function deployCommand(stacks, options) {
|
|
|
62735
63179
|
let recreateTargets;
|
|
62736
63180
|
if (options.recreateViaCcApi?.length || options.recreateViaSdkProvider?.length) {
|
|
62737
63181
|
const stateForRecreateCheck = await stackStateBackend.getState(stackInfo.stackName, stackRegion);
|
|
63182
|
+
const syncValidation = validateRecreateTargets({
|
|
63183
|
+
template: stackInfo.template,
|
|
63184
|
+
state: stateForRecreateCheck?.state ?? {
|
|
63185
|
+
version: 9,
|
|
63186
|
+
stackName: stackInfo.stackName,
|
|
63187
|
+
region: stackRegion,
|
|
63188
|
+
resources: {},
|
|
63189
|
+
outputs: {},
|
|
63190
|
+
lastModified: Date.now()
|
|
63191
|
+
},
|
|
63192
|
+
recreateViaCcApi: options.recreateViaCcApi ?? [],
|
|
63193
|
+
recreateViaSdkProvider: options.recreateViaSdkProvider ?? [],
|
|
63194
|
+
allowUnsupportedProperties: new Set(options.allowUnsupportedProperties ?? []),
|
|
63195
|
+
forceStatefulRecreation: options.forceStatefulRecreation ?? false,
|
|
63196
|
+
hasSdkProvider: (rt) => stackProviderRegistry.getProviderType(rt) === "sdk"
|
|
63197
|
+
});
|
|
62738
63198
|
const validation = await probeAndRevalidateStateful({
|
|
62739
|
-
validation:
|
|
62740
|
-
template: stackInfo.template,
|
|
62741
|
-
state: stateForRecreateCheck?.state ?? {
|
|
62742
|
-
version: 9,
|
|
62743
|
-
stackName: stackInfo.stackName,
|
|
62744
|
-
region: stackRegion,
|
|
62745
|
-
resources: {},
|
|
62746
|
-
outputs: {},
|
|
62747
|
-
lastModified: Date.now()
|
|
62748
|
-
},
|
|
62749
|
-
recreateViaCcApi: options.recreateViaCcApi ?? [],
|
|
62750
|
-
recreateViaSdkProvider: options.recreateViaSdkProvider ?? [],
|
|
62751
|
-
allowUnsupportedProperties: new Set(options.allowUnsupportedProperties ?? []),
|
|
62752
|
-
forceStatefulRecreation: options.forceStatefulRecreation ?? false,
|
|
62753
|
-
hasSdkProvider: (rt) => stackProviderRegistry.getProviderType(rt) === "sdk"
|
|
62754
|
-
}),
|
|
63199
|
+
validation: syncValidation,
|
|
62755
63200
|
clients: {
|
|
62756
63201
|
s3: stackAwsClients.s3,
|
|
62757
63202
|
cloudWatchLogs: stackAwsClients.cloudWatchLogs,
|
|
@@ -63100,12 +63545,30 @@ function valuesEqual(a, b) {
|
|
|
63100
63545
|
* the expression that state stores post-redaction.
|
|
63101
63546
|
*
|
|
63102
63547
|
* `storedOutputs` is the bag currently in state. It is consulted for exactly
|
|
63103
|
-
*
|
|
63104
|
-
* #1942 at that branch
|
|
63548
|
+
* two decisions — the LITERAL `Export.Name` of a secret-bearing stack, see issue
|
|
63549
|
+
* #1942 at that branch, and whether a skipped-output record still applies, see
|
|
63550
|
+
* issue #2740 below — and never merged into the result: everything else here
|
|
63105
63551
|
* previews what the template says, and reading state anywhere else would make
|
|
63106
63552
|
* the preview agree with the past instead of with the next deploy.
|
|
63107
|
-
|
|
63108
|
-
|
|
63553
|
+
*
|
|
63554
|
+
* `skippedOutputKeys` (issue #2740) are the keys the LAST DEPLOY could not
|
|
63555
|
+
* resolve and skipped, already narrowed by the caller on BOTH halves of the
|
|
63556
|
+
* binding rule: the recorded digest still matches today's template, AND no
|
|
63557
|
+
* resource the output references is changing on this run
|
|
63558
|
+
* (`bindingSkippedOutputs`, over a pre-resolution snapshot and this run's
|
|
63559
|
+
* resource diff — see `skipped-outputs.ts` for why the snapshot is the
|
|
63560
|
+
* caller's and why the change map cannot come from the digest). Such a key,
|
|
63561
|
+
* while still absent from `storedOutputs`, is previewed as ABSENT — skipped
|
|
63562
|
+
* without resolving, no bag entry, no row — because that is what the next
|
|
63563
|
+
* deploy will leave in state: this resolver runs
|
|
63564
|
+
* with `skipDynamicReferences`, so a failure that happened inside a secret
|
|
63565
|
+
* lookup would otherwise assemble cleanly and preview an `ADD` the deploy will
|
|
63566
|
+
* never perform. NOT a resolution failure: the sibling outputs are previewed
|
|
63567
|
+
* normally, so a genuine change beside the broken output still renders and
|
|
63568
|
+
* `--fail` still exits 1 for it. The key appearing in state (a later deploy
|
|
63569
|
+
* resolved it) ends the suppression.
|
|
63570
|
+
*/
|
|
63571
|
+
async function resolveTemplateOutputs(template, resolveFn, conditions, storedOutputs, skippedOutputKeys) {
|
|
63109
63572
|
const logger = getLogger().child("OutputsDiff");
|
|
63110
63573
|
const outputs = Object.create(null);
|
|
63111
63574
|
const exportNames = /* @__PURE__ */ new Set();
|
|
@@ -63146,6 +63609,10 @@ async function resolveTemplateOutputs(template, resolveFn, conditions, storedOut
|
|
|
63146
63609
|
logger.debug(`Skipping output ${outputKey} — condition ${output.Condition} is false`);
|
|
63147
63610
|
continue;
|
|
63148
63611
|
}
|
|
63612
|
+
if (skippedOutputKeys?.has(outputKey) && !(storedOutputs !== void 0 && Object.prototype.hasOwnProperty.call(storedOutputs, outputKey))) {
|
|
63613
|
+
logger.debug(`Diff previewing output ${stripControlChars(outputKey)} as absent — the last deploy could not resolve it and its template inputs are unchanged`);
|
|
63614
|
+
continue;
|
|
63615
|
+
}
|
|
63149
63616
|
const sourceUsedSub = templateUsesSub(output.Value);
|
|
63150
63617
|
let value;
|
|
63151
63618
|
try {
|
|
@@ -63174,21 +63641,23 @@ async function resolveTemplateOutputs(template, resolveFn, conditions, storedOut
|
|
|
63174
63641
|
resolutionFailed = true;
|
|
63175
63642
|
continue;
|
|
63176
63643
|
}
|
|
63177
|
-
if (typeof exportName === "string" && !isUnresolvedValue(exportName, exportSourceUsedSub))
|
|
63178
|
-
|
|
63179
|
-
|
|
63180
|
-
|
|
63181
|
-
|
|
63182
|
-
|
|
63183
|
-
|
|
63184
|
-
|
|
63185
|
-
|
|
63186
|
-
|
|
63187
|
-
|
|
63188
|
-
|
|
63189
|
-
|
|
63190
|
-
|
|
63191
|
-
|
|
63644
|
+
if (typeof exportName === "string" && !isUnresolvedValue(exportName, exportSourceUsedSub)) {
|
|
63645
|
+
if (declaredExportIsIntrinsic && isSecretDynamicReference(exportName)) logger.debug(`Diff skipping export alias of ${stripControlChars(outputKey)} — the name carries a secret reference`);
|
|
63646
|
+
else if (isExportAliasCollision(exportName, outputKey, publishedOutputNames)) logger.debug(`Diff skipping export alias ${stripControlChars(exportName)} of ${stripControlChars(outputKey)} — collides with an output name`);
|
|
63647
|
+
else if (!declaredExportIsIntrinsic && secretSourceKeys.size > 0) {
|
|
63648
|
+
if (storedOutputs !== void 0 && Object.prototype.hasOwnProperty.call(storedOutputs, exportName)) {
|
|
63649
|
+
outputs[exportName] = value;
|
|
63650
|
+
exportNames.add(exportName);
|
|
63651
|
+
} else {
|
|
63652
|
+
logger.debug(`Diff cannot decide the export alias of ${stripControlChars(outputKey)} — a literal name may contain a resolved secret and state does not hold that key`);
|
|
63653
|
+
failedKeys.add(exportName);
|
|
63654
|
+
resolutionFailed = true;
|
|
63655
|
+
}
|
|
63656
|
+
} else {
|
|
63657
|
+
outputs[exportName] = value;
|
|
63658
|
+
exportNames.add(exportName);
|
|
63659
|
+
}
|
|
63660
|
+
} else resolutionFailed = true;
|
|
63192
63661
|
}
|
|
63193
63662
|
}
|
|
63194
63663
|
return {
|
|
@@ -64471,6 +64940,10 @@ function tokenValueForComparison(token, declaredType) {
|
|
|
64471
64940
|
async function computeStackDiff(currentState, template, region, stackName, stateBackend, diffCalculator, options = {}) {
|
|
64472
64941
|
const { parameters, canonicalizeProperties, cfnFallback, inheritSecretBearingTemplate } = options;
|
|
64473
64942
|
const intrinsicResolver = new IntrinsicFunctionResolver(region, { cfnFallback: cfnFallback ?? true });
|
|
64943
|
+
const outputsDigestSource = structuredClone({
|
|
64944
|
+
...template,
|
|
64945
|
+
Resources: {}
|
|
64946
|
+
});
|
|
64474
64947
|
let mergedParameters = parameters;
|
|
64475
64948
|
let parametersBound = false;
|
|
64476
64949
|
const tokenParameterNames = new Set(Object.entries(parameters ?? {}).filter(([, value]) => carriesDynamicReference(value)).map(([name]) => name));
|
|
@@ -64520,7 +64993,9 @@ async function computeStackDiff(currentState, template, region, stackName, state
|
|
|
64520
64993
|
skipDynamicReferences: true
|
|
64521
64994
|
});
|
|
64522
64995
|
const changes = await diffCalculator.calculateDiff(currentState, effectiveTemplate, resolveFn, canonicalizeProperties);
|
|
64523
|
-
const
|
|
64996
|
+
const changedLogicalIds = new Set([...changes.values()].filter((c) => c.changeType !== "NO_CHANGE").map((c) => c.logicalId));
|
|
64997
|
+
const bindingSkipped = bindingSkippedOutputs(outputsDigestSource, currentState.skippedOutputs, changedLogicalIds);
|
|
64998
|
+
const resolved = await resolveTemplateOutputs(effectiveTemplate, resolveFn, conditions, currentState.outputs, bindingSkipped);
|
|
64524
64999
|
let outputChanges = [];
|
|
64525
65000
|
if (resolved.resolutionFailed) {
|
|
64526
65001
|
if (computeOutputsDiff(currentState.outputs, resolved.outputs, resolved.exportNames, resolved.secretSourceKeys, {
|
|
@@ -64900,7 +65375,6 @@ function renderChangeLines(changes, logFn, ccApiRoutes) {
|
|
|
64900
65375
|
case "DELETE":
|
|
64901
65376
|
deleteCount++;
|
|
64902
65377
|
logFn(` [-] ${logicalId} (${change.resourceType})`);
|
|
64903
|
-
break;
|
|
64904
65378
|
}
|
|
64905
65379
|
return {
|
|
64906
65380
|
create: createCount,
|
|
@@ -64975,7 +65449,6 @@ function renderOutputChangeLines(outputChanges, logFn) {
|
|
|
64975
65449
|
counts.remove++;
|
|
64976
65450
|
logFn(` [-] ${name}${exported}`);
|
|
64977
65451
|
logFn(` old: ${renderOld(change)}`);
|
|
64978
|
-
break;
|
|
64979
65452
|
}
|
|
64980
65453
|
}
|
|
64981
65454
|
return counts;
|
|
@@ -65166,6 +65639,24 @@ var ScrubNeededError = class ScrubNeededError extends CdkdError {
|
|
|
65166
65639
|
* leak" from "scrub refused to look" — the two call for opposite responses
|
|
65167
65640
|
* (rotate the secret vs. re-spell the reference and re-run).
|
|
65168
65641
|
*/
|
|
65642
|
+
/**
|
|
65643
|
+
* An internal invariant of the exports-index repair, thrown where this code
|
|
65644
|
+
* believes a case is unreachable (issue #2667 review).
|
|
65645
|
+
*
|
|
65646
|
+
* A distinct CLASS because the repair runs inside the per-stack `try` whose
|
|
65647
|
+
* `catch` attributes everything to "the exports index could not be READ": a
|
|
65648
|
+
* plain `Error` there is reported as an S3 read failure, sending an operator to
|
|
65649
|
+
* their bucket policy over a bug in cdkd. That catch rethrows this class
|
|
65650
|
+
* instead, so it surfaces as the per-stack failure it actually is.
|
|
65651
|
+
*/
|
|
65652
|
+
var ScrubIndexInvariantError = class ScrubIndexInvariantError extends CdkdError {
|
|
65653
|
+
exitCode = 2;
|
|
65654
|
+
constructor(message) {
|
|
65655
|
+
super(message, "SCRUB_EXPORT_INDEX_INVARIANT");
|
|
65656
|
+
this.name = "ScrubIndexInvariantError";
|
|
65657
|
+
Object.setPrototypeOf(this, ScrubIndexInvariantError.prototype);
|
|
65658
|
+
}
|
|
65659
|
+
};
|
|
65169
65660
|
var ScrubRefusalError = class ScrubRefusalError extends CdkdError {
|
|
65170
65661
|
exitCode = 2;
|
|
65171
65662
|
constructor(message, code) {
|
|
@@ -65210,6 +65701,140 @@ function scrubStacksFailedError(failures) {
|
|
|
65210
65701
|
return new ScrubRefusalError(`${failures.length} stack(s) could not be scrubbed: ${failures.map((f) => f.stackName).join(", ")}. Each one's reason was logged as it happened — see the 'Scrub of <stack> failed:' line above for it.`, "SCRUB_STACKS_FAILED");
|
|
65211
65702
|
}
|
|
65212
65703
|
/**
|
|
65704
|
+
* Which of `entries` this stack's post-scrub `outputs` bag disagrees with.
|
|
65705
|
+
*
|
|
65706
|
+
* OWNERSHIP is read off the entry (`producerStack` / `producerRegion`), never
|
|
65707
|
+
* inferred from the name: a pre-v9 state record makes `importableOutputKeys`
|
|
65708
|
+
* return every output key (`src/types/state.ts`), so a rebuild can key an entry
|
|
65709
|
+
* by a plain output name, and on a name collision the index keeps the latest
|
|
65710
|
+
* writer — so a name alone says nothing about whose entry it is.
|
|
65711
|
+
*
|
|
65712
|
+
* The rule is CONVERGENCE, not a match against scrub's plaintext map. The map
|
|
65713
|
+
* is built by LIVE RESOLUTION against Secrets Manager / SSM, so it holds the
|
|
65714
|
+
* secret's value AS IT IS NOW; an index entry written before a rotation holds
|
|
65715
|
+
* the value as it was THEN, and the two are different strings. Keying on
|
|
65716
|
+
* `state.outputs` instead means the rule reads the record a redeploy and
|
|
65717
|
+
* `ExportIndexStore.rebuild()` both write, and the plaintext map is not
|
|
65718
|
+
* consulted here at all.
|
|
65719
|
+
*
|
|
65720
|
+
* The lookup runs in ONE direction, and the inverse is not available: an
|
|
65721
|
+
* `Export.Name` alias is written into the outputs bag itself
|
|
65722
|
+
* (`deploy-engine.ts`, beside `resolvedExportNames.push(exportName)`), so an
|
|
65723
|
+
* index entry NAME is a key of that bag — while `ExportIndexEntry` carries no
|
|
65724
|
+
* output-key field, so nothing maps an entry back to the output it came from.
|
|
65725
|
+
*
|
|
65726
|
+
* WHAT CAN STILL GO WRONG. A name absent from `outputs` is reported and
|
|
65727
|
+
* nothing is written for it, so an entry in that position keeps its value —
|
|
65728
|
+
* `outputs` is `undefined` here whenever the stack has no state record at all,
|
|
65729
|
+
* which puts every one of its entries in that arm. An entry whose state value
|
|
65730
|
+
* carries no `{{resolve:` is left alone, so a plaintext an index entry holds
|
|
65731
|
+
* whose state counterpart is also plaintext is not reported by this pass and
|
|
65732
|
+
* not written by it. And `entries` is one region's index: an entry in another
|
|
65733
|
+
* region's `exports.json` is outside this call.
|
|
65734
|
+
*/
|
|
65735
|
+
function planExportIndexRepair(entries, stackName, producerRegion, outputs) {
|
|
65736
|
+
const examined = [];
|
|
65737
|
+
const findings = [];
|
|
65738
|
+
for (const [exportName, entry] of entries) {
|
|
65739
|
+
if (entry.producerStack !== stackName || entry.producerRegion !== producerRegion) continue;
|
|
65740
|
+
examined.push(exportName);
|
|
65741
|
+
if (outputs === void 0 || !Object.hasOwn(outputs, exportName)) {
|
|
65742
|
+
findings.push({
|
|
65743
|
+
kind: "absent",
|
|
65744
|
+
exportName
|
|
65745
|
+
});
|
|
65746
|
+
continue;
|
|
65747
|
+
}
|
|
65748
|
+
const stateValue = outputs[exportName];
|
|
65749
|
+
if (!carriesDynamicReference(stateValue)) continue;
|
|
65750
|
+
if (JSON.stringify(entry.value) === JSON.stringify(stateValue)) continue;
|
|
65751
|
+
findings.push({
|
|
65752
|
+
kind: "converge",
|
|
65753
|
+
exportName,
|
|
65754
|
+
stateValue
|
|
65755
|
+
});
|
|
65756
|
+
}
|
|
65757
|
+
return {
|
|
65758
|
+
examined,
|
|
65759
|
+
findings
|
|
65760
|
+
};
|
|
65761
|
+
}
|
|
65762
|
+
/**
|
|
65763
|
+
* The error a run ends with when `state.json` was rewritten and the exports
|
|
65764
|
+
* index was not (issue #2667).
|
|
65765
|
+
*
|
|
65766
|
+
* EXPLICIT rather than a warn: an entry this run went to converge and did not
|
|
65767
|
+
* keeps the value it held, in a region-wide object every principal with
|
|
65768
|
+
* `s3:GetObject` on the state bucket can read. `ExportIndexStore.patchEntry`
|
|
65769
|
+
* does not throw on a non-retryable S3 failure or an exhausted If-Match budget
|
|
65770
|
+
* — it logs and returns `false` — so without this the command would exit 0
|
|
65771
|
+
* over exactly that.
|
|
65772
|
+
*
|
|
65773
|
+
* Naming the remainder is what makes the re-run instruction actionable: the
|
|
65774
|
+
* convergence rule reads `state.outputs`, which a completed half already
|
|
65775
|
+
* matches, so a second invocation of the same command decides `converge` only
|
|
65776
|
+
* for the entries still differing.
|
|
65777
|
+
*/
|
|
65778
|
+
function exportIndexIncompleteError(unwritten, unreadable, dryRun) {
|
|
65779
|
+
const parts = [dryRun ? "no state written (--dry-run)" : "state.json complete"];
|
|
65780
|
+
if (unwritten.length > 0) parts.push(`${unwritten.length} exports index entr${unwritten.length === 1 ? "y" : "ies"} unwritten (${unwritten.map((u) => `${u.shown} in ${u.region}`).join(", ")})`);
|
|
65781
|
+
if (unreadable.length > 0) parts.push(`${unreadable.length} region(s) whose exports index could not be read (${unreadable.map((u) => `${u.region}: ${u.reason}`).join("; ")})`);
|
|
65782
|
+
return new ScrubRefusalError(`${parts.join("; ")}. Re-run the same command once the cause is cleared: each entry is written only while its value differs from the producer's state.outputs, so a re-run writes the remainder and leaves the rest alone.`, "SCRUB_EXPORT_INDEX_INCOMPLETE");
|
|
65783
|
+
}
|
|
65784
|
+
/**
|
|
65785
|
+
* Converge one scrubbed stack's entries in one region's exports index.
|
|
65786
|
+
*
|
|
65787
|
+
* Run as its OWN step by `scrubCommand`, after `scrubStack` returned — never
|
|
65788
|
+
* from inside the resolver. `scrubStack`'s resolve contexts deliberately
|
|
65789
|
+
* withhold `exportIndex` so `resolveImportValue`'s scan arm cannot `patchEntry`
|
|
65790
|
+
* as a side effect of resolution, at a point in the run nothing chose; that
|
|
65791
|
+
* withholding is unchanged, and this is a separate call with its own condition.
|
|
65792
|
+
*
|
|
65793
|
+
* Under `dryRun` no `patchEntry` is issued. The READ still happens:
|
|
65794
|
+
* `readPersistedEntries` is a GET that does not rebuild, so the audit half runs
|
|
65795
|
+
* without an S3 write.
|
|
65796
|
+
*
|
|
65797
|
+
* TWO PROPERTIES OF THE WRITE a reader will ask about. It runs OUTSIDE the
|
|
65798
|
+
* stack lock — `scrubStack` releases that in its own `finally` — because the
|
|
65799
|
+
* lock guards `state.json`, one key per stack, while `exports.json` is shared
|
|
65800
|
+
* region-wide and carries its own If-Match optimistic lock plus bounded retry;
|
|
65801
|
+
* that is the same concurrency control the deploy path's index write relies on.
|
|
65802
|
+
* And it is ONE PutObject PER ENTRY rather than one for the batch: `patchEntry`
|
|
65803
|
+
* is the operation that changes a value without touching membership, and a
|
|
65804
|
+
* remediation command pays that round trip so a failure names the entry it
|
|
65805
|
+
* could not write.
|
|
65806
|
+
*/
|
|
65807
|
+
async function repairExportIndexForStack(store, stackName, producerRegion, outputs, opts) {
|
|
65808
|
+
const entries = await store.readPersistedEntries();
|
|
65809
|
+
if (!entries) return {
|
|
65810
|
+
examined: [],
|
|
65811
|
+
findings: [],
|
|
65812
|
+
unwritten: []
|
|
65813
|
+
};
|
|
65814
|
+
const plan = planExportIndexRepair(entries, stackName, producerRegion, outputs);
|
|
65815
|
+
const unwritten = [];
|
|
65816
|
+
if (opts.dryRun) return {
|
|
65817
|
+
...plan,
|
|
65818
|
+
unwritten
|
|
65819
|
+
};
|
|
65820
|
+
for (const finding of plan.findings) {
|
|
65821
|
+
if (finding.kind !== "converge") continue;
|
|
65822
|
+
const entry = entries.get(finding.exportName);
|
|
65823
|
+
if (!entry) throw new ScrubIndexInvariantError("exports index repair: a planned entry is absent from the snapshot it was planned from");
|
|
65824
|
+
if (!await store.patchEntry(finding.exportName, {
|
|
65825
|
+
...entry,
|
|
65826
|
+
value: finding.stateValue
|
|
65827
|
+
}, { requireOwner: {
|
|
65828
|
+
producerStack: stackName,
|
|
65829
|
+
producerRegion
|
|
65830
|
+
} })) unwritten.push(finding.exportName);
|
|
65831
|
+
}
|
|
65832
|
+
return {
|
|
65833
|
+
...plan,
|
|
65834
|
+
unwritten
|
|
65835
|
+
};
|
|
65836
|
+
}
|
|
65837
|
+
/**
|
|
65213
65838
|
* `cdkd scrub` — rewrite persisted state so any resolved secret dynamic
|
|
65214
65839
|
* reference is stored as its UNRESOLVED expression rather than the plaintext
|
|
65215
65840
|
* value (GHSA fix). "Secret" here is whatever the RESOLVER classifies as one,
|
|
@@ -65229,10 +65854,14 @@ function scrubStacksFailedError(failures) {
|
|
|
65229
65854
|
* re-resolves each resource's properties to learn the resolved secret VALUES
|
|
65230
65855
|
* (recorded, never printed or re-persisted), and replaces those values in the
|
|
65231
65856
|
* state record's `properties` / `attributes` / `observedProperties` with the
|
|
65232
|
-
* expression. No AWS resource is created, updated, or deleted
|
|
65233
|
-
* is
|
|
65234
|
-
*
|
|
65235
|
-
*
|
|
65857
|
+
* expression. No AWS resource is created, updated, or deleted. What it WRITES
|
|
65858
|
+
* is the state bucket: each targeted stack's state.json, and — since issue
|
|
65859
|
+
* [#2667](https://github.com/go-to-k/cdkd/issues/2667) — the entries a scrubbed
|
|
65860
|
+
* stack publishes in the shared exports index at
|
|
65861
|
+
* `{prefix}/_index/{region}/exports.json`, as a separate step after that
|
|
65862
|
+
* stack's state.json write. This is why it is a top-level command and not
|
|
65863
|
+
* `cdkd state scrub` — the `cdkd state ...` family operates on the state bucket
|
|
65864
|
+
* alone and deliberately needs no CDK code.
|
|
65236
65865
|
*
|
|
65237
65866
|
* IMPORTANT: scrubbing does not un-expose an already-leaked secret. A value
|
|
65238
65867
|
* that was stored in plaintext should be treated as compromised and ROTATED at
|
|
@@ -65290,10 +65919,11 @@ async function scrubCommand(stacks, options) {
|
|
|
65290
65919
|
if (!app) throw new Error("CDK app is required (scrub needs the template to identify secret references). Pass --app, set CDKD_APP, or add \"app\" to cdk.json.");
|
|
65291
65920
|
const region = namedCliRegion(options.region) ?? "us-east-1";
|
|
65292
65921
|
const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
|
|
65293
|
-
|
|
65922
|
+
const awsClients = new AwsClients({
|
|
65294
65923
|
...options.region && { region: options.region },
|
|
65295
65924
|
...options.profile && { profile: options.profile }
|
|
65296
|
-
})
|
|
65925
|
+
});
|
|
65926
|
+
setAwsClients(awsClients);
|
|
65297
65927
|
logger.info(synthesisStatusMessage(app, "Synthesizing CDK app..."));
|
|
65298
65928
|
const synthesizer = new Synthesizer();
|
|
65299
65929
|
const context = parseContextOptions(options.context);
|
|
@@ -65333,6 +65963,20 @@ async function scrubCommand(stacks, options) {
|
|
|
65333
65963
|
let totalStacksWithUnscrubbableKeys = 0;
|
|
65334
65964
|
let totalStacksWithUnverifiableReads = 0;
|
|
65335
65965
|
const failures = [];
|
|
65966
|
+
const exportIndexStores = /* @__PURE__ */ new Map();
|
|
65967
|
+
const exportIndexFor = (indexRegion) => {
|
|
65968
|
+
const existing = exportIndexStores.get(indexRegion);
|
|
65969
|
+
if (existing) return existing;
|
|
65970
|
+
const store = new ExportIndexStore(stateS3.s3, stateBucket, options.statePrefix, indexRegion, stateBackend);
|
|
65971
|
+
exportIndexStores.set(indexRegion, store);
|
|
65972
|
+
return store;
|
|
65973
|
+
};
|
|
65974
|
+
const examinedIndexNames = /* @__PURE__ */ new Map();
|
|
65975
|
+
let totalIndexEntriesConverged = 0;
|
|
65976
|
+
let totalIndexEntriesAbsent = 0;
|
|
65977
|
+
let totalIndexEntriesUnexamined = 0;
|
|
65978
|
+
const indexUnwritten = [];
|
|
65979
|
+
const indexUnreadable = [];
|
|
65336
65980
|
for (const stack of targetStacks) {
|
|
65337
65981
|
const stackRegion = stack.region || region;
|
|
65338
65982
|
let scrubbed;
|
|
@@ -65349,10 +65993,51 @@ async function scrubCommand(stacks, options) {
|
|
|
65349
65993
|
if (err instanceof Error && err.stack) logger.debug("Stack trace:", err.stack);
|
|
65350
65994
|
continue;
|
|
65351
65995
|
}
|
|
65996
|
+
let indexConverged = 0;
|
|
65997
|
+
try {
|
|
65998
|
+
const repair = await repairExportIndexForStack(exportIndexFor(stackRegion), stack.stackName, stackRegion, scrubbed.outputs, { dryRun: options.dryRun ?? false });
|
|
65999
|
+
const seen = examinedIndexNames.get(stackRegion) ?? /* @__PURE__ */ new Set();
|
|
66000
|
+
for (const name of repair.examined) seen.add(name);
|
|
66001
|
+
examinedIndexNames.set(stackRegion, seen);
|
|
66002
|
+
const named = (exportName) => {
|
|
66003
|
+
const shown = scrubbed.exportNameDisplay(exportName);
|
|
66004
|
+
switch (shown.kind) {
|
|
66005
|
+
case "safe": return `'${shown.text}'`;
|
|
66006
|
+
case "masked": return `(masked: "${shown.text}")`;
|
|
66007
|
+
case "withheld": return "(name withheld: it holds a secret this run recorded)";
|
|
66008
|
+
}
|
|
66009
|
+
};
|
|
66010
|
+
const unwrittenNames = new Set(repair.unwritten);
|
|
66011
|
+
for (const exportName of repair.unwritten) indexUnwritten.push({
|
|
66012
|
+
region: stackRegion,
|
|
66013
|
+
shown: named(exportName)
|
|
66014
|
+
});
|
|
66015
|
+
for (const finding of repair.findings) if (finding.kind === "converge") {
|
|
66016
|
+
indexConverged++;
|
|
66017
|
+
if (!options.dryRun && unwrittenNames.has(finding.exportName)) logger.warn(`Exports index entry ${named(finding.exportName)} (${stackRegion}) differs from ${stack.stackName}'s state.outputs and could NOT be written — it keeps the value it holds.`);
|
|
66018
|
+
else {
|
|
66019
|
+
totalIndexEntriesConverged++;
|
|
66020
|
+
logger.info(`${options.dryRun ? "Would converge" : "Converged"} exports index entry ${named(finding.exportName)} (${stackRegion}) to ${stack.stackName}'s state.outputs value.`);
|
|
66021
|
+
}
|
|
66022
|
+
} else {
|
|
66023
|
+
totalIndexEntriesAbsent++;
|
|
66024
|
+
logger.warn(`Exports index entry ${named(finding.exportName)} (${stackRegion}) is published by ${stack.stackName}, whose state.outputs has no key of that name — nothing was written for it and it keeps the value it holds. Redeploy ${stack.stackName} to rewrite the index from its own outputs.`);
|
|
66025
|
+
}
|
|
66026
|
+
} catch (err) {
|
|
66027
|
+
if (err instanceof ScrubIndexInvariantError) throw err;
|
|
66028
|
+
if (!indexUnreadable.some((u) => u.region === stackRegion)) {
|
|
66029
|
+
const reason = displaySafe(err instanceof Error ? err.message : String(err));
|
|
66030
|
+
indexUnreadable.push({
|
|
66031
|
+
region: stackRegion,
|
|
66032
|
+
reason
|
|
66033
|
+
});
|
|
66034
|
+
logger.error(`Exports index for ${stackRegion} could not be read (first seen while scrubbing ${stack.stackName}): ${reason}`);
|
|
66035
|
+
}
|
|
66036
|
+
}
|
|
65352
66037
|
if (scrubbed.recordsChanged > 0) {
|
|
65353
66038
|
totalStacksScrubbed++;
|
|
65354
66039
|
logger.info(`${options.dryRun ? "Would scrub" : "Scrubbed"} ${scrubbed.recordsChanged} resource record(s) in ${stack.stackName}`);
|
|
65355
|
-
} else if (scrubbed.secretBearingKeys === 0 && scrubbed.unverifiableReads === 0) logger.info(`No plaintext secrets found in ${stack.stackName}`);
|
|
66040
|
+
} else if (scrubbed.secretBearingKeys === 0 && scrubbed.unverifiableReads === 0 && indexConverged === 0) logger.info(`No plaintext secrets found in ${stack.stackName}`);
|
|
65356
66041
|
if (scrubbed.unverifiableReads > 0) {
|
|
65357
66042
|
totalStacksWithUnverifiableReads++;
|
|
65358
66043
|
logger.warn(`${scrubbed.unverifiableReads} cross-stack read(s) in ${stack.stackName} could NOT be verified — cdkd declines them by design (see the warnings above), so this stack is not reported clean.`);
|
|
@@ -65362,7 +66047,28 @@ async function scrubCommand(stacks, options) {
|
|
|
65362
66047
|
logger.warn(`${scrubbed.secretBearingKeys} output KEY(s) in ${stack.stackName} hold plaintext and CANNOT be scrubbed — rename the Export.Name and redeploy (see the warning above).`);
|
|
65363
66048
|
}
|
|
65364
66049
|
}
|
|
65365
|
-
|
|
66050
|
+
for (const [indexRegion, store] of exportIndexStores) {
|
|
66051
|
+
if (indexUnreadable.some((u) => u.region === indexRegion)) continue;
|
|
66052
|
+
let entries;
|
|
66053
|
+
try {
|
|
66054
|
+
entries = await store.readPersistedEntries();
|
|
66055
|
+
} catch (err) {
|
|
66056
|
+
const reason = displaySafe(err instanceof Error ? err.message : String(err));
|
|
66057
|
+
indexUnreadable.push({
|
|
66058
|
+
region: indexRegion,
|
|
66059
|
+
reason
|
|
66060
|
+
});
|
|
66061
|
+
logger.error(`Exports index for ${indexRegion} could not be read for the coverage report: ${reason}`);
|
|
66062
|
+
continue;
|
|
66063
|
+
}
|
|
66064
|
+
if (!entries) continue;
|
|
66065
|
+
const examined = examinedIndexNames.get(indexRegion) ?? /* @__PURE__ */ new Set();
|
|
66066
|
+
const unexamined = [...entries.keys()].filter((name) => !examined.has(name));
|
|
66067
|
+
if (unexamined.length === 0) continue;
|
|
66068
|
+
totalIndexEntriesUnexamined += unexamined.length;
|
|
66069
|
+
logger.info(`Exports index (${indexRegion}): ${unexamined.length} entr${unexamined.length === 1 ? "y is" : "ies are"} published by a producer this run did not scrub, so nothing here read ${unexamined.length === 1 ? "its" : "their"} value. Coverage composes across the apps sharing this bucket and region — run each app's own cdkd scrub to reach its entries.`);
|
|
66070
|
+
}
|
|
66071
|
+
if (totalStacksScrubbed === 0 && totalStacksWithUnscrubbableKeys === 0 && totalStacksWithUnverifiableReads === 0 && totalIndexEntriesConverged === 0 && indexUnwritten.length === 0 && indexUnreadable.length === 0) {
|
|
65366
66072
|
if (failures.length === 0) {
|
|
65367
66073
|
logger.info("\nNo plaintext secrets found in any target stack state. Nothing to scrub.");
|
|
65368
66074
|
return;
|
|
@@ -65373,16 +66079,25 @@ async function scrubCommand(stacks, options) {
|
|
|
65373
66079
|
const failureNote = failures.length > 0 ? ` ${failures.length} stack(s) could NOT be scrubbed at all and are NOT covered by this summary — see the errors above.` : "";
|
|
65374
66080
|
const keyNote = totalStacksWithUnscrubbableKeys > 0 ? ` ${totalStacksWithUnscrubbableKeys} stack(s) hold plaintext in an output KEY, which cdkd scrub cannot rewrite — rename that output's Export.Name and redeploy.` : "";
|
|
65375
66081
|
const unverifiableNote = totalStacksWithUnverifiableReads > 0 ? ` ${totalStacksWithUnverifiableReads} stack(s) carry a cross-stack read cdkd declines to perform, so their imported values could NOT be checked — export a non-secret value (e.g. the secret's ARN) from the producer, or reference it from within its own account.` : "";
|
|
66082
|
+
const indexNote = (() => {
|
|
66083
|
+
const parts = [];
|
|
66084
|
+
if (totalIndexEntriesConverged > 0) parts.push(options.dryRun ? ` ${totalIndexEntriesConverged} exports index entr${totalIndexEntriesConverged === 1 ? "y" : "ies"} differ from the producer's scrubbed state.outputs and would be converged to it (--dry-run, nothing written).` : ` ${totalIndexEntriesConverged} exports index entr${totalIndexEntriesConverged === 1 ? "y" : "ies"} converged to the producer's state.outputs value. exports.json is written with the same PutObject state.json is, so on a VERSIONED bucket the pre-repair body survives as a noncurrent version and stays readable with GetObject and a VersionId.`);
|
|
66085
|
+
if (totalIndexEntriesAbsent > 0) parts.push(` ${totalIndexEntriesAbsent} exports index entr${totalIndexEntriesAbsent === 1 ? "y is" : "ies are"} published by a scrubbed stack under a name its state.outputs does not carry; nothing was written for ${totalIndexEntriesAbsent === 1 ? "it" : "them"} — redeploy that producer.`);
|
|
66086
|
+
if (totalIndexEntriesUnexamined > 0) parts.push(` ${totalIndexEntriesUnexamined} exports index entr${totalIndexEntriesUnexamined === 1 ? "y" : "ies"} belong to producers outside this run and were NOT examined.`);
|
|
66087
|
+
return parts.join("");
|
|
66088
|
+
})();
|
|
65376
66089
|
if (options.dryRun) {
|
|
65377
|
-
if (totalStacksScrubbed > 0) logger.info(`\nPlan: ${totalStacksScrubbed} stack(s) hold plaintext secrets and would be scrubbed (--dry-run, no state written).${keyNote}${unverifiableNote}${failureNote} ROTATE any exposed secret in Secrets Manager.`);
|
|
65378
|
-
else logger.info(`\nPlan:
|
|
66090
|
+
if (totalStacksScrubbed > 0) logger.info(`\nPlan: ${totalStacksScrubbed} stack(s) hold plaintext secrets and would be scrubbed (--dry-run, no state written).${keyNote}${unverifiableNote}${indexNote}${failureNote} ROTATE any exposed secret in Secrets Manager.`);
|
|
66091
|
+
else logger.info(`\nPlan: no state record can be rewritten.${keyNote}${unverifiableNote}${indexNote}${failureNote} ROTATE any exposed secret.`);
|
|
65379
66092
|
if (failures.length > 0) throw scrubStacksFailedError(failures);
|
|
66093
|
+
if (indexUnwritten.length > 0 || indexUnreadable.length > 0) throw exportIndexIncompleteError(indexUnwritten, indexUnreadable, true);
|
|
65380
66094
|
if (options.fail) throw new ScrubNeededError();
|
|
65381
66095
|
return;
|
|
65382
66096
|
}
|
|
65383
|
-
if (totalStacksScrubbed > 0) logger.info(`\nDone: scrubbed ${totalStacksScrubbed} stack(s). The CURRENT state.json no longer holds the plaintext. Where the state bucket is VERSIONED — which cdkd bootstrap enables — the pre-scrub body survives as a noncurrent version, stays readable with GetObject and a VersionId, and scrub does not purge it. So a value that was ever persisted must be treated as compromised — ROTATE it in Secrets Manager (scrub matches the current value, so scrub BEFORE rotating); rotation is what makes any surviving version harmless.${keyNote}${unverifiableNote}${failureNote}`);
|
|
65384
|
-
else logger.info(`\
|
|
66097
|
+
if (totalStacksScrubbed > 0) logger.info(`\nDone: scrubbed ${totalStacksScrubbed} stack(s). The CURRENT state.json no longer holds the plaintext. Where the state bucket is VERSIONED — which cdkd bootstrap enables — the pre-scrub body survives as a noncurrent version, stays readable with GetObject and a VersionId, and scrub does not purge it. So a value that was ever persisted must be treated as compromised — ROTATE it in Secrets Manager (scrub matches the current value, so scrub BEFORE rotating); rotation is what makes any surviving version harmless.${keyNote}${unverifiableNote}${indexNote}${failureNote}`);
|
|
66098
|
+
else logger.info(`\nNo state record was rewritten.${keyNote}${unverifiableNote}${indexNote}${failureNote} ROTATE any exposed secret.`);
|
|
65385
66099
|
if (failures.length > 0) throw scrubStacksFailedError(failures);
|
|
66100
|
+
if (indexUnwritten.length > 0 || indexUnreadable.length > 0) throw exportIndexIncompleteError(indexUnwritten, indexUnreadable, false);
|
|
65386
66101
|
if (options.fail && (totalStacksWithUnscrubbableKeys > 0 || totalStacksWithUnverifiableReads > 0)) throw new ScrubNeededError();
|
|
65387
66102
|
}
|
|
65388
66103
|
/**
|
|
@@ -67006,7 +67721,8 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
67006
67721
|
recordsChanged: 0,
|
|
67007
67722
|
secretsFound: 0,
|
|
67008
67723
|
secretBearingKeys: 0,
|
|
67009
|
-
unverifiableReads: 0
|
|
67724
|
+
unverifiableReads: 0,
|
|
67725
|
+
exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
|
|
67010
67726
|
};
|
|
67011
67727
|
}
|
|
67012
67728
|
const state = loaded.state;
|
|
@@ -67149,7 +67865,9 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
67149
67865
|
recordsChanged: 0,
|
|
67150
67866
|
secretsFound: 0,
|
|
67151
67867
|
secretBearingKeys: secretBearingKeys.length,
|
|
67152
|
-
unverifiableReads: prePassFindings.unverifiable.length
|
|
67868
|
+
unverifiableReads: prePassFindings.unverifiable.length,
|
|
67869
|
+
outputs: state.outputs,
|
|
67870
|
+
exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
|
|
67153
67871
|
};
|
|
67154
67872
|
let recordsChanged = 0;
|
|
67155
67873
|
const newResources = {};
|
|
@@ -67168,8 +67886,9 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
67168
67886
|
const newOutputs = redactUnaccountedOutputs(outputSecrets.size > 0 ? redactSecretsForState(state.outputs, outputSecrets, outputsSourceUntrusted ? void 0 : outputsTemplateSource, TEMPLATE_SOURCED_RULES) : state.outputs, state.outputs, accountedOutputKeys, allRecordedSecrets(outputSecrets, perResourceSecrets));
|
|
67169
67887
|
if (JSON.stringify(newOutputs) !== JSON.stringify(state.outputs)) recordsChanged++;
|
|
67170
67888
|
if (recordsChanged > 0 && !opts.dryRun) {
|
|
67889
|
+
const { skippedOutputs: _droppedByScrub, ...carriedState } = state;
|
|
67171
67890
|
const nextState = {
|
|
67172
|
-
...
|
|
67891
|
+
...carriedState,
|
|
67173
67892
|
resources: newResources,
|
|
67174
67893
|
outputs: newOutputs,
|
|
67175
67894
|
lastModified: Date.now()
|
|
@@ -67180,7 +67899,9 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
67180
67899
|
recordsChanged,
|
|
67181
67900
|
secretsFound: totalSecrets,
|
|
67182
67901
|
secretBearingKeys: secretBearingKeys.length,
|
|
67183
|
-
unverifiableReads: prePassFindings.unverifiable.length
|
|
67902
|
+
unverifiableReads: prePassFindings.unverifiable.length,
|
|
67903
|
+
outputs: newOutputs,
|
|
67904
|
+
exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
|
|
67184
67905
|
};
|
|
67185
67906
|
} catch (err) {
|
|
67186
67907
|
throw maskSecretsInError(err, allRecordedSecrets(outputSecrets, perResourceSecrets));
|
|
@@ -69332,18 +70053,19 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
|
|
|
69332
70053
|
unorderedPaths
|
|
69333
70054
|
}), secrets, secretResolutionFailed ? seededSecretPaths : secretPaths, maskSecretPaths);
|
|
69334
70055
|
const notComparedCause = secretResolutionFailed ? "refused" : unresolvedTokens.size > 0 ? "unresolvedToken" : void 0;
|
|
69335
|
-
if (reported.changes.length === 0)
|
|
69336
|
-
|
|
69337
|
-
|
|
69338
|
-
|
|
69339
|
-
|
|
69340
|
-
|
|
69341
|
-
|
|
69342
|
-
|
|
69343
|
-
|
|
69344
|
-
|
|
69345
|
-
|
|
69346
|
-
|
|
70056
|
+
if (reported.changes.length === 0) {
|
|
70057
|
+
if (notComparedCause !== void 0) outcomes.push({
|
|
70058
|
+
kind: "notCompared",
|
|
70059
|
+
logicalId,
|
|
70060
|
+
resourceType: resource.resourceType,
|
|
70061
|
+
notComparedCause
|
|
70062
|
+
});
|
|
70063
|
+
else outcomes.push({
|
|
70064
|
+
kind: "clean",
|
|
70065
|
+
logicalId,
|
|
70066
|
+
resourceType: resource.resourceType
|
|
70067
|
+
});
|
|
70068
|
+
} else outcomes.push({
|
|
69347
70069
|
kind: "drifted",
|
|
69348
70070
|
logicalId,
|
|
69349
70071
|
resourceType: resource.resourceType,
|
|
@@ -69524,8 +70246,9 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
|
|
|
69524
70246
|
properties: redactedBaseline
|
|
69525
70247
|
};
|
|
69526
70248
|
}
|
|
70249
|
+
const { skippedOutputs: _droppedByAccept, ...carriedState } = report.state;
|
|
69527
70250
|
const newState = {
|
|
69528
|
-
...
|
|
70251
|
+
...carriedState,
|
|
69529
70252
|
resources,
|
|
69530
70253
|
lastModified: Date.now()
|
|
69531
70254
|
};
|
|
@@ -70345,8 +71068,9 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
|
|
|
70345
71068
|
};
|
|
70346
71069
|
}
|
|
70347
71070
|
if (recordedCount === 0) continue;
|
|
71071
|
+
const { skippedOutputs: _droppedByRevert, ...carriedState } = report.state;
|
|
70348
71072
|
const newState = {
|
|
70349
|
-
...
|
|
71073
|
+
...carriedState,
|
|
70350
71074
|
resources,
|
|
70351
71075
|
lastModified: Date.now()
|
|
70352
71076
|
};
|
|
@@ -70585,10 +71309,11 @@ function writeHumanReport(reports) {
|
|
|
70585
71309
|
const inspected = inspectedCount;
|
|
70586
71310
|
const notCompared = notComparedOutcomes(report);
|
|
70587
71311
|
const checked = inspected - notCompared.length;
|
|
70588
|
-
if (drifted.length === 0)
|
|
70589
|
-
|
|
70590
|
-
|
|
70591
|
-
|
|
71312
|
+
if (drifted.length === 0) {
|
|
71313
|
+
if (notCompared.length === 0 && checked === 0 && report.outcomes.length > 0) process.stdout.write(`⚠ ${report.stackName} (${report.region}): no drift detected, but NOTHING was compared — 0 of ${report.outcomes.length} resource${report.outcomes.length === 1 ? "" : "s"} checked (${unsupported.length} unsupported, ${skippedCount} skipped)\n`);
|
|
71314
|
+
else if (notCompared.length > 0) process.stdout.write(`⚠ ${report.stackName} (${report.region}): no drift detected, but ${checked} of ${inspected} resource${inspected === 1 ? "" : "s"} fully checked ` + (notCompared.some((n) => n.cause === "readFailed") ? `(${notCompared.length} not fully compared), ` : `(${notCompared.length} only partially compared), `) + `${unsupported.length} unsupported\n`);
|
|
71315
|
+
else process.stdout.write(`✓ ${report.stackName} (${report.region}): no drift detected (${checked} resource${checked === 1 ? "" : "s"} checked, ${unsupported.length} unsupported)\n`);
|
|
71316
|
+
} else {
|
|
70592
71317
|
const word = drifted.length === 1 ? "resource" : "resources";
|
|
70593
71318
|
process.stdout.write(`\n⚠ ${report.stackName} (${report.region}): drift detected on ${drifted.length} ${word}\n\n`);
|
|
70594
71319
|
for (const o of drifted) {
|
|
@@ -70860,12 +71585,14 @@ async function destroyCommand(stackArgs, options) {
|
|
|
70860
71585
|
const refs = stateRefsByName.get(stackName) ?? [];
|
|
70861
71586
|
const synthStack = appStacks.find((s) => s.stackName === stackName);
|
|
70862
71587
|
const synthRegion = synthStack?.region;
|
|
70863
|
-
if (synthStack?.terminationProtection === true)
|
|
70864
|
-
|
|
70865
|
-
|
|
70866
|
-
|
|
70867
|
-
|
|
70868
|
-
|
|
71588
|
+
if (synthStack?.terminationProtection === true) {
|
|
71589
|
+
if (options.removeProtection) logger.warn(`Stack ${stackName} has terminationProtection: true — bypassing because --remove-protection set`);
|
|
71590
|
+
else {
|
|
71591
|
+
const err = new StackTerminationProtectionError(stackName);
|
|
71592
|
+
logger.error(` ✗ ${err.message}`);
|
|
71593
|
+
totalErrors++;
|
|
71594
|
+
continue;
|
|
71595
|
+
}
|
|
70869
71596
|
}
|
|
70870
71597
|
let stackTargetRegion;
|
|
70871
71598
|
if (refs.length === 0) {
|
|
@@ -75121,7 +75848,7 @@ async function stateListCommand(options) {
|
|
|
75121
75848
|
* shape; plain `tree` renders `tree(1)`-style box-drawing.
|
|
75122
75849
|
*/
|
|
75123
75850
|
async function renderTreeMode(refs, stateBackend, asJson) {
|
|
75124
|
-
const
|
|
75851
|
+
const entries = await Promise.all(refs.map(async (ref) => {
|
|
75125
75852
|
if (!ref.region) return { stackName: ref.stackName };
|
|
75126
75853
|
let state;
|
|
75127
75854
|
try {
|
|
@@ -75139,7 +75866,8 @@ async function renderTreeMode(refs, stateBackend, asJson) {
|
|
|
75139
75866
|
...state?.parentLogicalId !== void 0 && { parentLogicalId: state.parentLogicalId },
|
|
75140
75867
|
...state?.parentRegion !== void 0 && { parentRegion: state.parentRegion }
|
|
75141
75868
|
};
|
|
75142
|
-
}))
|
|
75869
|
+
}));
|
|
75870
|
+
const roots = buildStackTree(entries);
|
|
75143
75871
|
if (asJson) {
|
|
75144
75872
|
process.stdout.write(`${JSON.stringify(stackTreeToJson(roots), null, 2)}\n`);
|
|
75145
75873
|
return;
|
|
@@ -75905,11 +76633,12 @@ async function listAssetStorageMarkers(s3, bucket) {
|
|
|
75905
76633
|
for (const key of keys) {
|
|
75906
76634
|
const region = key.slice(BOOTSTRAP_MARKER_PREFIX.length, -5);
|
|
75907
76635
|
try {
|
|
75908
|
-
const
|
|
76636
|
+
const body = await (await s3.send(new GetObjectCommand({
|
|
75909
76637
|
Bucket: bucket,
|
|
75910
76638
|
Key: key,
|
|
75911
76639
|
...await expectedOwnerParam(s3)
|
|
75912
|
-
}))).Body?.transformToString() ?? ""
|
|
76640
|
+
}))).Body?.transformToString() ?? "";
|
|
76641
|
+
const marker = parseBootstrapMarker(body, key);
|
|
75913
76642
|
entries.push({
|
|
75914
76643
|
region,
|
|
75915
76644
|
assetBucket: marker.assetBucket,
|
|
@@ -76131,7 +76860,7 @@ async function stateRefreshObservedCommand(stackArgs, options) {
|
|
|
76131
76860
|
* Shared rather than constructed per resource because nothing writes to it:
|
|
76132
76861
|
* `redactSecretsForState` only reads its argument.
|
|
76133
76862
|
*/
|
|
76134
|
-
const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
|
|
76863
|
+
const NO_RECORDED_SECRETS$1 = /* @__PURE__ */ new Map();
|
|
76135
76864
|
/**
|
|
76136
76865
|
* Warn before `cdkd state orphan` force-releases a lock that is still LIVE.
|
|
76137
76866
|
*
|
|
@@ -76244,7 +76973,7 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
|
|
|
76244
76973
|
unsupported++;
|
|
76245
76974
|
return;
|
|
76246
76975
|
}
|
|
76247
|
-
resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS, resource.properties ?? {}, STATE_SOURCED_READBACK_RULES);
|
|
76976
|
+
resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS$1, resource.properties ?? {}, STATE_SOURCED_READBACK_RULES);
|
|
76248
76977
|
refreshed++;
|
|
76249
76978
|
} catch (err) {
|
|
76250
76979
|
failed++;
|
|
@@ -76254,6 +76983,7 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
|
|
|
76254
76983
|
await Promise.all(tasks);
|
|
76255
76984
|
});
|
|
76256
76985
|
state.lastModified = Date.now();
|
|
76986
|
+
if (refreshed > 0) delete state.skippedOutputs;
|
|
76257
76987
|
const saveOptions = { expectedEtag: etag };
|
|
76258
76988
|
if (migrationPending) saveOptions.migrateLegacy = true;
|
|
76259
76989
|
await stateBackend.saveState(stackName, region, state, saveOptions);
|
|
@@ -76454,8 +77184,10 @@ async function rollbackCommand(stackArg, options) {
|
|
|
76454
77184
|
let stackAwsClients;
|
|
76455
77185
|
try {
|
|
76456
77186
|
let ref;
|
|
76457
|
-
if (stackArg)
|
|
76458
|
-
|
|
77187
|
+
if (stackArg) {
|
|
77188
|
+
const refs = await setup.stateBackend.listStacks();
|
|
77189
|
+
ref = resolveSingleRegion(stackArg, refs, options.stackRegion);
|
|
77190
|
+
} else {
|
|
76459
77191
|
const candidates = await findJournalCandidates(setup.stateBackend, setup.prefix);
|
|
76460
77192
|
const scoped = options.stackRegion ? candidates.filter((c) => c.region === options.stackRegion) : candidates;
|
|
76461
77193
|
if (scoped.length === 0) {
|
|
@@ -76508,11 +77240,13 @@ async function rollbackCommand(stackArg, options) {
|
|
|
76508
77240
|
for (let s = journal.segments.length - 1; s >= 0; s--) {
|
|
76509
77241
|
const segment = journal.segments[s];
|
|
76510
77242
|
logger.info(`\n Segment ${s + 1}/${journal.segments.length} (${segment.reason}${segment.runId ? `, run ${segment.runId}` : ""}):`);
|
|
76511
|
-
if (segment.failedOperations && segment.failedOperations.length > 0)
|
|
76512
|
-
|
|
76513
|
-
|
|
76514
|
-
|
|
76515
|
-
|
|
77243
|
+
if (segment.failedOperations && segment.failedOperations.length > 0) {
|
|
77244
|
+
if (options.revertFailed) {
|
|
77245
|
+
const failedPlan = planFailedOps(segment.failedOperations, planStateView);
|
|
77246
|
+
for (const item of failedPlan) logger.info(failedActionLabel(item, options.skipFinalSnapshot === true));
|
|
77247
|
+
applyFailedPlanToPreview(failedPlan, planStateView, options.skipFinalSnapshot === true);
|
|
77248
|
+
} else for (const fop of segment.failedOperations) logger.info(` - (left as-is) ${fop.logicalId} (${fop.resourceType}) — its ${fop.changeType} FAILED mid-deploy; pass --revert-failed to attempt reverting it`);
|
|
77249
|
+
}
|
|
76516
77250
|
const plan = planRollback(segment.operations, planStateView, orphanLogicalIds);
|
|
76517
77251
|
for (const item of plan) logger.info(actionLabel(item, options.skipFinalSnapshot === true));
|
|
76518
77252
|
applyPlanToPreview(plan, planStateView, options.skipFinalSnapshot === true);
|
|
@@ -76541,8 +77275,9 @@ async function rollbackCommand(stackArg, options) {
|
|
|
76541
77275
|
};
|
|
76542
77276
|
let currentEtag = stateData.etag;
|
|
76543
77277
|
const saveState = async () => {
|
|
77278
|
+
const { skippedOutputs: _droppedByRollback, ...carriedState } = baseState;
|
|
76544
77279
|
const next = () => ({
|
|
76545
|
-
...
|
|
77280
|
+
...carriedState,
|
|
76546
77281
|
version: 9,
|
|
76547
77282
|
region,
|
|
76548
77283
|
resources: { ...stateResources },
|
|
@@ -76686,10 +77421,7 @@ function applyPlanToPreview(plan, previewState, skipFinalSnapshot) {
|
|
|
76686
77421
|
break;
|
|
76687
77422
|
case "revert":
|
|
76688
77423
|
case "reverse-replacement":
|
|
76689
|
-
case "reverse-replacement-readopt":
|
|
76690
|
-
if (op.previousState) previewState[op.logicalId] = op.previousState;
|
|
76691
|
-
break;
|
|
76692
|
-
default: break;
|
|
77424
|
+
case "reverse-replacement-readopt": if (op.previousState) previewState[op.logicalId] = op.previousState;
|
|
76693
77425
|
}
|
|
76694
77426
|
}
|
|
76695
77427
|
}
|
|
@@ -76709,10 +77441,7 @@ function applyFailedPlanToPreview(plan, previewState, skipFinalSnapshot) {
|
|
|
76709
77441
|
case "orphan-failed-create-retain":
|
|
76710
77442
|
delete previewState[op.logicalId];
|
|
76711
77443
|
break;
|
|
76712
|
-
case "revert-failed-update":
|
|
76713
|
-
if (op.previousState) previewState[op.logicalId] = op.previousState;
|
|
76714
|
-
break;
|
|
76715
|
-
default: break;
|
|
77444
|
+
case "revert-failed-update": if (op.previousState) previewState[op.logicalId] = op.previousState;
|
|
76716
77445
|
}
|
|
76717
77446
|
}
|
|
76718
77447
|
}
|
|
@@ -77491,7 +78220,7 @@ function looksLikeEcrHostWithForeignSuffix(imageUri) {
|
|
|
77491
78220
|
* a foreign-bucket refusal like the create / teardown sides.
|
|
77492
78221
|
*/
|
|
77493
78222
|
/** Default `--older-than` when the flag is not passed. */
|
|
77494
|
-
const DEFAULT_OLDER_THAN_MS =
|
|
78223
|
+
const DEFAULT_OLDER_THAN_MS = 2592e6;
|
|
77495
78224
|
/** S3 `DeleteObjects` accepts at most 1,000 keys per call. */
|
|
77496
78225
|
const S3_DELETE_BATCH_SIZE = 1e3;
|
|
77497
78226
|
/** ECR `BatchDeleteImage` accepts at most 100 image ids per call. */
|
|
@@ -77512,7 +78241,7 @@ function parseOlderThan(value) {
|
|
|
77512
78241
|
if (!match) throw new Error(`Invalid --older-than "${value}": expected <number>d or <number>h (e.g. 30d, 12h)`);
|
|
77513
78242
|
const num = Number(match[1]);
|
|
77514
78243
|
if (!Number.isFinite(num) || num <= 0) throw new Error(`Invalid --older-than "${value}": must be greater than zero`);
|
|
77515
|
-
const multiplier = match[2] === "d" ?
|
|
78244
|
+
const multiplier = match[2] === "d" ? 864e5 : 36e5;
|
|
77516
78245
|
return Math.round(num * multiplier);
|
|
77517
78246
|
}
|
|
77518
78247
|
/**
|
|
@@ -77879,7 +78608,8 @@ async function scanReferencedAssets(stateBackend, marker, logger) {
|
|
|
77879
78608
|
} catch (error) {
|
|
77880
78609
|
const described = describeStateKey(key, STATE_FILE_SUFFIX, DEFAULT_STATE_PREFIX);
|
|
77881
78610
|
const regionMatch = /^(\S+) \((\S+)\)$/.exec(described);
|
|
77882
|
-
|
|
78611
|
+
const inspectHint = regionMatch ? `cdkd state show ${regionMatch[1]} --stack-region ${regionMatch[2]}` : `cdkd state show ${described}`;
|
|
78612
|
+
throw new CdkdError(`State file '${key}' is not valid JSON — aborting: gc must know every referenced asset before deleting anything, and this file's references are unreadable. Repair or remove the corrupt state file ('${inspectHint}' to inspect), then re-run.`, "GC_STATE_UNREADABLE", error);
|
|
77883
78613
|
}
|
|
77884
78614
|
collectAssetReferences(parsed, marker, refs);
|
|
77885
78615
|
}
|
|
@@ -78094,9 +78824,9 @@ function formatBytes(bytes) {
|
|
|
78094
78824
|
/** Age of a timestamp relative to now, in whole days (or hours under 1d). */
|
|
78095
78825
|
function formatAge(date) {
|
|
78096
78826
|
const ageMs = Date.now() - date.getTime();
|
|
78097
|
-
const days = Math.floor(ageMs /
|
|
78827
|
+
const days = Math.floor(ageMs / 864e5);
|
|
78098
78828
|
if (days >= 1) return `${days}d old`;
|
|
78099
|
-
return `${Math.max(0, Math.floor(ageMs /
|
|
78829
|
+
return `${Math.max(0, Math.floor(ageMs / 36e5))}h old`;
|
|
78100
78830
|
}
|
|
78101
78831
|
/**
|
|
78102
78832
|
* Interactive confirmation for the deletion. Follows the repo's
|
|
@@ -78591,9 +79321,10 @@ async function rewriteResourceReferences(state, orphanLogicalIds, providerRegist
|
|
|
78591
79321
|
}
|
|
78592
79322
|
const newOutputs = {};
|
|
78593
79323
|
for (const [name, value] of Object.entries(state.outputs ?? {})) newOutputs[name] = await rewriteValue(value, `outputs.${name}`, `<output:${name}>`, orphanSet, fetcher, rewrites, unresolvable);
|
|
79324
|
+
const { skippedOutputs: _droppedByOrphanRewrite, ...carriedState } = state;
|
|
78594
79325
|
return {
|
|
78595
79326
|
state: {
|
|
78596
|
-
...
|
|
79327
|
+
...carriedState,
|
|
78597
79328
|
resources: newResources,
|
|
78598
79329
|
outputs: newOutputs,
|
|
78599
79330
|
lastModified: Date.now()
|
|
@@ -78717,20 +79448,21 @@ async function rewriteSubTemplate(template, ownerLogicalId, pathPrefix, orphanSe
|
|
|
78717
79448
|
continue;
|
|
78718
79449
|
}
|
|
78719
79450
|
const dot = inner.indexOf(".");
|
|
78720
|
-
if (dot < 0)
|
|
78721
|
-
|
|
78722
|
-
|
|
78723
|
-
|
|
78724
|
-
|
|
78725
|
-
|
|
78726
|
-
|
|
78727
|
-
|
|
78728
|
-
|
|
78729
|
-
|
|
78730
|
-
|
|
78731
|
-
|
|
78732
|
-
|
|
78733
|
-
|
|
79451
|
+
if (dot < 0) {
|
|
79452
|
+
if (orphanSet.has(inner)) {
|
|
79453
|
+
const replaced = fetcher.ref(inner);
|
|
79454
|
+
rewrites.push({
|
|
79455
|
+
logicalId: ownerLogicalId,
|
|
79456
|
+
path: pathPrefix,
|
|
79457
|
+
kind: "sub",
|
|
79458
|
+
before: m[0],
|
|
79459
|
+
after: replaced,
|
|
79460
|
+
orphanLogicalId: inner
|
|
79461
|
+
});
|
|
79462
|
+
out += replaced;
|
|
79463
|
+
didChange = true;
|
|
79464
|
+
} else out += m[0];
|
|
79465
|
+
} else {
|
|
78734
79466
|
const target = inner.slice(0, dot);
|
|
78735
79467
|
const attribute = inner.slice(dot + 1);
|
|
78736
79468
|
if (orphanSet.has(target)) {
|
|
@@ -79118,13 +79850,14 @@ async function publishAssetsCommand(stacks, options) {
|
|
|
79118
79850
|
region: baseRegion,
|
|
79119
79851
|
...options.profile && { profile: options.profile }
|
|
79120
79852
|
});
|
|
79121
|
-
const
|
|
79853
|
+
const backend = new S3StateBackend(markerClients.s3, {
|
|
79122
79854
|
bucket,
|
|
79123
79855
|
prefix: options.statePrefix
|
|
79124
79856
|
}, {
|
|
79125
79857
|
region: baseRegion,
|
|
79126
79858
|
...options.profile && { profile: options.profile }
|
|
79127
|
-
})
|
|
79859
|
+
});
|
|
79860
|
+
const resolver = new AssetModeResolver(backend, accountId, { ...options.profile && { profile: options.profile } });
|
|
79128
79861
|
markerAccess = {
|
|
79129
79862
|
resolver,
|
|
79130
79863
|
dispose: () => markerClients.destroy()
|
|
@@ -79454,13 +80187,15 @@ async function importCommand(stackArg, options) {
|
|
|
79454
80187
|
const existingState = existingResult?.state ?? null;
|
|
79455
80188
|
const existingEtag = existingResult?.etag;
|
|
79456
80189
|
const migrationPending = existingResult?.migrationPending ?? false;
|
|
79457
|
-
if (existingState)
|
|
79458
|
-
if (!
|
|
79459
|
-
|
|
79460
|
-
|
|
79461
|
-
|
|
79462
|
-
|
|
79463
|
-
|
|
80190
|
+
if (existingState) {
|
|
80191
|
+
if (!selectiveMode) {
|
|
80192
|
+
if (!options.force) throw new Error(`State already exists for stack '${stackInfo.stackName}' (${targetRegion}). Auto / whole-stack import rebuilds the entire resource map from the template, which would drop any state entry not re-imported. Pass --force to confirm. To add specific resources without affecting unlisted ones, use --resource <id>=<physicalId> (selective merge — no --force needed).`);
|
|
80193
|
+
} else {
|
|
80194
|
+
const conflicts = [...overrides.keys()].filter((id) => Object.prototype.hasOwnProperty.call(existingState.resources, id));
|
|
80195
|
+
if (conflicts.length > 0 && !options.force) throw new Error(`Selective import would overwrite resource(s) already in state: ${conflicts.join(", ")}. Pass --force to confirm the overwrite, or remove these IDs from --resource / --resource-mapping.`);
|
|
80196
|
+
const preservedCount = Object.keys(existingState.resources).filter((id) => !overrides.has(id)).length;
|
|
80197
|
+
logger.info(`Merging into existing state for ${stackInfo.stackName} (${targetRegion}): preserving ${preservedCount} unlisted resource(s)` + (conflicts.length > 0 ? `, overwriting ${conflicts.length} listed entry(ies)` : ""));
|
|
80198
|
+
}
|
|
79464
80199
|
}
|
|
79465
80200
|
for (const overrideId of overrides.keys()) if (!templateLogicalIds.has(overrideId)) throw new Error(`--resource / --resource-mapping references logical ID '${overrideId}' which is not in the synthesized template for stack '${stackInfo.stackName}'. Available IDs: ${[...templateLogicalIds].join(", ")}`);
|
|
79466
80201
|
const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
|
|
@@ -79528,8 +80263,7 @@ async function importCommand(stackArg, options) {
|
|
|
79528
80263
|
}
|
|
79529
80264
|
}
|
|
79530
80265
|
const stackState = buildStackState(stackInfo.stackName, targetRegion, rows, templateParser, stateTemplate, existingState, selectiveMode);
|
|
79531
|
-
await resolveImportedProperties(stackState, stateTemplate, targetRegion, stateBackend, logger);
|
|
79532
|
-
await captureObservedForImportedResources(stackState, providerRegistry, logger);
|
|
80266
|
+
await captureObservedForImportedResources(stackState, providerRegistry, logger, await resolveImportedProperties(stackState, stateTemplate, targetRegion, stateBackend, logger));
|
|
79533
80267
|
const saveOptions = {};
|
|
79534
80268
|
if (existingEtag) saveOptions.expectedEtag = existingEtag;
|
|
79535
80269
|
if (migrationPending) saveOptions.migrateLegacy = true;
|
|
@@ -79962,14 +80696,90 @@ function defaultOnlyParameterTemplate(template) {
|
|
|
79962
80696
|
* already-stored, which on the v3 baseline is already resolved-shape
|
|
79963
80697
|
* from a prior import / deploy, so re-resolving is a no-op.
|
|
79964
80698
|
*
|
|
80699
|
+
* RETURNS the logical ids for which an `observedProperties` baseline must NOT
|
|
80700
|
+
* be captured, because this walk cannot vouch that the persisted `properties`
|
|
80701
|
+
* still SPELL the dynamic reference the template had.
|
|
80702
|
+
* `captureObservedForImportedResources` refuses to write a baseline for those
|
|
80703
|
+
* (issue [#2828](https://github.com/go-to-k/cdkd/issues/2828) review); the
|
|
80704
|
+
* redaction it performs is POSITION-based, so a bag that no longer spells the
|
|
80705
|
+
* reference gives it no evidence and the DECRYPTED readback is persisted in
|
|
80706
|
+
* the clear.
|
|
80707
|
+
*
|
|
80708
|
+
* The predicate is deliberately CONSERVATIVE — it over-refuses — and that is a
|
|
80709
|
+
* decision a review round paid for. Two arms:
|
|
80710
|
+
*
|
|
80711
|
+
* - **Resolution THREW** — refuse, unconditionally. A throw means the
|
|
80712
|
+
* resolver could not finish, so nothing about the persisted bag is
|
|
80713
|
+
* trustworthy evidence. This is BEHAVIOURALLY IDENTICAL to the arm this PR
|
|
80714
|
+
* first shipped: two attempts to sharpen it in between each shipped a leak
|
|
80715
|
+
* (the wrapped-complete-token shape, then the
|
|
80716
|
+
* reference-sourced-from-outside-the-bag shape), and saying so is the
|
|
80717
|
+
* strongest available form of "every sharpening leaked". The arm's own
|
|
80718
|
+
* comment carries both measurements.
|
|
80719
|
+
* - **Resolution SUCCEEDED but LOST an opener.** An `Fn::If` whose condition
|
|
80720
|
+
* was DOWNGRADED resolves without throwing and selects the branch AWS did
|
|
80721
|
+
* not take, so `properties` hold the false-branch literal and no reference
|
|
80722
|
+
* at all. The downgrade needs a parameter with NO `Default` — `cdkd import`
|
|
80723
|
+
* accepts no parameter VALUES from the user, but a `Default`-carrying
|
|
80724
|
+
* parameter IS bound (issue #2321's retry), so only an unbindable one leaves
|
|
80725
|
+
* `evaluateConditions` catching per condition and recording `false`.
|
|
80726
|
+
*
|
|
80727
|
+
* ARM 2 IS KNOWN OVER-BROAD for a PUBLIC `{{resolve:ssm:...}}` reference: a
|
|
80728
|
+
* `String` / `StringList` parameter is stored RESOLVED (issue #1901), so its
|
|
80729
|
+
* opener disappears across the resolve and the resource is refused although
|
|
80730
|
+
* nothing was ever secret, costing it a drift baseline on a common config
|
|
80731
|
+
* pattern. Threading the resolve's own `recordedSecretValues` into the
|
|
80732
|
+
* capture corrects it -- measured on issue
|
|
80733
|
+
* [#2852](https://github.com/go-to-k/cdkd/issues/2852), which carries that
|
|
80734
|
+
* remedy -- because a populated map makes absence from it real evidence of
|
|
80735
|
+
* a public parameter, exactly as the deploy path already relies on.
|
|
80736
|
+
*
|
|
80737
|
+
* The cost of a false refusal is one resource's drift baseline until its next
|
|
80738
|
+
* deploy; the cost of a false pass is a plaintext secret in `state.json`. So an
|
|
80739
|
+
* unmeasurable bag refuses too.
|
|
80740
|
+
*
|
|
80741
|
+
* WHAT CAN STILL GO WRONG. No claim is made here about what this closes, and
|
|
80742
|
+
* that is deliberate: successive review rounds falsified a closure claim, then
|
|
80743
|
+
* a closed-set list, then a "reduces" claim -- the mechanism survived every
|
|
80744
|
+
* round of measurement and the SENTENCES about it did not. What follows states only
|
|
80745
|
+
* the DANGER direction, which can become false only once the residue is gone.
|
|
80746
|
+
*
|
|
80747
|
+
* PLAINTEXT CAN STILL BE PERSISTED by this capture. Known ways, each with its
|
|
80748
|
+
* issue and none of them proven to be all of them:
|
|
80749
|
+
*
|
|
80750
|
+
* - the resolve drops a reference, or trades one away, in a manner the opener
|
|
80751
|
+
* comparison below cannot see --
|
|
80752
|
+
* [#2850](https://github.com/go-to-k/cdkd/issues/2850);
|
|
80753
|
+
* - a parameter binds to a placeholder `Default` while the DEPLOYED value was
|
|
80754
|
+
* the reference, so no opener exists in any template this walk is handed --
|
|
80755
|
+
* [#2854](https://github.com/go-to-k/cdkd/issues/2854);
|
|
80756
|
+
* - `redactSecretsForState` cannot PAIR the readback against the source and
|
|
80757
|
+
* returns the bag unchanged: a reshaped container, an identity key AWS
|
|
80758
|
+
* normalised, a readback key the source lacks, an array whose anchors do not
|
|
80759
|
+
* corroborate -- [#2852](https://github.com/go-to-k/cdkd/issues/2852), which
|
|
80760
|
+
* carries the measured probe table;
|
|
80761
|
+
* - the readback holds a secret with NO counterpart in the source at all, set
|
|
80762
|
+
* out of band -- [#2868](https://github.com/go-to-k/cdkd/issues/2868), filed
|
|
80763
|
+
* as a remit question rather than a defect in this mechanism.
|
|
80764
|
+
*
|
|
80765
|
+
* WHY NO EXHAUSTIVE LIST IS OFFERED, as mechanism rather than framing: this
|
|
80766
|
+
* redaction's only evidence is a POST-HOC comparison between the persisted bag
|
|
80767
|
+
* and the readback, so anything that comparison cannot see is trusted.
|
|
80768
|
+
* Establishing more needs PROVENANCE carried out of the resolver or the
|
|
80769
|
+
* deployer, which is where the structural remedy for all of the above lives.
|
|
80770
|
+
*
|
|
80771
|
+
* The set is returned rather than recorded on the state, because it describes
|
|
80772
|
+
* THIS run's resolution and nothing downstream of the save has any use for it.
|
|
80773
|
+
*
|
|
79965
80774
|
* Exported for unit testing — internal to the command flow otherwise. The
|
|
79966
80775
|
* masking this walk applies (issue #2803) is only provable against the REAL
|
|
79967
80776
|
* resolver, since the plaintext reaches the message through the resolver's
|
|
79968
80777
|
* own throw rather than through anything this function writes.
|
|
79969
80778
|
*/
|
|
79970
80779
|
async function resolveImportedProperties(stackState, template, region, stateBackend, logger) {
|
|
80780
|
+
const unsafeObservedBaselineLogicalIds = /* @__PURE__ */ new Set();
|
|
79971
80781
|
const entries = Object.entries(stackState.resources);
|
|
79972
|
-
if (entries.length === 0) return;
|
|
80782
|
+
if (entries.length === 0) return unsafeObservedBaselineLogicalIds;
|
|
79973
80783
|
const resolver = new IntrinsicFunctionResolver(region);
|
|
79974
80784
|
let parameters = {};
|
|
79975
80785
|
let conditions = {};
|
|
@@ -80004,8 +80814,9 @@ async function resolveImportedProperties(stackState, template, region, stateBack
|
|
|
80004
80814
|
};
|
|
80005
80815
|
for (const [logicalId, resource] of entries) {
|
|
80006
80816
|
const recordedSecretValues = /* @__PURE__ */ new Map();
|
|
80817
|
+
const unresolvedProperties = resource.properties ?? {};
|
|
80818
|
+
let threw = false;
|
|
80007
80819
|
try {
|
|
80008
|
-
const unresolvedProperties = resource.properties ?? {};
|
|
80009
80820
|
const resolved = await resolver.resolve(unresolvedProperties, {
|
|
80010
80821
|
...baseContext,
|
|
80011
80822
|
recordedSecretValues
|
|
@@ -80013,7 +80824,46 @@ async function resolveImportedProperties(stackState, template, region, stateBack
|
|
|
80013
80824
|
resource.properties = recordedSecretValues.size > 0 ? redactSecretsForState(resolved, recordedSecretValues, unresolvedProperties) : resolved;
|
|
80014
80825
|
} catch (err) {
|
|
80015
80826
|
logger.warn(`Failed to resolve intrinsics in Properties for imported resource '${logicalId}' (${resource.resourceType}): ${maskSecretsInText(err instanceof Error ? err.message : String(err), recordedSecretValues)}. State will be written with the raw intrinsic shape, which may cause 'cdkd destroy' to fail on this resource — re-import once every referenced sibling is in state, or remove this resource via 'cdkd state orphan'.` + (unboundParameterNames.length > 0 ? ` This template also declares parameter(s) with no 'Default' that an import cannot bind (${unboundParameterNames.join(", ")}), and 'cdkd import' accepts no parameter values — if this property was built from one of those, re-importing a sibling will not change it: give the parameter a 'Default' in the template and re-import, or correct the recorded properties before the next 'cdkd deploy'.` : ""));
|
|
80827
|
+
threw = true;
|
|
80828
|
+
}
|
|
80829
|
+
if (threw) {
|
|
80830
|
+
unsafeObservedBaselineLogicalIds.add(logicalId);
|
|
80831
|
+
continue;
|
|
80016
80832
|
}
|
|
80833
|
+
const rawOpeners = countDynamicReferenceOpeners(unresolvedProperties);
|
|
80834
|
+
const persistedOpeners = countDynamicReferenceOpeners(resource.properties);
|
|
80835
|
+
if (rawOpeners === void 0 || persistedOpeners === void 0 || persistedOpeners < rawOpeners) unsafeObservedBaselineLogicalIds.add(logicalId);
|
|
80836
|
+
}
|
|
80837
|
+
return unsafeObservedBaselineLogicalIds;
|
|
80838
|
+
}
|
|
80839
|
+
/** The opener every dynamic reference starts with, secret-bearing or not. */
|
|
80840
|
+
const DYNAMIC_REFERENCE_OPENER = "{{resolve:";
|
|
80841
|
+
/**
|
|
80842
|
+
* How many dynamic-reference OPENERS a property bag carries, counted over its
|
|
80843
|
+
* JSON serialization rather than by walking string leaves — which is the whole
|
|
80844
|
+
* point on the RAW side: a token assembled by `Fn::Join` is SPLIT across array
|
|
80845
|
+
* elements, so `{{resolve:secretsmanager:` survives as a fragment that no scan
|
|
80846
|
+
* for a COMPLETE token would see. `secretValueFromJson` renders exactly that
|
|
80847
|
+
* shape, so the split form is the common one, not the exotic one.
|
|
80848
|
+
*
|
|
80849
|
+
* `undefined` when the bag cannot be serialized. Callers treat that as "cannot
|
|
80850
|
+
* vouch for this bag" rather than as zero — the only safe direction, since the
|
|
80851
|
+
* cost of a false refusal is a drift baseline and the cost of a false pass is a
|
|
80852
|
+
* plaintext secret in `state.json`.
|
|
80853
|
+
*/
|
|
80854
|
+
function countDynamicReferenceOpeners(bag) {
|
|
80855
|
+
let text;
|
|
80856
|
+
try {
|
|
80857
|
+
text = JSON.stringify(bag ?? {}) ?? "";
|
|
80858
|
+
} catch {
|
|
80859
|
+
return;
|
|
80860
|
+
}
|
|
80861
|
+
let count = 0;
|
|
80862
|
+
for (let from = 0;;) {
|
|
80863
|
+
const at = text.indexOf(DYNAMIC_REFERENCE_OPENER, from);
|
|
80864
|
+
if (at === -1) return count;
|
|
80865
|
+
count++;
|
|
80866
|
+
from = at + 10;
|
|
80017
80867
|
}
|
|
80018
80868
|
}
|
|
80019
80869
|
function printSummary(rows) {
|
|
@@ -80105,6 +80955,14 @@ function collectMultiple(value, previous) {
|
|
|
80105
80955
|
return [...previous ?? [], value];
|
|
80106
80956
|
}
|
|
80107
80957
|
/**
|
|
80958
|
+
* The empty secrets map the observed-capture redaction below passes. Shared
|
|
80959
|
+
* and module-level because `redactSecretsForState` only ever READS its map,
|
|
80960
|
+
* and because an empty one is not an oversight here but the POSITION-only
|
|
80961
|
+
* configuration the call site's note argues for — the same constant
|
|
80962
|
+
* `cdkd state refresh-observed` passes for the same reason.
|
|
80963
|
+
*/
|
|
80964
|
+
const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
|
|
80965
|
+
/**
|
|
80108
80966
|
* Populate `observedProperties` for every resource in a freshly-built
|
|
80109
80967
|
* import StackState by calling the matching provider's
|
|
80110
80968
|
* `readCurrentState`. Mirrors what `cdkd deploy` does after each
|
|
@@ -80123,11 +80981,25 @@ function collectMultiple(value, previous) {
|
|
|
80123
80981
|
* (incremental rollout — see `ResourceProvider.readCurrentState`'s
|
|
80124
80982
|
* doc-comment) keep `observedProperties: undefined`; the drift comparator
|
|
80125
80983
|
* falls back to `properties` for those, matching pre-v3 behavior.
|
|
80984
|
+
*
|
|
80985
|
+
* Exported for unit testing -- internal to the command flow otherwise. The
|
|
80986
|
+
* input-space matrix drives THIS function rather than replaying its
|
|
80987
|
+
* `redactSecretsForState` call by hand, so a change to the arguments it passes
|
|
80988
|
+
* cannot leave that fence green.
|
|
80989
|
+
*
|
|
80990
|
+
* Every captured bag is REDACTED before it lands on the record, and a resource
|
|
80991
|
+
* whose redaction POSITION SOURCE is unusable is skipped entirely — see the
|
|
80992
|
+
* call site's own note (issue
|
|
80993
|
+
* [#2828](https://github.com/go-to-k/cdkd/issues/2828)).
|
|
80126
80994
|
*/
|
|
80127
|
-
async function captureObservedForImportedResources(stackState, providerRegistry, logger) {
|
|
80995
|
+
async function captureObservedForImportedResources(stackState, providerRegistry, logger, unsafeObservedBaselineLogicalIds) {
|
|
80128
80996
|
const entries = Object.entries(stackState.resources);
|
|
80129
80997
|
if (entries.length === 0) return;
|
|
80130
80998
|
await Promise.all(entries.map(async ([logicalId, resource]) => {
|
|
80999
|
+
if (unsafeObservedBaselineLogicalIds.has(logicalId)) {
|
|
81000
|
+
logger.debug(`observedProperties capture SKIPPED for imported ${logicalId} (${resource.resourceType}): the recorded properties no longer spell the template's dynamic reference, so they cannot position a redaction — capturing an AWS readback against them could persist a resolved secret in plaintext. Drift will compare against the recorded properties for this resource until the next successful deploy.`);
|
|
81001
|
+
return;
|
|
81002
|
+
}
|
|
80131
81003
|
try {
|
|
80132
81004
|
const provider = providerRegistry.getProviderFor({
|
|
80133
81005
|
resourceType: resource.resourceType,
|
|
@@ -80135,9 +81007,9 @@ async function captureObservedForImportedResources(stackState, providerRegistry,
|
|
|
80135
81007
|
}).provider;
|
|
80136
81008
|
if (!provider.readCurrentState) return;
|
|
80137
81009
|
const observed = await provider.readCurrentState(resource.physicalId, logicalId, resource.resourceType, resource.properties ?? {}, buildReadCurrentStateContext(stackState, logicalId));
|
|
80138
|
-
if (observed !== void 0) resource.observedProperties = observed;
|
|
81010
|
+
if (observed !== void 0) resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS, resource.properties ?? {}, STATE_SOURCED_READBACK_RULES);
|
|
80139
81011
|
} catch (err) {
|
|
80140
|
-
logger.debug(`observedProperties capture for imported ${logicalId} (${resource.resourceType}) failed: ${err instanceof Error ? err.
|
|
81012
|
+
logger.debug(`observedProperties capture for imported ${logicalId} (${resource.resourceType}) failed: ${err instanceof Error ? err.name : typeof err} — drift will fall back to template properties for this resource until the next successful deploy.`);
|
|
80141
81013
|
}
|
|
80142
81014
|
}));
|
|
80143
81015
|
}
|
|
@@ -80307,8 +81179,7 @@ async function importNestedStackChildrenRecursive(args) {
|
|
|
80307
81179
|
childStackState.parentStack = parentStackName;
|
|
80308
81180
|
childStackState.parentLogicalId = childLogicalId;
|
|
80309
81181
|
childStackState.parentRegion = parentRegion;
|
|
80310
|
-
await resolveImportedProperties(childStackState, childStateTemplate, childRegion, stateBackend, logger);
|
|
80311
|
-
await captureObservedForImportedResources(childStackState, providerRegistry, logger);
|
|
81182
|
+
await captureObservedForImportedResources(childStackState, providerRegistry, logger, await resolveImportedProperties(childStackState, childStateTemplate, childRegion, stateBackend, logger));
|
|
80312
81183
|
await stateBackend.saveState(childStackName, childRegion, childStackState);
|
|
80313
81184
|
logger.info(`✓ Nested stack state written: ${childStackName} (${childRegion}) — ${rows.filter((r) => r.outcome === "imported").length} resource(s) imported.`);
|
|
80314
81185
|
if (childTreeNode.nested.size > 0) await importNestedStackChildrenRecursive({
|
|
@@ -80551,10 +81422,11 @@ async function loadBootstrapContainerRepo(synthRegion, opts) {
|
|
|
80551
81422
|
bucket: stateBucket,
|
|
80552
81423
|
prefix: opts.statePrefix
|
|
80553
81424
|
};
|
|
80554
|
-
const
|
|
81425
|
+
const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
|
|
80555
81426
|
...clientRegion !== void 0 && { region: clientRegion },
|
|
80556
81427
|
...opts.profile !== void 0 && { profile: opts.profile }
|
|
80557
|
-
})
|
|
81428
|
+
});
|
|
81429
|
+
const { body, resolvedKey } = await readBootstrapMarkerBody(stateBackend, rawRegion, { logPrefix: prefix });
|
|
80558
81430
|
if (body === null) {
|
|
80559
81431
|
const rawKey = getBootstrapMarkerKey(rawRegion);
|
|
80560
81432
|
logger.debug(`${prefix}: no bootstrap marker at '${resolvedKey}'${rawKey !== resolvedKey ? ` (nor '${rawKey}')` : ""} in bucket '${stateBucket}' — assuming conventional asset-repo names.`);
|
|
@@ -80727,14 +81599,15 @@ var S3LocalStateProvider = class {
|
|
|
80727
81599
|
this.opts = opts;
|
|
80728
81600
|
}
|
|
80729
81601
|
async load(stackName, synthRegion) {
|
|
80730
|
-
const
|
|
81602
|
+
const loadOpts = {
|
|
80731
81603
|
statePrefix: this.opts.statePrefix,
|
|
80732
81604
|
...this.opts.stackRegion !== void 0 && { stackRegion: this.opts.stackRegion },
|
|
80733
81605
|
...this.opts.rawStackRegion !== void 0 && { rawStackRegion: this.opts.rawStackRegion },
|
|
80734
81606
|
...this.opts.stateBucket !== void 0 && { stateBucket: this.opts.stateBucket },
|
|
80735
81607
|
...this.opts.region !== void 0 && { region: this.opts.region },
|
|
80736
81608
|
...this.opts.profile !== void 0 && { profile: this.opts.profile }
|
|
80737
|
-
}
|
|
81609
|
+
};
|
|
81610
|
+
const loaded = await loadStateForStack(stackName, synthRegion, loadOpts);
|
|
80738
81611
|
if (!loaded) return void 0;
|
|
80739
81612
|
const outputs = {};
|
|
80740
81613
|
for (const [k, v] of Object.entries(loaded.state.outputs ?? {})) if (typeof v === "string") outputs[k] = v;
|
|
@@ -80747,12 +81620,13 @@ var S3LocalStateProvider = class {
|
|
|
80747
81620
|
};
|
|
80748
81621
|
}
|
|
80749
81622
|
async buildCrossStackResolver(consumerRegion) {
|
|
80750
|
-
const
|
|
81623
|
+
const buildOpts = {
|
|
80751
81624
|
statePrefix: this.opts.statePrefix,
|
|
80752
81625
|
...this.opts.stateBucket !== void 0 && { stateBucket: this.opts.stateBucket },
|
|
80753
81626
|
...this.opts.region !== void 0 && { region: this.opts.region },
|
|
80754
81627
|
...this.opts.profile !== void 0 && { profile: this.opts.profile }
|
|
80755
|
-
}
|
|
81628
|
+
};
|
|
81629
|
+
const built = await buildCrossStackResolver(consumerRegion, buildOpts);
|
|
80756
81630
|
if (!built) return void 0;
|
|
80757
81631
|
this.disposers.push(built.dispose);
|
|
80758
81632
|
return built.resolver;
|
|
@@ -82551,7 +83425,7 @@ async function runDetached(opts) {
|
|
|
82551
83425
|
if (collisions.length > 0) logger.warn(`Env var(s) ${collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a docker-client environment variable or have a malformed name (empty, or containing '=' / NUL), and were NOT passed to the container at all (a colliding name would hijack the docker client; a malformed name cannot form a valid environment variable). Rename the env var if the container needs it.`);
|
|
82552
83426
|
try {
|
|
82553
83427
|
const { stdout } = await execFileAsync$3(getDockerCmd(), args, {
|
|
82554
|
-
maxBuffer:
|
|
83428
|
+
maxBuffer: 10485760,
|
|
82555
83429
|
env: dockerSpawnEnvWithSensitive(passthroughEnv)
|
|
82556
83430
|
});
|
|
82557
83431
|
return stdout.trim();
|
|
@@ -82785,7 +83659,7 @@ const ASSUMED_ROLE_CACHE = /* @__PURE__ */ new Map();
|
|
|
82785
83659
|
*/
|
|
82786
83660
|
const CALLER_IDENTITY_CACHE = /* @__PURE__ */ new Map();
|
|
82787
83661
|
/** 5-minute safety margin against the recorded STS expiration timestamp. */
|
|
82788
|
-
const STS_CREDENTIAL_SAFETY_MARGIN_MS =
|
|
83662
|
+
const STS_CREDENTIAL_SAFETY_MARGIN_MS = 3e5;
|
|
82789
83663
|
function isCredentialFresh(creds) {
|
|
82790
83664
|
if (!creds.expiration) return false;
|
|
82791
83665
|
return creds.expiration.getTime() - Date.now() > STS_CREDENTIAL_SAFETY_MARGIN_MS;
|
|
@@ -82893,7 +83767,8 @@ async function assumeRoleForEcr(roleArn, callerRegion, logger) {
|
|
|
82893
83767
|
};
|
|
82894
83768
|
} catch (err) {
|
|
82895
83769
|
if (err instanceof LocalInvokeBuildError$1) throw err;
|
|
82896
|
-
|
|
83770
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
83771
|
+
throw new LocalInvokeBuildError$1(`Failed to assume role ${roleArn} for ECR pull: ${reason}. Verify the role exists and its trust policy permits the caller's identity to assume it.`);
|
|
82897
83772
|
} finally {
|
|
82898
83773
|
sts.destroy();
|
|
82899
83774
|
}
|
|
@@ -83513,7 +84388,8 @@ function attachWebSocketServer(opts) {
|
|
|
83513
84388
|
* route table.
|
|
83514
84389
|
*/
|
|
83515
84390
|
async function handleManagementRequest(req, res, registry) {
|
|
83516
|
-
|
|
84391
|
+
const url = req.url ?? "";
|
|
84392
|
+
if (parseConnectionsPath(url) === null) return false;
|
|
83517
84393
|
await handleConnectionsRequest({
|
|
83518
84394
|
req,
|
|
83519
84395
|
res,
|
|
@@ -83781,8 +84657,9 @@ function createContainerPool(specs, options) {
|
|
|
83781
84657
|
readOnly: true
|
|
83782
84658
|
}] : optMount;
|
|
83783
84659
|
const containerCodePath = resolveRuntimeCodeMountPath(spec.lambda.runtime);
|
|
84660
|
+
const image = resolveRuntimeImage(spec.lambda.runtime);
|
|
83784
84661
|
containerId = await runDetached({
|
|
83785
|
-
image
|
|
84662
|
+
image,
|
|
83786
84663
|
mounts: [{
|
|
83787
84664
|
hostPath: spec.codeDir,
|
|
83788
84665
|
containerPath: containerCodePath,
|
|
@@ -85682,7 +86559,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
85682
86559
|
logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
|
|
85683
86560
|
try {
|
|
85684
86561
|
const { stdout } = await execFileAsync$2(getDockerCmd(), sidecarArgs, {
|
|
85685
|
-
maxBuffer:
|
|
86562
|
+
maxBuffer: 10485760,
|
|
85686
86563
|
env: dockerSpawnEnvWithSensitive(sidecarSensitiveEnv)
|
|
85687
86564
|
});
|
|
85688
86565
|
return stdout.trim();
|
|
@@ -86055,7 +86932,7 @@ async function runEcsTask(task, options, state) {
|
|
|
86055
86932
|
try {
|
|
86056
86933
|
const sensitiveEnv = dockerEnvs.get(container.name);
|
|
86057
86934
|
const { stdout } = await execFileAsync$1(getDockerCmd(), args, {
|
|
86058
|
-
maxBuffer:
|
|
86935
|
+
maxBuffer: 10485760,
|
|
86059
86936
|
env: dockerSpawnEnvWithSensitive(sensitiveEnv)
|
|
86060
86937
|
});
|
|
86061
86938
|
id = stdout.trim();
|
|
@@ -86146,9 +87023,7 @@ async function awaitDependencies(container, started) {
|
|
|
86146
87023
|
if (code !== 0) throw new EcsTaskRunnerError(`Container '${container.name}' requires dependency '${dep.containerName}' to exit 0, but it exited ${code}.`);
|
|
86147
87024
|
break;
|
|
86148
87025
|
}
|
|
86149
|
-
case "HEALTHY":
|
|
86150
|
-
await waitForContainerHealthy(entry.id, dep.containerName);
|
|
86151
|
-
break;
|
|
87026
|
+
case "HEALTHY": await waitForContainerHealthy(entry.id, dep.containerName);
|
|
86152
87027
|
}
|
|
86153
87028
|
}
|
|
86154
87029
|
}
|
|
@@ -86161,7 +87036,7 @@ async function awaitDependencies(container, started) {
|
|
|
86161
87036
|
*/
|
|
86162
87037
|
async function waitForContainerHealthy(containerId, displayName) {
|
|
86163
87038
|
const logger = getLogger().child("ecs-runner");
|
|
86164
|
-
const deadline = Date.now() +
|
|
87039
|
+
const deadline = Date.now() + 3e5;
|
|
86165
87040
|
let lastStatus = "";
|
|
86166
87041
|
const inspectArgs = [
|
|
86167
87042
|
"inspect",
|
|
@@ -86190,7 +87065,7 @@ async function waitForContainerHealthy(containerId, displayName) {
|
|
|
86190
87065
|
async function waitForContainerExit(containerId) {
|
|
86191
87066
|
const waitArgs = ["wait", containerId];
|
|
86192
87067
|
try {
|
|
86193
|
-
const { stdout } = await execFileAsync$1(getDockerCmd(), waitArgs, { maxBuffer:
|
|
87068
|
+
const { stdout } = await execFileAsync$1(getDockerCmd(), waitArgs, { maxBuffer: 1048576 });
|
|
86194
87069
|
const code = Number.parseInt(stdout.trim(), 10);
|
|
86195
87070
|
return Number.isFinite(code) ? code : 1;
|
|
86196
87071
|
} catch (err) {
|
|
@@ -86389,11 +87264,12 @@ function buildDockerRunArgs(opts) {
|
|
|
86389
87264
|
for (const mp of container.mountPoints) {
|
|
86390
87265
|
const v = volumeByName.get(mp.sourceVolume);
|
|
86391
87266
|
if (!v) continue;
|
|
86392
|
-
if (v.kind === "host")
|
|
86393
|
-
|
|
86394
|
-
|
|
86395
|
-
|
|
86396
|
-
|
|
87267
|
+
if (v.kind === "host") {
|
|
87268
|
+
if (v.hostPath) {
|
|
87269
|
+
const ro = mp.readOnly ? ":ro" : "";
|
|
87270
|
+
args.push("-v", `${v.hostPath}:${mp.containerPath}${ro}`);
|
|
87271
|
+
} else args.push("-v", mp.containerPath);
|
|
87272
|
+
} else {
|
|
86397
87273
|
const name = v.dockerVolumeName ?? v.name;
|
|
86398
87274
|
const ro = mp.readOnly ? ":ro" : "";
|
|
86399
87275
|
args.push("-v", `${name}:${mp.containerPath}${ro}`);
|
|
@@ -86418,7 +87294,8 @@ function buildDockerRunArgs(opts) {
|
|
|
86418
87294
|
applyOverrideMap(finalEnv, overrides["Parameters"]);
|
|
86419
87295
|
applyOverrideMap(finalEnv, overrides[container.name]);
|
|
86420
87296
|
}
|
|
86421
|
-
const
|
|
87297
|
+
const sensitiveKeys = /* @__PURE__ */ new Set([...SENSITIVE_ENV_KEYS, ...secrets.map((s) => s.name)]);
|
|
87298
|
+
const { flags, sensitiveEnv, collisions } = partitionSensitiveEnv(finalEnv, sensitiveKeys);
|
|
86422
87299
|
args.push(...flags);
|
|
86423
87300
|
if (container.user) args.push("--user", container.user);
|
|
86424
87301
|
if (container.privileged) args.push("--privileged");
|
|
@@ -86533,16 +87410,20 @@ async function localRunTaskCommand(target, options) {
|
|
|
86533
87410
|
const { context: imageContext, stateRecordRegion } = await buildEcsImageResolutionContext$1(candidate, stateProvider, options);
|
|
86534
87411
|
const task = resolveEcsTaskTarget(target, stacks, imageContext);
|
|
86535
87412
|
logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
|
|
86536
|
-
const
|
|
87413
|
+
const taskStack = stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack;
|
|
87414
|
+
const taskNeeds = detectEcsImageResolutionNeeds(taskStack);
|
|
86537
87415
|
if (stateProvider && taskNeeds.needsCrossStackResolver) {
|
|
86538
87416
|
const consumerRegion = resolveEcsConsumerRegion(stateRecordRegion, options, task.stack.region);
|
|
86539
87417
|
const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
|
|
86540
|
-
if (resolver)
|
|
86541
|
-
|
|
86542
|
-
|
|
86543
|
-
|
|
86544
|
-
|
|
86545
|
-
|
|
87418
|
+
if (resolver) {
|
|
87419
|
+
const subContext = {
|
|
87420
|
+
resources: imageContext?.stateResources ?? {},
|
|
87421
|
+
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
87422
|
+
consumerRegion,
|
|
87423
|
+
crossStackResolver: resolver
|
|
87424
|
+
};
|
|
87425
|
+
await applyCrossStackResolverToTask(task, subContext);
|
|
87426
|
+
}
|
|
86546
87427
|
} else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute them against deployed state.");
|
|
86547
87428
|
sigintHandler = () => {
|
|
86548
87429
|
sigintCount += 1;
|
|
@@ -87082,7 +87963,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
|
|
|
87082
87963
|
`${containerId}:${workdirDest}`
|
|
87083
87964
|
];
|
|
87084
87965
|
try {
|
|
87085
|
-
await execFileAsync(dockerCmd, cpArgs, { maxBuffer:
|
|
87966
|
+
await execFileAsync(dockerCmd, cpArgs, { maxBuffer: 67108864 });
|
|
87086
87967
|
} catch (err) {
|
|
87087
87968
|
throw new CdkdError(`softReloadAgentContainer: docker cp into '${containerId}:${workdir}' failed: ${describeDockerExecFailure(err, cpArgs)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_CP_FAILED");
|
|
87088
87969
|
}
|
|
@@ -87621,8 +88502,9 @@ async function buildSigV4HeadersIfRequested(options, resolved, loaded, host, por
|
|
|
87621
88502
|
}
|
|
87622
88503
|
const region = canonicalizeRegion(options.region ?? options.stackRegion ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? resolved.stack.region);
|
|
87623
88504
|
if (!region) throw new CdkdError("--sigv4: no region resolved for the AgentCore signing scope. Pass --region <region>, set AWS_REGION, or use --from-cfn-stack with a region-bound stack.", "LOCAL_INVOKE_AGENTCORE_SIGV4_NO_REGION");
|
|
88505
|
+
const credentials = await resolveHostCredentialsForSigV4(options, resolved, loaded, region);
|
|
87624
88506
|
const signed = await signAgentCoreInvocation({
|
|
87625
|
-
credentials
|
|
88507
|
+
credentials,
|
|
87626
88508
|
region,
|
|
87627
88509
|
host,
|
|
87628
88510
|
port,
|
|
@@ -88742,15 +89624,16 @@ async function localInvokeCommand(target, options) {
|
|
|
88742
89624
|
}
|
|
88743
89625
|
let resolvedAssumeRoleArn;
|
|
88744
89626
|
if (typeof options.assumeRole === "string") resolvedAssumeRoleArn = options.assumeRole;
|
|
88745
|
-
else if (options.assumeRole === true)
|
|
88746
|
-
|
|
88747
|
-
|
|
88748
|
-
|
|
88749
|
-
|
|
88750
|
-
|
|
88751
|
-
|
|
88752
|
-
|
|
88753
|
-
|
|
89627
|
+
else if (options.assumeRole === true) {
|
|
89628
|
+
if (!stateForRoleHint) logger.warn("--assume-role passed without an ARN, but no cdkd state was loaded. Pair it with --from-state, or pass the ARN explicitly: --assume-role <arn>. Falling back to the developer's shell credentials.");
|
|
89629
|
+
else {
|
|
89630
|
+
const arn = resolveExecutionRoleArnFromState(stateForRoleHint, lambda.logicalId);
|
|
89631
|
+
if (arn) {
|
|
89632
|
+
resolvedAssumeRoleArn = arn;
|
|
89633
|
+
logger.info(`--assume-role: auto-resolved execution role from cdkd state: ${arn}`);
|
|
89634
|
+
} else logger.warn(`--assume-role: could not resolve the execution role ARN from cdkd state for '${lambda.logicalId}'. Pass the ARN explicitly: --assume-role <arn>. Falling back to the developer's shell credentials.`);
|
|
89635
|
+
}
|
|
89636
|
+
} else maybeSuggestAssumeRole(options, stateForRoleHint, lambda.logicalId);
|
|
88754
89637
|
const event = await readEvent(options);
|
|
88755
89638
|
const dockerEnv = {
|
|
88756
89639
|
AWS_LAMBDA_FUNCTION_NAME: lambda.logicalId,
|
|
@@ -88807,7 +89690,8 @@ async function localInvokeCommand(target, options) {
|
|
|
88807
89690
|
};
|
|
88808
89691
|
process.on("SIGINT", sigintHandler);
|
|
88809
89692
|
await waitForRieReady(containerHost, hostPort, 5e3);
|
|
88810
|
-
const
|
|
89693
|
+
const invokeTimeoutMs = Math.max(3e4, lambda.timeoutSec * 2 * 1e3);
|
|
89694
|
+
const result = await invokeRie(containerHost, hostPort, event, invokeTimeoutMs);
|
|
88811
89695
|
await new Promise((resolveDelay) => setTimeout(resolveDelay, 250));
|
|
88812
89696
|
process.stdout.write(`${result.raw}\n`);
|
|
88813
89697
|
} finally {
|
|
@@ -89526,4 +90410,4 @@ function buildProgram() {
|
|
|
89526
90410
|
|
|
89527
90411
|
//#endregion
|
|
89528
90412
|
export { buildProgram };
|
|
89529
|
-
//# sourceMappingURL=program-
|
|
90413
|
+
//# sourceMappingURL=program-BXH3FeeV.js.map
|