@go-to-k/cdkd 0.280.31 → 0.280.33
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-DNSNrDqX.js → asg-provider-DrM2HEGB.js} +2 -2
- package/dist/{asg-provider-DNSNrDqX.js.map → asg-provider-DrM2HEGB.js.map} +1 -1
- package/dist/cli.js +193 -11
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-DKMfEz_h.js → deploy-engine-Dn60Zkq5.js} +30 -12
- package/dist/{deploy-engine-DKMfEz_h.js.map → deploy-engine-Dn60Zkq5.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as requireConfigObject, $t as AssemblyReader, A as green, At as runDockerForeground, B as disableInstanceApiTermination, Bt as resolveCaptureObservedState, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as StackTerminationProtectionError, Ct as getBootstrapMarkerKey, D as bold, Dt as buildDockerImage, E as formatResourceLine, Et as validateContainerRepoName, F as clearOnUpdateRemoval, Ft as synthesisStatusMessage, G as WAFv2WebACLProvider, Gt as stateBucketExistenceConfirmed, H as IntrinsicFunctionResolver, Ht as resolveStateBucketWithDefault, I as ProviderRegistry, It as getDefaultStateBucketName, J as assertRegionMatch, Jt as CFN_TEMPLATE_URL_LIMIT, K as normalizeAwsTagsToCfn, Kt as warnDeprecatedNoPrefixCliFlag, L as findActionableSilentDrops, Lt as getLegacyStateBucketName, M as yellow, Mt as AssetManifestLoader, N as IAMRoleProvider, Nt as getDockerImageBySourceHash, O as cyan, On as normalizeAwsError, Ot as formatDockerLoginError, P as collectInlinePolicyNamesManagedBySiblings, Pt as Synthesizer, Q as requireConfigArray, Qt as expectedOwnerParam, R as CloudControlProvider, Rt as resolveApp, S as extractDeploymentEventError, Sn as StackHasActiveImportsError, St as ensureAssetStorage, T as renderStatefulReason, Tt as validateAssetBucketName, U as cfnRefValueFromPhysicalId, Ut as resolveStateBucketWithDefaultAndSource, V as isTerminationProtectionPropagationError, Vt as resolveSkipPrefix, W as refStateLookupFromResource, Wt as resolveUseCdkBootstrapAssets, X as readConfigString, Xt as findLargeInlineResources, Y as configStringRefusal, Yt as MIGRATE_TMP_PREFIX, Z as replayWarn, Zt as uploadCfnTemplate, _ as createPreDeleteFinalSnapshot, _n as NestedStackChildDirectDestroyError, _t as createAssetRedirectResolver, a as DeploymentEventsStore, an as resetAwsClients, at as isRetryableTransientError, b as unsupportedFinalSnapshotError, bn as ResourceTimeoutError, bt as AssetModeResolver, c as replayFailedOperations, cn as CdkdError, ct as TemplateParser, d as IMPLICIT_DELETE_DEPENDENCIES, dn as DeployCancelledError, dt as rebuildClientForBucketRegion, en as processStackMessages, et as requireConfigString, f as computeImplicitDeleteEdges, fn as LocalInvokeBuildError$1, ft as shouldRetainResource, g as ccRoutedFinalSnapshotError, gn as MissingCdkCliError, gt as buildAssetRedirectMap, h as buildFinalSnapshotIdentifier, ht as WorkGraph, i as DeploymentEventsReader, in as getAwsClients, it as withRetry, j as red, jt as runDockerStreaming, k as gray, kn as withErrorHandling, kt as getDockerCmd, l as replayRollback, lt as LockManager, m as PRE_DELETE_SNAPSHOT_TYPES, mn as LocalStartServiceError, mt as stringifyValue, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resolveBucketRegion, nt as DiffCalculator, o as planFailedOps, on as setAwsClients, ot as isThrottlingError, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalMigrateError, pt as AssetPublisher, q as resolveExplicitPhysicalId, qt as CFN_TEMPLATE_BODY_LIMIT, r as DeployEngine, rn as AwsClients, rt as describeTypeWithThrottleRetry, s as planRollback, st as DagBuilder, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as applyRoleArnIfSet, u as withResourceDeadline, ut as S3StateBackend, v as isFinalSnapshotError, vn as PartialFailureError, vt as loadPublishableAssetManifest, w as isStatefulRecreateTargetSync, wt as parseBootstrapMarker, x as makeCanonicalizePropertiesFn, xn as ResourceUpdateNotSupportedError, xt as BOOTSTRAP_MARKER_PREFIX, y as refusesFinalSnapshot, yn as ProvisioningError, yt as rewriteTemplateAssetReferences, z as slowCcOperationTimeoutMs, zt as resolveAutoAssetStorage } from "./deploy-engine-
|
|
2
|
+
import { $ as requireConfigObject, $t as AssemblyReader, A as green, At as runDockerForeground, B as disableInstanceApiTermination, Bt as resolveCaptureObservedState, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as StackTerminationProtectionError, Ct as getBootstrapMarkerKey, D as bold, Dt as buildDockerImage, E as formatResourceLine, Et as validateContainerRepoName, F as clearOnUpdateRemoval, Ft as synthesisStatusMessage, G as WAFv2WebACLProvider, Gt as stateBucketExistenceConfirmed, H as IntrinsicFunctionResolver, Ht as resolveStateBucketWithDefault, I as ProviderRegistry, It as getDefaultStateBucketName, J as assertRegionMatch, Jt as CFN_TEMPLATE_URL_LIMIT, K as normalizeAwsTagsToCfn, Kt as warnDeprecatedNoPrefixCliFlag, L as findActionableSilentDrops, Lt as getLegacyStateBucketName, M as yellow, Mt as AssetManifestLoader, N as IAMRoleProvider, Nt as getDockerImageBySourceHash, O as cyan, On as normalizeAwsError, Ot as formatDockerLoginError, P as collectInlinePolicyNamesManagedBySiblings, Pt as Synthesizer, Q as requireConfigArray, Qt as expectedOwnerParam, R as CloudControlProvider, Rt as resolveApp, S as extractDeploymentEventError, Sn as StackHasActiveImportsError, St as ensureAssetStorage, T as renderStatefulReason, Tt as validateAssetBucketName, U as cfnRefValueFromPhysicalId, Ut as resolveStateBucketWithDefaultAndSource, V as isTerminationProtectionPropagationError, Vt as resolveSkipPrefix, W as refStateLookupFromResource, Wt as resolveUseCdkBootstrapAssets, X as readConfigString, Xt as findLargeInlineResources, Y as configStringRefusal, Yt as MIGRATE_TMP_PREFIX, Z as replayWarn, Zt as uploadCfnTemplate, _ as createPreDeleteFinalSnapshot, _n as NestedStackChildDirectDestroyError, _t as createAssetRedirectResolver, a as DeploymentEventsStore, an as resetAwsClients, at as isRetryableTransientError, b as unsupportedFinalSnapshotError, bn as ResourceTimeoutError, bt as AssetModeResolver, c as replayFailedOperations, cn as CdkdError, ct as TemplateParser, d as IMPLICIT_DELETE_DEPENDENCIES, dn as DeployCancelledError, dt as rebuildClientForBucketRegion, en as processStackMessages, et as requireConfigString, f as computeImplicitDeleteEdges, fn as LocalInvokeBuildError$1, ft as shouldRetainResource, g as ccRoutedFinalSnapshotError, gn as MissingCdkCliError, gt as buildAssetRedirectMap, h as buildFinalSnapshotIdentifier, ht as WorkGraph, i as DeploymentEventsReader, in as getAwsClients, it as withRetry, j as red, jt as runDockerStreaming, k as gray, kn as withErrorHandling, kt as getDockerCmd, l as replayRollback, lt as LockManager, m as PRE_DELETE_SNAPSHOT_TYPES, mn as LocalStartServiceError, mt as stringifyValue, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resolveBucketRegion, nt as DiffCalculator, o as planFailedOps, on as setAwsClients, ot as isThrottlingError, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalMigrateError, pt as AssetPublisher, q as resolveExplicitPhysicalId, qt as CFN_TEMPLATE_BODY_LIMIT, r as DeployEngine, rn as AwsClients, rt as describeTypeWithThrottleRetry, s as planRollback, st as DagBuilder, t as DEFAULT_RESOURCE_TIMEOUT_MS, tt as applyRoleArnIfSet, u as withResourceDeadline, ut as S3StateBackend, v as isFinalSnapshotError, vn as PartialFailureError, vt as loadPublishableAssetManifest, w as isStatefulRecreateTargetSync, wt as parseBootstrapMarker, x as makeCanonicalizePropertiesFn, xn as ResourceUpdateNotSupportedError, xt as BOOTSTRAP_MARKER_PREFIX, y as refusesFinalSnapshot, yn as ProvisioningError, yt as rewriteTemplateAssetReferences, z as slowCcOperationTimeoutMs, zt as resolveAutoAssetStorage } from "./deploy-engine-Dn60Zkq5.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-DrM2HEGB.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -11930,6 +11930,55 @@ function hasUsableTableCapacity(value) {
|
|
|
11930
11930
|
return true;
|
|
11931
11931
|
}
|
|
11932
11932
|
/**
|
|
11933
|
+
* A capacity member, or `undefined` when the value is not a usable number.
|
|
11934
|
+
*
|
|
11935
|
+
* `Number()` coercion is NOT good enough here and the first draft of this
|
|
11936
|
+
* helper shipped that bug: `Number(null)`, `Number('')`, `Number([])` and
|
|
11937
|
+
* `Number(false)` are all **0**, not `NaN` — so a live `0` would have compared
|
|
11938
|
+
* EQUAL to a desired `null` / `''` / `[]` / `false` and suppressed the call.
|
|
11939
|
+
* The blast radius on DynamoDB is small (a PROVISIONED index's capacity is
|
|
11940
|
+
* always >= 1), but the rule this encodes is copied to other providers, so it
|
|
11941
|
+
* is spelled correctly rather than relying on the type that happens to be
|
|
11942
|
+
* unreachable today.
|
|
11943
|
+
*
|
|
11944
|
+
* A YAML-borne numeric STRING is still accepted, because that is a real
|
|
11945
|
+
* template shape and `'5'` genuinely means 5.
|
|
11946
|
+
*/
|
|
11947
|
+
function capacityNumber(value) {
|
|
11948
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
11949
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
11950
|
+
const n = Number(value);
|
|
11951
|
+
return Number.isFinite(n) ? n : void 0;
|
|
11952
|
+
}
|
|
11953
|
+
}
|
|
11954
|
+
/**
|
|
11955
|
+
* Does the capacity AWS currently holds for a GSI already equal the pair the
|
|
11956
|
+
* template requests? (issue #1630)
|
|
11957
|
+
*
|
|
11958
|
+
* Compared member by member, deliberately not with `deepEqual` /
|
|
11959
|
+
* `JSON.stringify`: `DescribeTable` returns a `ProvisionedThroughputDescription`
|
|
11960
|
+
* carrying `NumberOfDecreasesToday` / `LastIncreaseDateTime` /
|
|
11961
|
+
* `LastDecreaseDateTime` alongside the two capacities, so a structural compare
|
|
11962
|
+
* against the two-member desired object could never match and the suppression
|
|
11963
|
+
* would be dead code that only LOOKED safe.
|
|
11964
|
+
*
|
|
11965
|
+
* Anything that is not a usable number on EITHER side fails OPEN (the
|
|
11966
|
+
* `UpdateTable` is still issued): an absent live entry, a malformed template
|
|
11967
|
+
* value, an unresolved intrinsic. That is the correct direction — the worst
|
|
11968
|
+
* case is the pre-fix behavior, whereas a false MATCH would silently drop a
|
|
11969
|
+
* capacity change the user asked for.
|
|
11970
|
+
*/
|
|
11971
|
+
function liveCapacityAlreadyMatches(live, requested) {
|
|
11972
|
+
if (live === void 0) return false;
|
|
11973
|
+
const liveRead = capacityNumber(live.ReadCapacityUnits);
|
|
11974
|
+
const liveWrite = capacityNumber(live.WriteCapacityUnits);
|
|
11975
|
+
const wantRead = capacityNumber(requested.ReadCapacityUnits);
|
|
11976
|
+
const wantWrite = capacityNumber(requested.WriteCapacityUnits);
|
|
11977
|
+
if (liveRead === void 0 || liveWrite === void 0) return false;
|
|
11978
|
+
if (wantRead === void 0 || wantWrite === void 0) return false;
|
|
11979
|
+
return liveRead === wantRead && liveWrite === wantWrite;
|
|
11980
|
+
}
|
|
11981
|
+
/**
|
|
11933
11982
|
* `DescribeTable` poll budget (seconds, 1 poll/s) after an ordinary
|
|
11934
11983
|
* `UpdateTable` — capacity, TTL, tags, table class. Calibrated on those and
|
|
11935
11984
|
* sufficient for them.
|
|
@@ -12105,6 +12154,8 @@ var DynamoDBTableProvider = class {
|
|
|
12105
12154
|
}
|
|
12106
12155
|
}
|
|
12107
12156
|
const currentLiveIndexNames = table ? new Set(liveIndexes.map((live) => live.IndexName).filter((name) => typeof name === "string").filter((name) => !preFlipDeletedIndexNames.has(name))) : void 0;
|
|
12157
|
+
const currentLiveIndexByName = table ? new Map(liveIndexes.filter((live) => typeof live.IndexName === "string").filter((live) => !preFlipDeletedIndexNames.has(live.IndexName)).map((live) => [live.IndexName, live])) : void 0;
|
|
12158
|
+
const liveCapacityComparable = liveBillingMode === "PROVISIONED";
|
|
12108
12159
|
const gsiHandledByBillingFlip = /* @__PURE__ */ new Set();
|
|
12109
12160
|
if (billingOrThroughputChanged || tableClassChanged) {
|
|
12110
12161
|
const updateInput = { TableName: physicalId };
|
|
@@ -12219,7 +12270,7 @@ var DynamoDBTableProvider = class {
|
|
|
12219
12270
|
this.logger.debug(`Disabled DynamoDB Stream on table ${physicalId}`);
|
|
12220
12271
|
}
|
|
12221
12272
|
}
|
|
12222
|
-
if (JSON.stringify(properties["GlobalSecondaryIndexes"]) !== JSON.stringify(previousProperties["GlobalSecondaryIndexes"])) await this.applyGsiUpdates(physicalId, resourceType, logicalId, previousProperties["GlobalSecondaryIndexes"], properties["GlobalSecondaryIndexes"], properties["AttributeDefinitions"], gsiHandledByBillingFlip, currentLiveIndexNames);
|
|
12273
|
+
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);
|
|
12223
12274
|
if (JSON.stringify(properties["PointInTimeRecoverySpecification"]) !== JSON.stringify(previousProperties["PointInTimeRecoverySpecification"])) await this.applyPointInTimeRecovery(physicalId, properties["PointInTimeRecoverySpecification"], previousProperties["PointInTimeRecoverySpecification"]);
|
|
12224
12275
|
if (JSON.stringify(properties["TimeToLiveSpecification"]) !== JSON.stringify(previousProperties["TimeToLiveSpecification"])) {
|
|
12225
12276
|
this.assertNoActiveTtlAttributeNameChange(logicalId, resourceType, physicalId, properties["TimeToLiveSpecification"], previousProperties["TimeToLiveSpecification"]);
|
|
@@ -12657,7 +12708,7 @@ var DynamoDBTableProvider = class {
|
|
|
12657
12708
|
* a remove-then-add pair, so this method throws on such a change rather than
|
|
12658
12709
|
* silently dropping it.
|
|
12659
12710
|
*/
|
|
12660
|
-
async applyGsiUpdates(physicalId, resourceType, logicalId, previousGsis, desiredGsis, desiredAttributeDefinitions, handledByBillingFlip = /* @__PURE__ */ new Set(), liveIndexNames) {
|
|
12711
|
+
async applyGsiUpdates(physicalId, resourceType, logicalId, previousGsis, desiredGsis, desiredAttributeDefinitions, handledByBillingFlip = /* @__PURE__ */ new Set(), liveIndexNames, liveIndexByName, liveCapacityComparable = false) {
|
|
12661
12712
|
const prev = previousGsis ?? [];
|
|
12662
12713
|
const desired = desiredGsis ?? [];
|
|
12663
12714
|
const prevByName = new Map(prev.filter((g) => g.IndexName).map((g) => [g.IndexName, g]));
|
|
@@ -12672,7 +12723,17 @@ var DynamoDBTableProvider = class {
|
|
|
12672
12723
|
ops.push({ Delete: { IndexName: name } });
|
|
12673
12724
|
}
|
|
12674
12725
|
for (const [name, gsi] of desiredByName) if (!prevByName.has(name)) {
|
|
12726
|
+
const live = liveIndexByName?.get(name);
|
|
12675
12727
|
if (!gsi.KeySchema) throw new ProvisioningError(`GlobalSecondaryIndex ${name} on DynamoDB table ${logicalId} is missing KeySchema`, resourceType, logicalId, physicalId);
|
|
12728
|
+
if (live !== void 0 && live.IndexStatus !== "DELETING") {
|
|
12729
|
+
this.logger.warn(`GSI ${name} on DynamoDB table ${physicalId} already exists in AWS but is absent from cdkd's recorded previous state, so its Create is being skipped. This usually means an earlier deploy created the index and then failed before state was written.`);
|
|
12730
|
+
if (liveCapacityComparable && gsi.ProvisionedThroughput && !handledByBillingFlip.has(name) && !liveCapacityAlreadyMatches(live.ProvisionedThroughput, gsi.ProvisionedThroughput)) ops.push({ Update: {
|
|
12731
|
+
IndexName: name,
|
|
12732
|
+
ProvisionedThroughput: gsi.ProvisionedThroughput
|
|
12733
|
+
} });
|
|
12734
|
+
this.logger.warn(`GSI ${name} was adopted from AWS rather than created, so cdkd could not verify its KeySchema / Projection match the template. Run \`cdkd drift ${physicalId}\` to confirm, and \`cdkd drift --accept\` if AWS is the source of truth.`);
|
|
12735
|
+
continue;
|
|
12736
|
+
}
|
|
12676
12737
|
ops.push({ Create: {
|
|
12677
12738
|
IndexName: name,
|
|
12678
12739
|
KeySchema: gsi.KeySchema,
|
|
@@ -12683,7 +12744,8 @@ var DynamoDBTableProvider = class {
|
|
|
12683
12744
|
} else {
|
|
12684
12745
|
const before = prevByName.get(name);
|
|
12685
12746
|
if (JSON.stringify(before.KeySchema) !== JSON.stringify(gsi.KeySchema) || JSON.stringify(before.Projection) !== JSON.stringify(gsi.Projection)) throw new ProvisioningError(`GlobalSecondaryIndex ${name} on DynamoDB table ${logicalId} changed its KeySchema or Projection, which DynamoDB cannot modify in place. Rename the index (so it is dropped and re-created) or replace the table.`, resourceType, logicalId, physicalId);
|
|
12686
|
-
if (gsi.ProvisionedThroughput && !handledByBillingFlip.has(name) && JSON.stringify(before.ProvisionedThroughput) !== JSON.stringify(gsi.ProvisionedThroughput))
|
|
12747
|
+
if (gsi.ProvisionedThroughput && !handledByBillingFlip.has(name) && JSON.stringify(before.ProvisionedThroughput) !== JSON.stringify(gsi.ProvisionedThroughput)) if (liveCapacityComparable && liveCapacityAlreadyMatches(liveIndexByName?.get(name)?.ProvisionedThroughput, gsi.ProvisionedThroughput)) this.logger.debug(`GSI ${name} on DynamoDB table ${physicalId} already carries the requested capacity in AWS; skipping its throughput Update`);
|
|
12748
|
+
else ops.push({ Update: {
|
|
12687
12749
|
IndexName: name,
|
|
12688
12750
|
ProvisionedThroughput: gsi.ProvisionedThroughput
|
|
12689
12751
|
} });
|
|
@@ -52990,7 +53052,7 @@ async function driftCommand(stacks, options) {
|
|
|
52990
53052
|
return;
|
|
52991
53053
|
}
|
|
52992
53054
|
if (options.accept) await runAccept(reports, stateBackend, stateConfig, awsClients, options);
|
|
52993
|
-
else await runRevert(reports, providerRegistry, stateConfig, awsClients, options);
|
|
53055
|
+
else await runRevert(reports, providerRegistry, stateBackend, stateConfig, awsClients, options);
|
|
52994
53056
|
} finally {
|
|
52995
53057
|
awsClients.destroy();
|
|
52996
53058
|
}
|
|
@@ -53716,6 +53778,69 @@ function buildRevertNewProperties(drifts, desiredProperties, awsProperties, opti
|
|
|
53716
53778
|
return result;
|
|
53717
53779
|
}
|
|
53718
53780
|
/**
|
|
53781
|
+
* The top-level keys a provider NARROWED on the revert path (issue #1644).
|
|
53782
|
+
*
|
|
53783
|
+
* `provider.update()` may answer with `effectiveProperties` — the bag it
|
|
53784
|
+
* ACTUALLY delivered, which is what AWS now holds. `DeployEngine` records that
|
|
53785
|
+
* in place of the desired bag (`propertiesToRecord`), and `--revert` used to
|
|
53786
|
+
* throw the return value away: state kept the un-narrowed value, so the very
|
|
53787
|
+
* next `cdkd drift` reported the same difference and `--revert` re-issued the
|
|
53788
|
+
* same call — the loop `effectiveProperties` exists to break, still live on
|
|
53789
|
+
* this one command.
|
|
53790
|
+
*
|
|
53791
|
+
* Returns a per-key DELTA rather than the whole bag, and that is the load-
|
|
53792
|
+
* bearing part. The bag handed to `update()` here is
|
|
53793
|
+
* {@link buildRevertNewProperties}'s output — AWS-current values for every
|
|
53794
|
+
* non-drifted key merged with the state baseline for the drifted ones — so
|
|
53795
|
+
* writing it back wholesale would import AWS-authored values into state for
|
|
53796
|
+
* keys nobody reverted, quietly turning `--revert` into `--accept`. Only the
|
|
53797
|
+
* keys the PROVIDER changed between what it was handed and what it delivered
|
|
53798
|
+
* belong in state.
|
|
53799
|
+
*
|
|
53800
|
+
* A key present in `sent` but absent from `effective` is a DROP: the provider
|
|
53801
|
+
* did not deliver it, so AWS does not hold it, and the baseline must lose it
|
|
53802
|
+
* too. That is represented by an explicit `undefined` value, which the caller
|
|
53803
|
+
* turns into a `delete` — a plain `{...baseline, ...delta}` spread would leave
|
|
53804
|
+
* an `undefined`-valued key in the JSON instead. Presence is decided by
|
|
53805
|
+
* `key in effective`, NOT by comparing against `undefined`: a provider that
|
|
53806
|
+
* delivers an explicit `undefined` and one that omits the key both mean "AWS
|
|
53807
|
+
* does not hold it", while a key whose value genuinely IS `null` on both sides
|
|
53808
|
+
* must not read as a drop.
|
|
53809
|
+
*
|
|
53810
|
+
* The comparison is a key-order-INDEPENDENT deep equality rather than
|
|
53811
|
+
* `JSON.stringify`. A provider that rebuilds a nested object while delivering
|
|
53812
|
+
* the same members would otherwise register as a change, and the value written
|
|
53813
|
+
* back for such a key is the one that was SENT — for a non-drifted key that is
|
|
53814
|
+
* the AWS-current value, i.e. the `--accept` behavior this delta exists to
|
|
53815
|
+
* prevent.
|
|
53816
|
+
*/
|
|
53817
|
+
function collectNarrowedTopLevelKeys(sent, effective) {
|
|
53818
|
+
const delta = {};
|
|
53819
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(sent), ...Object.keys(effective)])) {
|
|
53820
|
+
const inSent = key in sent;
|
|
53821
|
+
const inEffective = key in effective;
|
|
53822
|
+
if (inSent && inEffective && deepEqualUnordered(sent[key], effective[key])) continue;
|
|
53823
|
+
if (!inSent && !inEffective) continue;
|
|
53824
|
+
delta[key] = inEffective ? effective[key] : void 0;
|
|
53825
|
+
}
|
|
53826
|
+
return delta;
|
|
53827
|
+
}
|
|
53828
|
+
/** Deep equality that does not depend on object key ORDER. */
|
|
53829
|
+
function deepEqualUnordered(a, b) {
|
|
53830
|
+
if (a === b) return true;
|
|
53831
|
+
if (a == null || b == null) return a === b;
|
|
53832
|
+
if (typeof a !== "object" || typeof b !== "object") return false;
|
|
53833
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
53834
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
53835
|
+
return a.every((v, i) => deepEqualUnordered(v, b[i]));
|
|
53836
|
+
}
|
|
53837
|
+
const ao = a;
|
|
53838
|
+
const bo = b;
|
|
53839
|
+
const ak = Object.keys(ao);
|
|
53840
|
+
if (ak.length !== Object.keys(bo).length) return false;
|
|
53841
|
+
return ak.every((k) => Object.prototype.hasOwnProperty.call(bo, k) && deepEqualUnordered(ao[k], bo[k]));
|
|
53842
|
+
}
|
|
53843
|
+
/**
|
|
53719
53844
|
* `--revert`: AWS ← state.
|
|
53720
53845
|
*
|
|
53721
53846
|
* For each drifted resource, call `provider.update(logicalId, physicalId,
|
|
@@ -53731,13 +53856,15 @@ function buildRevertNewProperties(drifts, desiredProperties, awsProperties, opti
|
|
|
53731
53856
|
* truth, captured during the drift read so we don't re-issue it).
|
|
53732
53857
|
*
|
|
53733
53858
|
* Per-resource failures are collected and surface as `PartialFailureError`
|
|
53734
|
-
* (exit 2) at the end. State is NOT updated by `--revert` — once the
|
|
53735
|
-
* update succeeds, AWS values match state by definition.
|
|
53859
|
+
* (exit 2) at the end. State is otherwise NOT updated by `--revert` — once the
|
|
53860
|
+
* update succeeds, AWS values match state by definition. The ONE exception is
|
|
53861
|
+
* a provider-reported NARROWING (issue #1644): see
|
|
53862
|
+
* {@link collectNarrowedTopLevelKeys}.
|
|
53736
53863
|
*
|
|
53737
53864
|
* The per-stack lock is acquired before any update so a concurrent
|
|
53738
53865
|
* `cdkd deploy` cannot race the in-flight property changes.
|
|
53739
53866
|
*/
|
|
53740
|
-
async function runRevert(reports, providerRegistry, stateConfig, awsClients, options) {
|
|
53867
|
+
async function runRevert(reports, providerRegistry, stateBackend, stateConfig, awsClients, options) {
|
|
53741
53868
|
const logger = getLogger();
|
|
53742
53869
|
printRevertPlan(reports);
|
|
53743
53870
|
if (options.dryRun) {
|
|
@@ -53760,6 +53887,7 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
|
|
|
53760
53887
|
const driftedOutcomes = report.outcomes.filter((o) => o.kind === "drifted");
|
|
53761
53888
|
if (driftedOutcomes.length === 0) continue;
|
|
53762
53889
|
await lockManager.acquireLock(report.stackName, report.region, owner, "drift-revert");
|
|
53890
|
+
const narrowedByLogicalId = /* @__PURE__ */ new Map();
|
|
53763
53891
|
try {
|
|
53764
53892
|
await runWithConcurrency(driftedOutcomes.map((outcome) => async () => {
|
|
53765
53893
|
const stateResource = report.state.resources[outcome.logicalId];
|
|
@@ -53775,9 +53903,17 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
|
|
|
53775
53903
|
const desiredProperties = stateResource.observedProperties ?? stateResource.properties ?? {};
|
|
53776
53904
|
const newProperties = buildRevertNewProperties(outcome.changes, desiredProperties, outcome.awsProperties, { preserveUntemplated: stateResource.observedProperties === void 0 });
|
|
53777
53905
|
try {
|
|
53778
|
-
await withRetry(() => provider.update(outcome.logicalId, stateResource.physicalId, outcome.resourceType, newProperties, outcome.awsProperties), outcome.logicalId, { logger: { debug: (msg) => logger.debug(msg) } });
|
|
53906
|
+
const updateResult = await withRetry(() => provider.update(outcome.logicalId, stateResource.physicalId, outcome.resourceType, newProperties, outcome.awsProperties), outcome.logicalId, { logger: { debug: (msg) => logger.debug(msg) } });
|
|
53779
53907
|
totalSucceeded++;
|
|
53780
53908
|
logger.info(` ✓ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): reverted.`);
|
|
53909
|
+
try {
|
|
53910
|
+
if (updateResult?.effectiveProperties) {
|
|
53911
|
+
const delta = collectNarrowedTopLevelKeys(newProperties, updateResult.effectiveProperties);
|
|
53912
|
+
if (Object.keys(delta).length > 0) narrowedByLogicalId.set(outcome.logicalId, delta);
|
|
53913
|
+
}
|
|
53914
|
+
} catch (captureErr) {
|
|
53915
|
+
logger.warn(` ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): reverted, but the provider's reported effective properties could not be read — ${captureErr instanceof Error ? captureErr.message : String(captureErr)}`);
|
|
53916
|
+
}
|
|
53781
53917
|
} catch (err) {
|
|
53782
53918
|
if (err instanceof ResourceUpdateNotSupportedError) {
|
|
53783
53919
|
totalUnsupported++;
|
|
@@ -53789,6 +53925,52 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
|
|
|
53789
53925
|
logger.error(` ✗ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): AWS update failed — ${msg}`);
|
|
53790
53926
|
}
|
|
53791
53927
|
}), concurrency);
|
|
53928
|
+
if (narrowedByLogicalId.size > 0) {
|
|
53929
|
+
const resources = { ...report.state.resources };
|
|
53930
|
+
let recordedCount = 0;
|
|
53931
|
+
for (const [logicalId, delta] of narrowedByLogicalId) {
|
|
53932
|
+
const existing = resources[logicalId];
|
|
53933
|
+
if (!existing) continue;
|
|
53934
|
+
const hasObserved = existing.observedProperties !== void 0;
|
|
53935
|
+
const baselineSource = hasObserved ? existing.observedProperties : existing.properties;
|
|
53936
|
+
const newBaseline = JSON.parse(JSON.stringify(baselineSource ?? {}));
|
|
53937
|
+
let changed = false;
|
|
53938
|
+
for (const [key, value] of Object.entries(delta)) {
|
|
53939
|
+
if (!Object.prototype.hasOwnProperty.call(newBaseline, key)) continue;
|
|
53940
|
+
if (value === void 0) {
|
|
53941
|
+
delete newBaseline[key];
|
|
53942
|
+
changed = true;
|
|
53943
|
+
continue;
|
|
53944
|
+
}
|
|
53945
|
+
if (!hasObserved) continue;
|
|
53946
|
+
newBaseline[key] = value;
|
|
53947
|
+
changed = true;
|
|
53948
|
+
}
|
|
53949
|
+
if (!changed) continue;
|
|
53950
|
+
recordedCount++;
|
|
53951
|
+
resources[logicalId] = hasObserved ? {
|
|
53952
|
+
...existing,
|
|
53953
|
+
observedProperties: newBaseline
|
|
53954
|
+
} : {
|
|
53955
|
+
...existing,
|
|
53956
|
+
properties: newBaseline
|
|
53957
|
+
};
|
|
53958
|
+
}
|
|
53959
|
+
if (recordedCount === 0) continue;
|
|
53960
|
+
const newState = {
|
|
53961
|
+
...report.state,
|
|
53962
|
+
resources,
|
|
53963
|
+
lastModified: Date.now()
|
|
53964
|
+
};
|
|
53965
|
+
const saveOptions = { expectedEtag: report.etag };
|
|
53966
|
+
if (report.migrationPending) saveOptions.migrateLegacy = true;
|
|
53967
|
+
try {
|
|
53968
|
+
await stateBackend.saveState(report.stackName, report.region, newState, saveOptions);
|
|
53969
|
+
logger.info(`✓ State updated for ${report.stackName} (${report.region}): recorded the value the provider actually applied on ${recordedCount} resource(s).`);
|
|
53970
|
+
} catch (err) {
|
|
53971
|
+
logger.warn(`Reverted ${report.stackName} (${report.region}), but could not record the value the provider actually applied: ${err instanceof Error ? err.message : String(err)}. The next 'cdkd drift' will report the same difference — re-run 'cdkd drift ${report.stackName} --revert' once the state write can succeed.`);
|
|
53972
|
+
}
|
|
53973
|
+
}
|
|
53792
53974
|
} finally {
|
|
53793
53975
|
await lockManager.releaseLock(report.stackName, report.region).catch((err) => {
|
|
53794
53976
|
logger.warn(`Failed to release lock for ${report.stackName} (${report.region}): ` + (err instanceof Error ? err.message : String(err)));
|
|
@@ -70802,7 +70984,7 @@ function createMigrateCommand() {
|
|
|
70802
70984
|
*/
|
|
70803
70985
|
function buildProgram() {
|
|
70804
70986
|
const program = new Command();
|
|
70805
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.
|
|
70987
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.33");
|
|
70806
70988
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
70807
70989
|
const { profile } = actionCommand.optsWithGlobals();
|
|
70808
70990
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|