@go-to-k/cdkd 0.284.70 → 0.284.72
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-Ag8vynRR.js → asg-provider-DcAqfBUo.js} +2 -2
- package/dist/{asg-provider-Ag8vynRR.js.map → asg-provider-DcAqfBUo.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-D4VFWSQr.js → deploy-engine-Bc0FBOuJ.js} +3 -3
- package/dist/{deploy-engine-D4VFWSQr.js.map → deploy-engine-Bc0FBOuJ.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-CP4DgOV1.js → program-B2i8bJUJ.js} +408 -45
- package/dist/{program-CP4DgOV1.js.map → program-B2i8bJUJ.js.map} +1 -1
- package/dist/{version-AKrmxFD7.js → version-DY_eLvXB.js} +2 -2
- package/dist/{version-AKrmxFD7.js.map → version-DY_eLvXB.js.map} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Cr as LockError, Dr as ProvisioningError, Fr as isCdkdError, G as IAMRoleProvider, Ht as TemplateParser, Ir as normalizeAwsError, J as ProviderRegistry, Ln as Synthesizer, Lt as DiffCalculator, Mr as StateError, Nr as SynthesisError, Pr as formatError, Vt as DagBuilder, _r as DependencyError, ar as AssemblyReader, cn as AssetPublisher, cr as resolveBucketRegion, dr as resetAwsClients, en as LockManager, fr as setAwsClients, hr as ConfigError, lr as AwsClients, mr as CdkdError, nn as S3StateBackend, pr as AssetError, r as DeployEngine, sr as clearBucketRegionCache, st as CloudControlProvider, ur as getAwsClients } from "./deploy-engine-
|
|
1
|
+
import { Cr as LockError, Dr as ProvisioningError, Fr as isCdkdError, G as IAMRoleProvider, Ht as TemplateParser, Ir as normalizeAwsError, J as ProviderRegistry, Ln as Synthesizer, Lt as DiffCalculator, Mr as StateError, Nr as SynthesisError, Pr as formatError, Vt as DagBuilder, _r as DependencyError, ar as AssemblyReader, cn as AssetPublisher, cr as resolveBucketRegion, dr as resetAwsClients, en as LockManager, fr as setAwsClients, hr as ConfigError, lr as AwsClients, mr as CdkdError, nn as S3StateBackend, pr as AssetError, r as DeployEngine, sr as clearBucketRegionCache, st as CloudControlProvider, ur as getAwsClients } from "./deploy-engine-Bc0FBOuJ.js";
|
|
2
2
|
import { i as setLogger, n as getLogger, t as ConsoleLogger } from "./logger-C9-E73FI.js";
|
|
3
3
|
|
|
4
4
|
export { AssemblyReader, AssetError, AssetPublisher, AwsClients, CdkdError, CloudControlProvider, ConfigError, ConsoleLogger, DagBuilder, DependencyError, DeployEngine, DiffCalculator, IAMRoleProvider, LockError, LockManager, ProviderRegistry, ProvisioningError, S3StateBackend, StateError, SynthesisError, Synthesizer, TemplateParser, clearBucketRegionCache, formatError, getAwsClients, getLogger, isCdkdError, normalizeAwsError, resetAwsClients, resolveBucketRegion, setAwsClients, setLogger };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { $ as maskerOrIdentity, $n as findLargeInlineResources, $t as scrubResourceRecord, A as renderStatefulReason, An as formatDockerLoginError, Ar as StackHasActiveImportsError, At as producerRegionsFromState, B as exportAliasCollisionScrubWarning, Bn as getLegacyStateBucketName, Br as isThrottlingError, Bt as withRetry, C as isFinalSnapshotError, Cn as parseBootstrapMarker, Ct as configStringRefusal, D as extractDeploymentEventError, Dn as buildDenyExternalAccessPolicy, Dr as ProvisioningError, Dt as requireConfigObject, E as makeCanonicalizePropertiesFn, En as validateContainerRepoName, Er as PartialFailureError, Et as requireConfigArray, F as green, Fn as AssetManifestLoader, Ft as s3BucketWebsiteUrl, G as IAMRoleProvider, Gn as resolveStateBucketWithDefault, Gt as TEMPLATE_SOURCED_RULES, H as secretBearingStateKeyWarning, Hn as resolveAutoAssetStorage, Ht as TemplateParser, I as red, In as getDockerImageBySourceHash, Ir as normalizeAwsError, It as applyRoleArnIfSet, J as ProviderRegistry, Jn as stateBucketExistenceConfirmed, Jt as errorCauseChain, K as collectInlinePolicyNamesManagedBySiblings, Kn as resolveStateBucketWithDefaultAndSource, Kt as createSecretMasker, L as yellow, Ln as Synthesizer, Lr as withErrorHandling, Lt as DiffCalculator, M as bold, Mn as partitionSensitiveEnv, Mt as s3BucketDomainName, N as cyan, Nn as runDockerForeground, Nt as s3BucketDualStackDomainName, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as buildDockerImage, Or as ResourceTimeoutError, Ot as requireConfigString, P as gray, Pn as runDockerStreaming, Pt as s3BucketRegionalDomainName, Q as maskDeep, Qn as MIGRATE_TMP_PREFIX, Qt as redactSecretsForState, R as collectDeclaredOutputNames, Rn as synthesisStatusMessage, Rr as isMarkedNonRetryable, Rt as INTRINSIC_KEYS, S as createPreDeleteFinalSnapshot, Sn as isCrossRegionRedirect, Sr as LocalStartServiceError, St as configBooleanRefusal, T as unsupportedFinalSnapshotError, Tn as validateAssetBucketName, Tr as NestedStackChildDirectDestroyError, Tt as replayWarn, U as stateKeySecretExposure, Un as resolveCaptureObservedState, Ut as STATE_SOURCED_CROSS_GENERATION_RULES, V as isExportAliasCollision, Vn as resolveApp, Vr as markNonRetryable, Vt as DagBuilder, W as getCurrentResourceSecrets, Wn as resolveSkipPrefix, Wt as STATE_SOURCED_READBACK_RULES, X as findSilentDropProperties, Xn as CFN_TEMPLATE_BODY_LIMIT, Xt as maskSecretsInError, Y as findActionableSilentDrops, Yn as warnDeprecatedNoPrefixCliFlag, Yt as isSingleDynamicReferenceToken, Z as createMaskedRetryLogger, Zn as CFN_TEMPLATE_URL_LIMIT, Zt as maskSecretsInText, _ as computeImplicitDeleteEdges, _n as AssetModeResolver, _t as WAFv2WebACLProvider, a as DeploymentEventsStore, an as importableOutputKeys, ar as AssemblyReader, at as isInterruptedWaitError, b as buildFinalSnapshotIdentifier, bn as ensureAssetStorage, br as LocalInvokeBuildError$1, bt as assertRegionMatch, c as replayFailedOperations, cn as AssetPublisher, cr as resolveBucketRegion, ct as slowCcOperationTimeoutMs, d as updatePartialReason, dn as buildAssetRedirectMap, dr as resetAwsClients, dt as IntrinsicFunctionResolver, en as LockManager, er as uploadCfnTemplate, et as CUSTOM_RESOURCE_RESPONSE_PREFIX, f as UNSPECIFIED_SKIP_REASON, fn as createAssetRedirectResolver, fr as setAwsClients, ft as carriesDynamicReference, g as IMPLICIT_DELETE_DEPENDENCIES, gn as stripControlChars, gr as CrossAccountSecretRefusalError, gt as refStateLookupFromResource, h as maskingRetryLogger, hn as escapeRegExp, ht as parameterTypeMayLoseSecretIdentity, i as DeploymentEventsReader, in as exportNamesCarriedFrom, ir as derivePartitionAndUrlSuffix, it as interruptWatchListenerCount, j as formatResourceLine, jn as getDockerCmd, jr as StackTerminationProtectionError, jt as s3BucketArn, k as isStatefulRecreateTargetSync, kn as dockerSpawnEnvWithSensitive, kr as ResourceUpdateNotSupportedError, kt as classifyReplaySecretRegion, l as replayRollback, ln as stringifyValue, lr as AwsClients, lt as disableInstanceApiTermination, m as withResourceDeadline, mn as rewriteTemplateAssetReferences, mr as CdkdError, mt as getAccountInfo, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as S3StateBackend, nr as PARTITION_TABLE, nt as beginCommandInterruptScope, o as planFailedOps, on as importableOutputs, or as processStackMessages, ot as startInterruptWatch, p as deleteSkipReason, pn as loadPublishableAssetManifest, pt as cfnRefValueFromPhysicalId, q as clearOnUpdateRemoval, qn as resolveUseCdkBootstrapAssets, qt as dynamicReferenceTokens, r as DeployEngine, rn as rebuildClientForBucketRegion, rr as canonicalizeRegion, rt as endCommandInterruptScope, s as planRollback, sn as shouldRetainResource, st as CloudControlProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as displaySafe, tr as expectedOwnerParam, tt as DEFAULT_STATE_PREFIX, u as updatePartialMessage, un as WorkGraph, ur as getAwsClients, ut as isTerminationProtectionPropagationError, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as BOOTSTRAP_MARKER_PREFIX, vr as DeployCancelledError, vt as normalizeAwsTagsToCfn, w as refusesFinalSnapshot, wn as readBootstrapMarkerBody, wr as MissingCdkCliError, wt as readConfigString, x as ccRoutedFinalSnapshotError, xn as getBootstrapMarkerKey, xr as LocalMigrateError, xt as coerceCfnBoolean, y as PRE_DELETE_SNAPSHOT_TYPES, yn as assertAssetBucketRegion, yr as DynamicReferenceRegionAmbiguousError, yt as resolveExplicitPhysicalId, z as collectPublishedOutputNames, zn as getDefaultStateBucketName, zr as isRetryableTransientError, zt as describeTypeWithThrottleRetry } from "./deploy-engine-
|
|
1
|
+
import { $ as maskerOrIdentity, $n as findLargeInlineResources, $t as scrubResourceRecord, A as renderStatefulReason, An as formatDockerLoginError, Ar as StackHasActiveImportsError, At as producerRegionsFromState, B as exportAliasCollisionScrubWarning, Bn as getLegacyStateBucketName, Br as isThrottlingError, Bt as withRetry, C as isFinalSnapshotError, Cn as parseBootstrapMarker, Ct as configStringRefusal, D as extractDeploymentEventError, Dn as buildDenyExternalAccessPolicy, Dr as ProvisioningError, Dt as requireConfigObject, E as makeCanonicalizePropertiesFn, En as validateContainerRepoName, Er as PartialFailureError, Et as requireConfigArray, F as green, Fn as AssetManifestLoader, Ft as s3BucketWebsiteUrl, G as IAMRoleProvider, Gn as resolveStateBucketWithDefault, Gt as TEMPLATE_SOURCED_RULES, H as secretBearingStateKeyWarning, Hn as resolveAutoAssetStorage, Ht as TemplateParser, I as red, In as getDockerImageBySourceHash, Ir as normalizeAwsError, It as applyRoleArnIfSet, J as ProviderRegistry, Jn as stateBucketExistenceConfirmed, Jt as errorCauseChain, K as collectInlinePolicyNamesManagedBySiblings, Kn as resolveStateBucketWithDefaultAndSource, Kt as createSecretMasker, L as yellow, Ln as Synthesizer, Lr as withErrorHandling, Lt as DiffCalculator, M as bold, Mn as partitionSensitiveEnv, Mt as s3BucketDomainName, N as cyan, Nn as runDockerForeground, Nt as s3BucketDualStackDomainName, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as buildDockerImage, Or as ResourceTimeoutError, Ot as requireConfigString, P as gray, Pn as runDockerStreaming, Pt as s3BucketRegionalDomainName, Q as maskDeep, Qn as MIGRATE_TMP_PREFIX, Qt as redactSecretsForState, R as collectDeclaredOutputNames, Rn as synthesisStatusMessage, Rr as isMarkedNonRetryable, Rt as INTRINSIC_KEYS, S as createPreDeleteFinalSnapshot, Sn as isCrossRegionRedirect, Sr as LocalStartServiceError, St as configBooleanRefusal, T as unsupportedFinalSnapshotError, Tn as validateAssetBucketName, Tr as NestedStackChildDirectDestroyError, Tt as replayWarn, U as stateKeySecretExposure, Un as resolveCaptureObservedState, Ut as STATE_SOURCED_CROSS_GENERATION_RULES, V as isExportAliasCollision, Vn as resolveApp, Vr as markNonRetryable, Vt as DagBuilder, W as getCurrentResourceSecrets, Wn as resolveSkipPrefix, Wt as STATE_SOURCED_READBACK_RULES, X as findSilentDropProperties, Xn as CFN_TEMPLATE_BODY_LIMIT, Xt as maskSecretsInError, Y as findActionableSilentDrops, Yn as warnDeprecatedNoPrefixCliFlag, Yt as isSingleDynamicReferenceToken, Z as createMaskedRetryLogger, Zn as CFN_TEMPLATE_URL_LIMIT, Zt as maskSecretsInText, _ as computeImplicitDeleteEdges, _n as AssetModeResolver, _t as WAFv2WebACLProvider, a as DeploymentEventsStore, an as importableOutputKeys, ar as AssemblyReader, at as isInterruptedWaitError, b as buildFinalSnapshotIdentifier, bn as ensureAssetStorage, br as LocalInvokeBuildError$1, bt as assertRegionMatch, c as replayFailedOperations, cn as AssetPublisher, cr as resolveBucketRegion, ct as slowCcOperationTimeoutMs, d as updatePartialReason, dn as buildAssetRedirectMap, dr as resetAwsClients, dt as IntrinsicFunctionResolver, en as LockManager, er as uploadCfnTemplate, et as CUSTOM_RESOURCE_RESPONSE_PREFIX, f as UNSPECIFIED_SKIP_REASON, fn as createAssetRedirectResolver, fr as setAwsClients, ft as carriesDynamicReference, g as IMPLICIT_DELETE_DEPENDENCIES, gn as stripControlChars, gr as CrossAccountSecretRefusalError, gt as refStateLookupFromResource, h as maskingRetryLogger, hn as escapeRegExp, ht as parameterTypeMayLoseSecretIdentity, i as DeploymentEventsReader, in as exportNamesCarriedFrom, ir as derivePartitionAndUrlSuffix, it as interruptWatchListenerCount, j as formatResourceLine, jn as getDockerCmd, jr as StackTerminationProtectionError, jt as s3BucketArn, k as isStatefulRecreateTargetSync, kn as dockerSpawnEnvWithSensitive, kr as ResourceUpdateNotSupportedError, kt as classifyReplaySecretRegion, l as replayRollback, ln as stringifyValue, lr as AwsClients, lt as disableInstanceApiTermination, m as withResourceDeadline, mn as rewriteTemplateAssetReferences, mr as CdkdError, mt as getAccountInfo, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as S3StateBackend, nr as PARTITION_TABLE, nt as beginCommandInterruptScope, o as planFailedOps, on as importableOutputs, or as processStackMessages, ot as startInterruptWatch, p as deleteSkipReason, pn as loadPublishableAssetManifest, pt as cfnRefValueFromPhysicalId, q as clearOnUpdateRemoval, qn as resolveUseCdkBootstrapAssets, qt as dynamicReferenceTokens, r as DeployEngine, rn as rebuildClientForBucketRegion, rr as canonicalizeRegion, rt as endCommandInterruptScope, s as planRollback, sn as shouldRetainResource, st as CloudControlProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as displaySafe, tr as expectedOwnerParam, tt as DEFAULT_STATE_PREFIX, u as updatePartialMessage, un as WorkGraph, ur as getAwsClients, ut as isTerminationProtectionPropagationError, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as BOOTSTRAP_MARKER_PREFIX, vr as DeployCancelledError, vt as normalizeAwsTagsToCfn, w as refusesFinalSnapshot, wn as readBootstrapMarkerBody, wr as MissingCdkCliError, wt as readConfigString, x as ccRoutedFinalSnapshotError, xn as getBootstrapMarkerKey, xr as LocalMigrateError, xt as coerceCfnBoolean, y as PRE_DELETE_SNAPSHOT_TYPES, yn as assertAssetBucketRegion, yr as DynamicReferenceRegionAmbiguousError, yt as resolveExplicitPhysicalId, z as collectPublishedOutputNames, zn as getDefaultStateBucketName, zr as isRetryableTransientError, zt as describeTypeWithThrottleRetry } from "./deploy-engine-Bc0FBOuJ.js";
|
|
2
2
|
import { a as runStackBuffered, c as PATTERN_B_NAME_PROPERTIES, d as generateResourceName, f as generateResourceNameWithFallback, g as withStackName, h as withSkipPrefix, l as PATTERN_B_RESOURCE_TYPES, n as getLogger, o as getLiveRenderer, p as getCurrentStackName, r as reserveStdoutForPayload, s as PATTERN_B_NAME_OPTIONS } from "./logger-C9-E73FI.js";
|
|
3
|
-
import { t as getCdkdVersion } from "./version-
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
3
|
+
import { t as getCdkdVersion } from "./version-DY_eLvXB.js";
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-DcAqfBUo.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
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, 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";
|
|
@@ -6969,6 +6969,141 @@ function bucketLocationToRegion(constraint) {
|
|
|
6969
6969
|
if (value === "eu") return "eu-west-1";
|
|
6970
6970
|
return value;
|
|
6971
6971
|
}
|
|
6972
|
+
/**
|
|
6973
|
+
* Whether an error from `GetBucketLocation` means "no bucket of that name".
|
|
6974
|
+
*
|
|
6975
|
+
* The test is the wire error CODE, which the SDK lifts onto `name`.
|
|
6976
|
+
*
|
|
6977
|
+
* An earlier revision of this comment said `GetBucketLocation` does NOT
|
|
6978
|
+
* deserialize its 404 into the modeled `NoSuchBucket` class "the way
|
|
6979
|
+
* `DeleteBucket`'s is", inferring that from `GetBucketLocationCommand.d.ts`
|
|
6980
|
+
* declaring only `@throws {@link S3ServiceException}`. That inference is
|
|
6981
|
+
* FALSE and was measured false on 2026-08-26 by feeding canned 404 XML through
|
|
6982
|
+
* the real client: both operations yield `ctor=NoSuchBucket`,
|
|
6983
|
+
* `name=NoSuchBucket`, `instanceof NoSuchBucket === true`, `status=404`.
|
|
6984
|
+
* `DeleteBucketCommand.d.ts` declares the same lone `S3ServiceException`, and
|
|
6985
|
+
* the deserializer resolves errors through a per-NAMESPACE schema registry
|
|
6986
|
+
* (`client-s3/dist-cjs/schemas/schemas_0.js`), not a per-operation one — so
|
|
6987
|
+
* the command's declared throw list never decided this. The PREDICATE was
|
|
6988
|
+
* right for the wrong reason; the reason is corrected rather than quietly
|
|
6989
|
+
* dropped, because the wrong one would have justified the wrong fixture in the
|
|
6990
|
+
* next provider that copied it.
|
|
6991
|
+
*
|
|
6992
|
+
* There is deliberately no `instanceof NoSuchBucket` arm. The generated class
|
|
6993
|
+
* assigns `name = 'NoSuchBucket'` on the instance
|
|
6994
|
+
* (`@aws-sdk/client-s3/dist-es/models/errors.js`), so every instance already
|
|
6995
|
+
* satisfies the check below: it would be an arm no case could ever make
|
|
6996
|
+
* decide, and a branch nothing can make decide is a branch nothing protects.
|
|
6997
|
+
*
|
|
6998
|
+
* A BARE HTTP 404 is deliberately NOT accepted, and this is the one place the
|
|
6999
|
+
* predicate is narrower than "the resource is missing". On the create path
|
|
7000
|
+
* `absent` is the answer that ENABLES the partial-create cleanup's
|
|
7001
|
+
* `DeleteBucket`, so anything that can 404 without S3 having said
|
|
7002
|
+
* `NoSuchBucket` — a corporate proxy, an `AWS_ENDPOINT_URL` override, an
|
|
7003
|
+
* S3-compatible gateway — would otherwise license a delete on the strength of
|
|
7004
|
+
* a response S3 never sent. Narrowing costs only that such a 404 becomes
|
|
7005
|
+
* `indeterminate`, and every caller's `indeterminate` branch is the
|
|
7006
|
+
* non-destructive one.
|
|
7007
|
+
*
|
|
7008
|
+
* Everything else — 403 from a bucket owned by another account, a throttle, a
|
|
7009
|
+
* network fault — is likewise NOT absence. Folding those into "absent"
|
|
7010
|
+
* is what would let a probe failure re-authorize the destructive branch each
|
|
7011
|
+
* caller below is guarding.
|
|
7012
|
+
*/
|
|
7013
|
+
function isNoSuchBucketError(error) {
|
|
7014
|
+
return error?.name === "NoSuchBucket";
|
|
7015
|
+
}
|
|
7016
|
+
/**
|
|
7017
|
+
* The bucket's region as reported on an error's own `x-amz-bucket-region`
|
|
7018
|
+
* header, canonicalized — the free answer, requiring no second round trip.
|
|
7019
|
+
*
|
|
7020
|
+
* Shared by {@link S3BucketProvider.resolveOwnedBucketRegion} (reading the
|
|
7021
|
+
* `BucketAlreadyOwnedByYou` 409) and {@link S3BucketProvider.probeBucketRegion}
|
|
7022
|
+
* (reading a FAILED probe), so the header-then-canonicalize pair has one
|
|
7023
|
+
* spelling rather than two that can drift apart.
|
|
7024
|
+
*/
|
|
7025
|
+
function regionFromErrorHeader(error) {
|
|
7026
|
+
const fromHeader = readBucketRegionHeader(error);
|
|
7027
|
+
return fromHeader ? canonicalizeRegion(fromHeader) : void 0;
|
|
7028
|
+
}
|
|
7029
|
+
/**
|
|
7030
|
+
* Whether this error's `x-amz-bucket-region` describes the BUCKET rather than
|
|
7031
|
+
* the endpoint that answered.
|
|
7032
|
+
*
|
|
7033
|
+
* The distinction is the whole value of the header, and reading it off any
|
|
7034
|
+
* failure throws it away. Only two failure shapes are S3 telling you where the
|
|
7035
|
+
* BUCKET is: the 301 cross-region redirect (`PermanentRedirect`) and
|
|
7036
|
+
* `AuthorizationHeaderMalformed`, which is the SigV4 region mismatch reporting
|
|
7037
|
+
* the region you should have signed for. Every other failure -- a 403 for a
|
|
7038
|
+
* bucket the caller cannot see, a throttle, a 404 -- is answered by the
|
|
7039
|
+
* endpoint that was ASKED, so its header names the region the QUESTION went to.
|
|
7040
|
+
* That is the same property that already orders the absence check first in
|
|
7041
|
+
* {@link S3BucketProvider.probeBucketRegion}; this gate applies it to the rest
|
|
7042
|
+
* of the failure space instead of to `NoSuchBucket` alone.
|
|
7043
|
+
*
|
|
7044
|
+
* Accepting an endpoint-region header as the bucket's answer inverted both
|
|
7045
|
+
* guards built on top of it: on delete a 403 whose header echoed the deploy's
|
|
7046
|
+
* own region compared EQUAL and returned silently, which is exactly the hostile
|
|
7047
|
+
* bucket-policy `Deny` population {@link
|
|
7048
|
+
* S3BucketProvider.announceUnverifiedBucketIdentity} exists for; on create it
|
|
7049
|
+
* promoted an unanswerable probe to "the bucket is already there", producing a
|
|
7050
|
+
* false ADOPTED-and-ACLs-reset warning over a genuinely new bucket and
|
|
7051
|
+
* suppressing the orphan cleanup with no warning at all.
|
|
7052
|
+
*
|
|
7053
|
+
* Matched on the wire error CODE alone, never on an HTTP STATUS. An earlier
|
|
7054
|
+
* revision also accepted a bare `httpStatusCode === 301`, which is the same
|
|
7055
|
+
* defect {@link isNoSuchBucketError} was narrowed to close: a status S3 never
|
|
7056
|
+
* NAMED is not S3 speaking. A proxy, an `AWS_ENDPOINT_URL` override or an
|
|
7057
|
+
* S3-compatible gateway answering 301 with an `x-amz-bucket-region` of the
|
|
7058
|
+
* deploy's own region would have made `probe.region` compare EQUAL, and the
|
|
7059
|
+
* delete guard would have returned silently -- the same silent pass the 403
|
|
7060
|
+
* arm above was removed for, arriving through the status instead of the header.
|
|
7061
|
+
* Unreachable against real S3, since the redirect always carries the code, so
|
|
7062
|
+
* dropping the arm costs nothing and the shape stops being expressible.
|
|
7063
|
+
*
|
|
7064
|
+
* The `BucketAlreadyOwnedByYou` 409 is a THIRD region-bearing shape (measured
|
|
7065
|
+
* for issue #2227) and is deliberately absent here: `GetBucketLocation` cannot
|
|
7066
|
+
* return it, and the create path reads it through
|
|
7067
|
+
* {@link S3BucketProvider.resolveOwnedBucketRegion}'s explicit error argument,
|
|
7068
|
+
* which never routes through this gate.
|
|
7069
|
+
*/
|
|
7070
|
+
function carriesBucketRegionHeader(error) {
|
|
7071
|
+
const candidate = error;
|
|
7072
|
+
if (candidate?.name === "PermanentRedirect") return true;
|
|
7073
|
+
return candidate?.name === "AuthorizationHeaderMalformed";
|
|
7074
|
+
}
|
|
7075
|
+
/** The probe was issued and could not answer. */
|
|
7076
|
+
function probeFailedCause(errorName) {
|
|
7077
|
+
return {
|
|
7078
|
+
why: `the GetBucketLocation identity check failed (${errorName})`,
|
|
7079
|
+
remedy: "Re-run with --verbose for AWS's own message. Re-run the destroy once the check can succeed, or grant s3:GetBucketLocation on the bucket (a bucket policy can Deny it), to have the guard verify the target first."
|
|
7080
|
+
};
|
|
7081
|
+
}
|
|
7082
|
+
/**
|
|
7083
|
+
* No probe was issued, because the state record names no region to check
|
|
7084
|
+
* against.
|
|
7085
|
+
*
|
|
7086
|
+
* `DeleteContext.expectedRegion` is absent exactly for a record written before
|
|
7087
|
+
* the region-keyed state layout, and `destroy-runner` omits the field rather
|
|
7088
|
+
* than inventing one. That cohort is the one MOST likely to predate the issue
|
|
7089
|
+
* #2227 create-path guard, so it is the likeliest carrier of a foreign-region
|
|
7090
|
+
* physical id -- and until this cause existed it was the one population that
|
|
7091
|
+
* got no probe, no warning and no debug line, i.e. the highest-risk shape with
|
|
7092
|
+
* the quietest output.
|
|
7093
|
+
*
|
|
7094
|
+
* Warn only. Refusing here would strand every legacy destroy with no IAM
|
|
7095
|
+
* remedy at all -- strictly worse than the stranding the guard already declines
|
|
7096
|
+
* to impose -- and defaulting `expectedRegion` to the client region in
|
|
7097
|
+
* `destroy-runner` would fabricate an expectation the record never made AND
|
|
7098
|
+
* change `assertRegionMatch` on the `NoSuchBucket` path, a far wider blast
|
|
7099
|
+
* radius than this.
|
|
7100
|
+
*/
|
|
7101
|
+
function noRecordedRegionCause() {
|
|
7102
|
+
return {
|
|
7103
|
+
why: `this state record carries no region, so there was nothing to check the bucket's location against`,
|
|
7104
|
+
remedy: "This record predates cdkd's region-keyed state layout. Re-deploy the stack to write a record that carries its region, or confirm the bucket's location yourself, to have the guard run on the next destroy."
|
|
7105
|
+
};
|
|
7106
|
+
}
|
|
6972
7107
|
var S3BucketProvider = class S3BucketProvider {
|
|
6973
7108
|
s3Client;
|
|
6974
7109
|
logger = getLogger().child("S3BucketProvider");
|
|
@@ -8731,7 +8866,23 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
8731
8866
|
const wantRegion = canonicalizeRegion(expectedRegion);
|
|
8732
8867
|
const actualRegion = await this.resolveOwnedBucketRegion(bucketName, createError);
|
|
8733
8868
|
if (actualRegion === wantRegion) return;
|
|
8734
|
-
|
|
8869
|
+
this.refuseForeignRegionAdopt(logicalId, resourceType, bucketName, wantRegion, actualRegion, createError);
|
|
8870
|
+
}
|
|
8871
|
+
/**
|
|
8872
|
+
* The refusal {@link assertExistingBucketRegion} raises, as its own method so
|
|
8873
|
+
* the `us-east-1` pre-flight can raise the IDENTICAL one.
|
|
8874
|
+
*
|
|
8875
|
+
* Two callers reach the same conclusion by different evidence -- a 409's
|
|
8876
|
+
* `x-amz-bucket-region` on one side, a pre-flight `GetBucketLocation` on the
|
|
8877
|
+
* other -- and the user-facing consequence is the same in both, so the
|
|
8878
|
+
* wording, the `markNonRetryable` marker and the deliberate avoidance of the
|
|
8879
|
+
* literal `does not exist` are stated once rather than paraphrased.
|
|
8880
|
+
*
|
|
8881
|
+
* `never`-returning so a caller can `this.refuseForeignRegionAdopt(...)` as a
|
|
8882
|
+
* statement without the compiler losing that control flow ends there.
|
|
8883
|
+
*/
|
|
8884
|
+
refuseForeignRegionAdopt(logicalId, resourceType, bucketName, wantRegion, actualRegion, cause) {
|
|
8885
|
+
throw markNonRetryable(new ProvisioningError(`Refusing to adopt existing S3 bucket ${bucketName} for ${logicalId} (${resourceType}): it is owned by this account but lives in ${actualRegion}, while this stack deploys to ${wantRegion}. S3 bucket names are globally unique, so owning the name (which is what 'BucketAlreadyOwnedByYou' and a successful location lookup each report) never means the bucket is in this region. Adopting it would apply this stack's bucket configuration to a bucket in ${actualRegion}, and would record a physical id that denotes no bucket in ${wantRegion}. Either give this stack a bucket name unique to ${wantRegion}, or deploy the stack to ${actualRegion}.`, resourceType, logicalId, bucketName, cause));
|
|
8735
8886
|
}
|
|
8736
8887
|
/**
|
|
8737
8888
|
* The region an already-owned bucket actually lives in, canonicalized.
|
|
@@ -8740,13 +8891,197 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
8740
8891
|
* failure PROPAGATES rather than degrading to a guess: the caller is a
|
|
8741
8892
|
* fail-closed guard, and a guessed region is exactly what would make it
|
|
8742
8893
|
* adopt the wrong bucket.
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8894
|
+
*
|
|
8895
|
+
* The header read here is deliberately NOT behind
|
|
8896
|
+
* {@link carriesBucketRegionHeader}, and that is licensed by the CALL SITE
|
|
8897
|
+
* rather than by the header being safer: the only error ever passed in is the
|
|
8898
|
+
* `BucketAlreadyOwnedByYou` 409 from `CreateBucket`, which is itself a
|
|
8899
|
+
* region-bearing shape (measured for issue #2227 -- a us-west-2 bucket
|
|
8900
|
+
* answered it with `x-amz-bucket-region: us-west-2` to clients in three
|
|
8901
|
+
* regions). The gate exists for {@link probeBucketRegion}, which catches the
|
|
8902
|
+
* WHOLE failure space of `GetBucketLocation` and so must sort the two kinds
|
|
8903
|
+
* apart. If this method ever takes an error from a second call site, it needs
|
|
8904
|
+
* the gate too.
|
|
8905
|
+
*
|
|
8906
|
+
* `awsError` is optional because the two PRE-FLIGHT callers (issues #2241 /
|
|
8907
|
+
* #2245) have no AWS error in hand — they ask the question BEFORE issuing
|
|
8908
|
+
* the call that would produce one, so only the `GetBucketLocation` half
|
|
8909
|
+
* applies. This is deliberately the same function rather than a second one
|
|
8910
|
+
* that "also reads GetBucketLocation": the `EU` and empty-`LocationConstraint`
|
|
8911
|
+
* folds and the `x-amz-bucket-region` header precedence are one predicate,
|
|
8912
|
+
* and a paraphrase of them is a divergence waiting to happen.
|
|
8913
|
+
*/
|
|
8914
|
+
async resolveOwnedBucketRegion(bucketName, awsError) {
|
|
8915
|
+
const fromHeader = awsError ? regionFromErrorHeader(awsError) : void 0;
|
|
8916
|
+
if (fromHeader) return fromHeader;
|
|
8747
8917
|
return bucketLocationToRegion((await this.s3Client.send(new GetBucketLocationCommand({ Bucket: bucketName }))).LocationConstraint);
|
|
8748
8918
|
}
|
|
8749
8919
|
/**
|
|
8920
|
+
* Ask S3 where a bucket NAME lives, before doing anything to it.
|
|
8921
|
+
*
|
|
8922
|
+
* The non-throwing pre-flight form of {@link resolveOwnedBucketRegion},
|
|
8923
|
+
* used by the three sites that must confirm a bucket is the one they mean
|
|
8924
|
+
* before acting on it: the `us-east-1` create (issue
|
|
8925
|
+
* [#2241](https://github.com/go-to-k/cdkd/issues/2241)) and the update /
|
|
8926
|
+
* delete paths (issue [#2245](https://github.com/go-to-k/cdkd/issues/2245)).
|
|
8927
|
+
*
|
|
8928
|
+
* Why `GetBucketLocation` and not `HeadBucket`: recorded at length on
|
|
8929
|
+
* {@link S3BucketProvider.assertExistingBucketRegion} and in
|
|
8930
|
+
* `src/utils/aws-region-resolver.ts`. A cross-region `HeadBucket` 301s and
|
|
8931
|
+
* SDK v3's region-redirect middleware mishandles the empty-body HEAD into a
|
|
8932
|
+
* synthetic `name: 'Unknown'`, so it cannot answer this question at all;
|
|
8933
|
+
* `GetBucketLocation` is a GET with an XML body and is answered by ANY
|
|
8934
|
+
* regional endpoint in the same partition for a bucket in any region of
|
|
8935
|
+
* that partition (measured 2026-08-13, see that module).
|
|
8936
|
+
*
|
|
8937
|
+
* Deliberately NOT `resolveBucketRegion` from that module: it never throws
|
|
8938
|
+
* and returns the caller's own `fallbackRegion` on a failed probe, which
|
|
8939
|
+
* would report "same region" for every failure and silently authorize each
|
|
8940
|
+
* branch below. Distinguishing `absent` from `indeterminate` is precisely
|
|
8941
|
+
* what this wrapper adds over it.
|
|
8942
|
+
*/
|
|
8943
|
+
async probeBucketRegion(bucketName) {
|
|
8944
|
+
try {
|
|
8945
|
+
return {
|
|
8946
|
+
kind: "region",
|
|
8947
|
+
region: await this.resolveOwnedBucketRegion(bucketName)
|
|
8948
|
+
};
|
|
8949
|
+
} catch (probeError) {
|
|
8950
|
+
if (isNoSuchBucketError(probeError)) return { kind: "absent" };
|
|
8951
|
+
if (carriesBucketRegionHeader(probeError)) {
|
|
8952
|
+
const fromHeader = regionFromErrorHeader(probeError);
|
|
8953
|
+
if (fromHeader) return {
|
|
8954
|
+
kind: "region",
|
|
8955
|
+
region: fromHeader
|
|
8956
|
+
};
|
|
8957
|
+
}
|
|
8958
|
+
return {
|
|
8959
|
+
kind: "indeterminate",
|
|
8960
|
+
reason: probeError instanceof Error ? probeError.message : String(probeError),
|
|
8961
|
+
errorName: probeError instanceof Error ? probeError.name : typeof probeError
|
|
8962
|
+
};
|
|
8963
|
+
}
|
|
8964
|
+
}
|
|
8965
|
+
/**
|
|
8966
|
+
* Refuse to act on a bucket a STATE RECORD names that does not live in
|
|
8967
|
+
* `expectedRegion` (issue [#2245](https://github.com/go-to-k/cdkd/issues/2245)).
|
|
8968
|
+
*
|
|
8969
|
+
* {@link assertExistingBucketRegion} is on the CREATE path only, so it stops
|
|
8970
|
+
* NEW poisoning and does nothing about a record already written by a build
|
|
8971
|
+
* that predates it. Two paths then act on such a record, and neither noticed:
|
|
8972
|
+
* `update()` had no region check at all, and `delete()`'s
|
|
8973
|
+
* {@link assertRegionMatch} fires only from the `NoSuchBucket` branch — which
|
|
8974
|
+
* a cross-region `DeleteBucket` never reaches, because SDK v3's
|
|
8975
|
+
* region-redirect middleware FOLLOWS the 301 for body-bearing operations and
|
|
8976
|
+
* the delete simply succeeds against the other region's bucket.
|
|
8977
|
+
*
|
|
8978
|
+
* ## Why this probes instead of reading state
|
|
8979
|
+
*
|
|
8980
|
+
* The cheap check would be state-vs-client region, and it cannot answer this
|
|
8981
|
+
* question: in the poisoned record the region field is the STACK's region and
|
|
8982
|
+
* MATCHES, while the physical id denotes a bucket somewhere else. The bucket's
|
|
8983
|
+
* own location is knowable only from S3, so one `GetBucketLocation` per
|
|
8984
|
+
* bucket update / delete is the floor. It is a rounding error next to what
|
|
8985
|
+
* those paths already spend — an S3 update issues a dozen or more Put/Get
|
|
8986
|
+
* round trips, and a delete may enumerate and remove every object version.
|
|
8987
|
+
*
|
|
8988
|
+
* ## Refuse rather than heal
|
|
8989
|
+
*
|
|
8990
|
+
* On update, healing would mean rewriting the recorded physical id, i.e. a
|
|
8991
|
+
* state mutation whose blast radius (every `Fn::GetAtt` consumer, the next
|
|
8992
|
+
* diff, the next destroy) is larger than the misconfiguration it repairs.
|
|
8993
|
+
* On delete, refusing strands the destroy and needs a human — a foot-gun, and
|
|
8994
|
+
* a smaller one than deleting a live bucket in another region, which nothing
|
|
8995
|
+
* can undo. Both refusals are `markNonRetryable` for the same reason the
|
|
8996
|
+
* create-side one is: they are deterministic, so letting `withRetry` re-run
|
|
8997
|
+
* the operation for its full budget only delays the same message.
|
|
8998
|
+
*
|
|
8999
|
+
* ## What an unanswered probe does, and the IAM dependency that creates
|
|
9000
|
+
*
|
|
9001
|
+
* `absent` and `indeterminate` both PROCEED, a deliberate asymmetry with the
|
|
9002
|
+
* create-side guard. `absent` is the ordinary already-gone destroy, which the
|
|
9003
|
+
* existing `NoSuchBucket` path handles idempotently one call later.
|
|
9004
|
+
* `indeterminate` is the load-bearing one, and the honest description of it
|
|
9005
|
+
* is NOT that the affected population is unrelated to the defect — an
|
|
9006
|
+
* earlier revision of this comment said exactly that and it is false. It is
|
|
9007
|
+
* the SAME population, merely unprobeable: a principal without
|
|
9008
|
+
* `s3:GetBucketLocation` can hold a poisoned record just as easily as any
|
|
9009
|
+
* other, and for that principal this guard is INERT on every call,
|
|
9010
|
+
* permanently. So this change introduces a new IAM dependency — the guard is
|
|
9011
|
+
* only as good as `s3:GetBucketLocation` on the target bucket, which a bucket
|
|
9012
|
+
* POLICY can also `Deny`.
|
|
9013
|
+
*
|
|
9014
|
+
* Proceeding anyway is still the right trade, for a reason about escape
|
|
9015
|
+
* hatches rather than about who is affected. Failing closed would strand
|
|
9016
|
+
* every update and destroy for a least-privilege role with no way out except
|
|
9017
|
+
* widening IAM — cdkd offers no per-resource override to force one through —
|
|
9018
|
+
* while the reachable hazard needs a bucket that EXISTS and is owned by this
|
|
9019
|
+
* account, for which `GetBucketLocation` answers from any endpoint in the
|
|
9020
|
+
* partition (see {@link probeBucketRegion}). Three fixes that look better and
|
|
9021
|
+
* are worse, recorded so they are not retried: refusing on `indeterminate` is
|
|
9022
|
+
* the stranding above; refusing only on 403 cannot work, because a missing
|
|
9023
|
+
* IAM grant and a hostile `Deny` are the same wire response; and folding 403
|
|
9024
|
+
* into `absent` is the precise collapse {@link BucketRegionProbe}'s third
|
|
9025
|
+
* state exists to prevent.
|
|
9026
|
+
*
|
|
9027
|
+
* What proceeding obliges instead is that it is never SILENT on the
|
|
9028
|
+
* irreversible path — see {@link announceUnverifiedBucketIdentity}.
|
|
9029
|
+
*/
|
|
9030
|
+
async assertStateBucketRegion(operation, logicalId, resourceType, physicalId, expectedRegion) {
|
|
9031
|
+
const wantRegion = canonicalizeRegion(expectedRegion);
|
|
9032
|
+
const probe = await this.probeBucketRegion(physicalId);
|
|
9033
|
+
if (probe.kind === "absent") return;
|
|
9034
|
+
if (probe.kind === "indeterminate") {
|
|
9035
|
+
this.logger.debug(`GetBucketLocation failed for S3 bucket ${physicalId} (${logicalId}) before ${operation}: ${probe.reason}`);
|
|
9036
|
+
this.announceUnverifiedBucketIdentity(operation, logicalId, physicalId, probeFailedCause(probe.errorName));
|
|
9037
|
+
return;
|
|
9038
|
+
}
|
|
9039
|
+
if (probe.region === wantRegion) return;
|
|
9040
|
+
const consequence = operation === "update" ? `Applying this stack's bucket configuration would reconfigure a bucket in ${probe.region} that this stack does not own the definition of.` : `Deleting it would destroy a bucket in ${probe.region} that this stack's region does not contain, and that deletion cannot be undone.`;
|
|
9041
|
+
const remedy = operation === "update" ? `Confirm which bucket you mean, then either rerun this stack against ${probe.region}, or — if the recorded id is stale — drop cdkd's record with 'cdkd state orphan <stack>', which removes the record without touching any AWS resource, and deploy again.` : `Confirm which bucket you mean. If ${physicalId} is genuinely yours to delete, delete it deliberately in ${probe.region}; if this record is simply stale, drop it with 'cdkd state orphan <stack>', which removes cdkd's record without touching any AWS resource, and the destroy will then have nothing to do for this resource.`;
|
|
9042
|
+
throw markNonRetryable(new ProvisioningError(`Refusing to ${operation} S3 bucket ${physicalId} for ${logicalId} (${resourceType}): the bucket lives in ${probe.region}, while this stack's state is for ${wantRegion}. S3 bucket names are globally unique, so a state record can name a bucket in another region — records written before the create-path region guard can carry one. ${consequence} ${remedy}`, resourceType, logicalId, physicalId));
|
|
9043
|
+
}
|
|
9044
|
+
/**
|
|
9045
|
+
* Say out loud that the identity guard did not run.
|
|
9046
|
+
*
|
|
9047
|
+
* `delete` gets a WARN and `update` a debug, and the asymmetry is the whole
|
|
9048
|
+
* point rather than a stylistic choice. Both proceed, but only one of them
|
|
9049
|
+
* proceeds into something irreversible, and cdkd's default log level is
|
|
9050
|
+
* `info` -- so a debug line here meant that on the one path where the guard
|
|
9051
|
+
* failing open destroys data, the operator's output was byte-identical to a
|
|
9052
|
+
* normal destroy.
|
|
9053
|
+
*
|
|
9054
|
+
* That is not merely a misconfiguration story. `GetBucketLocation` is
|
|
9055
|
+
* DENIABLE on the target bucket by anyone holding `s3:PutBucketPolicy` on it,
|
|
9056
|
+
* and a `Deny` there is indistinguishable on the wire from a missing IAM
|
|
9057
|
+
* grant. A 403 leaves the probe `indeterminate` -- deliberately, since a 403
|
|
9058
|
+
* is answered by the endpoint that was asked and
|
|
9059
|
+
* {@link carriesBucketRegionHeader} therefore refuses to read its header as
|
|
9060
|
+
* the bucket's region -- so a poisoned record naming a bucket in another
|
|
9061
|
+
* region PROCEEDS: the auto-empty and `DeleteBucket` follow the SDK's 301 and
|
|
9062
|
+
* the bucket is gone. A throttled probe during a wide destroy reaches the
|
|
9063
|
+
* same place by accident, and a state record with no region at all never gets
|
|
9064
|
+
* a probe to begin with. None of the three can be refused -- see the caller's
|
|
9065
|
+
* note on why failing closed is the wrong trade -- so the remaining
|
|
9066
|
+
* obligation is that it never happens QUIETLY.
|
|
9067
|
+
*
|
|
9068
|
+
* Deliberately NOT retried on `isThrottlingError`. It would not touch the
|
|
9069
|
+
* hostile-`Deny` path at all (a 403 carries neither a throttling name nor a
|
|
9070
|
+
* retryable status), the provider has no injected sleep seam here so a
|
|
9071
|
+
* bounded retry either adds real wall-clock to every throttled destroy or
|
|
9072
|
+
* needs a new timer seam plus fixtures to control it, and with this warning
|
|
9073
|
+
* in place a throttled probe is now visible and re-runnable -- which was the
|
|
9074
|
+
* actual defect.
|
|
9075
|
+
*/
|
|
9076
|
+
announceUnverifiedBucketIdentity(operation, logicalId, physicalId, cause) {
|
|
9077
|
+
const message = `Could not confirm which bucket ${physicalId} for ${logicalId} is before ${operation}: ${cause.why}, so the cross-region guard did NOT run and cdkd is proceeding on the recorded physical id alone.`;
|
|
9078
|
+
if (operation === "delete") {
|
|
9079
|
+
this.logger.warn(`${message} If this state record names a bucket in another region, this destroy will delete THAT bucket. ${cause.remedy}`);
|
|
9080
|
+
return;
|
|
9081
|
+
}
|
|
9082
|
+
this.logger.debug(`${message} Proceeding.`);
|
|
9083
|
+
}
|
|
9084
|
+
/**
|
|
8750
9085
|
* Create an S3 bucket
|
|
8751
9086
|
*/
|
|
8752
9087
|
async create(logicalId, resourceType, properties, context) {
|
|
@@ -8761,12 +9096,16 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
8761
9096
|
const region = await this.getRegion();
|
|
8762
9097
|
if (region !== "us-east-1") createParams.CreateBucketConfiguration = { LocationConstraint: region };
|
|
8763
9098
|
if (properties["ObjectLockEnabled"] === true || properties["ObjectLockEnabled"] === "true") createParams.ObjectLockEnabledForBucket = true;
|
|
9099
|
+
const preflight = canonicalizeRegion(region) === "us-east-1" ? await this.probeBucketRegion(bucketName) : { kind: "absent" };
|
|
8764
9100
|
let createdNewBucket = false;
|
|
8765
9101
|
try {
|
|
8766
9102
|
await this.s3Client.send(new CreateBucketCommand(createParams));
|
|
8767
|
-
createdNewBucket =
|
|
9103
|
+
createdNewBucket = preflight.kind === "absent";
|
|
9104
|
+
if (preflight.kind === "region" && preflight.region !== "us-east-1") this.refuseForeignRegionAdopt(logicalId, resourceType, bucketName, "us-east-1", preflight.region);
|
|
9105
|
+
if (preflight.kind === "region") this.logger.warn(`S3 bucket ${bucketName} for ${logicalId} (${resourceType}) already existed in ${preflight.region} and was ADOPTED, not created. In us-east-1 S3 answers a re-create of a bucket you already own with 200 OK and RESETS that bucket's access control lists, so any ACL previously set on ${bucketName} is now the default. cdkd will not delete this bucket if the rest of this create fails.`);
|
|
8768
9106
|
this.logger.debug(`Created S3 bucket: ${bucketName}`);
|
|
8769
9107
|
} catch (createError) {
|
|
9108
|
+
if (createError instanceof ProvisioningError) throw createError;
|
|
8770
9109
|
if (createError instanceof Error && (createError.name === "BucketAlreadyOwnedByYou" || createError.message.includes("you already own it"))) {
|
|
8771
9110
|
await this.assertExistingBucketRegion(logicalId, resourceType, bucketName, region, createError);
|
|
8772
9111
|
this.logger.debug(`S3 bucket ${bucketName} already exists and is owned by you`);
|
|
@@ -8781,7 +9120,12 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
8781
9120
|
await this.s3Client.send(new DeleteBucketCommand({ Bucket: bucketName }));
|
|
8782
9121
|
this.logger.debug(`Cleaned up partially-created S3 bucket ${logicalId} (${bucketName}) after wiring failure`);
|
|
8783
9122
|
} catch (cleanupError) {
|
|
8784
|
-
this.logger.
|
|
9123
|
+
this.logger.debug(`DeleteBucket cleanup failed for S3 bucket ${logicalId} (${bucketName}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
9124
|
+
this.logger.warn(`Failed to clean up partially-created S3 bucket ${logicalId} (${bucketName}) (${cleanupError instanceof Error ? cleanupError.name : typeof cleanupError}). Re-run with --verbose for AWS's own message. Manual deletion may be required before the next deploy: aws s3api delete-bucket --bucket '${bucketName}'`);
|
|
9125
|
+
}
|
|
9126
|
+
else if (preflight.kind === "indeterminate") {
|
|
9127
|
+
this.logger.debug(`GetBucketLocation failed for S3 bucket ${bucketName} (${logicalId}) during create: ${preflight.reason}`);
|
|
9128
|
+
this.logger.warn(`Not cleaning up S3 bucket ${logicalId} (${bucketName}) after a wiring failure: this deploy could not confirm whether it created the bucket (region probe failed: ${preflight.errorName}), and in us-east-1 a successful CreateBucket does not prove it. Re-run with --verbose for AWS's own message. If cdkd created it, delete it manually before the next deploy: aws s3api delete-bucket --bucket '${bucketName}'`);
|
|
8785
9129
|
}
|
|
8786
9130
|
throw innerError;
|
|
8787
9131
|
}
|
|
@@ -8811,6 +9155,7 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
8811
9155
|
wasReplaced: true
|
|
8812
9156
|
};
|
|
8813
9157
|
}
|
|
9158
|
+
await this.assertStateBucketRegion("update", logicalId, resourceType, physicalId, await this.getRegion());
|
|
8814
9159
|
try {
|
|
8815
9160
|
const effectiveOverrides = await this.applyConfiguration(physicalId, properties, {
|
|
8816
9161
|
skipTags: true,
|
|
@@ -8840,6 +9185,8 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
8840
9185
|
async delete(logicalId, physicalId, resourceType, properties, context) {
|
|
8841
9186
|
this.logger.debug(`Deleting S3 bucket ${logicalId}: ${physicalId}`);
|
|
8842
9187
|
const allowAutoEmpty = context?.forceDataDelete === true || hasCdkAutoDeleteTag(properties, "aws-cdk:auto-delete-objects");
|
|
9188
|
+
if (context?.expectedRegion) await this.assertStateBucketRegion("delete", logicalId, resourceType, physicalId, context.expectedRegion);
|
|
9189
|
+
else this.announceUnverifiedBucketIdentity("delete", logicalId, physicalId, noRecordedRegionCause());
|
|
8843
9190
|
try {
|
|
8844
9191
|
await this.deleteBucketWithEmptyRetry(logicalId, physicalId, allowAutoEmpty);
|
|
8845
9192
|
} catch (error) {
|
|
@@ -16259,22 +16606,30 @@ function isTerminalDeleteFailure(error) {
|
|
|
16259
16606
|
* table with its guard down, which is the exact residue this mechanism
|
|
16260
16607
|
* exists to remove, so the message carries the physical id and the one
|
|
16261
16608
|
* command that fixes it.
|
|
16609
|
+
*
|
|
16610
|
+
* It REPORTS what it did rather than only doing it (see
|
|
16611
|
+
* {@link ProtectionCompensationOutcome}), because the caller's decision to drop
|
|
16612
|
+
* the flip record is not the same decision as whether to compensate: dropping
|
|
16613
|
+
* it after a re-enable that FAILED discards the only record that cdkd still
|
|
16614
|
+
* owes this table its guard back.
|
|
16262
16615
|
*/
|
|
16263
16616
|
async function compensateRemovedDeletionProtection(opts) {
|
|
16264
|
-
if (!opts.flip.flippedOffByThisRun) return;
|
|
16265
|
-
if (opts.flip.deleteAccepted) return;
|
|
16266
|
-
if (!isTerminalDeleteFailure(opts.error)) return;
|
|
16617
|
+
if (!opts.flip.flippedOffByThisRun) return "not-applicable";
|
|
16618
|
+
if (opts.flip.deleteAccepted) return "not-applicable";
|
|
16619
|
+
if (!isTerminalDeleteFailure(opts.error)) return "not-applicable";
|
|
16267
16620
|
try {
|
|
16268
16621
|
await opts.reEnable();
|
|
16269
16622
|
opts.logger.warn(`DynamoDB ${opts.typeLabel} ${opts.logicalId}: the delete failed after --remove-protection had turned DeletionProtectionEnabled off, so it was re-enabled on ${opts.physicalId}. The delete failure below is the outcome.`);
|
|
16623
|
+
return "restored";
|
|
16270
16624
|
} catch (reEnableError) {
|
|
16271
16625
|
const detail = reEnableError instanceof Error ? reEnableError.message : String(reEnableError);
|
|
16272
16626
|
if (isResourceNotFoundError(reEnableError)) {
|
|
16273
16627
|
const regionArg = opts.region ? ` --region ${opts.region}` : "";
|
|
16274
16628
|
opts.logger.warn(`DynamoDB ${opts.typeLabel} ${opts.logicalId}: could not re-enable DeletionProtectionEnabled on ${opts.physicalId} after the delete failed — DynamoDB answered ResourceNotFound. That most commonly means the table is gone or its status is not ACTIVE, and it can also mean the table is not in this region or account. If it still exists, its deletion protection is OFF. Check with: aws dynamodb describe-table --table-name ${opts.physicalId}${regionArg} and if it is there, restore it with: aws dynamodb update-table --table-name ${opts.physicalId}${regionArg} --deletion-protection-enabled. (${detail})`);
|
|
16275
|
-
return;
|
|
16629
|
+
return "failed";
|
|
16276
16630
|
}
|
|
16277
16631
|
opts.logger.error(`DynamoDB ${opts.typeLabel} ${opts.logicalId}: could NOT re-enable DeletionProtectionEnabled on ${opts.physicalId} after the delete failed — that table is LIVE with its deletion protection still off. Restore it with: aws dynamodb update-table --table-name ${opts.physicalId} --deletion-protection-enabled. (${detail})`);
|
|
16632
|
+
return "failed";
|
|
16278
16633
|
}
|
|
16279
16634
|
}
|
|
16280
16635
|
/**
|
|
@@ -17245,21 +17600,25 @@ var DynamoDBTableProvider = class {
|
|
|
17245
17600
|
try {
|
|
17246
17601
|
await this.deleteTableResource(logicalId, physicalId, resourceType, context, flip);
|
|
17247
17602
|
} catch (error) {
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
17254
|
-
|
|
17255
|
-
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
|
|
17603
|
+
let outcome = "failed";
|
|
17604
|
+
try {
|
|
17605
|
+
outcome = await compensateRemovedDeletionProtection({
|
|
17606
|
+
flip,
|
|
17607
|
+
error,
|
|
17608
|
+
logicalId,
|
|
17609
|
+
physicalId,
|
|
17610
|
+
typeLabel: "table",
|
|
17611
|
+
logger: this.logger,
|
|
17612
|
+
...context?.expectedRegion ? { region: context.expectedRegion } : {},
|
|
17613
|
+
reEnable: async () => {
|
|
17614
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
17615
|
+
TableName: physicalId,
|
|
17616
|
+
DeletionProtectionEnabled: true
|
|
17617
|
+
}));
|
|
17618
|
+
}
|
|
17619
|
+
});
|
|
17620
|
+
} catch {}
|
|
17621
|
+
if (isTerminalDeleteFailure(error) && outcome !== "failed") this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
|
|
17263
17622
|
throw error;
|
|
17264
17623
|
}
|
|
17265
17624
|
}
|
|
@@ -19812,21 +20171,25 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
19812
20171
|
try {
|
|
19813
20172
|
await this.deleteGlobalTableResource(logicalId, physicalId, resourceType, context, flip);
|
|
19814
20173
|
} catch (error) {
|
|
19815
|
-
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19819
|
-
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
19825
|
-
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
19829
|
-
|
|
20174
|
+
let outcome = "failed";
|
|
20175
|
+
try {
|
|
20176
|
+
outcome = await compensateRemovedDeletionProtection({
|
|
20177
|
+
flip,
|
|
20178
|
+
error,
|
|
20179
|
+
logicalId,
|
|
20180
|
+
physicalId,
|
|
20181
|
+
typeLabel: "GlobalTable",
|
|
20182
|
+
logger: this.logger,
|
|
20183
|
+
...context?.expectedRegion ? { region: context.expectedRegion } : {},
|
|
20184
|
+
reEnable: async () => {
|
|
20185
|
+
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
20186
|
+
TableName: physicalId,
|
|
20187
|
+
DeletionProtectionEnabled: true
|
|
20188
|
+
}));
|
|
20189
|
+
}
|
|
20190
|
+
});
|
|
20191
|
+
} catch {}
|
|
20192
|
+
if (isTerminalDeleteFailure(error) && outcome !== "failed") this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
|
|
19830
20193
|
throw error;
|
|
19831
20194
|
}
|
|
19832
20195
|
}
|
|
@@ -88060,4 +88423,4 @@ function buildProgram() {
|
|
|
88060
88423
|
|
|
88061
88424
|
//#endregion
|
|
88062
88425
|
export { buildProgram };
|
|
88063
|
-
//# sourceMappingURL=program-
|
|
88426
|
+
//# sourceMappingURL=program-B2i8bJUJ.js.map
|