@go-to-k/cdkd 0.281.24 → 0.281.26
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-CxK7d3pH.js → asg-provider-II3TmZ4t.js} +2 -2
- package/dist/{asg-provider-CxK7d3pH.js.map → asg-provider-II3TmZ4t.js.map} +1 -1
- package/dist/cli.js +648 -66
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-BkXNtI7L.js → deploy-engine-XC8HOR51.js} +3 -3
- package/dist/{deploy-engine-BkXNtI7L.js.map → deploy-engine-XC8HOR51.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as configStringRefusal, $t as MIGRATE_TMP_PREFIX, A as green, At as validateContainerRepoName, B as slowCcOperationTimeoutMs, Bt as getDefaultStateBucketName, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as PartialFailureError, Ct as rewriteTemplateAssetReferences, D as bold, Dn as StackHasActiveImportsError, Dt as getBootstrapMarkerKey, E as formatResourceLine, En as ResourceUpdateNotSupportedError, Et as ensureAssetStorage, F as clearOnUpdateRemoval, Ft as runDockerStreaming, G as getAccountInfo, Gt as resolveSkipPrefix, H as isTerminationProtectionPropagationError, Ht as resolveApp, I as ProviderRegistry, It as AssetManifestLoader, J as normalizeAwsTagsToCfn, Jt as resolveUseCdkBootstrapAssets, K as refStateLookupFromResource, Kt as resolveStateBucketWithDefault, L as findActionableSilentDrops, Lt as getDockerImageBySourceHash, M as yellow, Mt as formatDockerLoginError, N as IAMRoleProvider, Nn as normalizeAwsError, Nt as getDockerCmd, O as cyan, On as StackTerminationProtectionError, Ot as parseBootstrapMarker, P as collectInlinePolicyNamesManagedBySiblings, Pn as withErrorHandling, Pt as runDockerForeground, Q as configBooleanRefusal, Qt as CFN_TEMPLATE_URL_LIMIT, R as findSilentDropProperties, Rt as Synthesizer, S as extractDeploymentEventError, Sn as NestedStackChildDirectDestroyError, St as loadPublishableAssetManifest, T as renderStatefulReason, Tn as ResourceTimeoutError, Tt as BOOTSTRAP_MARKER_PREFIX, U as IntrinsicFunctionResolver, Ut as resolveAutoAssetStorage, V as disableInstanceApiTermination, Vt as getLegacyStateBucketName, W as cfnRefValueFromPhysicalId, Wt as resolveCaptureObservedState, X as assertRegionMatch, Xt as warnDeprecatedNoPrefixCliFlag, Y as resolveExplicitPhysicalId, Yt as stateBucketExistenceConfirmed, Z as coerceCfnBoolean, Zt as CFN_TEMPLATE_BODY_LIMIT, _ as createPreDeleteFinalSnapshot, _n as LocalInvokeBuildError$1, _t as AssetPublisher, a as DeploymentEventsStore, an as processStackMessages, at as applyRoleArnIfSet, b as unsupportedFinalSnapshotError, bt as buildAssetRedirectMap, c as replayFailedOperations, cn as AwsClients, ct as withRetry, d as IMPLICIT_DELETE_DEPENDENCIES, dn as setAwsClients, dt as DagBuilder, en as findLargeInlineResources, et as readConfigString, f as computeImplicitDeleteEdges, ft as TemplateParser, g as ccRoutedFinalSnapshotError, gn as DeployCancelledError, gt as shouldRetainResource, h as buildFinalSnapshotIdentifier, ht as rebuildClientForBucketRegion, i as DeploymentEventsReader, in as AssemblyReader, it as requireConfigString, j as red, jt as buildDockerImage, k as gray, kt as validateAssetBucketName, l as replayRollback, ln as getAwsClients, lt as isRetryableTransientError, m as PRE_DELETE_SNAPSHOT_TYPES, mt as S3StateBackend, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as expectedOwnerParam, nt as requireConfigArray, o as planFailedOps, ot as DiffCalculator, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as CdkdError, pt as LockManager, q as WAFv2WebACLProvider, qt as resolveStateBucketWithDefaultAndSource, r as DeployEngine, rn as derivePartitionAndUrlSuffix, rt as requireConfigObject, s as planRollback, sn as resolveBucketRegion, st as describeTypeWithThrottleRetry, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as uploadCfnTemplate, tt as replayWarn, u as withResourceDeadline, un as resetAwsClients, ut as isThrottlingError, v as isFinalSnapshotError, vn as LocalMigrateError, vt as stringifyValue, w as isStatefulRecreateTargetSync, wn as ProvisioningError, wt as AssetModeResolver, x as makeCanonicalizePropertiesFn, xn as MissingCdkCliError, xt as createAssetRedirectResolver, y as refusesFinalSnapshot, yn as LocalStartServiceError, yt as WorkGraph, z as CloudControlProvider, zt as synthesisStatusMessage } from "./deploy-engine-
|
|
2
|
+
import { $ as configStringRefusal, $t as MIGRATE_TMP_PREFIX, A as green, At as validateContainerRepoName, B as slowCcOperationTimeoutMs, Bt as getDefaultStateBucketName, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as PartialFailureError, Ct as rewriteTemplateAssetReferences, D as bold, Dn as StackHasActiveImportsError, Dt as getBootstrapMarkerKey, E as formatResourceLine, En as ResourceUpdateNotSupportedError, Et as ensureAssetStorage, F as clearOnUpdateRemoval, Ft as runDockerStreaming, G as getAccountInfo, Gt as resolveSkipPrefix, H as isTerminationProtectionPropagationError, Ht as resolveApp, I as ProviderRegistry, It as AssetManifestLoader, J as normalizeAwsTagsToCfn, Jt as resolveUseCdkBootstrapAssets, K as refStateLookupFromResource, Kt as resolveStateBucketWithDefault, L as findActionableSilentDrops, Lt as getDockerImageBySourceHash, M as yellow, Mt as formatDockerLoginError, N as IAMRoleProvider, Nn as normalizeAwsError, Nt as getDockerCmd, O as cyan, On as StackTerminationProtectionError, Ot as parseBootstrapMarker, P as collectInlinePolicyNamesManagedBySiblings, Pn as withErrorHandling, Pt as runDockerForeground, Q as configBooleanRefusal, Qt as CFN_TEMPLATE_URL_LIMIT, R as findSilentDropProperties, Rt as Synthesizer, S as extractDeploymentEventError, Sn as NestedStackChildDirectDestroyError, St as loadPublishableAssetManifest, T as renderStatefulReason, Tn as ResourceTimeoutError, Tt as BOOTSTRAP_MARKER_PREFIX, U as IntrinsicFunctionResolver, Ut as resolveAutoAssetStorage, V as disableInstanceApiTermination, Vt as getLegacyStateBucketName, W as cfnRefValueFromPhysicalId, Wt as resolveCaptureObservedState, X as assertRegionMatch, Xt as warnDeprecatedNoPrefixCliFlag, Y as resolveExplicitPhysicalId, Yt as stateBucketExistenceConfirmed, Z as coerceCfnBoolean, Zt as CFN_TEMPLATE_BODY_LIMIT, _ as createPreDeleteFinalSnapshot, _n as LocalInvokeBuildError$1, _t as AssetPublisher, a as DeploymentEventsStore, an as processStackMessages, at as applyRoleArnIfSet, b as unsupportedFinalSnapshotError, bt as buildAssetRedirectMap, c as replayFailedOperations, cn as AwsClients, ct as withRetry, d as IMPLICIT_DELETE_DEPENDENCIES, dn as setAwsClients, dt as DagBuilder, en as findLargeInlineResources, et as readConfigString, f as computeImplicitDeleteEdges, ft as TemplateParser, g as ccRoutedFinalSnapshotError, gn as DeployCancelledError, gt as shouldRetainResource, h as buildFinalSnapshotIdentifier, ht as rebuildClientForBucketRegion, i as DeploymentEventsReader, in as AssemblyReader, it as requireConfigString, j as red, jt as buildDockerImage, k as gray, kt as validateAssetBucketName, l as replayRollback, ln as getAwsClients, lt as isRetryableTransientError, m as PRE_DELETE_SNAPSHOT_TYPES, mt as S3StateBackend, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as expectedOwnerParam, nt as requireConfigArray, o as planFailedOps, ot as DiffCalculator, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as CdkdError, pt as LockManager, q as WAFv2WebACLProvider, qt as resolveStateBucketWithDefaultAndSource, r as DeployEngine, rn as derivePartitionAndUrlSuffix, rt as requireConfigObject, s as planRollback, sn as resolveBucketRegion, st as describeTypeWithThrottleRetry, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as uploadCfnTemplate, tt as replayWarn, u as withResourceDeadline, un as resetAwsClients, ut as isThrottlingError, v as isFinalSnapshotError, vn as LocalMigrateError, vt as stringifyValue, w as isStatefulRecreateTargetSync, wn as ProvisioningError, wt as AssetModeResolver, x as makeCanonicalizePropertiesFn, xn as MissingCdkCliError, xt as createAssetRedirectResolver, y as refusesFinalSnapshot, yn as LocalStartServiceError, yt as WorkGraph, z as CloudControlProvider, zt as synthesisStatusMessage } from "./deploy-engine-XC8HOR51.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-II3TmZ4t.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";
|
|
@@ -5172,10 +5172,81 @@ function foldToleratedAliases(bag, aliases) {
|
|
|
5172
5172
|
}
|
|
5173
5173
|
return out ?? bag;
|
|
5174
5174
|
}
|
|
5175
|
+
/** The transition date member, held in a CONST so the fold writes it COMPUTED. */
|
|
5176
|
+
const TRANSITION_DATE_KEY = "TransitionDate";
|
|
5175
5177
|
/** The lifecycle `Transitions[]` aliases the applier accepts (issue #1748). */
|
|
5176
5178
|
const TRANSITION_ALIASES = [["TransitionInDays", "Days"], ["TransitionDate", "Date"]];
|
|
5177
5179
|
/** The lifecycle `NoncurrentVersionTransitions[]` alias (issue #1748). */
|
|
5178
5180
|
const NONCURRENT_VERSION_TRANSITION_ALIASES = [["TransitionInDays", "NoncurrentDays"]];
|
|
5181
|
+
/** The refusal path for the notification EventBridge boolean (issue #1759). */
|
|
5182
|
+
const EVENTBRIDGE_CONFIG_PATH = "AWS::S3::Bucket NotificationConfiguration.EventBridgeConfiguration";
|
|
5183
|
+
/**
|
|
5184
|
+
* The block's own key, held in a CONST so the fold below writes it as a
|
|
5185
|
+
* COMPUTED key.
|
|
5186
|
+
*
|
|
5187
|
+
* Load-bearing, not style: `gen-nested-key-coverage`'s write-evidence walk reads
|
|
5188
|
+
* a bag-derived spread-and-patch literal that NAMES a member
|
|
5189
|
+
* (`{ ...config, EventBridgeConfiguration: … }`) as the #1475 whole-blob
|
|
5190
|
+
* HAND-OFF and wildcard-credits everything beneath that path as DELIVERED — so
|
|
5191
|
+
* a DIFF-side fold spelled that way would switch the write pass off for the
|
|
5192
|
+
* whole notification subtree, and the critic's only visible complaint would be
|
|
5193
|
+
* that some allow-list entries went "stale". Same reason
|
|
5194
|
+
* {@link canonicalizeBlock} exists.
|
|
5195
|
+
*/
|
|
5196
|
+
const EVENTBRIDGE_CONFIG_KEY = "EventBridgeConfiguration";
|
|
5197
|
+
/**
|
|
5198
|
+
* What a DECLARED `EventBridgeConfiguration` block with no `EventBridgeEnabled`
|
|
5199
|
+
* key means. CFn marks the member required, so the block's presence is the
|
|
5200
|
+
* affirmative declaration; this is also the pre-#1759 behavior for that shape.
|
|
5201
|
+
*/
|
|
5202
|
+
const EVENTBRIDGE_DEFAULT_ENABLED = true;
|
|
5203
|
+
/**
|
|
5204
|
+
* The boolean the wire derives from a USABLE `EventBridgeConfiguration` block
|
|
5205
|
+
* (issue #1759) — the ONE definition `applyNotificationConfiguration` and
|
|
5206
|
+
* {@link effectiveNotificationConfiguration} share, so the value that goes on
|
|
5207
|
+
* the wire and the value recorded into state cannot drift apart.
|
|
5208
|
+
*
|
|
5209
|
+
* Callers MUST have cleared `configBooleanRefusal` first; the `??` is only
|
|
5210
|
+
* correct once every declared-but-unusable value has been refused.
|
|
5211
|
+
*/
|
|
5212
|
+
function eventBridgeEnabledOf(eb) {
|
|
5213
|
+
return coerceCfnBoolean(isPlainObject$1(eb) ? eb["EventBridgeEnabled"] : void 0) ?? EVENTBRIDGE_DEFAULT_ENABLED;
|
|
5214
|
+
}
|
|
5215
|
+
/** The notification families whose empty declaration is never SENT (issue #1759). */
|
|
5216
|
+
const NOTIFICATION_LIST_KEYS = [
|
|
5217
|
+
"TopicConfigurations",
|
|
5218
|
+
"QueueConfigurations",
|
|
5219
|
+
"LambdaConfigurations"
|
|
5220
|
+
];
|
|
5221
|
+
/**
|
|
5222
|
+
* Drop a declared-but-EMPTY collection the applier never sends (issue #1759).
|
|
5223
|
+
*
|
|
5224
|
+
* MEASURED before choosing this answer, because the sibling shape answers the
|
|
5225
|
+
* opposite way: the lifecycle / CORS empty-collection skip RECORDS its
|
|
5226
|
+
* placeholder (issue #1718) precisely because `readLifecycle` / `readCors`
|
|
5227
|
+
* always emit `{Rules: []}` / `{CorsRules: []}` for an unconfigured bucket, so
|
|
5228
|
+
* the declared empty collection already equals the readback. `readNotification`
|
|
5229
|
+
* does NOT: each family is emitted only under
|
|
5230
|
+
* `resp.<Family> && resp.<Family>.length > 0`, so an unconfigured bucket comes
|
|
5231
|
+
* back with the key ABSENT. A record keeping `[]` therefore describes a key set
|
|
5232
|
+
* AWS can never report, and the honest answer here is the DROP.
|
|
5233
|
+
*
|
|
5234
|
+
* Scoped to a genuinely EMPTY ARRAY. A non-array declaration is skipped by the
|
|
5235
|
+
* applier's `Array.isArray(...)` gate too, but it is MALFORMED: dropping it
|
|
5236
|
+
* would make the desired side compare equal and stop the difference being
|
|
5237
|
+
* reported, which is the concealment `.claude/rules/providers.md` finding 3
|
|
5238
|
+
* refuses. It is left visible instead.
|
|
5239
|
+
*/
|
|
5240
|
+
function dropEmptyNotificationFamilies(config) {
|
|
5241
|
+
let out;
|
|
5242
|
+
for (const listKey of NOTIFICATION_LIST_KEYS) {
|
|
5243
|
+
const list = config[listKey];
|
|
5244
|
+
if (!Array.isArray(list) || list.length > 0) continue;
|
|
5245
|
+
out ??= { ...config };
|
|
5246
|
+
delete out[listKey];
|
|
5247
|
+
}
|
|
5248
|
+
return out ?? config;
|
|
5249
|
+
}
|
|
5179
5250
|
/** The `NotificationConfiguration` ARN aliases, per family (issue #1748). */
|
|
5180
5251
|
const NOTIFICATION_ARN_ALIASES = [
|
|
5181
5252
|
["TopicConfigurations", [["Topic", "TopicArn"]]],
|
|
@@ -5248,6 +5319,439 @@ function effectiveNotificationConfiguration(config) {
|
|
|
5248
5319
|
if (!isPlainObject$1(config)) return config;
|
|
5249
5320
|
let out = config;
|
|
5250
5321
|
for (const [listKey, aliases] of NOTIFICATION_ARN_ALIASES) out = canonicalizeItemList(out, listKey, (item) => foldNotificationEvents(foldToleratedAliases(item, aliases)));
|
|
5322
|
+
out = dropEmptyNotificationFamilies(out);
|
|
5323
|
+
return foldEventBridgeConfiguration(out);
|
|
5324
|
+
}
|
|
5325
|
+
/**
|
|
5326
|
+
* Normalize `EventBridgeConfiguration` onto the shape `readNotification` emits
|
|
5327
|
+
* (issue #1759, items 2 and 3 of the same block).
|
|
5328
|
+
*
|
|
5329
|
+
* CFn is stringly typed, so `EventBridgeEnabled: 'true'` is a legitimate
|
|
5330
|
+
* declaration; the wire turns the value into presence/absence and
|
|
5331
|
+
* `readNotification` emits the BOOLEAN (`{EventBridgeEnabled: <bool>}`, the
|
|
5332
|
+
* #1430 shape). A record holding the STRING can therefore never match the
|
|
5333
|
+
* readback. This is #1633's lossless-coercion arm and it meets the #1643 bar —
|
|
5334
|
+
* what AWS reports back is exactly the boolean cdkd derived at send time. A
|
|
5335
|
+
* declared `null` folds to `false` for the same reason: the applier reads it as
|
|
5336
|
+
* "block omitted" and sends nothing.
|
|
5337
|
+
*
|
|
5338
|
+
* Keyed off the DECLARED shape and off the wire's own predicate, never off a
|
|
5339
|
+
* refusal: a value `configBooleanRefusal` rejects is left COMPLETELY alone (no
|
|
5340
|
+
* patch, and the key not dropped), because the wire SKIPS the whole Put for it
|
|
5341
|
+
* and AWS keeps the previous configuration. Folding it would collapse the
|
|
5342
|
+
* desired side onto the retained value, the provider would never be called, and
|
|
5343
|
+
* the skip warning would stop — the "mirror what the wire does" rule.
|
|
5344
|
+
*
|
|
5345
|
+
* MEASURED AND DELIBERATELY NOT FOLDED (1) — the per-item `Id`, which issue
|
|
5346
|
+
* #1755 asked to be measured rather than assumed. The applier sends
|
|
5347
|
+
* `Id: t['Id']`, so a template declaring none sends none, AWS GENERATES one, and
|
|
5348
|
+
* `readNotification` emits it (`if (t.Id !== undefined) e['Id'] = t.Id`). That is
|
|
5349
|
+
* an AWS-COMPUTED value, not a spelling: it belongs in `observedProperties`,
|
|
5350
|
+
* which `readCurrentState` already populates, and `.claude/rules/providers.md` is
|
|
5351
|
+
* explicit that a computed value in `properties` makes the DESIRED baseline drift
|
|
5352
|
+
* from the template and disables the #1160 absent-field removal derivation. There
|
|
5353
|
+
* is also nothing to fold TOWARDS — the value does not exist at send time, which
|
|
5354
|
+
* is the #1643 "knowable at send time" test failing. So: no fold, by measurement
|
|
5355
|
+
* rather than by omission.
|
|
5356
|
+
*
|
|
5357
|
+
* MEASURED AND DELIBERATELY NOT FOLDED (2): `readNotification` emits
|
|
5358
|
+
* `EventBridgeConfiguration` UNCONDITIONALLY, so a notification configuration
|
|
5359
|
+
* that declares no EventBridge block at all is one key short of the readback.
|
|
5360
|
+
* That is NOT this class — nothing was declared, so there is no spelling to
|
|
5361
|
+
* normalize, and supplying the key would record one the template does not have
|
|
5362
|
+
* on EVERY notification-configured bucket (the shape #1723 records as the
|
|
5363
|
+
* reason its own arm was left unanswered). `readNotification`'s own comment
|
|
5364
|
+
* already scopes that difference: it is a ONE-TIME, self-clearing drift on the
|
|
5365
|
+
* `properties`-fallback baseline, deliberately accepted by #1430, not the
|
|
5366
|
+
* permanent phantom drift this fold exists to remove.
|
|
5367
|
+
*
|
|
5368
|
+
* @returns `config` BY IDENTITY when nothing is declared, when the declared
|
|
5369
|
+
* block is already the emitted shape, or when it is unfoldable.
|
|
5370
|
+
*/
|
|
5371
|
+
function foldEventBridgeConfiguration(config) {
|
|
5372
|
+
const eb = config[EVENTBRIDGE_CONFIG_KEY];
|
|
5373
|
+
if (eb === void 0) return config;
|
|
5374
|
+
if (eb !== null && configBooleanRefusal(eb, "EventBridgeEnabled", EVENTBRIDGE_CONFIG_PATH)) return config;
|
|
5375
|
+
const enabled = eb === null ? false : eventBridgeEnabledOf(eb);
|
|
5376
|
+
const declared = isPlainObject$1(eb) ? eb["EventBridgeEnabled"] : void 0;
|
|
5377
|
+
if (isPlainObject$1(eb) && Object.keys(eb).length === 1 && Object.is(declared, enabled)) return config;
|
|
5378
|
+
return {
|
|
5379
|
+
...config,
|
|
5380
|
+
[EVENTBRIDGE_CONFIG_KEY]: { EventBridgeEnabled: enabled }
|
|
5381
|
+
};
|
|
5382
|
+
}
|
|
5383
|
+
/** The refusal paths the lifecycle applier and its fold SHARE (issue #1755). */
|
|
5384
|
+
const LIFECYCLE_RULE_PATH = "AWS::S3::Bucket LifecycleConfiguration.Rules[]";
|
|
5385
|
+
const LIFECYCLE_FILTER_PATH = `${LIFECYCLE_RULE_PATH}.Filter`;
|
|
5386
|
+
const LIFECYCLE_TAG_FILTERS_PATH = `${LIFECYCLE_RULE_PATH}.TagFilters`;
|
|
5387
|
+
/** What `readConfigString(rule, 'Status', ...)` substitutes for an ABSENT key. */
|
|
5388
|
+
const LIFECYCLE_DEFAULT_STATUS = "Enabled";
|
|
5389
|
+
/** The rule's status member, held in a CONST so the fold writes it COMPUTED. */
|
|
5390
|
+
const LIFECYCLE_STATUS_KEY = "Status";
|
|
5391
|
+
/**
|
|
5392
|
+
* Gather a rule's full location scope — module-level so the applier and the
|
|
5393
|
+
* fold run the SAME function rather than two copies that can disagree about
|
|
5394
|
+
* which key wins (`.claude/rules/providers.md`: share ONE helper with the
|
|
5395
|
+
* provisioning path, or state and template end up narrowed to different keys
|
|
5396
|
+
* and the fix becomes the bug).
|
|
5397
|
+
*
|
|
5398
|
+
* `TagFilters` lives at the RULE level in the CFn schema and CDK synthesizes it
|
|
5399
|
+
* there; the `Filter`-side reads are cdkd's accommodation for SDK-shaped /
|
|
5400
|
+
* imported input.
|
|
5401
|
+
*/
|
|
5402
|
+
function lifecycleRuleScope(rule) {
|
|
5403
|
+
const filter = rule["Filter"];
|
|
5404
|
+
return {
|
|
5405
|
+
prefix: filter?.["Prefix"] ?? rule["Prefix"],
|
|
5406
|
+
tagFilters: filter?.["TagFilters"] ?? rule["TagFilters"],
|
|
5407
|
+
sizeGt: filter?.["ObjectSizeGreaterThan"] ?? rule["ObjectSizeGreaterThan"],
|
|
5408
|
+
sizeLt: filter?.["ObjectSizeLessThan"] ?? rule["ObjectSizeLessThan"]
|
|
5409
|
+
};
|
|
5410
|
+
}
|
|
5411
|
+
/**
|
|
5412
|
+
* Can this rule stay in the deprecated V1 (top-level `Prefix`) form? See
|
|
5413
|
+
* {@link lifecycleUsesFilterForm} — S3 forbids MIXING the two forms in one
|
|
5414
|
+
* configuration, so the answer is decided across the whole rule list.
|
|
5415
|
+
*/
|
|
5416
|
+
function lifecycleRuleIsPlainPrefixOnly(rule) {
|
|
5417
|
+
if (rule["Filter"] != null) return false;
|
|
5418
|
+
const s = lifecycleRuleScope(rule);
|
|
5419
|
+
return s.prefix !== void 0 && (s.tagFilters == null || s.tagFilters.length === 0) && s.sizeGt === void 0 && s.sizeLt === void 0;
|
|
5420
|
+
}
|
|
5421
|
+
/** V1 vs V2 is decided ONCE for the whole configuration — see the applier's note. */
|
|
5422
|
+
function lifecycleUsesFilterForm(rules) {
|
|
5423
|
+
return !rules.every(lifecycleRuleIsPlainPrefixOnly);
|
|
5424
|
+
}
|
|
5425
|
+
/** Discards the message; the fold only needs the PREDICATE's answer. */
|
|
5426
|
+
const IGNORE_REFUSAL_MESSAGE = () => {};
|
|
5427
|
+
/**
|
|
5428
|
+
* Would `applyLifecycleConfiguration` REFUSE this rule, and therefore skip the
|
|
5429
|
+
* whole Put? (issue #1755)
|
|
5430
|
+
*
|
|
5431
|
+
* Runs the applier's own three guards rather than restating them — the
|
|
5432
|
+
* "do not HAND-ROLL the mirror, run the wire's own predicate" rule, whose
|
|
5433
|
+
* cautionary tale is the S3 schedule fold, where an approximation disagreed
|
|
5434
|
+
* with the wire on three of nine container shapes.
|
|
5435
|
+
*/
|
|
5436
|
+
function lifecycleRuleRefused(rule) {
|
|
5437
|
+
const rawFilter = rule["Filter"];
|
|
5438
|
+
if (rawFilter != null && requireConfigObject(rawFilter, LIFECYCLE_FILTER_PATH, { onUnusable: IGNORE_REFUSAL_MESSAGE }) === void 0) return true;
|
|
5439
|
+
const rawTagFilters = rawFilter?.["TagFilters"] ?? rule["TagFilters"];
|
|
5440
|
+
if (rawTagFilters != null && requireConfigArray(rawTagFilters, LIFECYCLE_TAG_FILTERS_PATH, { onUnusable: IGNORE_REFUSAL_MESSAGE }) === void 0) return true;
|
|
5441
|
+
return configStringRefusal(rule, "Status", LIFECYCLE_DEFAULT_STATUS, LIFECYCLE_RULE_PATH) !== void 0;
|
|
5442
|
+
}
|
|
5443
|
+
/**
|
|
5444
|
+
* Deep, key-ORDER-insensitive equality — the identity test for a fold that
|
|
5445
|
+
* rebuilds nested objects (`Filter`) even when nothing about them changed.
|
|
5446
|
+
*
|
|
5447
|
+
* `Object.is` alone would report every folded rule as changed and rewrite the
|
|
5448
|
+
* state record of every lifecycle-configured bucket; `JSON.stringify` would
|
|
5449
|
+
* report a pure reordering as a change, which is not one worth recording.
|
|
5450
|
+
*/
|
|
5451
|
+
function deepSameValue(a, b) {
|
|
5452
|
+
if (Object.is(a, b)) return true;
|
|
5453
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
5454
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
5455
|
+
return a.every((element, index) => deepSameValue(element, b[index]));
|
|
5456
|
+
}
|
|
5457
|
+
if (isPlainObject$1(a) && isPlainObject$1(b)) {
|
|
5458
|
+
const keysA = Object.keys(a);
|
|
5459
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
5460
|
+
return keysA.every((key) => key in b && deepSameValue(a[key], b[key]));
|
|
5461
|
+
}
|
|
5462
|
+
return false;
|
|
5463
|
+
}
|
|
5464
|
+
/**
|
|
5465
|
+
* The ISO string `readLifecycle` emits for a date the applier sends as
|
|
5466
|
+
* `new Date(value)`.
|
|
5467
|
+
*
|
|
5468
|
+
* DETERMINISM IS A PRECONDITION, not a nicety (PR review). `new Date(str)`
|
|
5469
|
+
* resolves an offset-less string in the HOST's local zone — `new Date('0')` is
|
|
5470
|
+
* `1999-12-31T15:00:00Z` under JST and `2000-01-01T00:00:00Z` under UTC, and
|
|
5471
|
+
* `'2030-01-01T00:00:00'` (ISO, no offset) is local by spec. The APPLIER shares
|
|
5472
|
+
* that dependence, so the wire and the readback still agree on any one machine —
|
|
5473
|
+
* but this feeds `canonicalizeDesiredProperties`, a DIFF-side pure function that
|
|
5474
|
+
* runs wherever the CLI runs. A CI runner in UTC and a laptop in JST would
|
|
5475
|
+
* canonicalize the SAME template to different ISO strings and manufacture a diff
|
|
5476
|
+
* neither machine can clear, which is strictly worse than the phantom drift the
|
|
5477
|
+
* fold exists to remove. So only the forms whose meaning is fixed by the ISO
|
|
5478
|
+
* 8601 spec are folded: a bare `YYYY-MM-DD` (spec-UTC), a date-time carrying
|
|
5479
|
+
* `Z` or a `±HH:MM` offset, and an epoch NUMBER. Everything else — `'Jan 1
|
|
5480
|
+
* 2030'`, `'0'`, an offset-less date-time — is left ALONE, which keeps the
|
|
5481
|
+
* record machine-independent at the cost of not normalizing a shape almost no
|
|
5482
|
+
* template uses.
|
|
5483
|
+
*
|
|
5484
|
+
* @returns `undefined` for a value that cannot become a valid `Date`, or whose
|
|
5485
|
+
* `Date` would depend on the host timezone — in both cases there is no
|
|
5486
|
+
* effective value this can claim AWS will report, so the fold leaves the rule
|
|
5487
|
+
* alone.
|
|
5488
|
+
*/
|
|
5489
|
+
const TIMEZONE_INDEPENDENT_DATE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?$/;
|
|
5490
|
+
function cfnDateToIso(value) {
|
|
5491
|
+
if (typeof value === "string" && !TIMEZONE_INDEPENDENT_DATE.test(value)) return void 0;
|
|
5492
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
5493
|
+
const parsed = new Date(value);
|
|
5494
|
+
return Number.isNaN(parsed.getTime()) ? void 0 : parsed.toISOString();
|
|
5495
|
+
}
|
|
5496
|
+
/** The legacy SINGULAR action objects the CFn schema still declares (issue #1754). */
|
|
5497
|
+
const LEGACY_SINGULAR_TRANSITIONS = [["Transitions", "Transition"], ["NoncurrentVersionTransitions", "NoncurrentVersionTransition"]];
|
|
5498
|
+
/**
|
|
5499
|
+
* Merge the legacy singular `Transition` / `NoncurrentVersionTransition` into
|
|
5500
|
+
* the plural array, exactly as the wire does (issue #1754).
|
|
5501
|
+
*
|
|
5502
|
+
* The CFn `Rule` definition still declares BOTH spellings and
|
|
5503
|
+
* `applyLifecycleConfiguration` folds the singular into the plural array via
|
|
5504
|
+
* {@link mergeLegacySingular}, so `readLifecycle` can only ever emit the plural.
|
|
5505
|
+
* A rule declaring the singular therefore recorded a key AWS can never
|
|
5506
|
+
* report — the #1686 never-emitted-KEY class through the SHAPE — with no
|
|
5507
|
+
* warning anywhere, because nothing is malformed and nothing is substituted.
|
|
5508
|
+
*
|
|
5509
|
+
* It shares `mergeLegacySingular` itself rather than re-deriving the collision
|
|
5510
|
+
* rule, per the share-ONE-helper rule. What it does NOT do is pass a no-op
|
|
5511
|
+
* callback and fold regardless: on a `StorageClass` COLLISION the wire DROPS the
|
|
5512
|
+
* singular and WARNS, so a colliding rule is left COMPLETELY alone (both keys
|
|
5513
|
+
* kept). The NON-colliding case, which is the population this exists for, has
|
|
5514
|
+
* no fault to report and folds.
|
|
5515
|
+
*
|
|
5516
|
+
* Be precise about WHICH signal that preserves, because the obvious answer is
|
|
5517
|
+
* wrong (PR review). It is NOT the deploy-time warning: the record and the
|
|
5518
|
+
* canonicalized template are folded by the same function either way, so they
|
|
5519
|
+
* compare equal after the first deploy and the Put stops re-firing whichever
|
|
5520
|
+
* choice is made. What leaving it alone preserves is the `cdkd drift` report —
|
|
5521
|
+
* state keeps a `Transition` key `readLifecycle` can never emit, so drift
|
|
5522
|
+
* re-reports the rule until the template drops one of the two declarations.
|
|
5523
|
+
* Folding it would make state match the readback exactly and render a
|
|
5524
|
+
* template fault S3 itself rejects completely invisible.
|
|
5525
|
+
*
|
|
5526
|
+
* @returns `rule` BY IDENTITY when no legacy singular is declared.
|
|
5527
|
+
*/
|
|
5528
|
+
function foldLegacySingularTransitions(rule) {
|
|
5529
|
+
let out;
|
|
5530
|
+
for (const [pluralKey, singularKey] of LEGACY_SINGULAR_TRANSITIONS) {
|
|
5531
|
+
const singular = rule[singularKey];
|
|
5532
|
+
const hasSingular = isPlainObject$1(singular);
|
|
5533
|
+
const plural = rule[pluralKey];
|
|
5534
|
+
const needsFilter = Array.isArray(plural) && plural.some((element) => !isPlainObject$1(element));
|
|
5535
|
+
if (!hasSingular && !needsFilter) continue;
|
|
5536
|
+
let collided = false;
|
|
5537
|
+
const merged = mergeLegacySingular(plural, hasSingular ? singular : void 0, () => {
|
|
5538
|
+
collided = true;
|
|
5539
|
+
});
|
|
5540
|
+
out ??= { ...rule };
|
|
5541
|
+
if (collided) {
|
|
5542
|
+
if (needsFilter) out[pluralKey] = merged;
|
|
5543
|
+
continue;
|
|
5544
|
+
}
|
|
5545
|
+
out[pluralKey] = merged;
|
|
5546
|
+
if (hasSingular) delete out[singularKey];
|
|
5547
|
+
}
|
|
5548
|
+
return out ?? rule;
|
|
5549
|
+
}
|
|
5550
|
+
/**
|
|
5551
|
+
* Emit a transition's `TransitionDate` as the ISO string the readback emits
|
|
5552
|
+
* (PR review of issues #1754 / #1755).
|
|
5553
|
+
*
|
|
5554
|
+
* The alias fold above only RENAMES `Date` -> `TransitionDate`, but the applier
|
|
5555
|
+
* sends `new Date(t['TransitionDate'] ?? t['Date'])` and `readLifecycle` emits
|
|
5556
|
+
* `t.Date.toISOString()` — so a hand-written / imported `TransitionDate:
|
|
5557
|
+
* '2030-01-01'` recorded a value the readback can never match, with no warning
|
|
5558
|
+
* and nothing malformed. Exactly the class {@link foldLifecycleExpiration}
|
|
5559
|
+
* closes for the RULE-level `ExpirationDate` one member over; found by review
|
|
5560
|
+
* because the fold and the readback were compared per member rather than
|
|
5561
|
+
* per item.
|
|
5562
|
+
*
|
|
5563
|
+
* `NoncurrentVersionTransitions` needs no twin of this: `toSdkNvt` has no date
|
|
5564
|
+
* member at all.
|
|
5565
|
+
*
|
|
5566
|
+
* @returns the item BY IDENTITY when no date is declared, or when it is one the
|
|
5567
|
+
* applier would put on the wire as an Invalid Date (nothing to record).
|
|
5568
|
+
*/
|
|
5569
|
+
function foldTransitionDate(item) {
|
|
5570
|
+
const declared = item[TRANSITION_DATE_KEY];
|
|
5571
|
+
if (!declared) return item;
|
|
5572
|
+
const iso = cfnDateToIso(declared);
|
|
5573
|
+
if (iso === void 0 || iso === declared) return item;
|
|
5574
|
+
const out = { ...item };
|
|
5575
|
+
out[TRANSITION_DATE_KEY] = iso;
|
|
5576
|
+
return out;
|
|
5577
|
+
}
|
|
5578
|
+
/**
|
|
5579
|
+
* Drop a declared-but-EMPTY transition list (issue #1755).
|
|
5580
|
+
*
|
|
5581
|
+
* The applier sets `sdkRule.Transitions` / `.NoncurrentVersionTransitions` only
|
|
5582
|
+
* when the merged array is non-empty, and `readLifecycle` emits each only under
|
|
5583
|
+
* `r.<List> && r.<List>.length > 0` — so an empty declaration is one key the
|
|
5584
|
+
* readback can never carry. Measured on the reverse-mapper, not assumed: the
|
|
5585
|
+
* lifecycle block's OWN empty-collection placeholder answers the opposite way
|
|
5586
|
+
* (issue #1718) because `readLifecycle` DOES emit `{Rules: []}`.
|
|
5587
|
+
*/
|
|
5588
|
+
function dropEmptyTransitionLists(rule) {
|
|
5589
|
+
let out;
|
|
5590
|
+
for (const [pluralKey] of LEGACY_SINGULAR_TRANSITIONS) {
|
|
5591
|
+
const list = rule[pluralKey];
|
|
5592
|
+
if (!Array.isArray(list) || list.length > 0) continue;
|
|
5593
|
+
out ??= { ...rule };
|
|
5594
|
+
delete out[pluralKey];
|
|
5595
|
+
}
|
|
5596
|
+
return out ?? rule;
|
|
5597
|
+
}
|
|
5598
|
+
/**
|
|
5599
|
+
* Reshape the rule's expiration onto the CFn spellings `readLifecycle` emits
|
|
5600
|
+
* (issue #1755 item 1).
|
|
5601
|
+
*
|
|
5602
|
+
* The CFn `Rule` definition declares `ExpirationDate` / `ExpirationInDays` and
|
|
5603
|
+
* the RULE-level `ExpiredObjectDeleteMarker`, and has NO `Expiration` member;
|
|
5604
|
+
* the applier additionally accepts a cdkd-only `Expiration` OBJECT as a third
|
|
5605
|
+
* source. The readback used to emit only the SDK-shaped `Expiration` object, so
|
|
5606
|
+
* state and the readback carried different key sets for essentially every
|
|
5607
|
+
* lifecycle-configured bucket CDK creates. The fix is split, per the issue: the
|
|
5608
|
+
* READBACK now emits the CFn scalars (recording the SDK shape instead would put
|
|
5609
|
+
* a spelling in state that no CFn template can declare), and this is the desired
|
|
5610
|
+
* side folding onto the same choice.
|
|
5611
|
+
*
|
|
5612
|
+
* The rule is left COMPLETELY alone in three cases, each because the wire does
|
|
5613
|
+
* something this fold must not conceal:
|
|
5614
|
+
*
|
|
5615
|
+
* - a rule-level `ExpiredObjectDeleteMarker` the wire cannot read
|
|
5616
|
+
* (`coerceCfnBoolean` answers `undefined`): the wire silently ignores it, and
|
|
5617
|
+
* nothing warns, so the difference is the user's only signal;
|
|
5618
|
+
* - a date the wire would send as an Invalid Date, or one whose `Date` depends
|
|
5619
|
+
* on the host timezone (see {@link cfnDateToIso});
|
|
5620
|
+
* - a rule-level `ExpiredObjectDeleteMarker: true` ALONGSIDE a `Days` / `Date`:
|
|
5621
|
+
* S3 forbids the combination, so the applier sends the `Days` / `Date` and
|
|
5622
|
+
* WARNS that the marker was not applied. Folding the marker away on both sides
|
|
5623
|
+
* would stop that difference being reported, so `cdkd drift` re-reporting it
|
|
5624
|
+
* until the template drops one of the two is the correct standing signal.
|
|
5625
|
+
*
|
|
5626
|
+
* @returns `rule` BY IDENTITY when nothing expiration-shaped is declared.
|
|
5627
|
+
*/
|
|
5628
|
+
function foldLifecycleExpiration(rule) {
|
|
5629
|
+
const declaredMarker = rule["ExpiredObjectDeleteMarker"];
|
|
5630
|
+
const ruleLevelMarker = coerceCfnBoolean(declaredMarker);
|
|
5631
|
+
if (declaredMarker !== void 0 && ruleLevelMarker === void 0) return rule;
|
|
5632
|
+
if (rule["ExpirationInDays"] === void 0 && rule["ExpirationDate"] === void 0 && rule["Expiration"] === void 0 && declaredMarker === void 0) return rule;
|
|
5633
|
+
const expiration = rule["ExpirationInDays"] || rule["ExpirationDate"] || rule["Expiration"];
|
|
5634
|
+
let days;
|
|
5635
|
+
let date;
|
|
5636
|
+
let marker;
|
|
5637
|
+
if (typeof expiration === "number") days = expiration;
|
|
5638
|
+
else if (typeof expiration === "string") {
|
|
5639
|
+
const iso = cfnDateToIso(expiration);
|
|
5640
|
+
if (iso === void 0) return rule;
|
|
5641
|
+
date = iso;
|
|
5642
|
+
} else if (isPlainObject$1(expiration)) {
|
|
5643
|
+
if (expiration["Days"] !== void 0) days = expiration["Days"];
|
|
5644
|
+
if (expiration["Date"]) {
|
|
5645
|
+
const iso = cfnDateToIso(expiration["Date"]);
|
|
5646
|
+
if (iso === void 0) return rule;
|
|
5647
|
+
date = iso;
|
|
5648
|
+
}
|
|
5649
|
+
const nested = expiration["ExpiredObjectDeleteMarker"];
|
|
5650
|
+
if (nested !== void 0) {
|
|
5651
|
+
if (typeof nested !== "boolean") return rule;
|
|
5652
|
+
marker = nested;
|
|
5653
|
+
}
|
|
5654
|
+
} else if (expiration) return rule;
|
|
5655
|
+
if (ruleLevelMarker === true) {
|
|
5656
|
+
if (days !== void 0 || date !== void 0) return rule;
|
|
5657
|
+
days = void 0;
|
|
5658
|
+
date = void 0;
|
|
5659
|
+
marker = true;
|
|
5660
|
+
}
|
|
5661
|
+
const out = { ...rule };
|
|
5662
|
+
delete out["ExpirationInDays"];
|
|
5663
|
+
delete out["ExpirationDate"];
|
|
5664
|
+
delete out["Expiration"];
|
|
5665
|
+
delete out["ExpiredObjectDeleteMarker"];
|
|
5666
|
+
if (days !== void 0) out["ExpirationInDays"] = days;
|
|
5667
|
+
if (date !== void 0) out["ExpirationDate"] = date;
|
|
5668
|
+
if (marker !== void 0) out["ExpiredObjectDeleteMarker"] = marker;
|
|
5669
|
+
return out;
|
|
5670
|
+
}
|
|
5671
|
+
/**
|
|
5672
|
+
* Fold the legacy scalar `NoncurrentVersionExpirationInDays` onto the OBJECT
|
|
5673
|
+
* form `readLifecycle` emits (issue #1755 item 2).
|
|
5674
|
+
*
|
|
5675
|
+
* CFn declares BOTH spellings here — unlike the expiration sibling above — so
|
|
5676
|
+
* the readback has to pick one and the desired side has to fold to the same
|
|
5677
|
+
* choice. The OBJECT wins because it is the only one that can express
|
|
5678
|
+
* `NewerNoncurrentVersions`, so folding towards it is lossless in both
|
|
5679
|
+
* directions; and it is also what the wire already prefers when both are
|
|
5680
|
+
* declared, which is why a rule carrying both simply DROPS the scalar here.
|
|
5681
|
+
*
|
|
5682
|
+
* `coerceCfnNumber`, not `typeof === 'number'`: it is the predicate the wire
|
|
5683
|
+
* runs, and the wire sends the COERCED number, so a hand-written `"365"` is
|
|
5684
|
+
* recorded as `365` — exactly what AWS reports back.
|
|
5685
|
+
*/
|
|
5686
|
+
function foldNoncurrentVersionExpiration(rule) {
|
|
5687
|
+
const legacy = rule["NoncurrentVersionExpirationInDays"];
|
|
5688
|
+
if (legacy === void 0) return rule;
|
|
5689
|
+
const declaredObject = rule["NoncurrentVersionExpiration"];
|
|
5690
|
+
if (declaredObject !== void 0 && !isPlainObject$1(declaredObject)) return rule;
|
|
5691
|
+
const out = { ...rule };
|
|
5692
|
+
delete out["NoncurrentVersionExpirationInDays"];
|
|
5693
|
+
if (isPlainObject$1(declaredObject)) return out;
|
|
5694
|
+
const days = coerceCfnNumber(legacy);
|
|
5695
|
+
if (days === void 0) return rule;
|
|
5696
|
+
out["NoncurrentVersionExpiration"] = { NoncurrentDays: days };
|
|
5697
|
+
return out;
|
|
5698
|
+
}
|
|
5699
|
+
/**
|
|
5700
|
+
* Rebuild the rule's location scope in the shape `readLifecycle` emits for the
|
|
5701
|
+
* `Filter` the applier actually sends (issue #1755 item 3).
|
|
5702
|
+
*
|
|
5703
|
+
* This is not a spelling at all but a defaulted-but-SENT member (#1718's
|
|
5704
|
+
* class): a rule with no declared scope is SENT `Filter: {Prefix: ''}` in a V2
|
|
5705
|
+
* configuration, AWS holds it, the readback reports it, and the record was one
|
|
5706
|
+
* key short. The fold is wider than the issue's headline because the same round
|
|
5707
|
+
* trip moves three more shapes — a bare top-level `Prefix` becomes
|
|
5708
|
+
* `Filter: {Prefix}`, rule-level `TagFilters` become `Filter: {TagFilters}`, and
|
|
5709
|
+
* rule-level `ObjectSize*` move under `Filter` — all of which the readback emits
|
|
5710
|
+
* only under `Filter`.
|
|
5711
|
+
*
|
|
5712
|
+
* `useFilterForm` is decided ACROSS ALL RULES, which is why this takes it as a
|
|
5713
|
+
* parameter instead of being a pure per-rule function: in a V1 configuration
|
|
5714
|
+
* (every rule scoped by exactly one top-level `Prefix`) nothing moves at all.
|
|
5715
|
+
*
|
|
5716
|
+
* WHY THIS RECORDS `Filter` WHEN THE EXPIRATION SIBLING REFUSED THE SDK SHAPE,
|
|
5717
|
+
* since the two answer the same objection opposite ways one member apart (PR
|
|
5718
|
+
* review — without this, the next author "fixes" one to match the other). The
|
|
5719
|
+
* CFn `Rule` definition declares no `Filter` member either, so by the
|
|
5720
|
+
* `Expiration` argument this too should have been fixed on the READBACK side.
|
|
5721
|
+
* It is not, for a reason that does not apply there: the rule-level CFn scalars
|
|
5722
|
+
* can express every `Expiration` the SDK returns, and so the reverse map is
|
|
5723
|
+
* LOSSLESS — whereas the V2 `Filter` carries a distinction with no rule-level
|
|
5724
|
+
* spelling at all. A scope-less rule is SENT `Filter: {Prefix: ''}`, and the
|
|
5725
|
+
* only rule-level rendering of that is a top-level `Prefix: ''`, which is the V1
|
|
5726
|
+
* form — a different configuration that S3 forbids mixing with V2. Reverse-
|
|
5727
|
+
* mapping would therefore destroy the V1/V2 distinction state needs to
|
|
5728
|
+
* reproduce the call. `readLifecycle` has emitted `Filter` since issue #1388, so
|
|
5729
|
+
* both sides already meet there; this fold makes the desired side agree rather
|
|
5730
|
+
* than moving the meeting point.
|
|
5731
|
+
*/
|
|
5732
|
+
function foldLifecycleScope(rule, useFilterForm) {
|
|
5733
|
+
const { prefix, tagFilters, sizeGt, sizeLt } = lifecycleRuleScope(rule);
|
|
5734
|
+
const hasTags = tagFilters != null && tagFilters.length > 0;
|
|
5735
|
+
const componentCount = (hasTags ? 1 : 0) + (prefix !== void 0 ? 1 : 0) + (sizeGt !== void 0 ? 1 : 0) + (sizeLt !== void 0 ? 1 : 0);
|
|
5736
|
+
let filter;
|
|
5737
|
+
if (componentCount > 1) {
|
|
5738
|
+
filter = {};
|
|
5739
|
+
if (prefix !== void 0) filter["Prefix"] = prefix;
|
|
5740
|
+
if (hasTags) filter["TagFilters"] = tagFilters;
|
|
5741
|
+
if (sizeGt !== void 0) filter["ObjectSizeGreaterThan"] = sizeGt;
|
|
5742
|
+
if (sizeLt !== void 0) filter["ObjectSizeLessThan"] = sizeLt;
|
|
5743
|
+
} else if (hasTags) filter = { TagFilters: tagFilters };
|
|
5744
|
+
else if (sizeGt !== void 0) filter = { ObjectSizeGreaterThan: sizeGt };
|
|
5745
|
+
else if (sizeLt !== void 0) filter = { ObjectSizeLessThan: sizeLt };
|
|
5746
|
+
else if (useFilterForm) filter = { Prefix: prefix ?? "" };
|
|
5747
|
+
const out = { ...rule };
|
|
5748
|
+
delete out["Filter"];
|
|
5749
|
+
delete out["Prefix"];
|
|
5750
|
+
delete out["TagFilters"];
|
|
5751
|
+
delete out["ObjectSizeGreaterThan"];
|
|
5752
|
+
delete out["ObjectSizeLessThan"];
|
|
5753
|
+
if (filter !== void 0) out["Filter"] = filter;
|
|
5754
|
+
else if (prefix !== void 0) out["Prefix"] = prefix;
|
|
5251
5755
|
return out;
|
|
5252
5756
|
}
|
|
5253
5757
|
/**
|
|
@@ -5268,29 +5772,65 @@ function effectiveNotificationConfiguration(config) {
|
|
|
5268
5772
|
* the same two item shapes, so fixing only the reported one would have left its
|
|
5269
5773
|
* siblings broken — the "diff the WHOLE blob, not the reported key" rule.
|
|
5270
5774
|
*
|
|
5271
|
-
*
|
|
5272
|
-
* the legacy SINGULAR `Transition` /
|
|
5273
|
-
*
|
|
5274
|
-
*
|
|
5275
|
-
*
|
|
5276
|
-
*
|
|
5277
|
-
*
|
|
5278
|
-
* `
|
|
5279
|
-
*
|
|
5280
|
-
*
|
|
5281
|
-
*
|
|
5282
|
-
* #1755 lands — which is the honest state, not a claim this makes lifecycle
|
|
5283
|
-
* whole.
|
|
5284
|
-
*
|
|
5285
|
-
* @returns `config` BY IDENTITY when no tolerated spelling is declared.
|
|
5775
|
+
* Issues #1754 / #1755 finished the block, so the "still differs on those keys"
|
|
5776
|
+
* caveat this doc carried is gone: the legacy SINGULAR `Transition` /
|
|
5777
|
+
* `NoncurrentVersionTransition` objects are MERGED here exactly as the wire
|
|
5778
|
+
* merges them ({@link foldLegacySingularTransitions}), the rule-level
|
|
5779
|
+
* `Expiration` / `NoncurrentVersionExpiration` reshapes are folded onto the
|
|
5780
|
+
* spellings the readback emits ({@link foldLifecycleExpiration} /
|
|
5781
|
+
* {@link foldNoncurrentVersionExpiration}), and the sent-but-unrecorded
|
|
5782
|
+
* `Filter` is supplied from the rule's full gathered SCOPE
|
|
5783
|
+
* ({@link foldLifecycleScope}).
|
|
5784
|
+
*
|
|
5785
|
+
* @returns `config` BY IDENTITY when the declared bag IS what goes on the wire.
|
|
5286
5786
|
*/
|
|
5287
5787
|
function effectiveLifecycleConfiguration(config) {
|
|
5288
5788
|
if (!isPlainObject$1(config)) return config;
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5789
|
+
const rules = config["Rules"];
|
|
5790
|
+
if (!Array.isArray(rules)) return config;
|
|
5791
|
+
const objectRules = rules.filter(isPlainObject$1);
|
|
5792
|
+
if (objectRules.length !== rules.length || objectRules.length === 0) return config;
|
|
5793
|
+
if (objectRules.some(lifecycleRuleRefused)) return config;
|
|
5794
|
+
const useFilterForm = lifecycleUsesFilterForm(objectRules);
|
|
5795
|
+
return canonicalizeItemList(config, "Rules", (rule) => effectiveLifecycleRule(rule, useFilterForm));
|
|
5796
|
+
}
|
|
5797
|
+
/**
|
|
5798
|
+
* The one rule fold both sides share — the CFn-shaped bag `readLifecycle` emits
|
|
5799
|
+
* for the SDK rule `applyLifecycleConfiguration` builds from this rule.
|
|
5800
|
+
*
|
|
5801
|
+
* Ordered the way the wire is: the legacy singular is merged into the plural
|
|
5802
|
+
* BEFORE the alias fold runs, because the singular can carry the SDK day/date
|
|
5803
|
+
* spellings too.
|
|
5804
|
+
*
|
|
5805
|
+
* MEASURED against the REAL synthesized `s3-lifecycle` fixture templates (every
|
|
5806
|
+
* bucket, both phases): the recorded rule and the emitted one carry identical
|
|
5807
|
+
* KEYS and identical VALUES, and differ only in key ORDER — the record keeps the
|
|
5808
|
+
* template's, the readback uses the reverse-mapper's. That is pre-existing and
|
|
5809
|
+
* unchanged here (the record was always template-ordered), and it does not reach
|
|
5810
|
+
* `cdkd drift`, whose baseline is `observedProperties` — captured from this very
|
|
5811
|
+
* reverse-mapper, so both sides carry its order. It is stated because a reader
|
|
5812
|
+
* comparing the two by `JSON.stringify` will see a difference and should know it
|
|
5813
|
+
* was measured rather than missed.
|
|
5814
|
+
*
|
|
5815
|
+
* @returns `rule` BY IDENTITY when the declared rule already IS that bag, so an
|
|
5816
|
+
* ordinary CFn-spelled template records byte-for-byte as before. The compare
|
|
5817
|
+
* is deep and key-ORDER-insensitive for the same reason: a reordering is not a
|
|
5818
|
+
* difference worth rewriting a state record for.
|
|
5819
|
+
*/
|
|
5820
|
+
function effectiveLifecycleRule(rule, useFilterForm) {
|
|
5821
|
+
let out = foldLegacySingularTransitions(rule);
|
|
5822
|
+
out = canonicalizeItemList(out, "Transitions", (t) => foldTransitionDate(foldToleratedAliases(t, TRANSITION_ALIASES)));
|
|
5823
|
+
out = canonicalizeItemList(out, "NoncurrentVersionTransitions", (nvt) => foldToleratedAliases(nvt, NONCURRENT_VERSION_TRANSITION_ALIASES));
|
|
5824
|
+
out = dropEmptyTransitionLists(out);
|
|
5825
|
+
out = foldLifecycleExpiration(out);
|
|
5826
|
+
out = foldNoncurrentVersionExpiration(out);
|
|
5827
|
+
out = foldLifecycleScope(out, useFilterForm);
|
|
5828
|
+
if (out[LIFECYCLE_STATUS_KEY] === void 0) {
|
|
5829
|
+
const withStatus = { ...out };
|
|
5830
|
+
withStatus[LIFECYCLE_STATUS_KEY] = LIFECYCLE_DEFAULT_STATUS;
|
|
5831
|
+
out = withStatus;
|
|
5832
|
+
}
|
|
5833
|
+
return deepSameValue(rule, out) ? rule : out;
|
|
5294
5834
|
}
|
|
5295
5835
|
/**
|
|
5296
5836
|
* Apply a WHOLE-BLOCK fold to one top-level property on BOTH sides of a diff —
|
|
@@ -5649,32 +6189,18 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
5649
6189
|
async applyLifecycleConfiguration(bucketName, lifecycleConfig, onUnusable) {
|
|
5650
6190
|
for (const rule of lifecycleConfig.Rules) {
|
|
5651
6191
|
const rawFilter = rule["Filter"];
|
|
5652
|
-
if (rawFilter != null && requireConfigObject(rawFilter,
|
|
6192
|
+
if (rawFilter != null && requireConfigObject(rawFilter, LIFECYCLE_FILTER_PATH, { ...onUnusable ? { onUnusable } : {} }) === void 0) return false;
|
|
5653
6193
|
const rawTagFilters = rawFilter?.["TagFilters"] ?? rule["TagFilters"];
|
|
5654
6194
|
if (rawTagFilters != null) {
|
|
5655
|
-
if (requireConfigArray(rawTagFilters,
|
|
6195
|
+
if (requireConfigArray(rawTagFilters, LIFECYCLE_TAG_FILTERS_PATH, { ...onUnusable ? { onUnusable } : {} }) === void 0) return false;
|
|
5656
6196
|
}
|
|
5657
|
-
if (this.skipOnUnusableConfigString(rule, "Status",
|
|
6197
|
+
if (this.skipOnUnusableConfigString(rule, "Status", LIFECYCLE_DEFAULT_STATUS, LIFECYCLE_RULE_PATH, "Leaving the whole lifecycle configuration unapplied here", onUnusable)) return false;
|
|
5658
6198
|
}
|
|
5659
|
-
const
|
|
5660
|
-
const filter = rule["Filter"];
|
|
5661
|
-
return {
|
|
5662
|
-
prefix: filter?.["Prefix"] ?? rule["Prefix"],
|
|
5663
|
-
tagFilters: filter?.["TagFilters"] ?? rule["TagFilters"],
|
|
5664
|
-
sizeGt: filter?.["ObjectSizeGreaterThan"] ?? rule["ObjectSizeGreaterThan"],
|
|
5665
|
-
sizeLt: filter?.["ObjectSizeLessThan"] ?? rule["ObjectSizeLessThan"]
|
|
5666
|
-
};
|
|
5667
|
-
};
|
|
5668
|
-
const isPlainPrefixOnly = (rule) => {
|
|
5669
|
-
if (rule["Filter"] != null) return false;
|
|
5670
|
-
const s = gatherScope(rule);
|
|
5671
|
-
return s.prefix !== void 0 && (s.tagFilters == null || s.tagFilters.length === 0) && s.sizeGt === void 0 && s.sizeLt === void 0;
|
|
5672
|
-
};
|
|
5673
|
-
const useFilterForm = !lifecycleConfig.Rules.every(isPlainPrefixOnly);
|
|
6199
|
+
const useFilterForm = lifecycleUsesFilterForm(lifecycleConfig.Rules);
|
|
5674
6200
|
const rules = lifecycleConfig.Rules.map((rule) => {
|
|
5675
6201
|
const sdkRule = {
|
|
5676
6202
|
ID: rule["Id"],
|
|
5677
|
-
Status: readConfigString(rule, "Status",
|
|
6203
|
+
Status: readConfigString(rule, "Status", LIFECYCLE_DEFAULT_STATUS, LIFECYCLE_RULE_PATH),
|
|
5678
6204
|
Prefix: useFilterForm ? void 0 : rule["Prefix"]
|
|
5679
6205
|
};
|
|
5680
6206
|
const expiration = rule["ExpirationInDays"] || rule["ExpirationDate"] || rule["Expiration"];
|
|
@@ -5720,7 +6246,7 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
5720
6246
|
if (allTransitions.length > 0) sdkRule.Transitions = allTransitions.map(toSdkTransition);
|
|
5721
6247
|
const abort = isPlainObject$1(rule["AbortIncompleteMultipartUpload"]) ? rule["AbortIncompleteMultipartUpload"] : void 0;
|
|
5722
6248
|
if (abort) sdkRule.AbortIncompleteMultipartUpload = { DaysAfterInitiation: abort["DaysAfterInitiation"] };
|
|
5723
|
-
const { prefix, tagFilters, sizeGt, sizeLt } =
|
|
6249
|
+
const { prefix, tagFilters, sizeGt, sizeLt } = lifecycleRuleScope(rule);
|
|
5724
6250
|
const hasTags = tagFilters != null && tagFilters.length > 0;
|
|
5725
6251
|
if ((hasTags ? 1 : 0) + (prefix !== void 0 ? 1 : 0) + (sizeGt !== void 0 ? 1 : 0) + (sizeLt !== void 0 ? 1 : 0) > 1) sdkRule.Filter = { And: {
|
|
5726
6252
|
Prefix: prefix,
|
|
@@ -5906,7 +6432,7 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
5906
6432
|
* SDK uses the same structure (PutBucketNotificationConfiguration replaces
|
|
5907
6433
|
* the entire notification configuration in one call).
|
|
5908
6434
|
*/
|
|
5909
|
-
async applyNotificationConfiguration(bucketName, notifConfig) {
|
|
6435
|
+
async applyNotificationConfiguration(bucketName, notifConfig, onUnusable) {
|
|
5910
6436
|
const cfg = {};
|
|
5911
6437
|
if (notifConfig) {
|
|
5912
6438
|
const topics = notifConfig["TopicConfigurations"];
|
|
@@ -5931,13 +6457,22 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
5931
6457
|
Filter: this.cfnNotifFilterToSdk(l["Filter"])
|
|
5932
6458
|
}));
|
|
5933
6459
|
const eb = notifConfig["EventBridgeConfiguration"];
|
|
5934
|
-
if (eb
|
|
6460
|
+
if (eb != null) {
|
|
6461
|
+
const ebRefusal = configBooleanRefusal(eb, "EventBridgeEnabled", EVENTBRIDGE_CONFIG_PATH);
|
|
6462
|
+
if (ebRefusal !== void 0) {
|
|
6463
|
+
if (!onUnusable) throw new Error(`${ebRefusal}. Omit the block entirely to leave EventBridge delivery disabled`);
|
|
6464
|
+
onUnusable(`${ebRefusal}. Leaving the whole notification configuration unapplied here: PutBucketNotificationConfiguration is a full replace, and treating an unreadable value as ${EVENTBRIDGE_DEFAULT_ENABLED} would ENABLE EventBridge delivery the template may have been disabling; the same value is REFUSED on a template-path create`);
|
|
6465
|
+
return false;
|
|
6466
|
+
}
|
|
6467
|
+
if (eventBridgeEnabledOf(eb)) cfg.EventBridgeConfiguration = {};
|
|
6468
|
+
}
|
|
5935
6469
|
}
|
|
5936
6470
|
await this.s3Client.send(new PutBucketNotificationConfigurationCommand({
|
|
5937
6471
|
Bucket: bucketName,
|
|
5938
6472
|
NotificationConfiguration: cfg
|
|
5939
6473
|
}));
|
|
5940
6474
|
this.logger.debug(`Applied notification configuration to bucket ${bucketName}`);
|
|
6475
|
+
return true;
|
|
5941
6476
|
}
|
|
5942
6477
|
/**
|
|
5943
6478
|
* Convert CFn notification Filter ({ S3Key: { Rules: [{ Name, Value }] } })
|
|
@@ -6951,9 +7486,11 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
6951
7486
|
await this.applyLoggingConfiguration(bucketName, void 0);
|
|
6952
7487
|
});
|
|
6953
7488
|
await this.diffSubConfig(bucketName, previousProperties["NotificationConfiguration"], properties["NotificationConfiguration"], async (cfg) => {
|
|
6954
|
-
await this.applyNotificationConfiguration(bucketName, cfg);
|
|
6955
|
-
recordEffectiveFold("NotificationConfiguration", effectiveNotificationConfiguration(cfg));
|
|
6956
|
-
}, async () =>
|
|
7489
|
+
if (!await this.applyNotificationConfiguration(bucketName, cfg, (m) => this.logger.warn(m))) retainPrevious("NotificationConfiguration");
|
|
7490
|
+
else recordEffectiveFold("NotificationConfiguration", effectiveNotificationConfiguration(cfg));
|
|
7491
|
+
}, async () => {
|
|
7492
|
+
await this.applyNotificationConfiguration(bucketName, void 0);
|
|
7493
|
+
});
|
|
6957
7494
|
await this.diffSubConfig(bucketName, previousProperties["ReplicationConfiguration"], properties["ReplicationConfiguration"], async (cfg) => {
|
|
6958
7495
|
if (!await this.applyReplicationConfiguration(bucketName, cfg, (m) => this.logger.warn(m))) retainPrevious("ReplicationConfiguration");
|
|
6959
7496
|
}, async () => {
|
|
@@ -7067,10 +7604,8 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
7067
7604
|
};
|
|
7068
7605
|
const replayOnUnusable = replayWarn(this.logger, context).onUnusable;
|
|
7069
7606
|
const notifConfig = properties["NotificationConfiguration"];
|
|
7070
|
-
if (notifConfig)
|
|
7071
|
-
|
|
7072
|
-
recordEffectiveFold("NotificationConfiguration", effectiveNotificationConfiguration(notifConfig));
|
|
7073
|
-
}
|
|
7607
|
+
if (notifConfig) if (!await this.applyNotificationConfiguration(bucketName, notifConfig, replayOnUnusable)) overrides.set("NotificationConfiguration", void 0);
|
|
7608
|
+
else recordEffectiveFold("NotificationConfiguration", effectiveNotificationConfiguration(notifConfig));
|
|
7074
7609
|
const corsConfig = properties["CorsConfiguration"];
|
|
7075
7610
|
if (corsConfig?.CorsRules && Array.isArray(corsConfig.CorsRules) && corsConfig.CorsRules.length > 0) await this.applyCorsConfiguration(bucketName, corsConfig);
|
|
7076
7611
|
else if (corsConfig != null) this.announceCreateEmptyCollectionSkip("CorsConfiguration", "CorsConfiguration.CorsRules");
|
|
@@ -7421,11 +7956,9 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
7421
7956
|
if (r.Status !== void 0) out["Status"] = r.Status;
|
|
7422
7957
|
if (r.Prefix !== void 0) out["Prefix"] = r.Prefix;
|
|
7423
7958
|
if (r.Expiration) {
|
|
7424
|
-
|
|
7425
|
-
if (r.Expiration.
|
|
7426
|
-
if (r.Expiration.
|
|
7427
|
-
if (r.Expiration.ExpiredObjectDeleteMarker !== void 0) exp["ExpiredObjectDeleteMarker"] = r.Expiration.ExpiredObjectDeleteMarker;
|
|
7428
|
-
out["Expiration"] = exp;
|
|
7959
|
+
if (r.Expiration.Days !== void 0) out["ExpirationInDays"] = r.Expiration.Days;
|
|
7960
|
+
if (r.Expiration.Date !== void 0) out["ExpirationDate"] = r.Expiration.Date.toISOString();
|
|
7961
|
+
if (r.Expiration.ExpiredObjectDeleteMarker !== void 0) out["ExpiredObjectDeleteMarker"] = r.Expiration.ExpiredObjectDeleteMarker;
|
|
7429
7962
|
}
|
|
7430
7963
|
if (r.Transitions && r.Transitions.length > 0) out["Transitions"] = r.Transitions.map((t) => {
|
|
7431
7964
|
const item = {};
|
|
@@ -62978,6 +63511,50 @@ function escapeRegExp(value) {
|
|
|
62978
63511
|
*/
|
|
62979
63512
|
const KEY_TERMINATORS = "[^\\s\"'?]";
|
|
62980
63513
|
/**
|
|
63514
|
+
* `(?:amazonaws\.com\.cn|amazonaws\.com|...)` — {@link AWS_URL_SUFFIXES} as a
|
|
63515
|
+
* non-capturing alternation, LONGEST FIRST so `amazonaws.com.cn` is tried
|
|
63516
|
+
* before its own `amazonaws.com` prefix instead of relying on backtracking.
|
|
63517
|
+
*
|
|
63518
|
+
* The `(?:` is load-bearing, not stylistic: every matcher below reads its
|
|
63519
|
+
* results by GROUP INDEX (`match[1]` is the S3 key / the ECR tag, `match[2]`
|
|
63520
|
+
* the ECR digest), and this alternation sits BEFORE those groups in all three
|
|
63521
|
+
* patterns. Making it capturing would shift every index by one and silently
|
|
63522
|
+
* collect the suffix as if it were a key.
|
|
63523
|
+
*/
|
|
63524
|
+
const URL_SUFFIX_ALTERNATION = `(?:${[...[
|
|
63525
|
+
"amazonaws.com",
|
|
63526
|
+
"amazonaws.com.cn",
|
|
63527
|
+
"amazonaws.eu",
|
|
63528
|
+
"c2s.ic.gov",
|
|
63529
|
+
"sc2s.sgov.gov",
|
|
63530
|
+
"cloud.adc-e.uk",
|
|
63531
|
+
"csp.hci.ic.gov"
|
|
63532
|
+
]].sort((a, b) => b.length - a.length).map(escapeRegExp).join("|")})`;
|
|
63533
|
+
/**
|
|
63534
|
+
* The HOST half of an ECR registry, as an alternation over the forms AWS
|
|
63535
|
+
* serves: `<acct>.dkr.ecr.<region>.<urlSuffix>`, its FIPS sibling
|
|
63536
|
+
* `<acct>.dkr.ecr-fips.<region>.<urlSuffix>`, and the short-form alias
|
|
63537
|
+
* `<acct>.dkr-ecr.<region>.on.aws`.
|
|
63538
|
+
*
|
|
63539
|
+
* Kept DELIBERATELY separate from the S3 matchers' shared
|
|
63540
|
+
* {@link URL_SUFFIX_ALTERNATION} rather than folded into it: `on.aws` is an
|
|
63541
|
+
* ECR-only endpoint, so adding it to the shared suffix set would also make
|
|
63542
|
+
* `https://<assetBucket>.s3.<region>.on.aws/<key>` a recognized S3 reference.
|
|
63543
|
+
* Widening either matcher only ever over-PROTECTS, but the two services' host
|
|
63544
|
+
* grammars genuinely differ and conflating them is how the next widening goes
|
|
63545
|
+
* wrong.
|
|
63546
|
+
*
|
|
63547
|
+
* Account and region are matched loosely on purpose (see the extractor doc):
|
|
63548
|
+
* collecting a reference from another account's or region's URI can only KEEP
|
|
63549
|
+
* more, never delete more — which is also why the two extra forms are worth
|
|
63550
|
+
* carrying even though cdkd's own publisher writes the plain one. Issue #1785
|
|
63551
|
+
* tracks deriving these host forms from ONE place shared with
|
|
63552
|
+
* `src/utils/ecr-uri.ts` instead of two hand-kept copies.
|
|
63553
|
+
*
|
|
63554
|
+
* Every group is non-capturing, for the {@link URL_SUFFIX_ALTERNATION} reason.
|
|
63555
|
+
*/
|
|
63556
|
+
const ECR_REGISTRY_HOST = `\\d{12}\\.(?:dkr\\.ecr(?:-fips)?\\.[a-z0-9-]+\\.${URL_SUFFIX_ALTERNATION}|dkr-ecr\\.[a-z0-9-]+\\.on\\.aws)`;
|
|
63557
|
+
/**
|
|
62981
63558
|
* cdkd's publishers write content-addressed keys (`<sha256>.<ext>`). A
|
|
62982
63559
|
* second, name-independent pass collects every such token from every
|
|
62983
63560
|
* scanned string — belt-and-braces against URL captures that ran through
|
|
@@ -63015,21 +63592,26 @@ function tryDecodeBase64Text(value) {
|
|
|
63015
63592
|
* ApiGateway BodyS3Location / SFN DefinitionS3Location, ...) — handled
|
|
63016
63593
|
* in the walk itself; every sibling string value is collected.
|
|
63017
63594
|
* - `s3://<assetBucket>/<key>` URIs.
|
|
63018
|
-
* - `https://<assetBucket>.s3[.<region>]
|
|
63595
|
+
* - `https://<assetBucket>.s3[.<region>].<urlSuffix>/<key>`
|
|
63019
63596
|
* (virtual-hosted style, region / dualstack variants included).
|
|
63020
|
-
* - `https://s3[.<region>]
|
|
63021
|
-
* - `<
|
|
63022
|
-
*
|
|
63023
|
-
* loosely on purpose: collecting a reference from another
|
|
63024
|
-
* region's URI can only over-protect (keep more), never
|
|
63597
|
+
* - `https://s3[.<region>].<urlSuffix>/<assetBucket>/<key>` (path style).
|
|
63598
|
+
* - `<ecrHost>/<containerRepo>:<tag>` and/or `...@sha256:<digest>` image
|
|
63599
|
+
* URIs, where `<ecrHost>` is {@link ECR_REGISTRY_HOST}. Account / region
|
|
63600
|
+
* are matched loosely on purpose: collecting a reference from another
|
|
63601
|
+
* account's or region's URI can only over-protect (keep more), never
|
|
63602
|
+
* delete more.
|
|
63603
|
+
*
|
|
63604
|
+
* `<urlSuffix>` in the two S3 shapes is {@link URL_SUFFIX_ALTERNATION}, i.e.
|
|
63605
|
+
* EVERY partition's suffix at once — see that constant for why a per-region
|
|
63606
|
+
* derived literal is the wrong shape here (issue #1781).
|
|
63025
63607
|
*/
|
|
63026
63608
|
function buildReferenceExtractors(marker) {
|
|
63027
63609
|
const bucket = escapeRegExp(marker.assetBucket);
|
|
63028
63610
|
const repo = escapeRegExp(marker.containerRepo);
|
|
63029
63611
|
const s3UriRe = new RegExp(`s3://${bucket}/(${KEY_TERMINATORS}+)`, "g");
|
|
63030
|
-
const virtualHostedRe = new RegExp(`https://${bucket}\\.s3[^/\\s]
|
|
63031
|
-
const pathStyleRe = new RegExp(`https://s3[^/\\s]
|
|
63032
|
-
const ecrRe = new RegExp(
|
|
63612
|
+
const virtualHostedRe = new RegExp(`https://${bucket}\\.s3[^/\\s]*\\.${URL_SUFFIX_ALTERNATION}/(${KEY_TERMINATORS}+)`, "g");
|
|
63613
|
+
const pathStyleRe = new RegExp(`https://s3[^/\\s]*\\.${URL_SUFFIX_ALTERNATION}/${bucket}/(${KEY_TERMINATORS}+)`, "g");
|
|
63614
|
+
const ecrRe = new RegExp(`${ECR_REGISTRY_HOST}/${repo}(?::([A-Za-z0-9_][A-Za-z0-9._-]{0,127}))?(?:@(sha256:[0-9a-f]{64}))?`, "g");
|
|
63033
63615
|
const extractOnce = (value, refs) => {
|
|
63034
63616
|
for (const re of [
|
|
63035
63617
|
s3UriRe,
|
|
@@ -74854,7 +75436,7 @@ function createMigrateCommand() {
|
|
|
74854
75436
|
*/
|
|
74855
75437
|
function buildProgram() {
|
|
74856
75438
|
const program = new Command();
|
|
74857
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.
|
|
75439
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.26");
|
|
74858
75440
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
74859
75441
|
const { profile } = actionCommand.optsWithGlobals();
|
|
74860
75442
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|