@go-to-k/cdkd 0.230.16 → 0.230.17
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/cli.js +11 -5
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-BqUZUty9.js → deploy-engine-CAG2RhWS.js} +129 -4
- package/dist/deploy-engine-CAG2RhWS.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-BqUZUty9.js.map +0 -1
|
@@ -4867,6 +4867,24 @@ var ReplacementRulesRegistry = class {
|
|
|
4867
4867
|
return false;
|
|
4868
4868
|
}
|
|
4869
4869
|
/**
|
|
4870
|
+
* Whether the registry has an EXPLICIT opinion about a property's
|
|
4871
|
+
* replacement behavior — i.e. the type has a rule AND the property is listed
|
|
4872
|
+
* in its `replacementProperties`, `updateableProperties`, OR
|
|
4873
|
+
* `conditionalReplacements`. Returns false for an unknown type, or for a
|
|
4874
|
+
* known type whose rule does not mention the property (the "default to
|
|
4875
|
+
* updateable" fall-through inside {@link requiresReplacement}).
|
|
4876
|
+
*
|
|
4877
|
+
* The diff calculator uses this to decide whether the CFn-schema
|
|
4878
|
+
* `createOnlyProperties` fallback may apply: the schema only fills the GAP
|
|
4879
|
+
* where the registry has no opinion, so a deliberate `updateableProperties`
|
|
4880
|
+
* (or conditional) classification is never overridden by the fallback.
|
|
4881
|
+
*/
|
|
4882
|
+
isClassified(resourceType, propertyPath) {
|
|
4883
|
+
const rule = this.rules.get(resourceType);
|
|
4884
|
+
if (!rule) return false;
|
|
4885
|
+
return rule.replacementProperties.has(propertyPath) || (rule.updateableProperties?.has(propertyPath) ?? false) || (rule.conditionalReplacements?.has(propertyPath) ?? false);
|
|
4886
|
+
}
|
|
4887
|
+
/**
|
|
4870
4888
|
* Initialize replacement rules for common AWS resource types
|
|
4871
4889
|
*/
|
|
4872
4890
|
initializeRules() {
|
|
@@ -5117,6 +5135,98 @@ var ReplacementRulesRegistry = class {
|
|
|
5117
5135
|
}
|
|
5118
5136
|
};
|
|
5119
5137
|
|
|
5138
|
+
//#endregion
|
|
5139
|
+
//#region src/provisioning/create-only-properties.ts
|
|
5140
|
+
/**
|
|
5141
|
+
* Create-only (immutable) property resolution for replacement detection.
|
|
5142
|
+
*
|
|
5143
|
+
* CloudFormation marks some properties "Update requires: Replacement" — the
|
|
5144
|
+
* resource type's registry schema lists them under `createOnlyProperties`.
|
|
5145
|
+
* Changing such a property cannot be an in-place UPDATE; CloudFormation
|
|
5146
|
+
* transparently DELETE+CREATEs the resource. cdkd's diff classifier
|
|
5147
|
+
* (`ReplacementRulesRegistry`) only knows the ~25 types with a hand-authored
|
|
5148
|
+
* rule, so for every other type an immutable-property change was previously
|
|
5149
|
+
* mis-classified as an in-place UPDATE (the provider's `update()` then either
|
|
5150
|
+
* rejected it with a typed error — at best — or silently dropped it).
|
|
5151
|
+
*
|
|
5152
|
+
* This module resolves each resource type's `createOnlyProperties` from the
|
|
5153
|
+
* CloudFormation registry schema via `cloudformation:DescribeType`, reduced to
|
|
5154
|
+
* the TOP-LEVEL containing property names (a nested path like
|
|
5155
|
+
* `/properties/Foo/Bar` strips to `Foo`) so the diff's top-level property keys
|
|
5156
|
+
* match. The diff calculator consults it as a fallback for any property the
|
|
5157
|
+
* registry does not explicitly classify, so a createOnly change on ANY type now
|
|
5158
|
+
* correctly drives a replacement.
|
|
5159
|
+
*
|
|
5160
|
+
* Caching / failure semantics are identical to {@link
|
|
5161
|
+
* ./write-only-properties.ts} (the sibling DescribeType-backed resolver): only
|
|
5162
|
+
* SUCCESSFUL lookups are cached per resource type for the process (deploy)
|
|
5163
|
+
* lifetime — `cloudformation:DescribeType` is throttled per-account and the
|
|
5164
|
+
* schema cannot change mid-deploy. A FAILED lookup (missing IAM permission,
|
|
5165
|
+
* transient throttle / 5xx) is logged as a warning and resolves to an empty set
|
|
5166
|
+
* WITHOUT poisoning the cache, so the caller gracefully falls back to the
|
|
5167
|
+
* registry-only classification for THIS resource while a later resource of the
|
|
5168
|
+
* same type retries. A caller permanently without `cloudformation:DescribeType`
|
|
5169
|
+
* simply keeps the pre-existing registry-only behavior — no regression.
|
|
5170
|
+
*/
|
|
5171
|
+
/**
|
|
5172
|
+
* Per-type cache of SUCCESSFUL lookups only. The value is the in-flight (or
|
|
5173
|
+
* settled) promise so concurrent diffs of the same resource type share a
|
|
5174
|
+
* single DescribeType call. A failed lookup removes its own entry so a later
|
|
5175
|
+
* call retries instead of being permanently poisoned by a transient throttle.
|
|
5176
|
+
*/
|
|
5177
|
+
const createOnlyPropertiesCache = /* @__PURE__ */ new Map();
|
|
5178
|
+
/**
|
|
5179
|
+
* Resolve the TOP-LEVEL create-only (immutable) property names for a resource
|
|
5180
|
+
* type.
|
|
5181
|
+
*
|
|
5182
|
+
* Never throws: a DescribeType failure logs a warning and resolves to an empty
|
|
5183
|
+
* set (graceful fallback to the registry-only classification). Only SUCCESSFUL
|
|
5184
|
+
* lookups are cached per resource type for the process lifetime; a failed
|
|
5185
|
+
* lookup is NOT cached, so a later call for the same type retries DescribeType
|
|
5186
|
+
* (a transient throttle must not poison the deploy's replacement detection).
|
|
5187
|
+
*/
|
|
5188
|
+
function getTopLevelCreateOnlyProperties(resourceType) {
|
|
5189
|
+
const cached = createOnlyPropertiesCache.get(resourceType);
|
|
5190
|
+
if (cached) return cached;
|
|
5191
|
+
const entry = fetchTopLevelCreateOnlyProperties(resourceType).catch((error) => {
|
|
5192
|
+
createOnlyPropertiesCache.delete(resourceType);
|
|
5193
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5194
|
+
getLogger().child("CreateOnlyProperties").warn(`Failed to resolve create-only properties for ${resourceType} via cloudformation:DescribeType (${message}). Falling back to the registry-only replacement classification for this resource — an immutable-property change may be mis-classified as an in-place update. Grant cloudformation:DescribeType to enable schema-driven replacement detection.`);
|
|
5195
|
+
return /* @__PURE__ */ new Set();
|
|
5196
|
+
});
|
|
5197
|
+
createOnlyPropertiesCache.set(resourceType, entry);
|
|
5198
|
+
return entry;
|
|
5199
|
+
}
|
|
5200
|
+
/**
|
|
5201
|
+
* Fetch + parse the type's create-only properties. THROWS on a DescribeType
|
|
5202
|
+
* failure — the caller ({@link getTopLevelCreateOnlyProperties}) catches,
|
|
5203
|
+
* warns, and declines to cache so the lookup can be retried later.
|
|
5204
|
+
*/
|
|
5205
|
+
async function fetchTopLevelCreateOnlyProperties(resourceType) {
|
|
5206
|
+
const logger = getLogger().child("CreateOnlyProperties");
|
|
5207
|
+
const response = await getAwsClients().cloudFormation.send(new DescribeTypeCommand({
|
|
5208
|
+
Type: "RESOURCE",
|
|
5209
|
+
TypeName: resourceType
|
|
5210
|
+
}));
|
|
5211
|
+
const result = /* @__PURE__ */ new Set();
|
|
5212
|
+
if (response.Schema) {
|
|
5213
|
+
const createOnly = JSON.parse(response.Schema).createOnlyProperties;
|
|
5214
|
+
if (Array.isArray(createOnly)) for (const path of createOnly) {
|
|
5215
|
+
if (typeof path !== "string") continue;
|
|
5216
|
+
const match = /^\/properties\/([^/]+)/.exec(path);
|
|
5217
|
+
if (match?.[1]) result.add(unescapeJsonPointerSegment$1(match[1]));
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
logger.debug(`Resolved ${result.size} top-level create-only properties for ${resourceType}` + (result.size > 0 ? `: ${[...result].join(", ")}` : ""));
|
|
5221
|
+
return result;
|
|
5222
|
+
}
|
|
5223
|
+
/**
|
|
5224
|
+
* Unescape an RFC 6901 JSON Pointer segment (`~1` -> `/`, `~0` -> `~`).
|
|
5225
|
+
*/
|
|
5226
|
+
function unescapeJsonPointerSegment$1(segment) {
|
|
5227
|
+
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5120
5230
|
//#endregion
|
|
5121
5231
|
//#region src/analyzer/diff-calculator.ts
|
|
5122
5232
|
/**
|
|
@@ -5191,7 +5301,7 @@ var DiffCalculator = class DiffCalculator {
|
|
|
5191
5301
|
} else {
|
|
5192
5302
|
const rawDesiredProps = desiredResource.Properties || {};
|
|
5193
5303
|
const desiredPropsForCompare = resolveFn ? await this.resolveBestEffort(rawDesiredProps, resolveFn) : rawDesiredProps;
|
|
5194
|
-
const propertyChanges = this.compareProperties(desiredResource.Type, currentResource.properties, desiredPropsForCompare);
|
|
5304
|
+
const propertyChanges = await this.compareProperties(desiredResource.Type, currentResource.properties, desiredPropsForCompare);
|
|
5195
5305
|
const attributeChanges = this.compareAttributes(currentResource, desiredResource);
|
|
5196
5306
|
if (propertyChanges.length > 0 || attributeChanges.length > 0) {
|
|
5197
5307
|
changes.set(logicalId, {
|
|
@@ -5497,17 +5607,25 @@ var DiffCalculator = class DiffCalculator {
|
|
|
5497
5607
|
* Uses ReplacementRulesRegistry to determine which property changes require replacement.
|
|
5498
5608
|
* Reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html
|
|
5499
5609
|
*/
|
|
5500
|
-
compareProperties(resourceType, currentProperties, desiredProperties) {
|
|
5610
|
+
async compareProperties(resourceType, currentProperties, desiredProperties) {
|
|
5501
5611
|
const changes = [];
|
|
5502
5612
|
const allKeys = new Set([...Object.keys(currentProperties), ...Object.keys(desiredProperties)]);
|
|
5503
5613
|
const ignoredProperties = /* @__PURE__ */ new Set();
|
|
5504
5614
|
if (resourceType === "AWS::CloudFormation::CustomResource" || resourceType.startsWith("Custom::")) ignoredProperties.add("Timestamp");
|
|
5615
|
+
let createOnlyProps;
|
|
5505
5616
|
for (const key of allKeys) {
|
|
5506
5617
|
if (ignoredProperties.has(key)) continue;
|
|
5507
5618
|
const oldValue = currentProperties[key];
|
|
5508
5619
|
const newValue = desiredProperties[key];
|
|
5509
5620
|
if (!this.valuesEqual(oldValue, newValue)) {
|
|
5510
|
-
|
|
5621
|
+
let requiresReplacement = this.replacementRules.requiresReplacement(resourceType, key, oldValue, newValue);
|
|
5622
|
+
if (!requiresReplacement && !this.replacementRules.isClassified(resourceType, key)) {
|
|
5623
|
+
if (createOnlyProps === void 0) createOnlyProps = await getTopLevelCreateOnlyProperties(resourceType);
|
|
5624
|
+
if (createOnlyProps.has(key)) {
|
|
5625
|
+
requiresReplacement = true;
|
|
5626
|
+
this.logger.debug(`Property ${key} of ${resourceType} is createOnly per the CFn schema — requires replacement`);
|
|
5627
|
+
}
|
|
5628
|
+
}
|
|
5511
5629
|
changes.push({
|
|
5512
5630
|
path: key,
|
|
5513
5631
|
oldValue,
|
|
@@ -13676,6 +13794,13 @@ var DeployEngine = class {
|
|
|
13676
13794
|
const needsReplacement = propertyDrivenReplacement || recreateFlagged;
|
|
13677
13795
|
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
13678
13796
|
if (needsReplacement) {
|
|
13797
|
+
if (propertyDrivenReplacement && !recreateFlagged) {
|
|
13798
|
+
const statefulReason = isStatefulRecreateTargetForReplace(resourceType, currentProps);
|
|
13799
|
+
if (statefulReason && this.options.forceStatefulRecreation !== true) {
|
|
13800
|
+
const immutableProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
|
|
13801
|
+
throw new CdkdError(`${logicalId} (${resourceType}) requires replacement (immutable property changed: ${immutableProps}) but it is a stateful resource — ${renderStatefulReason(statefulReason)}. Re-run with --force-stateful-recreation to confirm the data loss, or change the resource definition to avoid the immutable-property change.`, "STATEFUL_REPLACE_BLOCKED");
|
|
13802
|
+
}
|
|
13803
|
+
}
|
|
13679
13804
|
let replacementReason;
|
|
13680
13805
|
if (recreateViaCcApi) replacementReason = "--recreate-via-cc-api flag (mid-life SDK→CC migration)";
|
|
13681
13806
|
else if (recreateViaSdkProvider) replacementReason = "--recreate-via-sdk-provider flag (mid-life CC→SDK migration)";
|
|
@@ -14036,4 +14161,4 @@ var DeployEngine = class {
|
|
|
14036
14161
|
|
|
14037
14162
|
//#endregion
|
|
14038
14163
|
export { CFN_TEMPLATE_BODY_LIMIT as $, LockManager as A, runDockerForeground as B, findActionableSilentDrops as C, DiffCalculator as D, applyRoleArnIfSet as E, stringifyValue as F, getDefaultStateBucketName as G, AssetManifestLoader as H, WorkGraph as I, resolveCaptureObservedState as J, getLegacyStateBucketName as K, buildDockerImage as L, rebuildClientForBucketRegion as M, shouldRetainResource as N, DagBuilder as O, AssetPublisher as P, warnDeprecatedNoPrefixCliFlag as Q, formatDockerLoginError as R, ProviderRegistry as S, IntrinsicFunctionResolver as T, getDockerImageBySourceHash as U, runDockerStreaming as V, Synthesizer as W, resolveStateBucketWithDefault as X, resolveSkipPrefix as Y, resolveStateBucketWithDefaultAndSource as Z, green as _, withRetry as a, clearBucketRegionCache as at, IAMRoleProvider as b, computeImplicitDeleteEdges as c, isStatefulRecreateTargetSync as d, CFN_TEMPLATE_URL_LIMIT as et, renderStatefulReason as f, gray as g, cyan as h, withResourceDeadline as i, AssemblyReader as it, S3StateBackend as j, TemplateParser as k, extractDeploymentEventError as l, bold as m, DEFAULT_RESOURCE_WARN_AFTER_MS as n, findLargeInlineResources as nt, isRetryableTransientError as o, resolveBucketRegion as ot, formatResourceLine as p, resolveApp as q, DeployEngine as r, uploadCfnTemplate as rt, IMPLICIT_DELETE_DEPENDENCIES as s, DEFAULT_RESOURCE_TIMEOUT_MS as t, MIGRATE_TMP_PREFIX as tt, MULTI_REGION_RECREATE_BLOCKED_TYPES as u, red as v, CloudControlProvider as w, collectInlinePolicyNamesManagedBySiblings as x, yellow as y, getDockerCmd as z };
|
|
14039
|
-
//# sourceMappingURL=deploy-engine-
|
|
14164
|
+
//# sourceMappingURL=deploy-engine-CAG2RhWS.js.map
|