@go-to-k/cdkd 0.290.35 → 0.290.37
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-BguZAQtF.js → asg-provider-CXLgFrPg.js} +2 -2
- package/dist/{asg-provider-BguZAQtF.js.map → asg-provider-CXLgFrPg.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-DhZtBBq0.js → deploy-engine-pVC4fG22.js} +362 -8
- package/dist/deploy-engine-pVC4fG22.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-COiS7GZJ.js → program-CUOo1x11.js} +591 -282
- package/dist/{program-COiS7GZJ.js.map → program-CUOo1x11.js.map} +1 -1
- package/dist/{version-BIpCWEQx.js → version-COEQwjCP.js} +2 -2
- package/dist/{version-BIpCWEQx.js.map → version-COEQwjCP.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DhZtBBq0.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as yellow, $i as NestedStackChildDirectDestroyError, $n as identityKeyFor, $r as getDockerCmd, $t as requireConfigObject, A as createPreDeleteFinalSnapshot, Ai as PARTITION_TABLE, An as refuseMalformedNestedChildOutputs, Ar as stringifyValue, At as carriesDynamicReference, B as isStatefulRecreateTargetSync, Bi as setAwsClients, Bn as findSilentDropProperties, Br as getBootstrapMarkerKey, Bt as isSettledInstanceState, C as namesAnyDeclaredParameter, Ci as CFN_TEMPLATE_URL_LIMIT, Cn as malformedOutputsWarning, Cr as importableOutputKeys, Ct as slowCcOperationTimeoutMs, D as PRE_DELETE_SNAPSHOT_TYPES, Di as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Dn as malformedStateRefusalMessage, Dr as orphansCarriedFrom, Dt as disableInstanceApiTermination, E as ATOMIC_FINAL_SNAPSHOT_TYPES, Ei as uploadCfnTemplate, En as malformedResourcesWarning, Er as orphansAfterRollback, Et as deleteSkipReason, F as extractDeploymentEventError, Fn as refuseMalformedState, Fr as rewriteTemplateAssetReferences, Ft as isImpossibleEmptyStoredAttribute, G as toCfnInteger, Gn as STATE_SOURCED_BASELINE_RULES, Gr as validateContainerRepoName, Gt as coerceCfnBoolean, H as WARM_THROUGHPUT_MEMBERS, Hi as CdkdError, Hn as withRetry, Hr as parseBootstrapMarker, Ht as normalizeAwsTagsToCfn, I as makeSiblingClaimReader, Ii as resolveBucketRegion, In as repairMalformedOutputsForReadOnly, Ir as AssetModeResolver, It as isNamelessDynamicReferenceError, J as bold, Ji as IntrinsicResolutionRefusalError, Jn as TEMPLATE_SOURCED_RULES, Jr as describeDockerCapturedOutput, Jt as configIntegerRefusal, K as toFiniteNumber, Ki as DeployCancelledError, Kn as STATE_SOURCED_CROSS_GENERATION_RULES, Kr as buildDenyExternalAccessPolicy, Kt as coerceCfnInteger, L as planOrphanAdoption, Li as AwsClients, Ln as repairMalformedResourcePropertiesForReadOnly, Lr as BOOTSTRAP_MARKER_PREFIX, Lt as isUnboundTemplateParameter, M as refusesFinalSnapshot, Mi as derivePartitionAndUrlSuffix, Mn as refuseMalformedOutputsForDestroy, Mr as buildAssetRedirectMap, Mt as cfnRefValueFromPhysicalId, N as unsupportedFinalSnapshotError, Ni as AssemblyReader, Nn as refuseMalformedResourcePropertiesForOrphan, Nr as createAssetRedirectResolver, Nt as coerceParameterTypedValue, O as buildFinalSnapshotIdentifier, Oi as decodeListingKey, On as producerRecordKey, Or as shouldRetainResource, Ot as isTerminationProtectionPropagationError, P as makeCanonicalizePropertiesFn, Pi as processStackMessages, Pn as refuseMalformedResourcesForDestroy, Pr as loadPublishableAssetManifest, Pt as getAccountInfo, Q as red, Qi as MalformedProducerRecordRefusalError, Qn as errorCauseChain, Qr as formatDockerLoginError, Qt as requireConfigArray, R as MULTI_REGION_RECREATE_BLOCKED_TYPES, Ri as getAwsClients, Rn as repairMalformedResourcesForReadOnly, Rr as assertAssetBucketRegion, Rt as parameterTypeMayLoseSecretIdentity, S as computeParameterTaint, Si as CFN_TEMPLATE_BODY_LIMIT, Sn as malformedOutputsRefusalMessage, Sr as hasUnverifiableParameterRefusal, St as isWaitAbandonedError, T as resourcesNamingDeclaredParameter, Ti as findLargeInlineResources, Tn as malformedResourcePropertiesWarning, Tr as isReadableBag, Tt as deleteIndeterminateGuards, U as coerceWarmThroughput$1, Un as DagBuilder, Ur as readBootstrapMarkerBody, Ut as resolveExplicitPhysicalId, V as renderStatefulReason, Vn as describeTypeWithThrottleRetry, Vr as isCrossRegionRedirect, Vt as WAFv2WebACLProvider, W as isWarmThroughputDecrease, Wi as CrossAccountSecretRefusalError, Wn as TemplateParser, Wr as validateAssetBucketName, Wt as assertRegionMatch, X as gray, Xi as LocalStartServiceError, Xn as createSecretMasker, Xr as describeDockerFailure, Xt as readConfigString, Y as cyan, Yi as LocalInvokeBuildError$1, Yn as carriesSecretMask, Yr as describeDockerExecFailure, Yt as configStringRefusal, Z as green, Zn as dynamicReferenceTokens, Zr as dockerSpawnEnvWithSensitive, Zt as replayWarn, _ as bagHoldsSecretExpression, _a as markRedactedCause, _i as resolveStateBucketWithDefaultAndSource, _n as hasReadableOutputs, _r as CUSTOM_RESOURCE_RESPONSE_PREFIX, _t as endCommandInterruptScope, a as DeploymentEventsStore, aa as StackTerminationProtectionError, ai as stripControlChars, an as s3BucketDomainName, ar as recoverMaskedOutput, at as secretBearingStateKeyWarning, b as mergeNoChangeOutputs, ba as safeStringify, bi as warnDeprecatedNoPrefixCliFlag, bn as malformedExportSourceWarning, br as hasReadableExportSet, bt as startInterruptWatch, c as replayFailedOperations, ci as Synthesizer, cn as s3BucketWebsiteUrl, cr as LockManager, ct as IAMRoleProvider, d as updatePartialMessage, da as withErrorHandling, di as getLegacyStateBucketName, dn as applyCallerIdentityCredentials, dr as UNREPRODUCIBLE_LOCK_CLAUSE, dt as ProviderRegistry, ea as PartialFailureError, ei as partitionSensitiveEnv, en as requireConfigString, er as isSingleDynamicReferenceToken, et as collectDeclaredOutputNames, f as updatePartialReason, fa as isMarkedNonRetryable, fi as resolveApp, fn as callerEnvCredentials, fr as buildForceUnlockCommand, ft as wouldReturnToSdkProvider, g as computeImplicitDeleteEdges, ga as markNonRetryable, gi as resolveStateBucketWithDefault, gn as STATE_RESOURCES_MALFORMED, gr as shellQuote, gt as beginCommandInterruptScope, h as IMPLICIT_DELETE_DEPENDENCIES, ha as isTransientServerError, hi as resolveSkipPrefix, hn as INTRINSIC_KEYS, hr as formatLockExpiry, ht as maskerOrIdentity, i as DeploymentEventsReader, ia as StackHasActiveImportsError, ii as escapeRegExp, in as s3BucketArn, ir as recordMaskOnlyValue, it as secretBearing, j as isFinalSnapshotError, ji as canonicalizeRegion, jn as refuseMalformedOutputs, jr as WorkGraph, jt as carriesFetchableDynamicReference, k as ccRoutedFinalSnapshotError, ki as expectedOwnerParam, kn as refuseDivergentRecordRegionForDestroy, kr as AssetPublisher, kt as IntrinsicFunctionResolver, l as replayRollback, li as synthesisStatusMessage, ln as applyRoleArnIfSet, lr as S3StateBackend, lt as collectInlinePolicyNamesManagedBySiblings, m as maskingRetryLogger, ma as isThrottlingError, mi as resolveCaptureObservedState, mn as DiffCalculator, mr as forceQuitRecoveryClause, mt as maskDeep, n as DEFAULT_RESOURCE_WARN_AFTER_MS, na as ResourceTimeoutError, ni as runDockerForeground, nn as producerRegionsFromState, nr as maskSecretsInError, nt as exportAliasCollisionScrubWarning, o as planFailedOps, oi as AssetManifestLoader, on as s3BucketDualStackDomainName, or as redactSecretsForState, ot as secretSafeKeyDisplay, p as withResourceDeadline, pa as isRetryableTransientError, pi as resolveAutoAssetStorage, pn as envCredentialsAreAssumedRole, pr as buildLockContentionMessage, pt as createMaskedRetryLogger, q as formatResourceLine, qi as DynamicReferenceRegionAmbiguousError, qn as STATE_SOURCED_READBACK_RULES, qr as buildDockerImage, qt as configBooleanRefusal, r as DeployEngine, ra as ResourceUpdateNotSupportedError, ri as runDockerStreaming, rn as withSharedDrainBudget, rr as maskSecretsInText, rt as isExportAliasCollision, s as planRollback, sa as SynthesisError, si as getDockerImageBySourceHash, sn as s3BucketRegionalDomainName, sr as scrubResourceRecord, st as getCurrentResourceSecrets, t as DEFAULT_RESOURCE_TIMEOUT_MS, ta as ProvisioningError, ti as redactDockerArgvValues, tn as classifyReplaySecretRegion, tr as markSameGenerationBag, tt as collectPublishedOutputNames, u as resolveReplacementOldType, ua as normalizeAwsError, ui as getDefaultStateBucketName, un as AWS_CREDENTIAL_ENV_KEYS, ur as rebuildClientForBucketRegion, ut as clearOnUpdateRemoval, v as isSecretBearingReferenceString, va as retryClassificationText, vi as resolveUseCdkBootstrapAssets, vn as hasReadableResources, vr as DEFAULT_STATE_PREFIX, vt as interruptWatchListenerCount, w as reachableDivergentParameters, wi as MIGRATE_TMP_PREFIX, wn as malformedRenderedContainersWarning, wr as importableOutputs, wt as UNSPECIFIED_SKIP_REASON, x as bindingSkippedOutputs, xi as ssmResolvedValueType, xn as malformedLocalOutputsWarning, xr as hasReasonlessBaselineRefusal, xt as CloudControlProvider, y as keptWholeReasonText, ya as describeAwsFailure, yi as stateBucketExistenceConfirmed, yn as malformedExportNamesWarning, yr as exportNamesCarriedFrom, yt as isInterruptedWaitError, z as isStatefulRecreateTargetForReplace, zi as resetAwsClients, zn as findActionableSilentDrops, zr as ensureAssetStorage, zt as refStateLookupFromResource } from "./deploy-engine-pVC4fG22.js";
|
|
2
2
|
import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, _ as withSkipPrefix, b as SECRET_REF_MAX_CODE_POINTS, c as PATTERN_B_NAME_OPTIONS, f as explicitNamePropertyFor, h as getCurrentStackName, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as generateResourceNameWithFallback, n as getLogger, o as runStackBuffered, p as generateResourceName, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS } from "./logger-BX4pJk0T.js";
|
|
3
3
|
import { n as awsClientDefaults } from "./aws-client-defaults-BoIxyCir.js";
|
|
4
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
-
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as getCdkdVersion } from "./version-COEQwjCP.js";
|
|
5
|
+
import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-CXLgFrPg.js";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
8
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, NotFound, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -12,7 +12,7 @@ import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesComman
|
|
|
12
12
|
import { AddPermissionCommand, CreateEventSourceMappingCommand, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteEventSourceMappingCommand, DeleteFunctionCodeSigningConfigCommand, DeleteFunctionCommand, DeleteFunctionConcurrencyCommand, DeleteFunctionEventInvokeConfigCommand, DeleteFunctionUrlConfigCommand, DeleteLayerVersionCommand, GetEventSourceMappingCommand, GetFunctionCodeSigningConfigCommand, GetFunctionCommand, GetFunctionConcurrencyCommand, GetFunctionEventInvokeConfigCommand, GetFunctionRecursionConfigCommand, GetFunctionUrlConfigCommand, GetLayerVersionByArnCommand, GetPolicyCommand as GetPolicyCommand$1, GetRuntimeManagementConfigCommand, LambdaClient, ListTagsCommand, PublishLayerVersionCommand, PutFunctionCodeSigningConfigCommand, PutFunctionConcurrencyCommand, PutFunctionEventInvokeConfigCommand, PutFunctionRecursionConfigCommand, PutRuntimeManagementConfigCommand, RemovePermissionCommand, ResourceNotFoundException, TagResourceCommand as TagResourceCommand$1, UntagResourceCommand as UntagResourceCommand$1, UpdateEventSourceMappingCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
|
|
13
13
|
import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
14
14
|
import { AllocateAddressCommand, AssociateAddressCommand, AssociateIamInstanceProfileCommand, AssociateRouteTableCommand, AttachInternetGatewayCommand, AuthorizeSecurityGroupEgressCommand, AuthorizeSecurityGroupIngressCommand, CreateInternetGatewayCommand, CreateNatGatewayCommand, CreateNetworkAclCommand, CreateNetworkAclEntryCommand, CreateRouteCommand, CreateRouteTableCommand, CreateSecurityGroupCommand, CreateSubnetCommand, CreateTagsCommand, CreateVpcCommand, DeleteInternetGatewayCommand, DeleteNatGatewayCommand, DeleteNetworkAclCommand, DeleteNetworkAclEntryCommand, DeleteNetworkInterfaceCommand, DeleteRouteCommand, DeleteRouteTableCommand, DeleteSecurityGroupCommand, DeleteSubnetCommand, DeleteTagsCommand, DeleteVpcCommand, DescribeAddressesCommand, DescribeAvailabilityZonesCommand, DescribeIamInstanceProfileAssociationsCommand, DescribeInstanceAttributeCommand, DescribeInstanceCreditSpecificationsCommand, DescribeInstancesCommand, DescribeInternetGatewaysCommand, DescribeNatGatewaysCommand, DescribeNetworkAclsCommand, DescribeNetworkInterfacesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupRulesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVolumesCommand, DescribeVpcAttributeCommand, DescribeVpcsCommand, DetachInternetGatewayCommand, DisassociateAddressCommand, DisassociateRouteTableCommand, EC2Client, ModifyInstanceAttributeCommand, ModifyInstanceCreditSpecificationCommand, ModifyInstanceMetadataOptionsCommand, ModifySubnetAttributeCommand, ModifyVpcAttributeCommand, MonitorInstancesCommand, ReleaseAddressCommand, ReplaceNetworkAclAssociationCommand, RevokeSecurityGroupEgressCommand, RevokeSecurityGroupIngressCommand, RunInstancesCommand, TerminateInstancesCommand, UnmonitorInstancesCommand, waitUntilInstanceRunning, waitUntilInstanceTerminated, waitUntilNatGatewayAvailable, waitUntilNatGatewayDeleted } from "@aws-sdk/client-ec2";
|
|
15
|
-
import { CreateTableCommand, DeleteResourcePolicyCommand, DeleteTableCommand, DescribeContinuousBackupsCommand, DescribeContributorInsightsCommand, DescribeKinesisStreamingDestinationCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DisableKinesisStreamingDestinationCommand, DynamoDBClient, EnableKinesisStreamingDestinationCommand, GetResourcePolicyCommand, ListTagsOfResourceCommand, PutResourcePolicyCommand, ResourceNotFoundException as ResourceNotFoundException$1, TagResourceCommand as TagResourceCommand$2, UntagResourceCommand as UntagResourceCommand$2, UpdateContinuousBackupsCommand, UpdateContributorInsightsCommand, UpdateTableCommand, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
|
|
15
|
+
import { CreateTableCommand, DeleteResourcePolicyCommand, DeleteTableCommand, DescribeContinuousBackupsCommand, DescribeContributorInsightsCommand, DescribeKinesisStreamingDestinationCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DisableKinesisStreamingDestinationCommand, DynamoDBClient, EnableKinesisStreamingDestinationCommand, GetResourcePolicyCommand, ListTagsOfResourceCommand, PolicyNotFoundException, PutResourcePolicyCommand, ResourceNotFoundException as ResourceNotFoundException$1, TagResourceCommand as TagResourceCommand$2, UntagResourceCommand as UntagResourceCommand$2, UpdateContinuousBackupsCommand, UpdateContributorInsightsCommand, UpdateTableCommand, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
|
|
16
16
|
import { CloudFormationClient, CreateChangeSetCommand, DeleteChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, DescribeStackEventsCommand, DescribeStackResourcesCommand, DescribeStacksCommand, ExecuteChangeSetCommand, GetTemplateCommand, UpdateStackCommand, waitUntilChangeSetCreateComplete, waitUntilStackDeleteComplete, waitUntilStackImportComplete, waitUntilStackUpdateComplete } from "@aws-sdk/client-cloudformation";
|
|
17
17
|
import { APIGatewayClient, CreateAuthorizerCommand, CreateDeploymentCommand, CreateResourceCommand, CreateStageCommand, DeleteAuthorizerCommand, DeleteDeploymentCommand, DeleteMethodCommand, DeleteResourceCommand, DeleteStageCommand, GetAccountCommand, GetAuthorizerCommand, GetDeploymentCommand, GetMethodCommand, GetResourceCommand, GetStageCommand, NotFoundException as NotFoundException$1, PutIntegrationCommand, PutIntegrationResponseCommand, PutMethodCommand, PutMethodResponseCommand, TagResourceCommand as TagResourceCommand$3, UntagResourceCommand as UntagResourceCommand$3, UpdateAccountCommand, UpdateAuthorizerCommand, UpdateMethodCommand, UpdateStageCommand } from "@aws-sdk/client-api-gateway";
|
|
18
18
|
import { CreateEventBusCommand, DeleteEventBusCommand, DeleteRuleCommand, DescribeEventBusCommand, DescribeRuleCommand, EventBridgeClient, ListRulesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$1, ListTargetsByRuleCommand, PutRuleCommand, PutTargetsCommand, RemoveTargetsCommand, ResourceNotFoundException as ResourceNotFoundException$2, TagResourceCommand as TagResourceCommand$4, UntagResourceCommand as UntagResourceCommand$4, UpdateEventBusCommand } from "@aws-sdk/client-eventbridge";
|
|
@@ -7137,7 +7137,7 @@ function manualBucketDeletionClause(bucketName, maskSecrets) {
|
|
|
7137
7137
|
* disagree about what an `Id` is.
|
|
7138
7138
|
*/
|
|
7139
7139
|
function configItemId(item) {
|
|
7140
|
-
if (!isPlainObject$
|
|
7140
|
+
if (!isPlainObject$2(item)) return void 0;
|
|
7141
7141
|
const id = item["Id"];
|
|
7142
7142
|
if (typeof id === "string") return id;
|
|
7143
7143
|
if (typeof id === "number" && Number.isFinite(id)) return String(id);
|
|
@@ -7157,7 +7157,7 @@ function configItemId(item) {
|
|
|
7157
7157
|
function declaresBucketObjectLock(properties) {
|
|
7158
7158
|
return properties["ObjectLockEnabled"] === true || properties["ObjectLockEnabled"] === "true";
|
|
7159
7159
|
}
|
|
7160
|
-
function isPlainObject$
|
|
7160
|
+
function isPlainObject$2(value) {
|
|
7161
7161
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7162
7162
|
}
|
|
7163
7163
|
/**
|
|
@@ -7192,7 +7192,7 @@ function isFlattenedDestination(dest) {
|
|
|
7192
7192
|
* via the critic's `--providers-dir=` seam.
|
|
7193
7193
|
*/
|
|
7194
7194
|
function s3BucketDestinationPath(dest, destinationPath) {
|
|
7195
|
-
return isPlainObject$
|
|
7195
|
+
return isPlainObject$2(dest) && isFlattenedDestination(dest) ? destinationPath : `${destinationPath}.S3BucketDestination`;
|
|
7196
7196
|
}
|
|
7197
7197
|
/**
|
|
7198
7198
|
* The FLATTENED CFn `Destination` block cdkd actually SENDS for `declared`
|
|
@@ -7231,9 +7231,9 @@ function s3BucketDestinationPath(dest, destinationPath) {
|
|
|
7231
7231
|
* resolver's own refusal to report).
|
|
7232
7232
|
*/
|
|
7233
7233
|
function effectiveS3BucketDestination(declared, sentFormat) {
|
|
7234
|
-
if (!isPlainObject$
|
|
7234
|
+
if (!isPlainObject$2(declared)) return declared;
|
|
7235
7235
|
const bag = isFlattenedDestination(declared) ? declared : declared["S3BucketDestination"];
|
|
7236
|
-
if (!isPlainObject$
|
|
7236
|
+
if (!isPlainObject$2(bag)) return declared;
|
|
7237
7237
|
if (!bag["BucketArn"] && !bag["Bucket"]) return declared;
|
|
7238
7238
|
const out = {};
|
|
7239
7239
|
const bucketArn = bag["BucketArn"] ?? bag["Bucket"];
|
|
@@ -7364,7 +7364,7 @@ function effectiveInventoryItem(item, sent) {
|
|
|
7364
7364
|
const scheduleContainerUsable = configStringRefusal(declaredSchedule, "Frequency", INVENTORY_DEFAULT_SCHEDULE_FREQUENCY, "AWS::S3::Bucket InventoryConfigurations[].Schedule") === void 0;
|
|
7365
7365
|
const canFoldSchedule = sent?.frequency !== void 0 || scheduleFrequencyUsable || !declaresScheduleFrequency && scheduleContainerUsable;
|
|
7366
7366
|
if (canFoldSchedule) {
|
|
7367
|
-
const declaredFrequency = declaresScheduleFrequency ? item["ScheduleFrequency"] : isPlainObject$
|
|
7367
|
+
const declaredFrequency = declaresScheduleFrequency ? item["ScheduleFrequency"] : isPlainObject$2(declaredSchedule) && declaredSchedule["Frequency"] !== void 0 ? declaredSchedule["Frequency"] : INVENTORY_DEFAULT_SCHEDULE_FREQUENCY;
|
|
7368
7368
|
const frequency = sent?.frequency ?? declaredFrequency;
|
|
7369
7369
|
if (!Object.is(item["ScheduleFrequency"], frequency)) patch["ScheduleFrequency"] = frequency;
|
|
7370
7370
|
}
|
|
@@ -7395,9 +7395,9 @@ function effectiveInventoryItem(item, sent) {
|
|
|
7395
7395
|
*/
|
|
7396
7396
|
function effectiveAnalyticsItem(item, sent) {
|
|
7397
7397
|
const storageClassAnalysis = item["StorageClassAnalysis"];
|
|
7398
|
-
if (!isPlainObject$
|
|
7398
|
+
if (!isPlainObject$2(storageClassAnalysis)) return item;
|
|
7399
7399
|
const dataExport = storageClassAnalysis["DataExport"];
|
|
7400
|
-
if (!isPlainObject$
|
|
7400
|
+
if (!isPlainObject$2(dataExport)) return item;
|
|
7401
7401
|
const patch = {};
|
|
7402
7402
|
const outputSchemaVersion = sent?.outputSchemaVersion ?? declaredOrDefault(dataExport, "OutputSchemaVersion", ANALYTICS_DEFAULT_OUTPUT_SCHEMA_VERSION);
|
|
7403
7403
|
if (!Object.is(dataExport["OutputSchemaVersion"], outputSchemaVersion)) patch["OutputSchemaVersion"] = outputSchemaVersion;
|
|
@@ -7513,7 +7513,7 @@ const EVENTBRIDGE_DEFAULT_ENABLED = true;
|
|
|
7513
7513
|
* correct once every declared-but-unusable value has been refused.
|
|
7514
7514
|
*/
|
|
7515
7515
|
function eventBridgeEnabledOf(eb) {
|
|
7516
|
-
const declared = isPlainObject$
|
|
7516
|
+
const declared = isPlainObject$2(eb) ? eb["EventBridgeEnabled"] : void 0;
|
|
7517
7517
|
return coerceCfnBoolean(declared) ?? EVENTBRIDGE_DEFAULT_ENABLED;
|
|
7518
7518
|
}
|
|
7519
7519
|
/** The notification families whose empty declaration is never SENT (issue #1759). */
|
|
@@ -7620,7 +7620,7 @@ function foldNotificationEvents(item) {
|
|
|
7620
7620
|
* handling).
|
|
7621
7621
|
*/
|
|
7622
7622
|
function effectiveNotificationConfiguration(config) {
|
|
7623
|
-
if (!isPlainObject$
|
|
7623
|
+
if (!isPlainObject$2(config)) return config;
|
|
7624
7624
|
let out = config;
|
|
7625
7625
|
for (const [listKey, aliases] of NOTIFICATION_ARN_ALIASES) out = canonicalizeItemList(out, listKey, (item) => foldNotificationEvents(foldToleratedAliases(item, aliases)));
|
|
7626
7626
|
out = dropEmptyNotificationFamilies(out);
|
|
@@ -7677,8 +7677,8 @@ function foldEventBridgeConfiguration(config) {
|
|
|
7677
7677
|
if (eb === void 0) return config;
|
|
7678
7678
|
if (eb !== null && configBooleanRefusal(eb, "EventBridgeEnabled", EVENTBRIDGE_CONFIG_PATH)) return config;
|
|
7679
7679
|
const enabled = eb === null ? false : eventBridgeEnabledOf(eb);
|
|
7680
|
-
const declared = isPlainObject$
|
|
7681
|
-
if (isPlainObject$
|
|
7680
|
+
const declared = isPlainObject$2(eb) ? eb["EventBridgeEnabled"] : void 0;
|
|
7681
|
+
if (isPlainObject$2(eb) && Object.keys(eb).length === 1 && Object.is(declared, enabled)) return config;
|
|
7682
7682
|
return {
|
|
7683
7683
|
...config,
|
|
7684
7684
|
[EVENTBRIDGE_CONFIG_KEY]: { EventBridgeEnabled: enabled }
|
|
@@ -7758,7 +7758,7 @@ function deepSameValue(a, b) {
|
|
|
7758
7758
|
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
7759
7759
|
return a.every((element, index) => deepSameValue(element, b[index]));
|
|
7760
7760
|
}
|
|
7761
|
-
if (isPlainObject$
|
|
7761
|
+
if (isPlainObject$2(a) && isPlainObject$2(b)) {
|
|
7762
7762
|
const keysA = Object.keys(a);
|
|
7763
7763
|
if (keysA.length !== Object.keys(b).length) return false;
|
|
7764
7764
|
return keysA.every((key) => key in b && deepSameValue(a[key], b[key]));
|
|
@@ -7833,9 +7833,9 @@ function foldLegacySingularTransitions(rule) {
|
|
|
7833
7833
|
let out;
|
|
7834
7834
|
for (const [pluralKey, singularKey] of LEGACY_SINGULAR_TRANSITIONS) {
|
|
7835
7835
|
const singular = rule[singularKey];
|
|
7836
|
-
const hasSingular = isPlainObject$
|
|
7836
|
+
const hasSingular = isPlainObject$2(singular);
|
|
7837
7837
|
const plural = rule[pluralKey];
|
|
7838
|
-
const needsFilter = Array.isArray(plural) && plural.some((element) => !isPlainObject$
|
|
7838
|
+
const needsFilter = Array.isArray(plural) && plural.some((element) => !isPlainObject$2(element));
|
|
7839
7839
|
if (!hasSingular && !needsFilter) continue;
|
|
7840
7840
|
let collided = false;
|
|
7841
7841
|
const merged = mergeLegacySingular(plural, hasSingular ? singular : void 0, () => {
|
|
@@ -7943,7 +7943,7 @@ function foldLifecycleExpiration(rule) {
|
|
|
7943
7943
|
const iso = cfnDateToIso(expiration);
|
|
7944
7944
|
if (iso === void 0) return rule;
|
|
7945
7945
|
date = iso;
|
|
7946
|
-
} else if (isPlainObject$
|
|
7946
|
+
} else if (isPlainObject$2(expiration)) {
|
|
7947
7947
|
if (expiration["Days"] !== void 0) days = expiration["Days"];
|
|
7948
7948
|
if (expiration["Date"]) {
|
|
7949
7949
|
const iso = cfnDateToIso(expiration["Date"]);
|
|
@@ -7991,10 +7991,10 @@ function foldNoncurrentVersionExpiration(rule) {
|
|
|
7991
7991
|
const legacy = rule["NoncurrentVersionExpirationInDays"];
|
|
7992
7992
|
if (legacy === void 0) return rule;
|
|
7993
7993
|
const declaredObject = rule["NoncurrentVersionExpiration"];
|
|
7994
|
-
if (declaredObject !== void 0 && !isPlainObject$
|
|
7994
|
+
if (declaredObject !== void 0 && !isPlainObject$2(declaredObject)) return rule;
|
|
7995
7995
|
const out = { ...rule };
|
|
7996
7996
|
delete out["NoncurrentVersionExpirationInDays"];
|
|
7997
|
-
if (isPlainObject$
|
|
7997
|
+
if (isPlainObject$2(declaredObject)) return out;
|
|
7998
7998
|
const days = coerceCfnNumber(legacy);
|
|
7999
7999
|
if (days === void 0) return rule;
|
|
8000
8000
|
out["NoncurrentVersionExpiration"] = { NoncurrentDays: days };
|
|
@@ -8089,10 +8089,10 @@ function foldLifecycleScope(rule, useFilterForm) {
|
|
|
8089
8089
|
* @returns `config` BY IDENTITY when the declared bag IS what goes on the wire.
|
|
8090
8090
|
*/
|
|
8091
8091
|
function effectiveLifecycleConfiguration(config) {
|
|
8092
|
-
if (!isPlainObject$
|
|
8092
|
+
if (!isPlainObject$2(config)) return config;
|
|
8093
8093
|
const rules = config["Rules"];
|
|
8094
8094
|
if (!Array.isArray(rules)) return config;
|
|
8095
|
-
const objectRules = rules.filter(isPlainObject$
|
|
8095
|
+
const objectRules = rules.filter(isPlainObject$2);
|
|
8096
8096
|
if (objectRules.length !== rules.length || objectRules.length === 0) return config;
|
|
8097
8097
|
if (objectRules.some(lifecycleRuleRefused)) return config;
|
|
8098
8098
|
const useFilterForm = lifecycleUsesFilterForm(objectRules);
|
|
@@ -8178,7 +8178,7 @@ function canonicalizeItemList(properties, key, fold) {
|
|
|
8178
8178
|
if (!Array.isArray(list)) return properties;
|
|
8179
8179
|
let changed = false;
|
|
8180
8180
|
const mapped = list.map((element) => {
|
|
8181
|
-
if (!isPlainObject$
|
|
8181
|
+
if (!isPlainObject$2(element)) return element;
|
|
8182
8182
|
const next = fold(element);
|
|
8183
8183
|
if (next !== element) changed = true;
|
|
8184
8184
|
return next;
|
|
@@ -8198,7 +8198,7 @@ function describeValue$1(value, maskSecrets = (text) => text) {
|
|
|
8198
8198
|
if (value === null) return "null";
|
|
8199
8199
|
if (Array.isArray(value)) return "an array";
|
|
8200
8200
|
if (typeof value === "string") return `a string ${JSON.stringify(maskDeep(value, maskSecrets))}`;
|
|
8201
|
-
if (isPlainObject$
|
|
8201
|
+
if (isPlainObject$2(value)) return `an object with keys [${Object.keys(value).map((key) => maskSecrets(key)).join(", ")}]`;
|
|
8202
8202
|
return `a ${typeof value} (${maskSecrets(String(value))})`;
|
|
8203
8203
|
}
|
|
8204
8204
|
/**
|
|
@@ -8223,8 +8223,8 @@ function coerceCfnNumber(value) {
|
|
|
8223
8223
|
* StorageClass collision, and say so rather than dropping in silence.
|
|
8224
8224
|
*/
|
|
8225
8225
|
function mergeLegacySingular(plural, singular, onCollision) {
|
|
8226
|
-
const out = (Array.isArray(plural) ? plural : []).filter(isPlainObject$
|
|
8227
|
-
if (!isPlainObject$
|
|
8226
|
+
const out = (Array.isArray(plural) ? plural : []).filter(isPlainObject$2);
|
|
8227
|
+
if (!isPlainObject$2(singular)) return out;
|
|
8228
8228
|
const sc = singular["StorageClass"];
|
|
8229
8229
|
if (typeof sc === "string" && out.some((e) => e["StorageClass"] === sc)) {
|
|
8230
8230
|
onCollision(sc);
|
|
@@ -8722,7 +8722,7 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
8722
8722
|
if (!(exp?.["Days"] !== void 0 || exp?.["Date"] !== void 0)) sdkRule.Expiration = { ExpiredObjectDeleteMarker: true };
|
|
8723
8723
|
else this.logger.warn(`Lifecycle rule '${rule["Id"] ?? "<unnamed>"}' on ${bucketName} sets ExpiredObjectDeleteMarker alongside an expiration Days/Date; S3 forbids combining them, so the delete-marker cleanup was not applied.`);
|
|
8724
8724
|
}
|
|
8725
|
-
const nve = isPlainObject$
|
|
8725
|
+
const nve = isPlainObject$2(rule["NoncurrentVersionExpiration"]) ? rule["NoncurrentVersionExpiration"] : void 0;
|
|
8726
8726
|
const legacyNveDays = rule["NoncurrentVersionExpirationInDays"];
|
|
8727
8727
|
if (nve) sdkRule.NoncurrentVersionExpiration = {
|
|
8728
8728
|
NoncurrentDays: nve["NoncurrentDays"],
|
|
@@ -8747,7 +8747,7 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
8747
8747
|
const singularTransition = rule["Transition"];
|
|
8748
8748
|
const allTransitions = mergeLegacySingular(transitions, singularTransition, (sc) => this.logger.warn(`Lifecycle rule '${rule["Id"] ?? "<unnamed>"}' on ${bucketName} declares both Transitions and the legacy Transition for storage class ${sc}; S3 rejects duplicates, so the legacy singular was ignored.`));
|
|
8749
8749
|
if (allTransitions.length > 0) sdkRule.Transitions = allTransitions.map(toSdkTransition);
|
|
8750
|
-
const abort = isPlainObject$
|
|
8750
|
+
const abort = isPlainObject$2(rule["AbortIncompleteMultipartUpload"]) ? rule["AbortIncompleteMultipartUpload"] : void 0;
|
|
8751
8751
|
if (abort) sdkRule.AbortIncompleteMultipartUpload = { DaysAfterInitiation: abort["DaysAfterInitiation"] };
|
|
8752
8752
|
const { prefix, tagFilters, sizeGt, sizeLt } = lifecycleRuleScope(rule);
|
|
8753
8753
|
const hasTags = tagFilters != null && tagFilters.length > 0;
|
|
@@ -9081,10 +9081,10 @@ var S3BucketProvider = class S3BucketProvider {
|
|
|
9081
9081
|
throw new Error(message);
|
|
9082
9082
|
};
|
|
9083
9083
|
if (dest === void 0 || dest === null) return;
|
|
9084
|
-
if (!isPlainObject$
|
|
9084
|
+
if (!isPlainObject$2(dest)) return drop(`${destinationPath} must be an object (got ${describeValue$1(dest, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
|
|
9085
9085
|
const bag = isFlattenedDestination(dest) ? dest : dest["S3BucketDestination"];
|
|
9086
9086
|
if (bag === void 0 || bag === null) return drop(`${destinationPath} carries neither a bucket (BucketArn / Bucket) nor a nested S3BucketDestination object (got ${describeValue$1(dest, maskSecrets)}) — the destination would otherwise be dropped from the request with no error`);
|
|
9087
|
-
if (!isPlainObject$
|
|
9087
|
+
if (!isPlainObject$2(bag)) return drop(`${nestedPath} must be an object (got ${describeValue$1(bag, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
|
|
9088
9088
|
if (!bag["BucketArn"] && !bag["Bucket"]) return drop(`${bag === dest ? destinationPath : nestedPath} has no destination bucket (BucketArn / Bucket) (got ${describeValue$1(bag, maskSecrets)}) — the request would be rejected by S3, or silently carry no destination`);
|
|
9089
9089
|
return bag;
|
|
9090
9090
|
}
|
|
@@ -17153,6 +17153,301 @@ function reverseMapIndexContributorInsights(status, mode, declaredBlock) {
|
|
|
17153
17153
|
return block;
|
|
17154
17154
|
}
|
|
17155
17155
|
|
|
17156
|
+
//#endregion
|
|
17157
|
+
//#region src/provisioning/dynamodb-stream-members.ts
|
|
17158
|
+
/**
|
|
17159
|
+
* The two `AWS::DynamoDB::Table` `StreamSpecification` members that are NOT
|
|
17160
|
+
* members of the SDK's `StreamSpecification`: `ResourcePolicy`
|
|
17161
|
+
* (`{ PolicyDocument }`, the resource-based policy of the STREAM) and `Tags`
|
|
17162
|
+
* (issue [#3458](https://github.com/go-to-k/cdkd/issues/3458)).
|
|
17163
|
+
*
|
|
17164
|
+
* `CreateTable` / `UpdateTable` take only `{ StreamEnabled, StreamViewType }`,
|
|
17165
|
+
* so a block carrying either member deployed green with neither applied — a
|
|
17166
|
+
* template granting access to a table's stream left the stream with no policy.
|
|
17167
|
+
* CloudFormation applies both against the STREAM arn (`LatestStreamArn`),
|
|
17168
|
+
* re-applies both after a `StreamViewType` change (which mints a NEW arn), and
|
|
17169
|
+
* REMOVES both when the member leaves a block whose stream stays (measured on
|
|
17170
|
+
* real CloudFormation; the measurements are on the issue).
|
|
17171
|
+
*
|
|
17172
|
+
* Pure, so the write plan, the create-time refusal and the drift read-back
|
|
17173
|
+
* gate cannot answer "does this block declare the member" differently.
|
|
17174
|
+
*/
|
|
17175
|
+
/** The CFn property holding both members. */
|
|
17176
|
+
const STREAM_SPECIFICATION_KEY = "StreamSpecification";
|
|
17177
|
+
/** The stream's resource-based policy member. */
|
|
17178
|
+
const STREAM_POLICY_KEY = "ResourcePolicy";
|
|
17179
|
+
/** The stream's tag-list member. */
|
|
17180
|
+
const STREAM_TAGS_KEY = "Tags";
|
|
17181
|
+
const POLICY_PATH = `${STREAM_SPECIFICATION_KEY}.${STREAM_POLICY_KEY}`;
|
|
17182
|
+
const TAGS_PATH = `${STREAM_SPECIFICATION_KEY}.${STREAM_TAGS_KEY}`;
|
|
17183
|
+
function isPlainObject$1(value) {
|
|
17184
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17185
|
+
}
|
|
17186
|
+
/** The message a `config-shape` guard would throw, or `undefined`. */
|
|
17187
|
+
function refusalOf(guard) {
|
|
17188
|
+
try {
|
|
17189
|
+
guard();
|
|
17190
|
+
return;
|
|
17191
|
+
} catch (error) {
|
|
17192
|
+
return describeAwsFailure(error).detail;
|
|
17193
|
+
}
|
|
17194
|
+
}
|
|
17195
|
+
/** The members of a `StreamSpecification` block, or none for a non-object. */
|
|
17196
|
+
function blockMembers(block) {
|
|
17197
|
+
return isPlainObject$1(block) ? block : {};
|
|
17198
|
+
}
|
|
17199
|
+
/**
|
|
17200
|
+
* Read `StreamSpecification.ResourcePolicy`.
|
|
17201
|
+
*
|
|
17202
|
+
* The CFn shape is `{ PolicyDocument: <object | JSON string> }` and the
|
|
17203
|
+
* document is REQUIRED, so a block without one is unreadable rather than "no
|
|
17204
|
+
* policy": reading it as absent would DELETE the live policy on an update.
|
|
17205
|
+
*/
|
|
17206
|
+
function readStreamPolicy(block) {
|
|
17207
|
+
const value = blockMembers(block)[STREAM_POLICY_KEY];
|
|
17208
|
+
if (value === void 0 || value === null) return { kind: "absent" };
|
|
17209
|
+
const containerRefusal = refusalOf(() => requireConfigObject(value, POLICY_PATH));
|
|
17210
|
+
if (containerRefusal !== void 0) return {
|
|
17211
|
+
kind: "unusable",
|
|
17212
|
+
reason: containerRefusal
|
|
17213
|
+
};
|
|
17214
|
+
const documentPath = `${POLICY_PATH}.PolicyDocument`;
|
|
17215
|
+
const document = value["PolicyDocument"];
|
|
17216
|
+
if (typeof document === "string") return document.trim() === "" ? {
|
|
17217
|
+
kind: "unusable",
|
|
17218
|
+
reason: `${documentPath} must not be a blank string`
|
|
17219
|
+
} : {
|
|
17220
|
+
kind: "usable",
|
|
17221
|
+
document
|
|
17222
|
+
};
|
|
17223
|
+
if (document === void 0 || document === null) return {
|
|
17224
|
+
kind: "unusable",
|
|
17225
|
+
reason: `${documentPath} is required when ${POLICY_PATH} is declared`
|
|
17226
|
+
};
|
|
17227
|
+
const documentRefusal = refusalOf(() => requireConfigObject(document, documentPath));
|
|
17228
|
+
if (documentRefusal !== void 0) return {
|
|
17229
|
+
kind: "unusable",
|
|
17230
|
+
reason: documentRefusal
|
|
17231
|
+
};
|
|
17232
|
+
return {
|
|
17233
|
+
kind: "usable",
|
|
17234
|
+
document: JSON.stringify(document)
|
|
17235
|
+
};
|
|
17236
|
+
}
|
|
17237
|
+
/**
|
|
17238
|
+
* Read `StreamSpecification.Tags`.
|
|
17239
|
+
*
|
|
17240
|
+
* Refusals name an entry by POSITION, never by its key: the key is a RESOLVED
|
|
17241
|
+
* template value, and a copy embedded here would reach only the substring
|
|
17242
|
+
* mask, which ignores a value shorter than its minimum needle.
|
|
17243
|
+
*
|
|
17244
|
+
* ONE unreadable entry makes the whole list unusable. Filtering it out would
|
|
17245
|
+
* send a list nobody declared, and on an update would untag a key the
|
|
17246
|
+
* unreadable entry may well have named.
|
|
17247
|
+
*/
|
|
17248
|
+
function readStreamTags(block) {
|
|
17249
|
+
const value = blockMembers(block)[STREAM_TAGS_KEY];
|
|
17250
|
+
if (value === void 0 || value === null) return { kind: "absent" };
|
|
17251
|
+
if (!Array.isArray(value)) return {
|
|
17252
|
+
kind: "unusable",
|
|
17253
|
+
reason: `${TAGS_PATH} must be an array of {Key, Value} entries`
|
|
17254
|
+
};
|
|
17255
|
+
const tags = /* @__PURE__ */ new Map();
|
|
17256
|
+
for (const [position, entry] of value.entries()) {
|
|
17257
|
+
const entryPath = `${TAGS_PATH}[${position}]`;
|
|
17258
|
+
const refusal = refusalOf(() => requireConfigObject(entry, entryPath)) ?? configStringRefusal(entry, "Key", "required", entryPath) ?? configStringRefusal(entry, "Value", "", entryPath);
|
|
17259
|
+
if (refusal !== void 0) return {
|
|
17260
|
+
kind: "unusable",
|
|
17261
|
+
reason: refusal
|
|
17262
|
+
};
|
|
17263
|
+
const { Key: key, Value: tagValue } = entry;
|
|
17264
|
+
if (typeof key !== "string") return {
|
|
17265
|
+
kind: "unusable",
|
|
17266
|
+
reason: `${entryPath}.Key is required`
|
|
17267
|
+
};
|
|
17268
|
+
tags.set(key, typeof tagValue === "string" ? tagValue : "");
|
|
17269
|
+
}
|
|
17270
|
+
return {
|
|
17271
|
+
kind: "usable",
|
|
17272
|
+
tags
|
|
17273
|
+
};
|
|
17274
|
+
}
|
|
17275
|
+
/**
|
|
17276
|
+
* Every refusal a CREATE must raise before `CreateTable` goes out. A create has
|
|
17277
|
+
* no live setting to leave alone, so an unreadable member there is a template
|
|
17278
|
+
* error, not a skip — and refusing after the table exists would create it only
|
|
17279
|
+
* to delete it again.
|
|
17280
|
+
*/
|
|
17281
|
+
function streamMemberRefusals(block) {
|
|
17282
|
+
const refusals = [];
|
|
17283
|
+
const policy = readStreamPolicy(block);
|
|
17284
|
+
if (policy.kind === "unusable") refusals.push(policy.reason);
|
|
17285
|
+
const tags = readStreamTags(block);
|
|
17286
|
+
if (tags.kind === "unusable") refusals.push(tags.reason);
|
|
17287
|
+
return refusals;
|
|
17288
|
+
}
|
|
17289
|
+
/**
|
|
17290
|
+
* The calls one update (or create) needs against the CURRENT stream arn.
|
|
17291
|
+
*
|
|
17292
|
+
* `freshStream` says the arn the calls will address was minted by THIS
|
|
17293
|
+
* operation — a create, an update-time enable, or the disable / re-enable a
|
|
17294
|
+
* `StreamViewType` change is applied as. A fresh stream holds nothing, so the
|
|
17295
|
+
* previous side is irrelevant there: every readable declared member is
|
|
17296
|
+
* applied, and nothing is deleted. Otherwise the stream is the one the previous
|
|
17297
|
+
* side was applied to, and the plan is the difference. `contentsUnknown` is
|
|
17298
|
+
* the third case: a live stream this operation did NOT mint and the previous
|
|
17299
|
+
* side does not describe (a re-run after a deploy that enabled the stream,
|
|
17300
|
+
* applied a policy, and failed before state recorded any of it). It may hold a
|
|
17301
|
+
* policy, so it is never planned as empty: a policy the desired side does not
|
|
17302
|
+
* declare is deleted, and a declared one is always put.
|
|
17303
|
+
*
|
|
17304
|
+
* On a stream the previous side describes:
|
|
17305
|
+
*
|
|
17306
|
+
* - a member `absent` with a DECLARED previous is a template REMOVAL and is
|
|
17307
|
+
* removed (CloudFormation does the same). Because the rule is symmetric in
|
|
17308
|
+
* its two sides, a rollback — which replays `update()` with the sides SWAPPED
|
|
17309
|
+
* — restores the old policy and tags through the same arms, a view-type
|
|
17310
|
+
* change included (the rollback mints yet another arn, and `freshStream`
|
|
17311
|
+
* applies the old members to it).
|
|
17312
|
+
* - an `unusable` member reaches this plan only from a STATE-borne caller (a
|
|
17313
|
+
* rollback replay, `drift --revert`): the provider refuses it on the template
|
|
17314
|
+
* path before any call. It reports through `onUnusable` and the live setting
|
|
17315
|
+
* is left alone rather than overwritten with a guess: no call on a stream
|
|
17316
|
+
* that stays, and the PREVIOUS member carried over onto a fresh one.
|
|
17317
|
+
* - an `unusable` PREVIOUS tag list cannot say which keys cdkd applied, so a
|
|
17318
|
+
* removal against it untags nothing.
|
|
17319
|
+
*
|
|
17320
|
+
* ORDER is part of the contract, because a resource policy is an access grant
|
|
17321
|
+
* and can condition on `aws:ResourceTag`: `deletePolicy` comes FIRST and
|
|
17322
|
+
* `putPolicy` LAST. A policy that CHANGES together with the tags is therefore
|
|
17323
|
+
* deleted before the first tag call and put back after the last, so neither
|
|
17324
|
+
* the old policy nor the new one is ever evaluated against the other side's
|
|
17325
|
+
* tag set — also when a call in between fails. The price is a window with NO
|
|
17326
|
+
* policy, which drops that policy's `Deny` statements along with its grants;
|
|
17327
|
+
* nothing here is atomic, and that window is the narrower of the two hazards.
|
|
17328
|
+
* An UNCHANGED policy stays in place across a tag-only change: both sides
|
|
17329
|
+
* declare it, and the two tag calls cannot be made one.
|
|
17330
|
+
*
|
|
17331
|
+
* The caller asks only while the DESIRED side has a stream: a disabled stream
|
|
17332
|
+
* is gone and takes both members with it, so nothing addresses its dead arn.
|
|
17333
|
+
*/
|
|
17334
|
+
function planStreamMemberOps(desiredBlock, previousBlock, options, onUnusable) {
|
|
17335
|
+
let desiredPolicy = readStreamPolicy(desiredBlock);
|
|
17336
|
+
let desiredTags = readStreamTags(desiredBlock);
|
|
17337
|
+
if (desiredPolicy.kind === "unusable") onUnusable(desiredPolicy.reason);
|
|
17338
|
+
if (desiredTags.kind === "unusable") onUnusable(desiredTags.reason);
|
|
17339
|
+
let previousPolicy = readStreamPolicy(previousBlock);
|
|
17340
|
+
let previousTags = readStreamTags(previousBlock);
|
|
17341
|
+
if (options.freshStream) {
|
|
17342
|
+
if (desiredPolicy.kind === "unusable" && previousPolicy.kind === "usable") desiredPolicy = previousPolicy;
|
|
17343
|
+
if (desiredTags.kind === "unusable" && previousTags.kind === "usable") desiredTags = previousTags;
|
|
17344
|
+
previousPolicy = { kind: "absent" };
|
|
17345
|
+
previousTags = { kind: "absent" };
|
|
17346
|
+
} else if (options.contentsUnknown === true) {
|
|
17347
|
+
previousPolicy = {
|
|
17348
|
+
kind: "unusable",
|
|
17349
|
+
reason: ""
|
|
17350
|
+
};
|
|
17351
|
+
previousTags = { kind: "absent" };
|
|
17352
|
+
}
|
|
17353
|
+
const tagOps = [];
|
|
17354
|
+
if (desiredTags.kind !== "unusable") {
|
|
17355
|
+
const desired = desiredTags.kind === "usable" ? desiredTags.tags : /* @__PURE__ */ new Map();
|
|
17356
|
+
const previous = previousTags.kind === "usable" ? previousTags.tags : /* @__PURE__ */ new Map();
|
|
17357
|
+
const keysToRemove = [...previous.keys()].filter((key) => !desired.has(key));
|
|
17358
|
+
if (keysToRemove.length > 0) tagOps.push({
|
|
17359
|
+
kind: "untag",
|
|
17360
|
+
keys: keysToRemove
|
|
17361
|
+
});
|
|
17362
|
+
const tagsToSet = [...desired].filter(([key, value]) => previous.get(key) !== value).map(([Key, Value]) => ({
|
|
17363
|
+
Key,
|
|
17364
|
+
Value
|
|
17365
|
+
}));
|
|
17366
|
+
if (tagsToSet.length > 0) tagOps.push({
|
|
17367
|
+
kind: "tag",
|
|
17368
|
+
tags: tagsToSet
|
|
17369
|
+
});
|
|
17370
|
+
}
|
|
17371
|
+
const policyPut = desiredPolicy.kind === "usable" && !(previousPolicy.kind === "usable" && samePolicyDocument(previousPolicy.document, desiredPolicy.document));
|
|
17372
|
+
const policyDelete = previousPolicy.kind !== "absent" && (desiredPolicy.kind === "absent" || policyPut && tagOps.length > 0);
|
|
17373
|
+
const ops = [];
|
|
17374
|
+
if (policyDelete) ops.push({ kind: "deletePolicy" });
|
|
17375
|
+
ops.push(...tagOps);
|
|
17376
|
+
if (policyPut && desiredPolicy.kind === "usable") ops.push({
|
|
17377
|
+
kind: "putPolicy",
|
|
17378
|
+
document: desiredPolicy.document
|
|
17379
|
+
});
|
|
17380
|
+
return ops;
|
|
17381
|
+
}
|
|
17382
|
+
/** Two policy JSON strings saying the same thing, whatever their key order. */
|
|
17383
|
+
function samePolicyDocument(left, right) {
|
|
17384
|
+
if (left === right) return true;
|
|
17385
|
+
try {
|
|
17386
|
+
return canonicalJson$1(JSON.parse(left)) === canonicalJson$1(JSON.parse(right));
|
|
17387
|
+
} catch {
|
|
17388
|
+
return false;
|
|
17389
|
+
}
|
|
17390
|
+
}
|
|
17391
|
+
/** The drift read-back gates: one API call each, so each is asked separately. */
|
|
17392
|
+
function streamDeclaresPolicy(desiredBlock) {
|
|
17393
|
+
return readStreamPolicy(desiredBlock).kind === "usable";
|
|
17394
|
+
}
|
|
17395
|
+
/** See {@link streamDeclaresPolicy}. */
|
|
17396
|
+
function streamDeclaresTags(desiredBlock) {
|
|
17397
|
+
return readStreamTags(desiredBlock).kind === "usable";
|
|
17398
|
+
}
|
|
17399
|
+
/**
|
|
17400
|
+
* Re-shape a `GetResourcePolicy` answer into the CFn member, in the DECLARED
|
|
17401
|
+
* spelling of `PolicyDocument`: a template may carry the document as an object
|
|
17402
|
+
* or as a JSON string, and the comparator does not convert between them.
|
|
17403
|
+
* `undefined` when AWS holds no policy.
|
|
17404
|
+
*/
|
|
17405
|
+
function reverseMapStreamPolicy(livePolicy, desiredBlock) {
|
|
17406
|
+
if (livePolicy === void 0 || livePolicy === "") return void 0;
|
|
17407
|
+
const declared = blockMembers(blockMembers(desiredBlock)[STREAM_POLICY_KEY])["PolicyDocument"];
|
|
17408
|
+
let parsed;
|
|
17409
|
+
try {
|
|
17410
|
+
parsed = JSON.parse(livePolicy);
|
|
17411
|
+
} catch {
|
|
17412
|
+
return { PolicyDocument: livePolicy };
|
|
17413
|
+
}
|
|
17414
|
+
if (typeof declared !== "string") return { PolicyDocument: parsed };
|
|
17415
|
+
let declaredParsed;
|
|
17416
|
+
try {
|
|
17417
|
+
declaredParsed = JSON.parse(declared);
|
|
17418
|
+
} catch {
|
|
17419
|
+
return { PolicyDocument: livePolicy };
|
|
17420
|
+
}
|
|
17421
|
+
return { PolicyDocument: canonicalJson$1(declaredParsed) === canonicalJson$1(parsed) ? declared : livePolicy };
|
|
17422
|
+
}
|
|
17423
|
+
/**
|
|
17424
|
+
* Does the live policy say what the DECLARED one says? The read-back's
|
|
17425
|
+
* "settled" test: `GetResourcePolicy` is eventually consistent and can answer
|
|
17426
|
+
* with the PREVIOUS policy right after a put, so an answer that differs from
|
|
17427
|
+
* the declaration is re-asked before it is believed.
|
|
17428
|
+
*/
|
|
17429
|
+
function streamPolicyMatchesDeclared(livePolicy, desiredBlock) {
|
|
17430
|
+
const declared = readStreamPolicy(desiredBlock);
|
|
17431
|
+
if (declared.kind !== "usable" || livePolicy === void 0) return false;
|
|
17432
|
+
try {
|
|
17433
|
+
return canonicalJson$1(JSON.parse(livePolicy)) === canonicalJson$1(JSON.parse(declared.document));
|
|
17434
|
+
} catch {
|
|
17435
|
+
return false;
|
|
17436
|
+
}
|
|
17437
|
+
}
|
|
17438
|
+
/** The tag twin of {@link streamPolicyMatchesDeclared}, over USER tags. */
|
|
17439
|
+
function streamTagsMatchDeclared(liveTags, desiredBlock) {
|
|
17440
|
+
const declared = readStreamTags(desiredBlock);
|
|
17441
|
+
if (declared.kind !== "usable" || liveTags.length !== declared.tags.size) return false;
|
|
17442
|
+
return liveTags.every((tag) => declared.tags.get(tag.Key) === tag.Value);
|
|
17443
|
+
}
|
|
17444
|
+
/** A key-order-independent serialization. */
|
|
17445
|
+
function canonicalJson$1(value) {
|
|
17446
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson$1).join(",")}]`;
|
|
17447
|
+
if (isPlainObject$1(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson$1(value[key])}`).join(",")}}`;
|
|
17448
|
+
return JSON.stringify(value) ?? "null";
|
|
17449
|
+
}
|
|
17450
|
+
|
|
17156
17451
|
//#endregion
|
|
17157
17452
|
//#region src/provisioning/dynamodb-index-busy-delete.ts
|
|
17158
17453
|
/**
|
|
@@ -19249,6 +19544,8 @@ var DynamoDBTableProvider = class {
|
|
|
19249
19544
|
if (context?.replayingState !== true) {
|
|
19250
19545
|
const [refusal] = contributorInsightsRefusals(properties["ContributorInsightsSpecification"], properties["GlobalSecondaryIndexes"]);
|
|
19251
19546
|
if (refusal !== void 0) throw new Error(`${refusal}. Fix the template value`);
|
|
19547
|
+
const [streamRefusal] = streamMemberRefusals(properties["StreamSpecification"]);
|
|
19548
|
+
if (streamRefusal !== void 0) throw new Error(`${streamRefusal}. Fix the template value`);
|
|
19252
19549
|
}
|
|
19253
19550
|
const createParams = {
|
|
19254
19551
|
TableName: tableName,
|
|
@@ -19293,6 +19590,7 @@ var DynamoDBTableProvider = class {
|
|
|
19293
19590
|
await this.applyKinesisStreamingDestination(tableName, properties["KinesisStreamSpecification"], void 0, maskSecrets);
|
|
19294
19591
|
await this.applyContributorInsights(tableName, properties["ContributorInsightsSpecification"], void 0, maskSecrets);
|
|
19295
19592
|
await this.applyIndexContributorInsights(tableName, properties["GlobalSecondaryIndexes"], void 0, maskSecrets);
|
|
19593
|
+
await this.applyStreamMembers(logicalId, resourceType, tableName, tableInfo.streamArn, properties["StreamSpecification"], void 0, true, maskSecrets);
|
|
19296
19594
|
this.logger.debug(`Successfully created DynamoDB table ${logicalId}: ${tableName}`);
|
|
19297
19595
|
return {
|
|
19298
19596
|
physicalId: tableName,
|
|
@@ -19328,6 +19626,10 @@ var DynamoDBTableProvider = class {
|
|
|
19328
19626
|
const debug = (message) => this.logger.debug(maskSecrets(message));
|
|
19329
19627
|
debug(`Updating DynamoDB table ${logicalId}: ${maskSecrets(physicalId)}`);
|
|
19330
19628
|
try {
|
|
19629
|
+
if (context?.replayingState !== true && context?.desiredFromAwsReadback !== true && JSON.stringify(properties["StreamSpecification"]) !== JSON.stringify(previousProperties["StreamSpecification"])) {
|
|
19630
|
+
const [streamRefusal] = streamMemberRefusals(properties["StreamSpecification"]);
|
|
19631
|
+
if (streamRefusal !== void 0) throw new ProvisioningError(`${streamRefusal}. Nothing was applied to DynamoDB table ${logicalId}; fix the template value`, resourceType, logicalId, physicalId);
|
|
19632
|
+
}
|
|
19331
19633
|
const table = (await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: physicalId }))).Table;
|
|
19332
19634
|
let latestStreamArn = table?.LatestStreamArn;
|
|
19333
19635
|
if (table?.TableArn) await this.applyTagDiff(table.TableArn, previousProperties["Tags"], properties["Tags"]);
|
|
@@ -19479,6 +19781,10 @@ var DynamoDBTableProvider = class {
|
|
|
19479
19781
|
if (JSON.stringify(properties["StreamSpecification"]) !== JSON.stringify(previousProperties["StreamSpecification"])) {
|
|
19480
19782
|
const newViewType = this.extractStreamViewType(properties["StreamSpecification"]);
|
|
19481
19783
|
const prevViewType = this.extractStreamViewType(previousProperties["StreamSpecification"]);
|
|
19784
|
+
const liveStream = table?.StreamSpecification;
|
|
19785
|
+
const preUpdateStreamArn = table?.LatestStreamArn;
|
|
19786
|
+
let mintedStream = false;
|
|
19787
|
+
let contentsUnknown = false;
|
|
19482
19788
|
if (newViewType && prevViewType && newViewType !== prevViewType) {
|
|
19483
19789
|
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
19484
19790
|
TableName: physicalId,
|
|
@@ -19493,9 +19799,11 @@ var DynamoDBTableProvider = class {
|
|
|
19493
19799
|
}
|
|
19494
19800
|
}));
|
|
19495
19801
|
await this.waitForTableActiveAfterUpdate(physicalId);
|
|
19496
|
-
latestStreamArn = reenable.TableDescription?.LatestStreamArn
|
|
19802
|
+
latestStreamArn = await this.resolveMintedStreamArn(physicalId, reenable.TableDescription?.LatestStreamArn, preUpdateStreamArn);
|
|
19803
|
+
mintedStream = true;
|
|
19497
19804
|
this.logger.debug(`Changed StreamViewType on DynamoDB table ${physicalId} to ${newViewType}`);
|
|
19498
|
-
} else if (newViewType)
|
|
19805
|
+
} else if (newViewType && liveStream?.StreamEnabled === true && liveStream.StreamViewType === newViewType) contentsUnknown = prevViewType !== newViewType;
|
|
19806
|
+
else if (newViewType) {
|
|
19499
19807
|
const enable = await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
19500
19808
|
TableName: physicalId,
|
|
19501
19809
|
StreamSpecification: {
|
|
@@ -19504,7 +19812,8 @@ var DynamoDBTableProvider = class {
|
|
|
19504
19812
|
}
|
|
19505
19813
|
}));
|
|
19506
19814
|
await this.waitForTableActiveAfterUpdate(physicalId);
|
|
19507
|
-
latestStreamArn = enable.TableDescription?.LatestStreamArn
|
|
19815
|
+
latestStreamArn = await this.resolveMintedStreamArn(physicalId, enable.TableDescription?.LatestStreamArn, preUpdateStreamArn);
|
|
19816
|
+
mintedStream = true;
|
|
19508
19817
|
this.logger.debug(`Enabled DynamoDB Stream on table ${physicalId} (${newViewType})`);
|
|
19509
19818
|
} else {
|
|
19510
19819
|
await this.dynamoDBClient.send(new UpdateTableCommand({
|
|
@@ -19515,6 +19824,7 @@ var DynamoDBTableProvider = class {
|
|
|
19515
19824
|
latestStreamArn = void 0;
|
|
19516
19825
|
this.logger.debug(`Disabled DynamoDB Stream on table ${physicalId}`);
|
|
19517
19826
|
}
|
|
19827
|
+
if (newViewType) await this.applyStreamMembers(logicalId, resourceType, physicalId, latestStreamArn, properties["StreamSpecification"], previousProperties["StreamSpecification"], mintedStream, maskSecrets, mintedStream ? preUpdateStreamArn : void 0, contentsUnknown);
|
|
19518
19828
|
}
|
|
19519
19829
|
if (JSON.stringify(properties["GlobalSecondaryIndexes"]) !== JSON.stringify(previousProperties["GlobalSecondaryIndexes"])) await this.applyGsiUpdates(physicalId, resourceType, logicalId, previousProperties["GlobalSecondaryIndexes"], properties["GlobalSecondaryIndexes"], properties["AttributeDefinitions"], gsiHandledByBillingFlip, currentLiveIndexNames, currentLiveIndexByName, liveCapacityComparable, effectiveBillingMode === "PAY_PER_REQUEST", maskSecrets);
|
|
19520
19830
|
if (JSON.stringify(properties["PointInTimeRecoverySpecification"]) !== JSON.stringify(previousProperties["PointInTimeRecoverySpecification"])) await this.applyPointInTimeRecovery(physicalId, properties["PointInTimeRecoverySpecification"], previousProperties["PointInTimeRecoverySpecification"], maskSecrets);
|
|
@@ -19745,15 +20055,16 @@ var DynamoDBTableProvider = class {
|
|
|
19745
20055
|
* Backoff: ~2s,4s,8s,16s,30s,30s... bounded to ~2min total, which comfortably
|
|
19746
20056
|
* covers the few-second PITR-enable window.
|
|
19747
20057
|
*/
|
|
19748
|
-
async retryOnTransientControlPlane(op, label, maxAttempts = 8) {
|
|
20058
|
+
async retryOnTransientControlPlane(op, label, maxAttempts = 8, options = {}) {
|
|
19749
20059
|
let delayMs = 2e3;
|
|
19750
20060
|
for (let attempt = 1;; attempt++) try {
|
|
19751
20061
|
return await op();
|
|
19752
20062
|
} catch (error) {
|
|
19753
20063
|
const msg = describeAwsFailure(error).detail;
|
|
19754
20064
|
const name = error instanceof Error ? error.name : "";
|
|
19755
|
-
if (!(/being enabled|being updated|please retry later|backups are being/i.test(msg) || name === "ResourceInUseException" || name === "LimitExceededException") || attempt >= maxAttempts) throw error;
|
|
19756
|
-
|
|
20065
|
+
if (!(/being enabled|being updated|please retry later|backups are being/i.test(msg) || name === "ResourceInUseException" || name === "LimitExceededException" || options.alsoTransient?.(error) === true) || attempt >= maxAttempts) throw error;
|
|
20066
|
+
const retryLine = `Transient error on "${label}" (attempt ${attempt}/${maxAttempts}): ${msg} — retrying in ${delayMs}ms`;
|
|
20067
|
+
this.logger.debug(options.maskSecrets ? options.maskSecrets(retryLine) : retryLine);
|
|
19757
20068
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
19758
20069
|
delayMs = Math.min(delayMs * 2, 3e4);
|
|
19759
20070
|
}
|
|
@@ -19929,6 +20240,90 @@ var DynamoDBTableProvider = class {
|
|
|
19929
20240
|
return typeof viewType === "string" && viewType.length > 0 ? viewType : void 0;
|
|
19930
20241
|
}
|
|
19931
20242
|
/**
|
|
20243
|
+
* Apply `StreamSpecification.ResourcePolicy` / `.Tags` (issue
|
|
20244
|
+
* [#3458](https://github.com/go-to-k/cdkd/issues/3458)) against the STREAM
|
|
20245
|
+
* arn. Neither is a member of the SDK's `StreamSpecification`, so until this
|
|
20246
|
+
* existed both were dropped on `CreateTable` / `UpdateTable`: the deploy was
|
|
20247
|
+
* green and the stream had no policy.
|
|
20248
|
+
*
|
|
20249
|
+
* `streamArn` must be the table's CURRENT `LatestStreamArn` — after a
|
|
20250
|
+
* `StreamViewType` change that is the arn the re-enable minted, never the
|
|
20251
|
+
* recorded one. What to call, in which order, and the removal / rollback /
|
|
20252
|
+
* unreadable-member rules live on {@link planStreamMemberOps}.
|
|
20253
|
+
*
|
|
20254
|
+
* A plan with calls to make and no arn to make them against — or, when this
|
|
20255
|
+
* operation minted the stream, only the `deadStreamArn` read before it —
|
|
20256
|
+
* fails LOUDLY: a
|
|
20257
|
+
* silent skip would record the policy in state as applied, the next deploy
|
|
20258
|
+
* would see no difference, and the grant would stay missing for good.
|
|
20259
|
+
*
|
|
20260
|
+
* The policy text is never logged: it names principals and can carry a
|
|
20261
|
+
* resolved secret in a condition value.
|
|
20262
|
+
*/
|
|
20263
|
+
async applyStreamMembers(logicalId, resourceType, tableName, streamArn, desiredBlock, previousBlock, freshStream, maskSecrets, deadStreamArn, contentsUnknown = false) {
|
|
20264
|
+
const warn = (message) => this.logger.warn(maskSecrets(message));
|
|
20265
|
+
const debug = (message) => this.logger.debug(maskSecrets(message));
|
|
20266
|
+
const ops = planStreamMemberOps(desiredBlock, previousBlock, {
|
|
20267
|
+
freshStream,
|
|
20268
|
+
contentsUnknown
|
|
20269
|
+
}, (reason) => warn(`DynamoDB table ${tableName}: ${reason}, so that member of the stream was left as it is. The value came from a recorded state, which a template edit cannot reach.`));
|
|
20270
|
+
if (ops.length === 0) return;
|
|
20271
|
+
if (streamArn === void 0 || streamArn === "" || streamArn === deadStreamArn) throw new ProvisioningError(`Cannot apply StreamSpecification.ResourcePolicy / Tags for DynamoDB table ${logicalId}: DescribeTable returned no ${deadStreamArn === void 0 ? "" : "new "}LatestStreamArn`, resourceType, logicalId, tableName);
|
|
20272
|
+
for (const op of ops) {
|
|
20273
|
+
await this.sendStreamMemberOp(streamArn, op, freshStream, maskSecrets);
|
|
20274
|
+
debug(`Applied ${op.kind} to the stream of DynamoDB table ${tableName}`);
|
|
20275
|
+
}
|
|
20276
|
+
}
|
|
20277
|
+
/** Issue ONE {@link StreamMemberOp} against the stream arn. */
|
|
20278
|
+
async sendStreamMemberOp(streamArn, op, freshStream, maskSecrets) {
|
|
20279
|
+
const target = maskSecrets(streamArn);
|
|
20280
|
+
const retryOptions = {
|
|
20281
|
+
maskSecrets,
|
|
20282
|
+
...freshStream ? { alsoTransient: (error) => error instanceof ResourceNotFoundException$1 } : {}
|
|
20283
|
+
};
|
|
20284
|
+
const send = (call, label) => this.retryOnTransientControlPlane(call, label, void 0, retryOptions);
|
|
20285
|
+
switch (op.kind) {
|
|
20286
|
+
case "deletePolicy":
|
|
20287
|
+
try {
|
|
20288
|
+
await send(() => this.dynamoDBClient.send(new DeleteResourcePolicyCommand({ ResourceArn: streamArn })), `delete the stream ResourcePolicy of ${target}`);
|
|
20289
|
+
} catch (error) {
|
|
20290
|
+
if (!(error instanceof PolicyNotFoundException)) throw error;
|
|
20291
|
+
}
|
|
20292
|
+
return;
|
|
20293
|
+
case "untag":
|
|
20294
|
+
await send(() => this.dynamoDBClient.send(new UntagResourceCommand$2({
|
|
20295
|
+
ResourceArn: streamArn,
|
|
20296
|
+
TagKeys: op.keys
|
|
20297
|
+
})), `untag the stream ${target}`);
|
|
20298
|
+
return;
|
|
20299
|
+
case "tag":
|
|
20300
|
+
await send(() => this.dynamoDBClient.send(new TagResourceCommand$2({
|
|
20301
|
+
ResourceArn: streamArn,
|
|
20302
|
+
Tags: op.tags
|
|
20303
|
+
})), `tag the stream ${target}`);
|
|
20304
|
+
return;
|
|
20305
|
+
case "putPolicy":
|
|
20306
|
+
await send(() => this.dynamoDBClient.send(new PutResourcePolicyCommand({
|
|
20307
|
+
ResourceArn: streamArn,
|
|
20308
|
+
Policy: op.document
|
|
20309
|
+
})), `put the stream ResourcePolicy of ${target}`);
|
|
20310
|
+
return;
|
|
20311
|
+
}
|
|
20312
|
+
}
|
|
20313
|
+
/**
|
|
20314
|
+
* The arn of the stream an enabling `UpdateTable` just minted.
|
|
20315
|
+
*
|
|
20316
|
+
* The arn that call echoes is believed only when it DIFFERS from the one read
|
|
20317
|
+
* before the update: DynamoDB keeps reporting a disabled stream's arn as
|
|
20318
|
+
* `LatestStreamArn`, so an echo equal to it (or no echo) is re-read from
|
|
20319
|
+
* `DescribeTable` after the ACTIVE wait. Shared by the enable and the
|
|
20320
|
+
* view-type arms — a table whose stream was disabled by an earlier deploy is
|
|
20321
|
+
* in the same state as one mid view-type change.
|
|
20322
|
+
*/
|
|
20323
|
+
async resolveMintedStreamArn(tableName, echoedArn, preUpdateArn) {
|
|
20324
|
+
return echoedArn !== void 0 && echoedArn !== preUpdateArn ? echoedArn : this.describeLatestStreamArn(tableName);
|
|
20325
|
+
}
|
|
20326
|
+
/**
|
|
19932
20327
|
* Fetch the table's current `LatestStreamArn` via DescribeTable. Used as a
|
|
19933
20328
|
* fallback when an UpdateTable that enabled a stream did not echo the ARN
|
|
19934
20329
|
* back in its TableDescription, so `Fn::GetAtt [Table, StreamArn]` still
|
|
@@ -21115,9 +21510,12 @@ var DynamoDBTableProvider = class {
|
|
|
21115
21510
|
* shape differences are wrapping:
|
|
21116
21511
|
* - BillingMode lives under `BillingModeSummary.BillingMode` in the API
|
|
21117
21512
|
* response, but the CFn property is a flat `BillingMode` string.
|
|
21118
|
-
* - StreamSpecification's CFn shape
|
|
21119
|
-
* API response carries `StreamEnabled`
|
|
21120
|
-
*
|
|
21513
|
+
* - StreamSpecification's CFn shape is `StreamViewType`, `ResourcePolicy`
|
|
21514
|
+
* and `Tags`; the API response carries `StreamEnabled` and
|
|
21515
|
+
* `StreamViewType` only. `StreamEnabled` is surfaced too, since the
|
|
21516
|
+
* comparator descends a `properties` baseline by the keys state holds.
|
|
21517
|
+
* `ResourcePolicy` / `Tags` (issue #3458) live on the STREAM arn and are
|
|
21518
|
+
* read from it, each only when the desired block declares that member.
|
|
21121
21519
|
* - GSI / LSI in the API response include `IndexStatus`, `Backfilling`,
|
|
21122
21520
|
* `ItemCount`, `IndexArn` and sizing fields cdkd never sets. They used to
|
|
21123
21521
|
* be forwarded VERBATIM under a comment claiming "the comparator filters
|
|
@@ -21173,10 +21571,14 @@ var DynamoDBTableProvider = class {
|
|
|
21173
21571
|
if (table.WarmThroughput.WriteUnitsPerSecond !== void 0) wt["WriteUnitsPerSecond"] = table.WarmThroughput.WriteUnitsPerSecond;
|
|
21174
21572
|
if (Object.keys(wt).length > 0) result["WarmThroughput"] = wt;
|
|
21175
21573
|
}
|
|
21176
|
-
if (table.StreamSpecification?.StreamEnabled && table.StreamSpecification.StreamViewType)
|
|
21177
|
-
|
|
21178
|
-
|
|
21179
|
-
|
|
21574
|
+
if (table.StreamSpecification?.StreamEnabled && table.StreamSpecification.StreamViewType) {
|
|
21575
|
+
const streamBlock = {
|
|
21576
|
+
StreamEnabled: true,
|
|
21577
|
+
StreamViewType: table.StreamSpecification.StreamViewType
|
|
21578
|
+
};
|
|
21579
|
+
result["StreamSpecification"] = streamBlock;
|
|
21580
|
+
await this.readStreamMembers(physicalId, table.LatestStreamArn, properties?.["StreamSpecification"], streamBlock);
|
|
21581
|
+
}
|
|
21180
21582
|
const bagInformative = desiredBagIsInformative(properties);
|
|
21181
21583
|
if (table.GlobalSecondaryIndexes && table.GlobalSecondaryIndexes.length > 0) {
|
|
21182
21584
|
const desiredByName = desiredIndexEntriesByName(properties?.["GlobalSecondaryIndexes"]);
|
|
@@ -21277,6 +21679,92 @@ var DynamoDBTableProvider = class {
|
|
|
21277
21679
|
}
|
|
21278
21680
|
}
|
|
21279
21681
|
/**
|
|
21682
|
+
* How long an eventually-consistent stream-member read that does NOT match
|
|
21683
|
+
* the declaration is re-asked before it is believed: one entry per extra
|
|
21684
|
+
* attempt.
|
|
21685
|
+
*/
|
|
21686
|
+
streamMemberRereadDelaysMs = [
|
|
21687
|
+
500,
|
|
21688
|
+
1e3,
|
|
21689
|
+
2e3
|
|
21690
|
+
];
|
|
21691
|
+
/** Run `read`, re-asking after each configured delay until `settled` accepts the answer. */
|
|
21692
|
+
async reAskUntilSettled(read, settled) {
|
|
21693
|
+
let answer = await read();
|
|
21694
|
+
for (const delayMs of this.streamMemberRereadDelaysMs) {
|
|
21695
|
+
if (settled(answer)) break;
|
|
21696
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
21697
|
+
answer = await read();
|
|
21698
|
+
}
|
|
21699
|
+
return answer;
|
|
21700
|
+
}
|
|
21701
|
+
/**
|
|
21702
|
+
* Read `StreamSpecification.ResourcePolicy` / `.Tags` (issue #3458) off the
|
|
21703
|
+
* table's CURRENT stream arn into `streamBlock`.
|
|
21704
|
+
*
|
|
21705
|
+
* One API call per member, so each is GATED on the desired block declaring a
|
|
21706
|
+
* member cdkd would send — the same reader the write plan runs. The gate is
|
|
21707
|
+
* also what keeps an undeclared member out of an `observedProperties`
|
|
21708
|
+
* baseline, which is compared by the UNION of both sides' keys.
|
|
21709
|
+
*
|
|
21710
|
+
* No policy (`PolicyNotFoundException`) and an empty user tag set emit
|
|
21711
|
+
* NOTHING: that is "absent" in the template's own spelling, which a
|
|
21712
|
+
* declared member then reports as drift (a declared EMPTY list is the one
|
|
21713
|
+
* exception, and reads back as the empty list it is). `aws:`-prefixed system tags are
|
|
21714
|
+
* dropped like the table-level ones, and the list needs no unordered path —
|
|
21715
|
+
* the comparator sorts every `{Key, ...}` list on both sides. Any other
|
|
21716
|
+
* failed read omits the member and says why at debug level, like the sibling
|
|
21717
|
+
* reads of this method.
|
|
21718
|
+
*
|
|
21719
|
+
* Both reads are EVENTUALLY consistent — right after a write they can answer
|
|
21720
|
+
* "absent" or with the PREVIOUS value (their command docs say so) — and the
|
|
21721
|
+
* deploy captures `observedProperties` right after the writes. So an answer
|
|
21722
|
+
* that does not match the DECLARED member is re-asked a few times before it
|
|
21723
|
+
* is believed, or a lagging read would freeze a baseline the settled stream
|
|
21724
|
+
* can never equal. A member that really drifted costs those few seconds per
|
|
21725
|
+
* drift run.
|
|
21726
|
+
*
|
|
21727
|
+
* `cdkd drift --revert` replays through `update()`, where the read-back is
|
|
21728
|
+
* the PREVIOUS side and {@link planStreamMemberOps} derives the restore.
|
|
21729
|
+
*/
|
|
21730
|
+
async readStreamMembers(tableName, streamArn, desiredBlock, streamBlock) {
|
|
21731
|
+
if (streamArn === void 0 || streamArn === "") return;
|
|
21732
|
+
if (streamDeclaresPolicy(desiredBlock)) try {
|
|
21733
|
+
const livePolicy = await this.reAskUntilSettled(async () => {
|
|
21734
|
+
try {
|
|
21735
|
+
return (await this.dynamoDBClient.send(new GetResourcePolicyCommand({ ResourceArn: streamArn }))).Policy;
|
|
21736
|
+
} catch (err) {
|
|
21737
|
+
if (err instanceof PolicyNotFoundException) return void 0;
|
|
21738
|
+
throw err;
|
|
21739
|
+
}
|
|
21740
|
+
}, (answer) => streamPolicyMatchesDeclared(answer, desiredBlock));
|
|
21741
|
+
const member = reverseMapStreamPolicy(livePolicy, desiredBlock);
|
|
21742
|
+
if (member !== void 0) streamBlock[STREAM_POLICY_KEY] = member;
|
|
21743
|
+
} catch (err) {
|
|
21744
|
+
this.logger.debug(`Could not read the stream ResourcePolicy for ${tableName}: ${describeAwsFailure(err).detail}`);
|
|
21745
|
+
}
|
|
21746
|
+
if (streamDeclaresTags(desiredBlock)) try {
|
|
21747
|
+
const listOnce = async () => {
|
|
21748
|
+
const liveTags = [];
|
|
21749
|
+
let nextToken;
|
|
21750
|
+
do {
|
|
21751
|
+
const response = await this.dynamoDBClient.send(new ListTagsOfResourceCommand({
|
|
21752
|
+
ResourceArn: streamArn,
|
|
21753
|
+
NextToken: nextToken
|
|
21754
|
+
}));
|
|
21755
|
+
liveTags.push(...response.Tags ?? []);
|
|
21756
|
+
nextToken = response.NextToken;
|
|
21757
|
+
} while (nextToken !== void 0 && nextToken !== "");
|
|
21758
|
+
return normalizeAwsTagsToCfn(liveTags);
|
|
21759
|
+
};
|
|
21760
|
+
const tags = await this.reAskUntilSettled(listOnce, (answer) => streamTagsMatchDeclared(answer, desiredBlock));
|
|
21761
|
+
const declared = desiredBlock[STREAM_TAGS_KEY];
|
|
21762
|
+
if (tags.length > 0 || Array.isArray(declared) && declared.length === 0) streamBlock[STREAM_TAGS_KEY] = tags;
|
|
21763
|
+
} catch (err) {
|
|
21764
|
+
this.logger.debug(`Could not read the stream Tags for ${tableName}: ${describeAwsFailure(err).detail}`);
|
|
21765
|
+
}
|
|
21766
|
+
}
|
|
21767
|
+
/**
|
|
21280
21768
|
* Adopt an existing DynamoDB table into cdkd state.
|
|
21281
21769
|
*
|
|
21282
21770
|
* Lookup order:
|
|
@@ -65944,7 +66432,7 @@ function templateUsesSub(templateValue) {
|
|
|
65944
66432
|
* The leaf predicate answers `false` for a non-string, and an output's `Value`
|
|
65945
66433
|
* is very often an OBJECT: `secret.secretValueFromJson(...)` renders the
|
|
65946
66434
|
* secret's ARN as a `Ref`, so the value is an `Fn::Join` / `Fn::Sub` — which
|
|
65947
|
-
*
|
|
66435
|
+
* AGENTS.md's #1916 note calls the DOMINANT CDK shape, not an edge case.
|
|
65948
66436
|
* Feeding the raw value to the leaf predicate therefore reported "no secret
|
|
65949
66437
|
* here" for exactly the templates most likely to have one, which silently
|
|
65950
66438
|
* disarmed BOTH signals built on it: the export-alias decision below (issue
|
|
@@ -85273,6 +85761,10 @@ function createForceUnlockCommand() {
|
|
|
85273
85761
|
* object renders nothing, and an SDK failure is reported by its error NAME
|
|
85274
85762
|
* alone, because an SDK message can echo request input.
|
|
85275
85763
|
*
|
|
85764
|
+
* THE DEPENDENCE WALK ITSELF lives in `src/analyzer/parameter-dependence.ts`
|
|
85765
|
+
* (`cdkd deploy` reads it too, issue #3468); this module keeps the half that
|
|
85766
|
+
* touches AWS and the deployed values.
|
|
85767
|
+
*
|
|
85276
85768
|
* TAINT THROUGH ANOTHER RESOURCE'S ATTRIBUTES IS FOLLOWED within one stack: the
|
|
85277
85769
|
* walk records the target of every `Fn::GetAtt` / `${A.Attr}`, and the caller
|
|
85278
85770
|
* closes the refused set over those targets, so a resource reading an attribute
|
|
@@ -85379,232 +85871,33 @@ function divergentParameterNames(template, bound, deployed) {
|
|
|
85379
85871
|
}
|
|
85380
85872
|
return divergent;
|
|
85381
85873
|
}
|
|
85382
|
-
/**
|
|
85383
|
-
* Every intrinsic the dependence walk knows how to read. An `Fn::`-prefixed
|
|
85384
|
-
* key OUTSIDE this list is unclassifiable: it may name a parameter in a way
|
|
85385
|
-
* the generic recursion cannot see, so the walk fails closed on it.
|
|
85386
|
-
*/
|
|
85387
|
-
const KNOWN_INTRINSICS = /* @__PURE__ */ new Set([
|
|
85388
|
-
"Fn::GetAtt",
|
|
85389
|
-
"Fn::Join",
|
|
85390
|
-
"Fn::Sub",
|
|
85391
|
-
"Fn::Select",
|
|
85392
|
-
"Fn::Split",
|
|
85393
|
-
"Fn::If",
|
|
85394
|
-
"Fn::Equals",
|
|
85395
|
-
"Fn::And",
|
|
85396
|
-
"Fn::Or",
|
|
85397
|
-
"Fn::Not",
|
|
85398
|
-
"Fn::ImportValue",
|
|
85399
|
-
"Fn::GetStackOutput",
|
|
85400
|
-
"Fn::FindInMap",
|
|
85401
|
-
"Fn::Base64",
|
|
85402
|
-
"Fn::GetAZs",
|
|
85403
|
-
"Fn::Cidr"
|
|
85404
|
-
]);
|
|
85405
|
-
/** `${Name}` / `${Name.Attr}` in an `Fn::Sub` string; `${!Literal}` is an escape. */
|
|
85406
|
-
const SUB_VARIABLE = /\$\{([^!}][^}]*)\}/g;
|
|
85407
|
-
/**
|
|
85408
|
-
* Which names (parameters, but also resources and pseudo parameters — the
|
|
85409
|
-
* caller intersects with the divergent set) the RAW `Properties` of a resource
|
|
85410
|
-
* depend on, plus its resource-level `Condition`.
|
|
85411
|
-
*
|
|
85412
|
-
* Generic by design: instead of modelling each intrinsic's argument grammar it
|
|
85413
|
-
* recurses into EVERY value, so a `Ref` nested in an `Fn::Join` / `Fn::Select`
|
|
85414
|
-
* / `Fn::Split` operand, an `Fn::FindInMap` key, or an `Fn::Sub` variable-map
|
|
85415
|
-
* value is found without being named. Only the three forms that name something
|
|
85416
|
-
* by BARE STRING need their own arm: `Ref`, `Fn::Sub`'s template text, and a
|
|
85417
|
-
* condition name (`Fn::If`'s first argument, and `Condition` inside the
|
|
85418
|
-
* `Conditions` section), which is followed transitively through
|
|
85419
|
-
* `template.Conditions`.
|
|
85420
|
-
*
|
|
85421
|
-
* May throw `RangeError` on a bag deeper than the call stack; the caller wraps
|
|
85422
|
-
* it fail-closed, like the discard walk beside it.
|
|
85423
|
-
*/
|
|
85424
|
-
function collectParameterDependencies(properties, resourceCondition, template) {
|
|
85425
|
-
const names = /* @__PURE__ */ new Set();
|
|
85426
|
-
const attributeTargets = /* @__PURE__ */ new Set();
|
|
85427
|
-
const visitedConditions = /* @__PURE__ */ new Set();
|
|
85428
|
-
let unclassifiable = false;
|
|
85429
|
-
const conditions = template.Conditions !== null && typeof template.Conditions === "object" ? template.Conditions : {};
|
|
85430
|
-
const followCondition = (name) => {
|
|
85431
|
-
if (typeof name !== "string") {
|
|
85432
|
-
unclassifiable = true;
|
|
85433
|
-
return;
|
|
85434
|
-
}
|
|
85435
|
-
if (visitedConditions.has(name)) return;
|
|
85436
|
-
visitedConditions.add(name);
|
|
85437
|
-
if (!Object.hasOwn(conditions, name)) {
|
|
85438
|
-
unclassifiable = true;
|
|
85439
|
-
return;
|
|
85440
|
-
}
|
|
85441
|
-
walk(conditions[name], true);
|
|
85442
|
-
};
|
|
85443
|
-
const walk = (node, inConditions) => {
|
|
85444
|
-
if (node === null || typeof node !== "object") return;
|
|
85445
|
-
if (Array.isArray(node)) {
|
|
85446
|
-
for (const element of node) walk(element, inConditions);
|
|
85447
|
-
return;
|
|
85448
|
-
}
|
|
85449
|
-
for (const [key, value] of Object.entries(node)) {
|
|
85450
|
-
if (key === "Ref") {
|
|
85451
|
-
if (typeof value === "string") names.add(value);
|
|
85452
|
-
else unclassifiable = true;
|
|
85453
|
-
continue;
|
|
85454
|
-
}
|
|
85455
|
-
if (key === "Fn::Sub") {
|
|
85456
|
-
const text = Array.isArray(value) ? value[0] : value;
|
|
85457
|
-
if (typeof text !== "string" || Array.isArray(value) && value.length !== 2) unclassifiable = true;
|
|
85458
|
-
else for (const match of text.matchAll(SUB_VARIABLE)) {
|
|
85459
|
-
const variable = (match[1] ?? "").trim();
|
|
85460
|
-
names.add(variable);
|
|
85461
|
-
const dot = variable.indexOf(".");
|
|
85462
|
-
if (dot > 0) {
|
|
85463
|
-
names.add(variable.slice(0, dot));
|
|
85464
|
-
attributeTargets.add(variable.slice(0, dot));
|
|
85465
|
-
}
|
|
85466
|
-
}
|
|
85467
|
-
if (Array.isArray(value)) walk(value[1], inConditions);
|
|
85468
|
-
continue;
|
|
85469
|
-
}
|
|
85470
|
-
if (key === "Fn::GetAtt") {
|
|
85471
|
-
const target = Array.isArray(value) ? value[0] : value;
|
|
85472
|
-
if (typeof target === "string") {
|
|
85473
|
-
const dot = target.indexOf(".");
|
|
85474
|
-
const logicalId = Array.isArray(value) || dot <= 0 ? target : target.slice(0, dot);
|
|
85475
|
-
names.add(logicalId);
|
|
85476
|
-
attributeTargets.add(logicalId);
|
|
85477
|
-
} else unclassifiable = true;
|
|
85478
|
-
walk(value, inConditions);
|
|
85479
|
-
continue;
|
|
85480
|
-
}
|
|
85481
|
-
if (key === "Fn::If") {
|
|
85482
|
-
if (Array.isArray(value) && value.length === 3) {
|
|
85483
|
-
followCondition(value[0]);
|
|
85484
|
-
walk(value[1], inConditions);
|
|
85485
|
-
walk(value[2], inConditions);
|
|
85486
|
-
} else unclassifiable = true;
|
|
85487
|
-
continue;
|
|
85488
|
-
}
|
|
85489
|
-
if (key === "Condition" && inConditions) {
|
|
85490
|
-
followCondition(value);
|
|
85491
|
-
continue;
|
|
85492
|
-
}
|
|
85493
|
-
if (key.startsWith("Fn::") && !KNOWN_INTRINSICS.has(key)) unclassifiable = true;
|
|
85494
|
-
walk(value, inConditions);
|
|
85495
|
-
}
|
|
85496
|
-
};
|
|
85497
|
-
walk(properties, false);
|
|
85498
|
-
if (resourceCondition !== void 0) followCondition(resourceCondition);
|
|
85499
|
-
return {
|
|
85500
|
-
names,
|
|
85501
|
-
attributeTargets,
|
|
85502
|
-
unclassifiable
|
|
85503
|
-
};
|
|
85504
|
-
}
|
|
85505
|
-
/**
|
|
85506
|
-
* The divergent parameters that ANYTHING in the template names. A declared
|
|
85507
|
-
* parameter nothing names cannot have shaped a resource — and CDK's
|
|
85508
|
-
* `BootstrapVersion` is exactly that on every default-synthesized stack: SSM
|
|
85509
|
-
* typed, never bound by the resolver because nothing references it, hence
|
|
85510
|
-
* always "divergent". Left in, it would arm the unclassifiable-shape refusal on
|
|
85511
|
-
* stacks whose every real parameter is proven.
|
|
85512
|
-
*
|
|
85513
|
-
* "Names" is the UNION of this module's walk and `TemplateParser`'s reference
|
|
85514
|
-
* scan — the same scan the resolver uses to decide an SSM-typed parameter is
|
|
85515
|
-
* unreferenced — so the two cannot disagree about `BootstrapVersion`. An
|
|
85516
|
-
* unclassifiable shape does NOT keep the whole set here: CloudFormation names a
|
|
85517
|
-
* parameter only through `Ref`, `Fn::Sub` text or a condition, and the generic
|
|
85518
|
-
* recursion reads those inside any intrinsic, known or not. A walk that THROWS
|
|
85519
|
-
* keeps the whole set (fail closed).
|
|
85520
|
-
*/
|
|
85521
|
-
function reachableDivergentParameters(template, divergent, rawBags) {
|
|
85522
|
-
if (divergent.size === 0) return /* @__PURE__ */ new Set();
|
|
85523
|
-
try {
|
|
85524
|
-
const roots = [
|
|
85525
|
-
rawBags,
|
|
85526
|
-
template.Resources,
|
|
85527
|
-
template.Outputs,
|
|
85528
|
-
template.Conditions
|
|
85529
|
-
];
|
|
85530
|
-
const named = new Set(collectParameterDependencies(roots, void 0, template).names);
|
|
85531
|
-
const parser = new TemplateParser();
|
|
85532
|
-
for (const root of roots) {
|
|
85533
|
-
if (root === null || typeof root !== "object") continue;
|
|
85534
|
-
for (const name of parser.extractReferences(root)) named.add(name);
|
|
85535
|
-
}
|
|
85536
|
-
return new Set([...divergent].filter((name) => named.has(name)));
|
|
85537
|
-
} catch {
|
|
85538
|
-
return new Set(divergent);
|
|
85539
|
-
}
|
|
85540
|
-
}
|
|
85541
|
-
/** Whether any DECLARED parameter is named by anything in the template. */
|
|
85542
|
-
function namesAnyDeclaredParameter(template) {
|
|
85543
|
-
const declared = template.Parameters;
|
|
85544
|
-
if (declared === null || typeof declared !== "object") return false;
|
|
85545
|
-
const names = new Set(Object.keys(declared));
|
|
85546
|
-
return names.size > 0 && reachableDivergentParameters(template, names, []).size > 0;
|
|
85547
|
-
}
|
|
85548
|
-
/**
|
|
85549
|
-
* The per-stack ARM 4 verdict. Each resource is judged on its TEMPLATE
|
|
85550
|
-
* definition (`Properties` + `Condition`) AND on the raw bag the state record
|
|
85551
|
-
* carries, unioned: a record preserved by a selective merge holds a previous
|
|
85552
|
-
* run's RESOLVED literal (`Value: 'CHANGEME'`), which names no parameter, so
|
|
85553
|
-
* judging it on the state bag alone would leave nothing to carry to the
|
|
85554
|
-
* freshly imported resource that reads its attribute. Template resources that
|
|
85555
|
-
* are not in state at all are judged too, for the same reason.
|
|
85556
|
-
*
|
|
85557
|
-
* Seeds are direct dependents and unclassifiable bags (a bag that cannot be
|
|
85558
|
-
* read cannot be vouched for, and neither can what reads its attributes); the
|
|
85559
|
-
* refused set is then closed over ATTRIBUTE reads, to a fixpoint.
|
|
85560
|
-
*/
|
|
85561
|
-
function computeParameterTaint(template, stateBags, divergent) {
|
|
85562
|
-
const refused = /* @__PURE__ */ new Set();
|
|
85563
|
-
const unclassifiable = /* @__PURE__ */ new Set();
|
|
85564
|
-
const parametersHit = /* @__PURE__ */ new Set();
|
|
85565
|
-
if (divergent.size === 0) return {
|
|
85566
|
-
refused,
|
|
85567
|
-
unclassifiable,
|
|
85568
|
-
parametersHit
|
|
85569
|
-
};
|
|
85570
|
-
const templateResources = template.Resources !== null && typeof template.Resources === "object" ? template.Resources : {};
|
|
85571
|
-
const readers = /* @__PURE__ */ new Map();
|
|
85572
|
-
for (const logicalId of /* @__PURE__ */ new Set([...Object.keys(templateResources), ...stateBags.keys()])) {
|
|
85573
|
-
const definition = Object.hasOwn(templateResources, logicalId) ? templateResources[logicalId] : void 0;
|
|
85574
|
-
const record = definition !== null && typeof definition === "object" ? definition : {};
|
|
85575
|
-
try {
|
|
85576
|
-
const dependencies = collectParameterDependencies([record["Properties"], stateBags.get(logicalId)], record["Condition"], template);
|
|
85577
|
-
readers.set(logicalId, new Set(dependencies.attributeTargets));
|
|
85578
|
-
const hits = [...dependencies.names].filter((name) => divergent.has(name));
|
|
85579
|
-
for (const name of hits) parametersHit.add(name);
|
|
85580
|
-
if (hits.length > 0) refused.add(logicalId);
|
|
85581
|
-
else if (dependencies.unclassifiable) {
|
|
85582
|
-
refused.add(logicalId);
|
|
85583
|
-
unclassifiable.add(logicalId);
|
|
85584
|
-
}
|
|
85585
|
-
} catch {
|
|
85586
|
-
refused.add(logicalId);
|
|
85587
|
-
unclassifiable.add(logicalId);
|
|
85588
|
-
}
|
|
85589
|
-
}
|
|
85590
|
-
for (let grew = refused.size > 0; grew;) {
|
|
85591
|
-
grew = false;
|
|
85592
|
-
for (const [logicalId, targets] of readers) {
|
|
85593
|
-
if (refused.has(logicalId)) continue;
|
|
85594
|
-
if (![...targets].some((target) => refused.has(target))) continue;
|
|
85595
|
-
refused.add(logicalId);
|
|
85596
|
-
grew = true;
|
|
85597
|
-
}
|
|
85598
|
-
}
|
|
85599
|
-
return {
|
|
85600
|
-
refused,
|
|
85601
|
-
unclassifiable,
|
|
85602
|
-
parametersHit
|
|
85603
|
-
};
|
|
85604
|
-
}
|
|
85605
85874
|
|
|
85606
85875
|
//#endregion
|
|
85607
85876
|
//#region src/cli/commands/import.ts
|
|
85877
|
+
/**
|
|
85878
|
+
* What {@link resolveImportedProperties} refused, as ONE value (issue #3462).
|
|
85879
|
+
*
|
|
85880
|
+
* It is still the set of logical ids whose observed baseline must not be
|
|
85881
|
+
* captured — every existing reader calls `.has()` — and it additionally carries
|
|
85882
|
+
* the two facts `captureObservedForImportedResources` needs to write, keep or
|
|
85883
|
+
* clear `ResourceState.observedBaselineRefusalReason`. They travel ON the set
|
|
85884
|
+
* rather than beside it, and the capture's parameter is THIS type, so a call
|
|
85885
|
+
* site that copies the ids into a plain `Set` — dropping both facts, and with
|
|
85886
|
+
* them the reason every deploy-side guard keys on — does not compile.
|
|
85887
|
+
*/
|
|
85888
|
+
var ObservedBaselineRefusals = class extends Set {
|
|
85889
|
+
/**
|
|
85890
|
+
* The subset ARM 4 named — directly, through an attribute read, or as an
|
|
85891
|
+
* unclassifiable bag — whether or not another arm refused it too.
|
|
85892
|
+
*/
|
|
85893
|
+
unverifiableParameter = /* @__PURE__ */ new Set();
|
|
85894
|
+
/**
|
|
85895
|
+
* Whether this run HAD deployed parameter values to compare against. Only
|
|
85896
|
+
* then does "ARM 4 did not name it" mean the placeholder was PROVEN; without
|
|
85897
|
+
* a source it means nothing was judged at all.
|
|
85898
|
+
*/
|
|
85899
|
+
hadDeployedParameterSource = false;
|
|
85900
|
+
};
|
|
85608
85901
|
async function importCommand(stackArg, options) {
|
|
85609
85902
|
const logger = getLogger();
|
|
85610
85903
|
if (options.verbose) {
|
|
@@ -86164,10 +86457,13 @@ function rebuiltLogicalIdsFrom(rows, template) {
|
|
|
86164
86457
|
* import flow never derives outputs (they're computed at deploy time from
|
|
86165
86458
|
* each resource's attributes), so even an auto-mode rebuild has no reason
|
|
86166
86459
|
* to wipe them.
|
|
86460
|
+
*
|
|
86461
|
+
* Exported for unit testing — internal to the command flow otherwise.
|
|
86167
86462
|
*/
|
|
86168
86463
|
function buildStackState(stackName, region, rows, templateParser, template, existingState, selectiveMode) {
|
|
86169
86464
|
const resources = selectiveMode && existingState ? { ...existingState.resources } : {};
|
|
86170
86465
|
const parameterNames = new Set(Object.keys(template.Parameters ?? {}));
|
|
86466
|
+
let namesParameter;
|
|
86171
86467
|
for (const row of rows) {
|
|
86172
86468
|
if (row.outcome !== "imported" || !row.physicalId) continue;
|
|
86173
86469
|
const tmplResource = template.Resources[row.logicalId];
|
|
@@ -86182,9 +86478,14 @@ function buildStackState(stackName, region, rows, templateParser, template, exis
|
|
|
86182
86478
|
properties: tmplResource.Properties ?? {},
|
|
86183
86479
|
attributes: rowAttributes ?? priorAttributes ?? {},
|
|
86184
86480
|
dependencies: deps,
|
|
86185
|
-
provisionedBy: "sdk"
|
|
86481
|
+
provisionedBy: "sdk",
|
|
86482
|
+
...prior && prior.physicalId === row.physicalId && (hasUnverifiableParameterRefusal(prior) || hasReasonlessBaselineRefusal(prior) && (namesParameter ??= resourcesNamingDeclaredParameter(template))(row.logicalId)) && {
|
|
86483
|
+
observedBaselineRefused: true,
|
|
86484
|
+
observedBaselineRefusalReason: "unverifiable-parameter"
|
|
86485
|
+
}
|
|
86186
86486
|
};
|
|
86187
86487
|
}
|
|
86488
|
+
if (namesParameter?.failedClosed !== void 0) getLogger().debug(`The template's parameter dependence could not be judged (${namesParameter.failedClosed}): every prior observed-baseline refusal recorded without a reason is carried as an unverifiable-parameter refusal.`);
|
|
86188
86489
|
return {
|
|
86189
86490
|
version: 10,
|
|
86190
86491
|
stackName,
|
|
@@ -86345,12 +86646,13 @@ function defaultOnlyParameterTemplate(template) {
|
|
|
86345
86646
|
* `DescribeStacks` parameters are the comparison); what REMAINS is an
|
|
86346
86647
|
* import with NO CloudFormation stack, where no deployed-parameter source
|
|
86347
86648
|
* exists, taint across the nested-stack `Outputs` boundary
|
|
86348
|
-
* (`import-deployed-parameters.ts` states it)
|
|
86349
|
-
*
|
|
86350
|
-
*
|
|
86351
|
-
*
|
|
86352
|
-
*
|
|
86353
|
-
*
|
|
86649
|
+
* (`import-deployed-parameters.ts` states it). The NEXT DEPLOY and a
|
|
86650
|
+
* source-less RE-IMPORT used to be on this list
|
|
86651
|
+
* ([#3462](https://github.com/go-to-k/cdkd/issues/3462)): both bind the
|
|
86652
|
+
* same placeholder `Default`, so both now honour
|
|
86653
|
+
* `ResourceState.observedBaselineRefusalReason`, which this arm's verdict
|
|
86654
|
+
* is recorded as. A binary older than that field still clears the marker on
|
|
86655
|
+
* any UPDATE. ARM 4 also WIDENS the population of
|
|
86354
86656
|
* [#2872](https://github.com/go-to-k/cdkd/issues/2872): a record preserved
|
|
86355
86657
|
* by a selective merge that a pre-#2854 import already gave a plaintext
|
|
86356
86658
|
* baseline is now refused, and a refusal leaves that old baseline in place;
|
|
@@ -86378,7 +86680,8 @@ function defaultOnlyParameterTemplate(template) {
|
|
|
86378
86680
|
* deployer, which is where the structural remedy for all of the above lives.
|
|
86379
86681
|
*
|
|
86380
86682
|
* The set is returned rather than recorded on the state, because it describes
|
|
86381
|
-
* THIS run's resolution
|
|
86683
|
+
* THIS run's resolution; `captureObservedForImportedResources` turns it into
|
|
86684
|
+
* the two record fields that DO outlive the run.
|
|
86382
86685
|
*
|
|
86383
86686
|
* Exported for unit testing — internal to the command flow otherwise. The
|
|
86384
86687
|
* masking this walk applies (issue #2803) is only provable against the REAL
|
|
@@ -86386,7 +86689,8 @@ function defaultOnlyParameterTemplate(template) {
|
|
|
86386
86689
|
* own throw rather than through anything this function writes.
|
|
86387
86690
|
*/
|
|
86388
86691
|
async function resolveImportedProperties(stackState, template, region, stateBackend, logger, deployedParameters) {
|
|
86389
|
-
const unsafeObservedBaselineLogicalIds =
|
|
86692
|
+
const unsafeObservedBaselineLogicalIds = new ObservedBaselineRefusals();
|
|
86693
|
+
unsafeObservedBaselineLogicalIds.hadDeployedParameterSource = deployedParameters !== void 0;
|
|
86390
86694
|
const entries = Object.entries(stackState.resources ?? {});
|
|
86391
86695
|
if (entries.length === 0) return unsafeObservedBaselineLogicalIds;
|
|
86392
86696
|
const resolver = new IntrinsicFunctionResolver(region);
|
|
@@ -86455,6 +86759,7 @@ async function resolveImportedProperties(stackState, template, region, stateBack
|
|
|
86455
86759
|
}
|
|
86456
86760
|
if (recordedSecretValues.size > 0 && resource.attributes !== void 0) resource.attributes = redactSecretsForState(resource.attributes, recordedSecretValues);
|
|
86457
86761
|
const dependsOnDivergent = parameterTaint?.refused.has(logicalId) === true;
|
|
86762
|
+
if (dependsOnDivergent) unsafeObservedBaselineLogicalIds.unverifiableParameter.add(logicalId);
|
|
86458
86763
|
if (threw || dependsOnDivergent) {
|
|
86459
86764
|
unsafeObservedBaselineLogicalIds.add(logicalId);
|
|
86460
86765
|
continue;
|
|
@@ -86835,15 +87140,19 @@ async function captureObservedForImportedResources(stackState, providerRegistry,
|
|
|
86835
87140
|
if (entries.length === 0) return;
|
|
86836
87141
|
await Promise.all(entries.map(async ([logicalId, resource]) => {
|
|
86837
87142
|
if (resource.observedBaselineRefused === true && !rebuiltLogicalIds.has(logicalId)) {
|
|
86838
|
-
logger.debug(`observedProperties capture SKIPPED for preserved ${logicalId} (${resource.resourceType}): a previous 'cdkd import' run refused this record's baseline and this run did not re-import it, so its recorded properties are still the ones that refusal distrusted.
|
|
87143
|
+
logger.debug(`observedProperties capture SKIPPED for preserved ${logicalId} (${resource.resourceType}): a previous 'cdkd import' run refused this record's baseline and this run did not re-import it, so its recorded properties are still the ones that refusal distrusted. Re-import it, or deploy a change to it, to restore a baseline — unless its refusal is an unverifiable-parameter one, which only a replacement or a proving re-import discharges.`);
|
|
86839
87144
|
return;
|
|
86840
87145
|
}
|
|
86841
|
-
|
|
87146
|
+
const refusals = unsafeObservedBaselineLogicalIds;
|
|
87147
|
+
const parameterRefusalStands = refusals.unverifiableParameter.has(logicalId) || hasUnverifiableParameterRefusal(resource) && !refusals.hadDeployedParameterSource;
|
|
87148
|
+
if (unsafeObservedBaselineLogicalIds.has(logicalId) || parameterRefusalStands) {
|
|
86842
87149
|
resource.observedBaselineRefused = true;
|
|
86843
|
-
|
|
87150
|
+
resource.observedBaselineRefusalReason = parameterRefusalStands ? "unverifiable-parameter" : "incomplete-resolution";
|
|
87151
|
+
logger.debug(`observedProperties capture SKIPPED for imported ${logicalId} (${resource.resourceType}): the recorded properties cannot be shown to spell every dynamic reference the deployed resource was built from, so they cannot position a redaction — capturing an AWS readback against them could persist a resolved secret in plaintext. Drift will compare against the recorded properties for this resource until ${parameterRefusalStands ? `it is REPLACED, or re-imported while a CloudFormation stack can prove its parameters were deployed at their 'Default' — an in-place deploy binds the same 'Default' and cannot restore the baseline` : `the next successful deploy`}.`);
|
|
86844
87152
|
return;
|
|
86845
87153
|
}
|
|
86846
87154
|
delete resource.observedBaselineRefused;
|
|
87155
|
+
delete resource.observedBaselineRefusalReason;
|
|
86847
87156
|
try {
|
|
86848
87157
|
const provider = providerRegistry.getProviderFor({
|
|
86849
87158
|
resourceType: resource.resourceType,
|
|
@@ -96714,4 +97023,4 @@ function buildProgram() {
|
|
|
96714
97023
|
|
|
96715
97024
|
//#endregion
|
|
96716
97025
|
export { buildProgram };
|
|
96717
|
-
//# sourceMappingURL=program-
|
|
97026
|
+
//# sourceMappingURL=program-CUOo1x11.js.map
|