@go-to-k/cdkd 0.263.2 → 0.264.1
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/README.md +7 -2
- package/dist/{asg-provider-CkNAViXd.js → asg-provider-ByH521ze.js} +2 -2
- package/dist/{asg-provider-CkNAViXd.js.map → asg-provider-ByH521ze.js.map} +1 -1
- package/dist/cli.js +80 -10
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-j-e2Q0XH.js → deploy-engine-DNLQYYBx.js} +310 -27
- package/dist/deploy-engine-DNLQYYBx.js.map +1 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-j-e2Q0XH.js.map +0 -1
|
@@ -5821,6 +5821,26 @@ var S3StateBackend = class {
|
|
|
5821
5821
|
await this.putRawObject(this.getRollbackJournalKey(stackName, region), JSON.stringify(journal, null, 2));
|
|
5822
5822
|
}
|
|
5823
5823
|
/**
|
|
5824
|
+
* Replace the `failedOperations` list on the NEWEST journal segment
|
|
5825
|
+
* (issue #1198) with the ops STILL pending after a `--revert-failed`
|
|
5826
|
+
* replay — an empty list removes the field. Called right after the
|
|
5827
|
+
* failed-op replay, BEFORE the segment's completed ops replay, so a later
|
|
5828
|
+
* completed-op failure that keeps the segment for a re-run does not
|
|
5829
|
+
* re-issue the already-applied failed-op reverts (the journal's
|
|
5830
|
+
* `attemptedProperties` would generate a patch undoing changes that are no
|
|
5831
|
+
* longer present, which can fail on patch-based providers). Per-op — a
|
|
5832
|
+
* partially-successful replay strips only the handled ops. No-op when the
|
|
5833
|
+
* journal / segment / field is absent.
|
|
5834
|
+
*/
|
|
5835
|
+
async setRollbackJournalFailedOperations(stackName, region, remaining) {
|
|
5836
|
+
const journal = await this.loadRollbackJournal(stackName, region);
|
|
5837
|
+
const newest = journal?.segments[journal.segments.length - 1];
|
|
5838
|
+
if (!journal || !newest || !newest.failedOperations) return;
|
|
5839
|
+
if (remaining.length === 0) delete newest.failedOperations;
|
|
5840
|
+
else newest.failedOperations = remaining;
|
|
5841
|
+
await this.putRawObject(this.getRollbackJournalKey(stackName, region), JSON.stringify(journal, null, 2));
|
|
5842
|
+
}
|
|
5843
|
+
/**
|
|
5824
5844
|
* Pop the newest segment off the stack's rollback journal after it has
|
|
5825
5845
|
* been fully replayed. When the last segment is removed, the journal
|
|
5826
5846
|
* object is deleted entirely. Returns the number of segments remaining.
|
|
@@ -11240,7 +11260,7 @@ var CloudControlProvider = class {
|
|
|
11240
11260
|
this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
|
|
11241
11261
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
11242
11262
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
11243
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
11263
|
+
const { ASGProvider } = await import("./asg-provider-ByH521ze.js").then((n) => n.n);
|
|
11244
11264
|
await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
11245
11265
|
return;
|
|
11246
11266
|
}
|
|
@@ -11914,10 +11934,10 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
11914
11934
|
responseClientResolveInFlight = null;
|
|
11915
11935
|
responseClientGeneration = 0;
|
|
11916
11936
|
/**
|
|
11917
|
-
* Whether `this.s3Client` is a provider-OWNED client (built
|
|
11918
|
-
*
|
|
11919
|
-
*
|
|
11920
|
-
*
|
|
11937
|
+
* Whether `this.s3Client` is a provider-OWNED client (built by a
|
|
11938
|
+
* region-correction rebuild) vs the shared `AwsClients.s3` instance
|
|
11939
|
+
* from the constructor. Owned clients are `destroy()`ed when replaced;
|
|
11940
|
+
* the shared one never is.
|
|
11921
11941
|
*/
|
|
11922
11942
|
ownsS3Client = false;
|
|
11923
11943
|
/**
|
|
@@ -11995,12 +12015,20 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
11995
12015
|
return this.asyncResponseTimeoutMs;
|
|
11996
12016
|
}
|
|
11997
12017
|
/**
|
|
11998
|
-
* Set the S3 bucket for custom resource responses
|
|
11999
|
-
* Called by ProviderRegistry when state bucket is configured
|
|
12000
|
-
|
|
12001
|
-
|
|
12018
|
+
* Set the S3 bucket for custom resource responses.
|
|
12019
|
+
* Called by ProviderRegistry when the state bucket is configured.
|
|
12020
|
+
*
|
|
12021
|
+
* There is deliberately NO region parameter (issue #1202): the bucket's
|
|
12022
|
+
* ACTUAL region is resolved lazily via `ensureResponseClient()` before
|
|
12023
|
+
* the first S3 operation (issue #1195), starting from the shared
|
|
12024
|
+
* `AwsClients.s3` client so `--profile` / static credentials carry into
|
|
12025
|
+
* both the `GetBucketLocation` probe and the rebuilt client. The former
|
|
12026
|
+
* deploy-region hint parameter built a default-credential-chain client
|
|
12027
|
+
* (dropping `--profile`) and added nothing — the probe resolves the
|
|
12028
|
+
* bucket's real region regardless of the starting client's region.
|
|
12029
|
+
*/
|
|
12030
|
+
setResponseBucket(bucket) {
|
|
12002
12031
|
this.responseBucket = bucket;
|
|
12003
|
-
if (bucketRegion) this.replaceS3Client(new S3Client({ region: bucketRegion }));
|
|
12004
12032
|
this.responseClientGeneration++;
|
|
12005
12033
|
this.responseClientResolved = false;
|
|
12006
12034
|
this.responseClientResolveInFlight = null;
|
|
@@ -14873,8 +14901,8 @@ var ProviderRegistry = class {
|
|
|
14873
14901
|
* Configure the response bucket for custom resources
|
|
14874
14902
|
* This allows Lambda handlers using cfn-response to send responses via S3
|
|
14875
14903
|
*/
|
|
14876
|
-
setCustomResourceResponseBucket(bucket
|
|
14877
|
-
this.customResourceProvider.setResponseBucket(bucket
|
|
14904
|
+
setCustomResourceResponseBucket(bucket) {
|
|
14905
|
+
this.customResourceProvider.setResponseBucket(bucket);
|
|
14878
14906
|
this.logger.debug(`Custom resource response bucket set to: ${bucket}`);
|
|
14879
14907
|
}
|
|
14880
14908
|
/**
|
|
@@ -16532,12 +16560,42 @@ function classifyRollbackOp(op, stateResources, orphanLogicalIds) {
|
|
|
16532
16560
|
}
|
|
16533
16561
|
const current = stateResources[op.logicalId];
|
|
16534
16562
|
if (!current) return "skip-absent";
|
|
16535
|
-
if (
|
|
16536
|
-
if (
|
|
16563
|
+
if (replacement) {
|
|
16564
|
+
if (current.physicalId === op.previousState.physicalId) return "skip-already-done";
|
|
16565
|
+
if (op.physicalId !== void 0 && current.physicalId !== op.physicalId) {
|
|
16566
|
+
if (deepEqual(current.properties, op.previousState.properties)) return "skip-already-done";
|
|
16567
|
+
return "skip-mismatch";
|
|
16568
|
+
}
|
|
16569
|
+
return op.previousState.updateReplacePolicy === "Retain" ? "reverse-replacement-readopt" : "reverse-replacement";
|
|
16537
16570
|
}
|
|
16571
|
+
if (op.previousState && deepEqual(current.properties, op.previousState.properties)) return "skip-already-done";
|
|
16538
16572
|
return "revert";
|
|
16539
16573
|
}
|
|
16540
16574
|
/**
|
|
16575
|
+
* Classify what reverting a FAILED in-flight op (issue #1198) will do
|
|
16576
|
+
* against the current state, without touching AWS. Pure — used by both the
|
|
16577
|
+
* command's `--revert-failed` plan preview and {@link replayFailedOperations}.
|
|
16578
|
+
*/
|
|
16579
|
+
function classifyFailedOp(op, stateResources) {
|
|
16580
|
+
if (op.changeType === "DELETE") return "skip-failed-noop";
|
|
16581
|
+
const current = stateResources[op.logicalId];
|
|
16582
|
+
if (op.changeType === "CREATE") {
|
|
16583
|
+
if (op.physicalId === void 0) return "skip-failed-unknown";
|
|
16584
|
+
if (!current) return "skip-failed-noop";
|
|
16585
|
+
if (current.physicalId !== op.physicalId) return "skip-failed-noop";
|
|
16586
|
+
return "delete-failed-create";
|
|
16587
|
+
}
|
|
16588
|
+
if (!current || !op.previousState) return "skip-failed-absent";
|
|
16589
|
+
return "revert-failed-update";
|
|
16590
|
+
}
|
|
16591
|
+
/** Build the plan items for a segment's failed ops (issue #1198). */
|
|
16592
|
+
function planFailedOps(failedOps, stateResources) {
|
|
16593
|
+
return failedOps.map((op) => ({
|
|
16594
|
+
op,
|
|
16595
|
+
action: classifyFailedOp(op, stateResources)
|
|
16596
|
+
}));
|
|
16597
|
+
}
|
|
16598
|
+
/**
|
|
16541
16599
|
* Build the full ordered plan for a list of ops (one segment). Mirrors the
|
|
16542
16600
|
* replay order: UPDATE/DELETE first (reverse completion order), then CREATE
|
|
16543
16601
|
* deletions in dependency-aware order.
|
|
@@ -16595,7 +16653,7 @@ async function replayRollback(operations, stateResources, stackName, ctx, option
|
|
|
16595
16653
|
result.interrupted = true;
|
|
16596
16654
|
break;
|
|
16597
16655
|
}
|
|
16598
|
-
await replaySingle(otherOps[i], stateResources, stackName, ctx, orphanLogicalIds, result, options.afterOp);
|
|
16656
|
+
await replaySingle(otherOps[i], stateResources, stackName, ctx, orphanLogicalIds, result, options.afterOp, options.isInterrupted);
|
|
16599
16657
|
}
|
|
16600
16658
|
if (!result.interrupted && createOps.length > 0) {
|
|
16601
16659
|
const sorted = sortRollbackCreates(createOps, stateResources);
|
|
@@ -16604,7 +16662,7 @@ async function replayRollback(operations, stateResources, stackName, ctx, option
|
|
|
16604
16662
|
result.interrupted = true;
|
|
16605
16663
|
break;
|
|
16606
16664
|
}
|
|
16607
|
-
await replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds, result, options.afterOp);
|
|
16665
|
+
await replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds, result, options.afterOp, options.isInterrupted);
|
|
16608
16666
|
}
|
|
16609
16667
|
}
|
|
16610
16668
|
ctx.logger.info("Rollback completed. Some resources may remain if deletion failed.");
|
|
@@ -16614,7 +16672,7 @@ async function replayRollback(operations, stateResources, stackName, ctx, option
|
|
|
16614
16672
|
});
|
|
16615
16673
|
return result;
|
|
16616
16674
|
}
|
|
16617
|
-
async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds, result, afterOp) {
|
|
16675
|
+
async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds, result, afterOp, isInterrupted) {
|
|
16618
16676
|
const action = classifyRollbackOp(op, stateResources, orphanLogicalIds);
|
|
16619
16677
|
const { logger } = ctx;
|
|
16620
16678
|
try {
|
|
@@ -16687,6 +16745,93 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
16687
16745
|
});
|
|
16688
16746
|
return;
|
|
16689
16747
|
}
|
|
16748
|
+
case "reverse-replacement-readopt": {
|
|
16749
|
+
const current = stateResources[op.logicalId];
|
|
16750
|
+
const prev = op.previousState;
|
|
16751
|
+
logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — deleting the new resource and re-adopting the retained old one (${prev.physicalId})`);
|
|
16752
|
+
const { provider: newDeleteProvider } = ctx.providerRegistry.getProviderFor({
|
|
16753
|
+
resourceType: op.resourceType,
|
|
16754
|
+
provisionedBy: current.provisionedBy ?? op.provisionedBy
|
|
16755
|
+
});
|
|
16756
|
+
await newDeleteProvider.delete(op.logicalId, current.physicalId, op.resourceType, current.properties, { expectedRegion: ctx.region });
|
|
16757
|
+
stateResources[op.logicalId] = prev;
|
|
16758
|
+
logger.info(` Rollback: ${op.logicalId} restored to the retained old resource`);
|
|
16759
|
+
await afterOp?.(op.logicalId);
|
|
16760
|
+
ctx.recordEvent?.({
|
|
16761
|
+
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
16762
|
+
stackName,
|
|
16763
|
+
operation: "UPDATE",
|
|
16764
|
+
logicalId: op.logicalId,
|
|
16765
|
+
resourceType: op.resourceType,
|
|
16766
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy }
|
|
16767
|
+
});
|
|
16768
|
+
return;
|
|
16769
|
+
}
|
|
16770
|
+
case "reverse-replacement": {
|
|
16771
|
+
const current = stateResources[op.logicalId];
|
|
16772
|
+
const prev = op.previousState;
|
|
16773
|
+
logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — re-creating the old resource and deleting the new one`);
|
|
16774
|
+
if (STATEFUL_TYPES.has(op.resourceType)) logger.warn(` ⚠ ${op.logicalId} (${op.resourceType}) is a stateful type — the old physical resource's data was destroyed by the replacement and CANNOT be recovered; the re-created resource starts empty.`);
|
|
16775
|
+
const { provider: createProvider } = ctx.providerRegistry.getProviderFor({
|
|
16776
|
+
resourceType: op.resourceType,
|
|
16777
|
+
provisionedBy: prev.provisionedBy
|
|
16778
|
+
});
|
|
16779
|
+
const { provider: newDeleteProvider } = ctx.providerRegistry.getProviderFor({
|
|
16780
|
+
resourceType: op.resourceType,
|
|
16781
|
+
provisionedBy: current.provisionedBy ?? op.provisionedBy
|
|
16782
|
+
});
|
|
16783
|
+
let deletedNewFirst = false;
|
|
16784
|
+
let createResult;
|
|
16785
|
+
try {
|
|
16786
|
+
createResult = await createProvider.create(op.logicalId, op.resourceType, { ...prev.properties });
|
|
16787
|
+
} catch (createError) {
|
|
16788
|
+
const msg = createError instanceof Error ? createError.message : String(createError);
|
|
16789
|
+
if (!(/already exists/i.test(msg) || msg.includes("AlreadyExists"))) throw createError;
|
|
16790
|
+
logger.info(` Rollback: re-create collided with the new resource's name — deleting the new resource (${current.physicalId}) first...`);
|
|
16791
|
+
await newDeleteProvider.delete(op.logicalId, current.physicalId, op.resourceType, current.properties, { expectedRegion: ctx.region });
|
|
16792
|
+
deletedNewFirst = true;
|
|
16793
|
+
delete stateResources[op.logicalId];
|
|
16794
|
+
await afterOp?.(op.logicalId);
|
|
16795
|
+
try {
|
|
16796
|
+
createResult = await withRetry(() => createProvider.create(op.logicalId, op.resourceType, { ...prev.properties }), op.logicalId, {
|
|
16797
|
+
maxRetries: 5,
|
|
16798
|
+
initialDelayMs: 2e3,
|
|
16799
|
+
maxDelayMs: 1e4,
|
|
16800
|
+
logger,
|
|
16801
|
+
...isInterrupted && {
|
|
16802
|
+
isInterrupted,
|
|
16803
|
+
onInterrupted: () => /* @__PURE__ */ new Error("Rollback interrupted while waiting for the old name to release")
|
|
16804
|
+
},
|
|
16805
|
+
isRetryable: (message) => /already exists/i.test(message) || message.includes("AlreadyExists")
|
|
16806
|
+
});
|
|
16807
|
+
} catch (recreateError) {
|
|
16808
|
+
throw new Error(`Failed to re-create the old ${op.logicalId} after the new resource (${current.physicalId}) was already deleted: ${recreateError instanceof Error ? recreateError.message : String(recreateError)}. The resource is now absent — fix forward with 'cdkd deploy'.`);
|
|
16809
|
+
}
|
|
16810
|
+
}
|
|
16811
|
+
const { observedProperties: _staleObserved, ...prevRecord } = prev;
|
|
16812
|
+
stateResources[op.logicalId] = {
|
|
16813
|
+
...prevRecord,
|
|
16814
|
+
physicalId: createResult.physicalId,
|
|
16815
|
+
attributes: createResult.attributes ?? {}
|
|
16816
|
+
};
|
|
16817
|
+
await afterOp?.(op.logicalId);
|
|
16818
|
+
if (!deletedNewFirst) try {
|
|
16819
|
+
await newDeleteProvider.delete(op.logicalId, current.physicalId, op.resourceType, current.properties, { expectedRegion: ctx.region });
|
|
16820
|
+
} catch (deleteError) {
|
|
16821
|
+
logger.warn(` Rollback: old ${op.logicalId} re-created, but deleting the new resource (${current.physicalId}) failed: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}. Delete it manually — it is no longer tracked in state.`);
|
|
16822
|
+
result.warnings++;
|
|
16823
|
+
}
|
|
16824
|
+
logger.info(` Rollback: ${op.logicalId} replacement reversed (old resource re-created as ${createResult.physicalId})`);
|
|
16825
|
+
ctx.recordEvent?.({
|
|
16826
|
+
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
16827
|
+
stackName,
|
|
16828
|
+
operation: "UPDATE",
|
|
16829
|
+
logicalId: op.logicalId,
|
|
16830
|
+
resourceType: op.resourceType,
|
|
16831
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy }
|
|
16832
|
+
});
|
|
16833
|
+
return;
|
|
16834
|
+
}
|
|
16690
16835
|
case "revert": {
|
|
16691
16836
|
if (!op.previousState) {
|
|
16692
16837
|
logger.warn(` Rollback: Cannot restore ${op.logicalId} — no previous state available`);
|
|
@@ -16734,6 +16879,115 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
16734
16879
|
});
|
|
16735
16880
|
}
|
|
16736
16881
|
}
|
|
16882
|
+
/**
|
|
16883
|
+
* Revert a segment's FAILED in-flight operations (issue #1198). Opt-in via
|
|
16884
|
+
* `cdkd rollback --revert-failed` — the failed resource's remote state is
|
|
16885
|
+
* unknown (the op died partway), so force-applying `previousState` is a
|
|
16886
|
+
* deliberate user decision, never the default. Runs BEFORE the segment's
|
|
16887
|
+
* completed ops (the failed op is the newest work of the failed deploy).
|
|
16888
|
+
*
|
|
16889
|
+
* Best-effort like {@link replayRollback}: per-op failures are caught,
|
|
16890
|
+
* warned, and counted.
|
|
16891
|
+
*/
|
|
16892
|
+
async function replayFailedOperations(failedOps, stateResources, stackName, ctx, options = {}) {
|
|
16893
|
+
const result = {
|
|
16894
|
+
failures: 0,
|
|
16895
|
+
warnings: 0,
|
|
16896
|
+
interrupted: false,
|
|
16897
|
+
remainingFailedOps: []
|
|
16898
|
+
};
|
|
16899
|
+
const { logger } = ctx;
|
|
16900
|
+
const emitEnvelope = options.emitEnvelope === true && failedOps.length > 0;
|
|
16901
|
+
if (emitEnvelope) ctx.recordEvent?.({
|
|
16902
|
+
eventType: "ROLLBACK_STARTED",
|
|
16903
|
+
stackName
|
|
16904
|
+
});
|
|
16905
|
+
const pending = /* @__PURE__ */ new Set();
|
|
16906
|
+
for (let i = failedOps.length - 1; i >= 0; i--) {
|
|
16907
|
+
if (options.isInterrupted?.()) {
|
|
16908
|
+
result.interrupted = true;
|
|
16909
|
+
for (let j = i; j >= 0; j--) pending.add(failedOps[j]);
|
|
16910
|
+
break;
|
|
16911
|
+
}
|
|
16912
|
+
const op = failedOps[i];
|
|
16913
|
+
const action = classifyFailedOp(op, stateResources);
|
|
16914
|
+
try {
|
|
16915
|
+
switch (action) {
|
|
16916
|
+
case "skip-failed-noop":
|
|
16917
|
+
logger.info(` Rollback: failed ${op.changeType} of ${op.logicalId} (${op.resourceType}) left nothing to revert, skipping`);
|
|
16918
|
+
break;
|
|
16919
|
+
case "skip-failed-unknown":
|
|
16920
|
+
logger.warn(` Rollback: failed CREATE of ${op.logicalId} (${op.resourceType}) recorded no physical id — if it was partially created in AWS, delete it manually`);
|
|
16921
|
+
result.warnings++;
|
|
16922
|
+
break;
|
|
16923
|
+
case "skip-failed-absent":
|
|
16924
|
+
logger.warn(` Rollback: cannot revert failed UPDATE of ${op.logicalId} — no previous state available, skipping`);
|
|
16925
|
+
result.warnings++;
|
|
16926
|
+
break;
|
|
16927
|
+
case "delete-failed-create": {
|
|
16928
|
+
logger.info(` Rollback: deleting partially-created ${op.logicalId} (${op.resourceType}) (--revert-failed)`);
|
|
16929
|
+
const { provider } = ctx.providerRegistry.getProviderFor({
|
|
16930
|
+
resourceType: op.resourceType,
|
|
16931
|
+
provisionedBy: op.provisionedBy
|
|
16932
|
+
});
|
|
16933
|
+
await provider.delete(op.logicalId, op.physicalId, op.resourceType, void 0, { expectedRegion: ctx.region });
|
|
16934
|
+
delete stateResources[op.logicalId];
|
|
16935
|
+
await options.afterOp?.(op.logicalId);
|
|
16936
|
+
ctx.recordEvent?.({
|
|
16937
|
+
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
16938
|
+
stackName,
|
|
16939
|
+
operation: "CREATE",
|
|
16940
|
+
logicalId: op.logicalId,
|
|
16941
|
+
resourceType: op.resourceType,
|
|
16942
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy }
|
|
16943
|
+
});
|
|
16944
|
+
break;
|
|
16945
|
+
}
|
|
16946
|
+
case "revert-failed-update": {
|
|
16947
|
+
const current = stateResources[op.logicalId];
|
|
16948
|
+
const prev = op.previousState;
|
|
16949
|
+
logger.info(` Rollback: force-reverting failed UPDATE of ${op.logicalId} (${op.resourceType}) to its pre-deploy properties (--revert-failed; remote state is unknown)`);
|
|
16950
|
+
const { provider } = ctx.providerRegistry.getProviderFor({
|
|
16951
|
+
resourceType: op.resourceType,
|
|
16952
|
+
provisionedBy: op.provisionedBy ?? current.provisionedBy
|
|
16953
|
+
});
|
|
16954
|
+
await provider.update(op.logicalId, current.physicalId, op.resourceType, prev.properties, op.attemptedProperties ?? current.properties);
|
|
16955
|
+
stateResources[op.logicalId] = prev;
|
|
16956
|
+
logger.info(` Rollback: ${op.logicalId} reverted successfully`);
|
|
16957
|
+
await options.afterOp?.(op.logicalId);
|
|
16958
|
+
ctx.recordEvent?.({
|
|
16959
|
+
eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
|
|
16960
|
+
stackName,
|
|
16961
|
+
operation: "UPDATE",
|
|
16962
|
+
logicalId: op.logicalId,
|
|
16963
|
+
resourceType: op.resourceType,
|
|
16964
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy }
|
|
16965
|
+
});
|
|
16966
|
+
break;
|
|
16967
|
+
}
|
|
16968
|
+
}
|
|
16969
|
+
} catch (revertError) {
|
|
16970
|
+
logger.warn(` Rollback failed for failed-op ${op.logicalId} (${op.changeType}): ${revertError instanceof Error ? revertError.message : String(revertError)}`);
|
|
16971
|
+
result.failures++;
|
|
16972
|
+
pending.add(op);
|
|
16973
|
+
ctx.recordEvent?.({
|
|
16974
|
+
eventType: "ROLLBACK_RESOURCE_FAILED",
|
|
16975
|
+
stackName,
|
|
16976
|
+
operation: op.changeType,
|
|
16977
|
+
logicalId: op.logicalId,
|
|
16978
|
+
resourceType: op.resourceType,
|
|
16979
|
+
...op.provisionedBy && { provisionedBy: op.provisionedBy },
|
|
16980
|
+
error: extractDeploymentEventError(revertError)
|
|
16981
|
+
});
|
|
16982
|
+
}
|
|
16983
|
+
}
|
|
16984
|
+
if (emitEnvelope) ctx.recordEvent?.({
|
|
16985
|
+
eventType: "ROLLBACK_FINISHED",
|
|
16986
|
+
stackName
|
|
16987
|
+
});
|
|
16988
|
+
result.remainingFailedOps = failedOps.filter((op) => pending.has(op));
|
|
16989
|
+
return result;
|
|
16990
|
+
}
|
|
16737
16991
|
function stateResourcesPolicyLabel(op, stateResources) {
|
|
16738
16992
|
return stateResources[op.logicalId]?.deletionPolicy ?? "Retain";
|
|
16739
16993
|
}
|
|
@@ -16826,7 +17080,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
16826
17080
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
16827
17081
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
16828
17082
|
function getCdkdVersion() {
|
|
16829
|
-
return "0.
|
|
17083
|
+
return "0.264.1";
|
|
16830
17084
|
}
|
|
16831
17085
|
/**
|
|
16832
17086
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -17383,6 +17637,14 @@ var DeployEngine = class {
|
|
|
17383
17637
|
*/
|
|
17384
17638
|
recordedOutputReads = [];
|
|
17385
17639
|
/**
|
|
17640
|
+
* Per-logical-id snapshot of the intrinsic-RESOLVED desired properties
|
|
17641
|
+
* each CREATE / UPDATE attempted (issue #1198). Written just before the
|
|
17642
|
+
* provider call; read only when the op FAILS, to journal the failed op's
|
|
17643
|
+
* `attemptedProperties` so `cdkd rollback --revert-failed` can generate a
|
|
17644
|
+
* patch that undoes a half-applied update.
|
|
17645
|
+
*/
|
|
17646
|
+
attemptedResolvedProps = /* @__PURE__ */ new Map();
|
|
17647
|
+
/**
|
|
17386
17648
|
* Target region for this stack. Required — load-bearing for the
|
|
17387
17649
|
* region-prefixed S3 state key and recorded in state.json for
|
|
17388
17650
|
* cross-region destroy.
|
|
@@ -17808,6 +18070,7 @@ var DeployEngine = class {
|
|
|
17808
18070
|
skipped: 0
|
|
17809
18071
|
};
|
|
17810
18072
|
const completedOperations = [];
|
|
18073
|
+
const failedOperations = [];
|
|
17811
18074
|
let pendingMigration = migrationPending;
|
|
17812
18075
|
let saveChain = Promise.resolve();
|
|
17813
18076
|
const saveStateAfterResource = (logicalId) => {
|
|
@@ -17869,6 +18132,15 @@ var DeployEngine = class {
|
|
|
17869
18132
|
} catch (provisionError) {
|
|
17870
18133
|
this.interrupted = true;
|
|
17871
18134
|
this.interruptCause ??= "sibling-failure";
|
|
18135
|
+
failedOperations.push({
|
|
18136
|
+
logicalId,
|
|
18137
|
+
changeType: change.changeType,
|
|
18138
|
+
resourceType: change.resourceType,
|
|
18139
|
+
provisionedBy: newResources[logicalId]?.provisionedBy ?? previousState?.provisionedBy,
|
|
18140
|
+
...previousState && { previousState },
|
|
18141
|
+
physicalId: newResources[logicalId]?.physicalId ?? previousState?.physicalId,
|
|
18142
|
+
attemptedProperties: this.attemptedResolvedProps.get(logicalId)
|
|
18143
|
+
});
|
|
17872
18144
|
throw provisionError;
|
|
17873
18145
|
}
|
|
17874
18146
|
completedOperations.push({
|
|
@@ -17907,6 +18179,14 @@ var DeployEngine = class {
|
|
|
17907
18179
|
} catch (provisionError) {
|
|
17908
18180
|
this.interrupted = true;
|
|
17909
18181
|
this.interruptCause ??= "sibling-failure";
|
|
18182
|
+
failedOperations.push({
|
|
18183
|
+
logicalId,
|
|
18184
|
+
changeType: "DELETE",
|
|
18185
|
+
resourceType: change.resourceType,
|
|
18186
|
+
provisionedBy: previousState?.provisionedBy,
|
|
18187
|
+
...previousState && { previousState },
|
|
18188
|
+
physicalId: previousState?.physicalId
|
|
18189
|
+
});
|
|
17910
18190
|
throw provisionError;
|
|
17911
18191
|
}
|
|
17912
18192
|
completedOperations.push({
|
|
@@ -17949,16 +18229,16 @@ var DeployEngine = class {
|
|
|
17949
18229
|
}
|
|
17950
18230
|
let autoRollbackClean = false;
|
|
17951
18231
|
if (error instanceof InterruptedError) {
|
|
17952
|
-
await this.writeRollbackJournalSegment(stackName, completedOperations, "interrupted", initialDeploy);
|
|
18232
|
+
await this.writeRollbackJournalSegment(stackName, completedOperations, failedOperations, "interrupted", initialDeploy);
|
|
17953
18233
|
this.logger.info(`Partial state saved (${Object.keys(newResources).length} resources). Run deploy again to resume, 'cdkd rollback' to revert, or destroy to clean up.`);
|
|
17954
18234
|
throw error;
|
|
17955
18235
|
}
|
|
17956
18236
|
if (this.options.noRollback) {
|
|
17957
|
-
await this.writeRollbackJournalSegment(stackName, completedOperations, "no-rollback-failure", initialDeploy);
|
|
18237
|
+
await this.writeRollbackJournalSegment(stackName, completedOperations, failedOperations, "no-rollback-failure", initialDeploy);
|
|
17958
18238
|
this.logger.warn("Deployment failed. --no-rollback is set, skipping rollback.");
|
|
17959
18239
|
this.logger.warn("Partial state has been saved. Run 'cdkd deploy' to resume, 'cdkd rollback' to revert, or destroy to clean up.");
|
|
17960
18240
|
} else {
|
|
17961
|
-
await this.writeRollbackJournalSegment(stackName, completedOperations, "auto-rollback-started", initialDeploy);
|
|
18241
|
+
await this.writeRollbackJournalSegment(stackName, completedOperations, failedOperations, "auto-rollback-started", initialDeploy);
|
|
17962
18242
|
autoRollbackClean = (await this.performRollback(completedOperations, newResources, stackName)).failures === 0;
|
|
17963
18243
|
}
|
|
17964
18244
|
try {
|
|
@@ -18003,7 +18283,7 @@ var DeployEngine = class {
|
|
|
18003
18283
|
outputs = await this.resolveOutputs(template, newResources, stackName, parameterValues, conditions);
|
|
18004
18284
|
} catch (outputError) {
|
|
18005
18285
|
await this.persistStateAfterOutputFailure(stackName, currentState, newResources, currentEtag, pendingMigration);
|
|
18006
|
-
await this.writeRollbackJournalSegment(stackName, completedOperations, "no-rollback-failure", currentEtag === void 0);
|
|
18286
|
+
await this.writeRollbackJournalSegment(stackName, completedOperations, failedOperations, "no-rollback-failure", currentEtag === void 0);
|
|
18007
18287
|
throw outputError;
|
|
18008
18288
|
}
|
|
18009
18289
|
return {
|
|
@@ -18113,8 +18393,8 @@ var DeployEngine = class {
|
|
|
18113
18393
|
* `cdkd rollback`. Best-effort like the partial-state save, but warns
|
|
18114
18394
|
* LOUDLY on failure — the user just lost the ability to `cdkd rollback`.
|
|
18115
18395
|
*/
|
|
18116
|
-
async writeRollbackJournalSegment(stackName, completedOperations, reason, initialDeploy) {
|
|
18117
|
-
if (completedOperations.length === 0) return;
|
|
18396
|
+
async writeRollbackJournalSegment(stackName, completedOperations, failedOperations, reason, initialDeploy) {
|
|
18397
|
+
if (completedOperations.length === 0 && failedOperations.length === 0) return;
|
|
18118
18398
|
try {
|
|
18119
18399
|
const segment = {
|
|
18120
18400
|
...this.options.eventRecorder?.runId !== void 0 && { runId: this.options.eventRecorder.runId },
|
|
@@ -18123,7 +18403,8 @@ var DeployEngine = class {
|
|
|
18123
18403
|
initialDeploy,
|
|
18124
18404
|
...this.options.roleArn && { roleArn: this.options.roleArn },
|
|
18125
18405
|
cdkdVersion: getCdkdVersion(),
|
|
18126
|
-
operations: completedOperations
|
|
18406
|
+
operations: completedOperations,
|
|
18407
|
+
...failedOperations.length > 0 && { failedOperations }
|
|
18127
18408
|
};
|
|
18128
18409
|
await this.stateBackend.appendRollbackJournalSegment(stackName, this.stackRegion, segment);
|
|
18129
18410
|
this.logger.debug(`Rollback journal segment written (${reason})`);
|
|
@@ -18255,6 +18536,7 @@ var DeployEngine = class {
|
|
|
18255
18536
|
}, stackName);
|
|
18256
18537
|
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
18257
18538
|
this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
|
|
18539
|
+
this.attemptedResolvedProps.set(logicalId, resolvedProps);
|
|
18258
18540
|
const createDecision = this.providerRegistry.getProviderFor({
|
|
18259
18541
|
resourceType,
|
|
18260
18542
|
properties: resolvedProps
|
|
@@ -18295,6 +18577,7 @@ var DeployEngine = class {
|
|
|
18295
18577
|
}, stackName);
|
|
18296
18578
|
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
18297
18579
|
this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
|
|
18580
|
+
this.attemptedResolvedProps.set(logicalId, resolvedProps);
|
|
18298
18581
|
if (JSON.stringify(resolvedProps) === JSON.stringify(currentProps)) {
|
|
18299
18582
|
if (change.attributeChanges && change.attributeChanges.length > 0) {
|
|
18300
18583
|
const attrSummary = change.attributeChanges.map((a) => `${a.attribute}: ${a.oldValue ?? "(unset)"} → ${a.newValue ?? "(unset)"}`).join(", ");
|
|
@@ -18733,5 +19016,5 @@ var DeployEngine = class {
|
|
|
18733
19016
|
};
|
|
18734
19017
|
|
|
18735
19018
|
//#endregion
|
|
18736
|
-
export {
|
|
18737
|
-
//# sourceMappingURL=deploy-engine-
|
|
19019
|
+
export { createAssetRedirectResolver as $, ResourceTimeoutError as $t, CloudControlProvider as A, MIGRATE_TMP_PREFIX as At, assertRegionMatch as B, setAwsClients as Bt, green as C, resolveSkipPrefix as Ct, collectInlinePolicyNamesManagedBySiblings as D, warnDeprecatedNoPrefixCliFlag as Dt, IAMRoleProvider as E, resolveUseCdkBootstrapAssets as Et, cfnRefValueFromPhysicalId as F, clearBucketRegionCache as Ft, LockManager as G, LocalInvokeBuildError as Gt, DiffCalculator as H, CdkdError as Ht, refStateLookupFromResource as I, resolveBucketRegion as It, shouldRetainResource as J, LockError as Jt, S3StateBackend as K, LocalMigrateError as Kt, WAFv2WebACLProvider as L, AwsClients as Lt, disableInstanceApiTermination as M, uploadCfnTemplate as Mt, isTerminationProtectionPropagationError as N, expectedOwnerParam as Nt, ProviderRegistry as O, CFN_TEMPLATE_BODY_LIMIT as Ot, IntrinsicFunctionResolver as P, AssemblyReader as Pt, buildAssetRedirectMap as Q, ProvisioningError as Qt, normalizeAwsTagsToCfn as R, getAwsClients as Rt, gray as S, resolveCaptureObservedState as St, yellow as T, resolveStateBucketWithDefaultAndSource as Tt, DagBuilder as U, ConfigError as Ut, applyRoleArnIfSet as V, AssetError as Vt, TemplateParser as W, DependencyError as Wt, stringifyValue as X, NestedStackChildDirectDestroyError as Xt, AssetPublisher as Y, MissingCdkCliError as Yt, WorkGraph as Z, PartialFailureError as Zt, isStatefulRecreateTargetSync as _, synthesisStatusMessage as _t, DeploymentEventsStore as a, formatError as an, getBootstrapMarkerKey as at, bold as b, resolveApp as bt, replayFailedOperations as c, withErrorHandling as cn, validateContainerRepoName as ct, withRetry as d, getDockerCmd as dt, ResourceUpdateNotSupportedError as en, loadPublishableAssetManifest as et, isRetryableTransientError as f, runDockerForeground as ft, MULTI_REGION_RECREATE_BLOCKED_TYPES as g, Synthesizer as gt, extractDeploymentEventError as h, getDockerImageBySourceHash as ht, DeploymentEventsReader as i, SynthesisError as in, ensureAssetStorage as it, slowCcOperationTimeoutMs as j, findLargeInlineResources as jt, findActionableSilentDrops as k, CFN_TEMPLATE_URL_LIMIT as kt, replayRollback as l, __exportAll as ln, buildDockerImage as lt, computeImplicitDeleteEdges as m, AssetManifestLoader as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, StackTerminationProtectionError as nn, AssetModeResolver as nt, planFailedOps as o, isCdkdError as on, parseBootstrapMarker as ot, IMPLICIT_DELETE_DEPENDENCIES as p, runDockerStreaming as pt, rebuildClientForBucketRegion as q, LocalStartServiceError as qt, DeployEngine as r, StateError as rn, BOOTSTRAP_MARKER_PREFIX as rt, planRollback as s, normalizeAwsError as sn, validateAssetBucketName as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, StackHasActiveImportsError as tn, rewriteTemplateAssetReferences as tt, withResourceDeadline as u, formatDockerLoginError as ut, renderStatefulReason as v, getDefaultStateBucketName as vt, red as w, resolveStateBucketWithDefault as wt, cyan as x, resolveAutoAssetStorage as xt, formatResourceLine as y, getLegacyStateBucketName as yt, resolveExplicitPhysicalId as z, resetAwsClients as zt };
|
|
19020
|
+
//# sourceMappingURL=deploy-engine-DNLQYYBx.js.map
|