@go-to-k/cdkd 0.219.6 → 0.219.8
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 +4 -3
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-CwB8POpC.js → deploy-engine-Bl8wyosc.js} +219 -3
- package/dist/deploy-engine-Bl8wyosc.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-CwB8POpC.js.map +0 -1
|
@@ -9,7 +9,7 @@ import { GetFunctionUrlConfigCommand, InvokeCommand, LambdaClient, UpdateFunctio
|
|
|
9
9
|
import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
10
10
|
import { DescribeAvailabilityZonesCommand, DescribeImagesCommand, DescribeLaunchTemplatesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVpcsCommand, DescribeVpnGatewaysCommand, EC2Client } from "@aws-sdk/client-ec2";
|
|
11
11
|
import { DescribeTableCommand } from "@aws-sdk/client-dynamodb";
|
|
12
|
-
import { CloudFormationClient, CreateChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, GetTemplateCommand, waitUntilChangeSetCreateComplete } from "@aws-sdk/client-cloudformation";
|
|
12
|
+
import { CloudFormationClient, CreateChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, DescribeTypeCommand, GetTemplateCommand, waitUntilChangeSetCreateComplete } from "@aws-sdk/client-cloudformation";
|
|
13
13
|
import { GetRestApiCommand } from "@aws-sdk/client-api-gateway";
|
|
14
14
|
import { GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
|
|
15
15
|
import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
@@ -3763,6 +3763,22 @@ var TemplateParser = class {
|
|
|
3763
3763
|
return dependencies;
|
|
3764
3764
|
}
|
|
3765
3765
|
/**
|
|
3766
|
+
* Extract resource references (Ref / Fn::GetAtt / Fn::Sub / Fn::Join /
|
|
3767
|
+
* Fn::Select / Fn::Split) from an arbitrary template value.
|
|
3768
|
+
*
|
|
3769
|
+
* Public wrapper over the same recursive extraction `extractDependencies`
|
|
3770
|
+
* uses for Properties — exposed so the diff phase can compute
|
|
3771
|
+
* per-property reference edges (issue #807 replacement propagation)
|
|
3772
|
+
* without duplicating the intrinsic walk. Unlike `extractDependencies`
|
|
3773
|
+
* this does NOT include `DependsOn` (which is pure ordering, not a data
|
|
3774
|
+
* reference — a replaced dependency carries no new value to propagate).
|
|
3775
|
+
*/
|
|
3776
|
+
extractReferences(value) {
|
|
3777
|
+
const references = /* @__PURE__ */ new Set();
|
|
3778
|
+
this.extractRefsFromValue(value, references);
|
|
3779
|
+
return references;
|
|
3780
|
+
}
|
|
3781
|
+
/**
|
|
3766
3782
|
* Recursively extract Ref and Fn::GetAtt from a value
|
|
3767
3783
|
*/
|
|
3768
3784
|
extractRefsFromValue(value, dependencies) {
|
|
@@ -4585,6 +4601,7 @@ var ReplacementRulesRegistry = class {
|
|
|
4585
4601
|
var DiffCalculator = class DiffCalculator {
|
|
4586
4602
|
logger = getLogger().child("DiffCalculator");
|
|
4587
4603
|
replacementRules = new ReplacementRulesRegistry();
|
|
4604
|
+
parser = new TemplateParser();
|
|
4588
4605
|
/**
|
|
4589
4606
|
* Calculate changes needed to reach desired state
|
|
4590
4607
|
*
|
|
@@ -4674,11 +4691,103 @@ var DiffCalculator = class DiffCalculator {
|
|
|
4674
4691
|
});
|
|
4675
4692
|
this.logger.debug(`DELETE: ${logicalId} (${currentResource.resourceType})`);
|
|
4676
4693
|
}
|
|
4694
|
+
this.promoteReplacementDependents(changes, desiredTemplate);
|
|
4677
4695
|
const summary = this.getSummary(changes);
|
|
4678
4696
|
this.logger.debug(`Diff calculated: ${summary.create} CREATE, ${summary.update} UPDATE, ${summary.delete} DELETE, ${summary.noChange} NO_CHANGE`);
|
|
4679
4697
|
return changes;
|
|
4680
4698
|
}
|
|
4681
4699
|
/**
|
|
4700
|
+
* Promote transitive dependents of to-be-replaced resources from
|
|
4701
|
+
* NO_CHANGE to UPDATE (issue #807).
|
|
4702
|
+
*
|
|
4703
|
+
* Diff-time intrinsic resolution runs against the CURRENT state, so a
|
|
4704
|
+
* dependent referencing a resource that will be REPLACED (new physical
|
|
4705
|
+
* ID — e.g. an `AWS::ECS::TaskDefinition` revision) compares equal and
|
|
4706
|
+
* stays NO_CHANGE; the deploy engine then never re-points it at the new
|
|
4707
|
+
* physical resource (for ECS: `UpdateService` is never issued and the
|
|
4708
|
+
* service keeps running the old, now-deregistered revision).
|
|
4709
|
+
* CloudFormation propagates the new physical ID to dependents — mirror
|
|
4710
|
+
* that here by walking reverse reference edges (`Ref` / `Fn::GetAtt` /
|
|
4711
|
+
* `Fn::Sub` and intrinsics nesting them — the same extraction the DAG
|
|
4712
|
+
* builder uses) from every replacement-triggering UPDATE and promoting
|
|
4713
|
+
* NO_CHANGE dependents to UPDATE.
|
|
4714
|
+
*
|
|
4715
|
+
* Promotion is safe even when speculative: the deploy engine re-resolves
|
|
4716
|
+
* the promoted resource's properties against the in-flight state map
|
|
4717
|
+
* (which the DAG guarantees already carries the dependency's new
|
|
4718
|
+
* physical ID) and skips the provider call if nothing actually changed.
|
|
4719
|
+
*
|
|
4720
|
+
* Promotion is transitive: each referencing property of a promoted
|
|
4721
|
+
* dependent is re-evaluated against the replacement rules, and if the
|
|
4722
|
+
* dependent is itself replacement-triggering (the referencing property
|
|
4723
|
+
* is immutable for its type), its own dependents are promoted in turn.
|
|
4724
|
+
* The same re-evaluation also applies to dependents that already had
|
|
4725
|
+
* their own (non-replacement) property changes — their referencing
|
|
4726
|
+
* property gains a synthetic PropertyChange so a replacement cascade is
|
|
4727
|
+
* not masked by an unrelated in-place change.
|
|
4728
|
+
*/
|
|
4729
|
+
promoteReplacementDependents(changes, desiredTemplate) {
|
|
4730
|
+
const queue = [];
|
|
4731
|
+
for (const [logicalId, change] of changes) if (change.changeType === "UPDATE" && change.propertyChanges?.some((pc) => pc.requiresReplacement)) queue.push(logicalId);
|
|
4732
|
+
if (queue.length === 0) return;
|
|
4733
|
+
const dependentsOf = /* @__PURE__ */ new Map();
|
|
4734
|
+
for (const [logicalId, resource] of Object.entries(desiredTemplate.Resources)) {
|
|
4735
|
+
if (resource.Type === "AWS::CDK::Metadata") continue;
|
|
4736
|
+
for (const [propKey, propValue] of Object.entries(resource.Properties ?? {})) for (const referencedId of this.parser.extractReferences(propValue)) {
|
|
4737
|
+
if (referencedId === logicalId) continue;
|
|
4738
|
+
let dependents = dependentsOf.get(referencedId);
|
|
4739
|
+
if (!dependents) {
|
|
4740
|
+
dependents = /* @__PURE__ */ new Map();
|
|
4741
|
+
dependentsOf.set(referencedId, dependents);
|
|
4742
|
+
}
|
|
4743
|
+
let propKeys = dependents.get(logicalId);
|
|
4744
|
+
if (!propKeys) {
|
|
4745
|
+
propKeys = /* @__PURE__ */ new Set();
|
|
4746
|
+
dependents.set(logicalId, propKeys);
|
|
4747
|
+
}
|
|
4748
|
+
propKeys.add(propKey);
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
const enqueued = new Set(queue);
|
|
4752
|
+
while (queue.length > 0) {
|
|
4753
|
+
const replacedId = queue.shift();
|
|
4754
|
+
const dependents = dependentsOf.get(replacedId);
|
|
4755
|
+
if (!dependents) continue;
|
|
4756
|
+
for (const [dependentId, refPropKeys] of dependents) {
|
|
4757
|
+
const change = changes.get(dependentId);
|
|
4758
|
+
if (!change) continue;
|
|
4759
|
+
if (change.changeType !== "NO_CHANGE" && change.changeType !== "UPDATE") continue;
|
|
4760
|
+
const existingPaths = new Set((change.propertyChanges ?? []).map((pc) => pc.path));
|
|
4761
|
+
const syntheticChanges = [];
|
|
4762
|
+
for (const propKey of refPropKeys) {
|
|
4763
|
+
if (existingPaths.has(propKey)) continue;
|
|
4764
|
+
const oldValue = change.currentProperties?.[propKey];
|
|
4765
|
+
const newValue = change.desiredProperties?.[propKey];
|
|
4766
|
+
syntheticChanges.push({
|
|
4767
|
+
path: propKey,
|
|
4768
|
+
oldValue,
|
|
4769
|
+
newValue,
|
|
4770
|
+
replacementPropagated: true,
|
|
4771
|
+
requiresReplacement: this.replacementRules.requiresReplacement(change.resourceType, propKey, void 0, void 0)
|
|
4772
|
+
});
|
|
4773
|
+
}
|
|
4774
|
+
if (syntheticChanges.length === 0) continue;
|
|
4775
|
+
if (change.changeType === "NO_CHANGE") {
|
|
4776
|
+
change.changeType = "UPDATE";
|
|
4777
|
+
change.propertyChanges = syntheticChanges;
|
|
4778
|
+
this.logger.debug(`UPDATE (promoted): ${dependentId} references replaced resource ${replacedId} via ${[...refPropKeys].join(", ")}`);
|
|
4779
|
+
} else {
|
|
4780
|
+
change.propertyChanges = [...change.propertyChanges ?? [], ...syntheticChanges];
|
|
4781
|
+
this.logger.debug(`UPDATE (augmented): ${dependentId} references replaced resource ${replacedId} via ${[...refPropKeys].join(", ")}`);
|
|
4782
|
+
}
|
|
4783
|
+
if (!enqueued.has(dependentId) && change.propertyChanges.some((pc) => pc.requiresReplacement)) {
|
|
4784
|
+
enqueued.add(dependentId);
|
|
4785
|
+
queue.push(dependentId);
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
/**
|
|
4682
4791
|
* Best-effort resolution of template property intrinsics against current state.
|
|
4683
4792
|
*
|
|
4684
4793
|
* Iterates top-level properties and resolves each independently: if resolution
|
|
@@ -6388,6 +6497,100 @@ var JsonPatchGenerator = class {
|
|
|
6388
6497
|
}
|
|
6389
6498
|
};
|
|
6390
6499
|
|
|
6500
|
+
//#endregion
|
|
6501
|
+
//#region src/provisioning/write-only-properties.ts
|
|
6502
|
+
/**
|
|
6503
|
+
* Write-only property resolution for Cloud Control API updates (issue #809).
|
|
6504
|
+
*
|
|
6505
|
+
* Cloud Control API applies UPDATE patch documents with read-modify-write
|
|
6506
|
+
* semantics: the type's read handler returns the current model, the patch is
|
|
6507
|
+
* applied on top, and the result becomes the desired state handed to the
|
|
6508
|
+
* update handler. Read handlers cannot return write-only properties, so any
|
|
6509
|
+
* write-only property that is not explicitly present in the patch document
|
|
6510
|
+
* vanishes from the desired state on every CC-routed UPDATE (e.g.
|
|
6511
|
+
* `AWS::ECS::Service` loses `VolumeConfigurations` and the update hard-fails;
|
|
6512
|
+
* for other types the loss is silent).
|
|
6513
|
+
*
|
|
6514
|
+
* This module resolves each resource type's `writeOnlyProperties` from the
|
|
6515
|
+
* CloudFormation registry schema via `cloudformation:DescribeType`, reduced
|
|
6516
|
+
* to the TOP-LEVEL containing property names (a nested path like
|
|
6517
|
+
* `/properties/Foo/Bar` strips to `Foo` — re-adding the whole containing
|
|
6518
|
+
* property is sufficient and matches terraform-provider-awscc's approach of
|
|
6519
|
+
* clearing write-only attribute paths in the prior state so the patch
|
|
6520
|
+
* generator always emits `add` ops for them).
|
|
6521
|
+
*
|
|
6522
|
+
* Only SUCCESSFUL lookups are cached per resource type for the process
|
|
6523
|
+
* (deploy) lifetime — `cloudformation:DescribeType` is throttled per-account,
|
|
6524
|
+
* and the schema cannot change mid-deploy. A FAILED lookup (missing IAM
|
|
6525
|
+
* permission, transient throttle / 5xx) is logged as a warning and resolves
|
|
6526
|
+
* to an empty set WITHOUT poisoning the cache, so the caller gracefully falls
|
|
6527
|
+
* back to the minimal patch for THIS update while a later update of the same
|
|
6528
|
+
* type retries `DescribeType`. Caching failures would let a single transient
|
|
6529
|
+
* throttle on the first CC-routed UPDATE silently disable write-only
|
|
6530
|
+
* re-inclusion for every CC-routed type for the rest of the deploy —
|
|
6531
|
+
* reintroducing the exact hard-fail this module fixes. No regression for
|
|
6532
|
+
* callers permanently without the `cloudformation:DescribeType` permission:
|
|
6533
|
+
* each update simply re-warns and re-falls-back.
|
|
6534
|
+
*/
|
|
6535
|
+
/**
|
|
6536
|
+
* Per-type cache of SUCCESSFUL lookups only. The value is the in-flight (or
|
|
6537
|
+
* settled) promise so that concurrent updates of the same resource type share
|
|
6538
|
+
* a single DescribeType call. A failed lookup removes its own entry so a
|
|
6539
|
+
* later call retries instead of being permanently poisoned by a transient
|
|
6540
|
+
* throttle / 5xx.
|
|
6541
|
+
*/
|
|
6542
|
+
const writeOnlyPropertiesCache = /* @__PURE__ */ new Map();
|
|
6543
|
+
/**
|
|
6544
|
+
* Resolve the TOP-LEVEL write-only property names for a resource type.
|
|
6545
|
+
*
|
|
6546
|
+
* Never throws: a DescribeType failure logs a warning and resolves to an
|
|
6547
|
+
* empty set (graceful fallback to the minimal patch). Only SUCCESSFUL
|
|
6548
|
+
* lookups are cached per resource type for the process lifetime; a failed
|
|
6549
|
+
* lookup is NOT cached, so a later call for the same type retries
|
|
6550
|
+
* DescribeType (a transient throttle must not poison the deploy).
|
|
6551
|
+
*/
|
|
6552
|
+
function getTopLevelWriteOnlyProperties(resourceType) {
|
|
6553
|
+
const cached = writeOnlyPropertiesCache.get(resourceType);
|
|
6554
|
+
if (cached) return cached;
|
|
6555
|
+
const entry = fetchTopLevelWriteOnlyProperties(resourceType).catch((error) => {
|
|
6556
|
+
writeOnlyPropertiesCache.delete(resourceType);
|
|
6557
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6558
|
+
getLogger().child("WriteOnlyProperties").warn(`Failed to resolve write-only properties for ${resourceType} via cloudformation:DescribeType (${message}). Falling back to a minimal update patch — write-only properties (if any) may be dropped by the Cloud Control read-modify-write update. Grant cloudformation:DescribeType to enable write-only property re-inclusion.`);
|
|
6559
|
+
return /* @__PURE__ */ new Set();
|
|
6560
|
+
});
|
|
6561
|
+
writeOnlyPropertiesCache.set(resourceType, entry);
|
|
6562
|
+
return entry;
|
|
6563
|
+
}
|
|
6564
|
+
/**
|
|
6565
|
+
* Fetch + parse the type's write-only properties. THROWS on a DescribeType
|
|
6566
|
+
* failure — the caller (`getTopLevelWriteOnlyProperties`) catches, warns, and
|
|
6567
|
+
* declines to cache so the lookup can be retried later.
|
|
6568
|
+
*/
|
|
6569
|
+
async function fetchTopLevelWriteOnlyProperties(resourceType) {
|
|
6570
|
+
const logger = getLogger().child("WriteOnlyProperties");
|
|
6571
|
+
const response = await getAwsClients().cloudFormation.send(new DescribeTypeCommand({
|
|
6572
|
+
Type: "RESOURCE",
|
|
6573
|
+
TypeName: resourceType
|
|
6574
|
+
}));
|
|
6575
|
+
const result = /* @__PURE__ */ new Set();
|
|
6576
|
+
if (response.Schema) {
|
|
6577
|
+
const writeOnly = JSON.parse(response.Schema).writeOnlyProperties;
|
|
6578
|
+
if (Array.isArray(writeOnly)) for (const path of writeOnly) {
|
|
6579
|
+
if (typeof path !== "string") continue;
|
|
6580
|
+
const match = /^\/properties\/([^/]+)/.exec(path);
|
|
6581
|
+
if (match?.[1]) result.add(unescapeJsonPointerSegment(match[1]));
|
|
6582
|
+
}
|
|
6583
|
+
}
|
|
6584
|
+
logger.debug(`Resolved ${result.size} top-level write-only properties for ${resourceType}` + (result.size > 0 ? `: ${[...result].join(", ")}` : ""));
|
|
6585
|
+
return result;
|
|
6586
|
+
}
|
|
6587
|
+
/**
|
|
6588
|
+
* Unescape an RFC 6901 JSON Pointer segment (`~1` -> `/`, `~0` -> `~`).
|
|
6589
|
+
*/
|
|
6590
|
+
function unescapeJsonPointerSegment(segment) {
|
|
6591
|
+
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6391
6594
|
//#endregion
|
|
6392
6595
|
//#region src/provisioning/unsupported-types.generated.ts
|
|
6393
6596
|
/**
|
|
@@ -6700,7 +6903,7 @@ var CloudControlProvider = class {
|
|
|
6700
6903
|
try {
|
|
6701
6904
|
const cleanPreviousProperties = stringifyJsonProperties(resourceType, stripNullValues(previousProperties));
|
|
6702
6905
|
const cleanProperties = stringifyJsonProperties(resourceType, stripNullValues(properties));
|
|
6703
|
-
|
|
6906
|
+
let patch = this.patchGenerator.generatePatch(cleanPreviousProperties, cleanProperties);
|
|
6704
6907
|
if (patch.length === 0) {
|
|
6705
6908
|
this.logger.debug(`No property changes detected for ${logicalId}, skipping update`);
|
|
6706
6909
|
return {
|
|
@@ -6708,6 +6911,19 @@ var CloudControlProvider = class {
|
|
|
6708
6911
|
wasReplaced: false
|
|
6709
6912
|
};
|
|
6710
6913
|
}
|
|
6914
|
+
const writeOnlyProperties = await getTopLevelWriteOnlyProperties(resourceType);
|
|
6915
|
+
if (writeOnlyProperties.size > 0) {
|
|
6916
|
+
const previousWithoutWriteOnly = { ...cleanPreviousProperties };
|
|
6917
|
+
for (const propertyName of writeOnlyProperties) delete previousWithoutWriteOnly[propertyName];
|
|
6918
|
+
patch = this.patchGenerator.generatePatch(previousWithoutWriteOnly, cleanProperties);
|
|
6919
|
+
if (patch.length === 0) {
|
|
6920
|
+
this.logger.debug(`Only removed write-only properties detected for ${logicalId}, skipping update`);
|
|
6921
|
+
return {
|
|
6922
|
+
physicalId,
|
|
6923
|
+
wasReplaced: false
|
|
6924
|
+
};
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6711
6927
|
this.logger.debug(`Generated ${patch.length} patch operations for ${logicalId}: ${JSON.stringify(patch)}`);
|
|
6712
6928
|
const updateResponse = await this.cloudControlClient.send(new UpdateResourceCommand({
|
|
6713
6929
|
TypeName: resourceType,
|
|
@@ -12272,4 +12488,4 @@ var DeployEngine = class {
|
|
|
12272
12488
|
|
|
12273
12489
|
//#endregion
|
|
12274
12490
|
export { WorkGraph as A, resolveCaptureObservedState as B, DagBuilder as C, shouldRetainResource as D, S3StateBackend as E, runDockerStreaming as F, CFN_TEMPLATE_BODY_LIMIT as G, resolveStateBucketWithDefault as H, Synthesizer as I, findLargeInlineResources as J, CFN_TEMPLATE_URL_LIMIT as K, getDefaultStateBucketName as L, formatDockerLoginError as M, getDockerCmd as N, AssetPublisher as O, runDockerForeground as P, resolveBucketRegion as Q, getLegacyStateBucketName as R, DiffCalculator as S, LockManager as T, resolveStateBucketWithDefaultAndSource as U, resolveSkipPrefix as V, warnDeprecatedNoPrefixCliFlag as W, AssemblyReader as X, uploadCfnTemplate as Y, clearBucketRegionCache as Z, ProviderRegistry as _, withRetry as a, IntrinsicFunctionResolver as b, formatResourceLine as c, gray as d, green as f, collectInlinePolicyNamesManagedBySiblings as g, IAMRoleProvider as h, withResourceDeadline as i, buildDockerImage as j, stringifyValue as k, bold as l, yellow as m, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isRetryableTransientError as o, red as p, MIGRATE_TMP_PREFIX as q, DeployEngine as r, IMPLICIT_DELETE_DEPENDENCIES as s, DEFAULT_RESOURCE_TIMEOUT_MS as t, cyan as u, findActionableSilentDrops as v, TemplateParser as w, applyRoleArnIfSet as x, CloudControlProvider as y, resolveApp as z };
|
|
12275
|
-
//# sourceMappingURL=deploy-engine-
|
|
12491
|
+
//# sourceMappingURL=deploy-engine-Bl8wyosc.js.map
|