@go-to-k/cdkd 0.24.0 → 0.26.0
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 +91 -14
- package/dist/cli.js +1268 -375
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.26.0.tgz +0 -0
- package/dist/index.js +384 -210
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.24.0.tgz +0 -0
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -613,6 +613,22 @@ var LiveRenderer = class {
|
|
|
613
613
|
if (this.active)
|
|
614
614
|
this.draw();
|
|
615
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* Replace the label of a previously-added task in place. No-op if the
|
|
618
|
+
* task is not currently tracked (e.g. it already finished). Used by the
|
|
619
|
+
* per-resource deadline wrapper to surface a "[taking longer than
|
|
620
|
+
* expected, Nm+]" suffix without disturbing the elapsed-time counter
|
|
621
|
+
* the renderer tracks via `startedAt`.
|
|
622
|
+
*/
|
|
623
|
+
updateTaskLabel(id, label) {
|
|
624
|
+
const stackName = getCurrentStackName();
|
|
625
|
+
const task = this.tasks.get(scopedKey(id, stackName));
|
|
626
|
+
if (!task)
|
|
627
|
+
return;
|
|
628
|
+
task.label = label;
|
|
629
|
+
if (this.active)
|
|
630
|
+
this.draw();
|
|
631
|
+
}
|
|
616
632
|
/**
|
|
617
633
|
* Print content above the live area. Clears the live area, runs the writer,
|
|
618
634
|
* then redraws the live area. When the renderer is inactive, the writer
|
|
@@ -898,6 +914,38 @@ var ProvisioningError = class _ProvisioningError extends CdkdError {
|
|
|
898
914
|
Object.setPrototypeOf(this, _ProvisioningError.prototype);
|
|
899
915
|
}
|
|
900
916
|
};
|
|
917
|
+
var ResourceTimeoutError = class _ResourceTimeoutError extends CdkdError {
|
|
918
|
+
constructor(logicalId, resourceType, region, elapsedMs, operation, timeoutMs) {
|
|
919
|
+
const elapsedLabel = formatDuration(elapsedMs);
|
|
920
|
+
const timeoutLabel = formatDuration(timeoutMs);
|
|
921
|
+
super(
|
|
922
|
+
`Resource ${logicalId} (${resourceType}) in ${region} timed out after ${timeoutLabel} during ${operation} (elapsed ${elapsedLabel}).
|
|
923
|
+
This may indicate a stuck Cloud Control polling loop, hung Custom Resource, or
|
|
924
|
+
slow ENI provisioning. Re-run with --resource-timeout 1h if the resource genuinely
|
|
925
|
+
needs more time, or --verbose to see the underlying provider activity.`,
|
|
926
|
+
"RESOURCE_TIMEOUT"
|
|
927
|
+
);
|
|
928
|
+
this.logicalId = logicalId;
|
|
929
|
+
this.resourceType = resourceType;
|
|
930
|
+
this.region = region;
|
|
931
|
+
this.elapsedMs = elapsedMs;
|
|
932
|
+
this.operation = operation;
|
|
933
|
+
this.timeoutMs = timeoutMs;
|
|
934
|
+
this.name = "ResourceTimeoutError";
|
|
935
|
+
Object.setPrototypeOf(this, _ResourceTimeoutError.prototype);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
function formatDuration(ms) {
|
|
939
|
+
if (ms < 6e4) {
|
|
940
|
+
return `${Math.round(ms / 1e3)}s`;
|
|
941
|
+
}
|
|
942
|
+
const totalMinutes = Math.round(ms / 6e4);
|
|
943
|
+
if (totalMinutes < 60)
|
|
944
|
+
return `${totalMinutes}m`;
|
|
945
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
946
|
+
const minutes = totalMinutes % 60;
|
|
947
|
+
return minutes === 0 ? `${hours}h` : `${hours}h${minutes}m`;
|
|
948
|
+
}
|
|
901
949
|
var DependencyError = class _DependencyError extends CdkdError {
|
|
902
950
|
constructor(message, cause) {
|
|
903
951
|
super(message, "DEPENDENCY_ERROR", cause);
|
|
@@ -8108,7 +8156,85 @@ async function withRetry(operation, logicalId, opts = {}) {
|
|
|
8108
8156
|
throw lastError;
|
|
8109
8157
|
}
|
|
8110
8158
|
|
|
8159
|
+
// src/deployment/resource-deadline.ts
|
|
8160
|
+
var InvalidResourceDeadlineError = class extends Error {
|
|
8161
|
+
constructor(message) {
|
|
8162
|
+
super(message);
|
|
8163
|
+
this.name = "InvalidResourceDeadlineError";
|
|
8164
|
+
}
|
|
8165
|
+
};
|
|
8166
|
+
function validateOptions(opts) {
|
|
8167
|
+
const { warnAfterMs, timeoutMs } = opts;
|
|
8168
|
+
if (!Number.isFinite(warnAfterMs) || !Number.isFinite(timeoutMs) || warnAfterMs <= 0 || timeoutMs <= 0) {
|
|
8169
|
+
throw new InvalidResourceDeadlineError(
|
|
8170
|
+
`withResourceDeadline: warnAfterMs and timeoutMs must be positive finite numbers (got warnAfterMs=${warnAfterMs}, timeoutMs=${timeoutMs})`
|
|
8171
|
+
);
|
|
8172
|
+
}
|
|
8173
|
+
if (warnAfterMs >= timeoutMs) {
|
|
8174
|
+
throw new InvalidResourceDeadlineError(
|
|
8175
|
+
`withResourceDeadline: warnAfterMs (${warnAfterMs}ms) must be less than timeoutMs (${timeoutMs}ms)`
|
|
8176
|
+
);
|
|
8177
|
+
}
|
|
8178
|
+
}
|
|
8179
|
+
async function withResourceDeadline(operation, opts) {
|
|
8180
|
+
validateOptions(opts);
|
|
8181
|
+
const startedAt = Date.now();
|
|
8182
|
+
return new Promise((resolve4, reject) => {
|
|
8183
|
+
let settled = false;
|
|
8184
|
+
let warnTimer;
|
|
8185
|
+
let timeoutTimer;
|
|
8186
|
+
const cleanup = () => {
|
|
8187
|
+
if (warnTimer !== void 0)
|
|
8188
|
+
clearTimeout(warnTimer);
|
|
8189
|
+
if (timeoutTimer !== void 0)
|
|
8190
|
+
clearTimeout(timeoutTimer);
|
|
8191
|
+
warnTimer = void 0;
|
|
8192
|
+
timeoutTimer = void 0;
|
|
8193
|
+
};
|
|
8194
|
+
if (opts.onWarn) {
|
|
8195
|
+
warnTimer = setTimeout(() => {
|
|
8196
|
+
if (settled)
|
|
8197
|
+
return;
|
|
8198
|
+
try {
|
|
8199
|
+
opts.onWarn(Date.now() - startedAt);
|
|
8200
|
+
} catch {
|
|
8201
|
+
}
|
|
8202
|
+
}, opts.warnAfterMs);
|
|
8203
|
+
if (typeof warnTimer.unref === "function")
|
|
8204
|
+
warnTimer.unref();
|
|
8205
|
+
}
|
|
8206
|
+
timeoutTimer = setTimeout(() => {
|
|
8207
|
+
if (settled)
|
|
8208
|
+
return;
|
|
8209
|
+
settled = true;
|
|
8210
|
+
cleanup();
|
|
8211
|
+
const elapsed = Date.now() - startedAt;
|
|
8212
|
+
reject(opts.onTimeout(elapsed));
|
|
8213
|
+
}, opts.timeoutMs);
|
|
8214
|
+
if (typeof timeoutTimer.unref === "function")
|
|
8215
|
+
timeoutTimer.unref();
|
|
8216
|
+
Promise.resolve().then(() => operation()).then(
|
|
8217
|
+
(value) => {
|
|
8218
|
+
if (settled)
|
|
8219
|
+
return;
|
|
8220
|
+
settled = true;
|
|
8221
|
+
cleanup();
|
|
8222
|
+
resolve4(value);
|
|
8223
|
+
},
|
|
8224
|
+
(err) => {
|
|
8225
|
+
if (settled)
|
|
8226
|
+
return;
|
|
8227
|
+
settled = true;
|
|
8228
|
+
cleanup();
|
|
8229
|
+
reject(err);
|
|
8230
|
+
}
|
|
8231
|
+
);
|
|
8232
|
+
});
|
|
8233
|
+
}
|
|
8234
|
+
|
|
8111
8235
|
// src/deployment/deploy-engine.ts
|
|
8236
|
+
var DEFAULT_RESOURCE_WARN_AFTER_MS = 5 * 60 * 1e3;
|
|
8237
|
+
var DEFAULT_RESOURCE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
8112
8238
|
var InterruptedError = class extends Error {
|
|
8113
8239
|
constructor() {
|
|
8114
8240
|
super("Deployment interrupted by user (Ctrl+C)");
|
|
@@ -8129,6 +8255,8 @@ var DeployEngine = class {
|
|
|
8129
8255
|
this.options.dryRun = options.dryRun ?? false;
|
|
8130
8256
|
this.options.lockTimeout = options.lockTimeout ?? 5 * 60 * 1e3;
|
|
8131
8257
|
this.options.noRollback = options.noRollback ?? false;
|
|
8258
|
+
this.options.resourceWarnAfterMs = options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
|
|
8259
|
+
this.options.resourceTimeoutMs = options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
|
|
8132
8260
|
}
|
|
8133
8261
|
logger = getLogger().child("DeployEngine");
|
|
8134
8262
|
resolver;
|
|
@@ -8727,259 +8855,305 @@ var DeployEngine = class {
|
|
|
8727
8855
|
*/
|
|
8728
8856
|
async provisionResource(logicalId, change, stateResources, stackName, template, parameterValues, conditions, counts, progress) {
|
|
8729
8857
|
const resourceType = change.resourceType;
|
|
8730
|
-
const provider = this.providerRegistry.getProvider(resourceType);
|
|
8731
8858
|
const renderer = getLiveRenderer();
|
|
8732
8859
|
const needsReplacement = change.changeType === "UPDATE" && (change.propertyChanges?.some((pc) => pc.requiresReplacement) ?? false);
|
|
8733
8860
|
const verb = change.changeType === "CREATE" ? "Creating" : change.changeType === "DELETE" ? "Deleting" : needsReplacement ? "Replacing" : "Updating";
|
|
8734
|
-
|
|
8861
|
+
const baseLabel = `${verb} ${logicalId} (${resourceType})`;
|
|
8862
|
+
renderer.addTask(logicalId, baseLabel);
|
|
8863
|
+
const operationKind = change.changeType === "CREATE" ? "CREATE" : change.changeType === "DELETE" ? "DELETE" : "UPDATE";
|
|
8864
|
+
const warnAfterMs = this.options.resourceWarnAfterByType?.[resourceType] ?? this.options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;
|
|
8865
|
+
const timeoutMs = this.options.resourceTimeoutByType?.[resourceType] ?? this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;
|
|
8735
8866
|
try {
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8867
|
+
await withResourceDeadline(
|
|
8868
|
+
async () => {
|
|
8869
|
+
await this.provisionResourceBody(
|
|
8870
|
+
logicalId,
|
|
8871
|
+
change,
|
|
8872
|
+
stateResources,
|
|
8873
|
+
stackName,
|
|
8740
8874
|
template,
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8875
|
+
parameterValues,
|
|
8876
|
+
conditions,
|
|
8877
|
+
counts,
|
|
8878
|
+
progress
|
|
8879
|
+
);
|
|
8880
|
+
},
|
|
8881
|
+
{
|
|
8882
|
+
warnAfterMs,
|
|
8883
|
+
timeoutMs,
|
|
8884
|
+
onWarn: (elapsedMs) => {
|
|
8885
|
+
const minutes = Math.max(1, Math.round(elapsedMs / 6e4));
|
|
8886
|
+
const warnSuffix = ` [taking longer than expected, ${minutes}m+]`;
|
|
8887
|
+
renderer.updateTaskLabel(logicalId, `${baseLabel}${warnSuffix}`);
|
|
8888
|
+
renderer.printAbove(() => {
|
|
8889
|
+
this.logger.warn(
|
|
8890
|
+
`${logicalId} (${resourceType}) has been ${operationKind === "CREATE" ? "creating" : operationKind === "DELETE" ? "deleting" : "updating"} for ${minutes}m \u2014 still waiting`
|
|
8891
|
+
);
|
|
8892
|
+
});
|
|
8893
|
+
},
|
|
8894
|
+
onTimeout: (elapsedMs) => new ResourceTimeoutError(
|
|
8895
|
+
logicalId,
|
|
8896
|
+
resourceType,
|
|
8897
|
+
this.stackRegion,
|
|
8898
|
+
elapsedMs,
|
|
8899
|
+
operationKind,
|
|
8900
|
+
timeoutMs
|
|
8901
|
+
)
|
|
8902
|
+
}
|
|
8903
|
+
);
|
|
8904
|
+
} catch (error) {
|
|
8905
|
+
renderer.removeTask(logicalId);
|
|
8906
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8907
|
+
this.logger.error(`Failed to ${change.changeType.toLowerCase()} ${logicalId}: ${message}`);
|
|
8908
|
+
throw new ProvisioningError(
|
|
8909
|
+
`Failed to ${change.changeType.toLowerCase()} resource ${logicalId}`,
|
|
8910
|
+
resourceType,
|
|
8911
|
+
logicalId,
|
|
8912
|
+
stateResources[logicalId]?.physicalId,
|
|
8913
|
+
error instanceof Error ? error : void 0
|
|
8914
|
+
);
|
|
8915
|
+
} finally {
|
|
8916
|
+
renderer.removeTask(logicalId);
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8919
|
+
/**
|
|
8920
|
+
* Inner body of provisionResource, extracted so the outer wrapper can
|
|
8921
|
+
* apply the per-resource deadline (`withResourceDeadline`) without
|
|
8922
|
+
* having the timeout / warn timer code dwarf the real provisioning
|
|
8923
|
+
* logic. Behaviour is unchanged from the pre-deadline implementation.
|
|
8924
|
+
*/
|
|
8925
|
+
async provisionResourceBody(logicalId, change, stateResources, stackName, template, parameterValues, conditions, counts, progress) {
|
|
8926
|
+
const resourceType = change.resourceType;
|
|
8927
|
+
const provider = this.providerRegistry.getProvider(resourceType);
|
|
8928
|
+
const renderer = getLiveRenderer();
|
|
8929
|
+
switch (change.changeType) {
|
|
8930
|
+
case "CREATE": {
|
|
8931
|
+
const desiredProps = change.desiredProperties || {};
|
|
8932
|
+
const context = {
|
|
8933
|
+
template,
|
|
8934
|
+
resources: stateResources,
|
|
8935
|
+
...parameterValues && Object.keys(parameterValues).length > 0 && { parameters: parameterValues },
|
|
8936
|
+
...conditions && Object.keys(conditions).length > 0 && { conditions },
|
|
8937
|
+
stateBackend: this.stateBackend,
|
|
8938
|
+
stackName
|
|
8939
|
+
};
|
|
8940
|
+
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
8941
|
+
const { provider: createProvider, properties: createProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
|
|
8942
|
+
const result = await this.withRetry(
|
|
8943
|
+
() => createProvider.create(logicalId, resourceType, createProps),
|
|
8944
|
+
logicalId
|
|
8945
|
+
);
|
|
8946
|
+
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
8947
|
+
stateResources[logicalId] = {
|
|
8948
|
+
physicalId: result.physicalId,
|
|
8949
|
+
resourceType,
|
|
8950
|
+
properties: resolvedProps,
|
|
8951
|
+
...result.attributes && { attributes: result.attributes },
|
|
8952
|
+
...dependencies && dependencies.length > 0 && { dependencies }
|
|
8953
|
+
};
|
|
8954
|
+
if (counts)
|
|
8955
|
+
counts.created++;
|
|
8956
|
+
if (progress)
|
|
8957
|
+
progress.current++;
|
|
8958
|
+
const createPrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
8959
|
+
renderer.removeTask(logicalId);
|
|
8960
|
+
this.logger.info(`${createPrefix}\u2705 ${logicalId} (${resourceType}) created`);
|
|
8961
|
+
break;
|
|
8962
|
+
}
|
|
8963
|
+
case "UPDATE": {
|
|
8964
|
+
const currentResource = stateResources[logicalId];
|
|
8965
|
+
if (!currentResource) {
|
|
8966
|
+
throw new Error(`Cannot update ${logicalId}: resource not found in state`);
|
|
8967
|
+
}
|
|
8968
|
+
const desiredProps = change.desiredProperties || {};
|
|
8969
|
+
const currentProps = change.currentProperties || {};
|
|
8970
|
+
const context = {
|
|
8971
|
+
template,
|
|
8972
|
+
resources: stateResources,
|
|
8973
|
+
...parameterValues && Object.keys(parameterValues).length > 0 && { parameters: parameterValues },
|
|
8974
|
+
...conditions && Object.keys(conditions).length > 0 && { conditions },
|
|
8975
|
+
stateBackend: this.stateBackend,
|
|
8976
|
+
stackName
|
|
8977
|
+
};
|
|
8978
|
+
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
8979
|
+
if (JSON.stringify(resolvedProps) === JSON.stringify(currentProps)) {
|
|
8980
|
+
this.logger.debug(
|
|
8981
|
+
`Skipping ${logicalId}: no actual changes after intrinsic function resolution`
|
|
8982
|
+
);
|
|
8983
|
+
if (counts)
|
|
8984
|
+
counts.skipped++;
|
|
8985
|
+
break;
|
|
8986
|
+
}
|
|
8987
|
+
const needsReplacement = change.propertyChanges?.some((pc) => pc.requiresReplacement);
|
|
8988
|
+
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
8989
|
+
if (needsReplacement) {
|
|
8990
|
+
const replacedProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
|
|
8991
|
+
this.logger.info(
|
|
8992
|
+
`Replacing ${logicalId} (${resourceType}) - immutable properties changed: ${replacedProps}`
|
|
8993
|
+
);
|
|
8994
|
+
this.logger.info(` Creating new ${logicalId}...`);
|
|
8995
|
+
const { provider: replaceProvider, properties: replaceProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
|
|
8996
|
+
const createResult = await this.withRetry(
|
|
8997
|
+
() => replaceProvider.create(logicalId, resourceType, replaceProps),
|
|
8751
8998
|
logicalId
|
|
8752
8999
|
);
|
|
8753
|
-
const
|
|
9000
|
+
const updateReplacePolicy = template?.Resources?.[logicalId]?.UpdateReplacePolicy;
|
|
9001
|
+
if (updateReplacePolicy === "Retain") {
|
|
9002
|
+
this.logger.info(
|
|
9003
|
+
` Retaining old ${logicalId} (${currentResource.physicalId}) - UpdateReplacePolicy: Retain`
|
|
9004
|
+
);
|
|
9005
|
+
} else {
|
|
9006
|
+
this.logger.info(` Deleting old ${logicalId} (${currentResource.physicalId})...`);
|
|
9007
|
+
try {
|
|
9008
|
+
await provider.delete(
|
|
9009
|
+
logicalId,
|
|
9010
|
+
currentResource.physicalId,
|
|
9011
|
+
resourceType,
|
|
9012
|
+
currentResource.properties,
|
|
9013
|
+
{ expectedRegion: this.stackRegion }
|
|
9014
|
+
);
|
|
9015
|
+
this.logger.info(` \u2713 Old resource deleted`);
|
|
9016
|
+
} catch (deleteError) {
|
|
9017
|
+
this.logger.warn(
|
|
9018
|
+
` \u26A0 Failed to delete old resource ${logicalId} (${currentResource.physicalId}): ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`
|
|
9019
|
+
);
|
|
9020
|
+
}
|
|
9021
|
+
}
|
|
8754
9022
|
stateResources[logicalId] = {
|
|
8755
|
-
physicalId:
|
|
9023
|
+
physicalId: createResult.physicalId,
|
|
8756
9024
|
resourceType,
|
|
8757
9025
|
properties: resolvedProps,
|
|
8758
|
-
...
|
|
9026
|
+
...createResult.attributes && { attributes: createResult.attributes },
|
|
8759
9027
|
...dependencies && dependencies.length > 0 && { dependencies }
|
|
8760
9028
|
};
|
|
8761
9029
|
if (counts)
|
|
8762
|
-
counts.
|
|
9030
|
+
counts.updated++;
|
|
8763
9031
|
if (progress)
|
|
8764
9032
|
progress.current++;
|
|
8765
|
-
const
|
|
9033
|
+
const replacePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
8766
9034
|
renderer.removeTask(logicalId);
|
|
8767
|
-
this.logger.info(`${
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
...conditions && Object.keys(conditions).length > 0 && { conditions },
|
|
8782
|
-
stateBackend: this.stateBackend,
|
|
8783
|
-
stackName
|
|
8784
|
-
};
|
|
8785
|
-
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
8786
|
-
if (JSON.stringify(resolvedProps) === JSON.stringify(currentProps)) {
|
|
8787
|
-
this.logger.debug(
|
|
8788
|
-
`Skipping ${logicalId}: no actual changes after intrinsic function resolution`
|
|
8789
|
-
);
|
|
8790
|
-
if (counts)
|
|
8791
|
-
counts.skipped++;
|
|
8792
|
-
break;
|
|
8793
|
-
}
|
|
8794
|
-
const needsReplacement2 = change.propertyChanges?.some((pc) => pc.requiresReplacement);
|
|
8795
|
-
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
8796
|
-
if (needsReplacement2) {
|
|
8797
|
-
const replacedProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
|
|
8798
|
-
this.logger.info(
|
|
8799
|
-
`Replacing ${logicalId} (${resourceType}) - immutable properties changed: ${replacedProps}`
|
|
8800
|
-
);
|
|
8801
|
-
this.logger.info(` Creating new ${logicalId}...`);
|
|
8802
|
-
const { provider: replaceProvider, properties: replaceProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
|
|
8803
|
-
const createResult = await this.withRetry(
|
|
8804
|
-
() => replaceProvider.create(logicalId, resourceType, replaceProps),
|
|
9035
|
+
this.logger.info(`${replacePrefix}\u2705 ${logicalId} (${resourceType}) replaced`);
|
|
9036
|
+
} else {
|
|
9037
|
+
this.logger.debug(`Updating ${logicalId} (${resourceType})`);
|
|
9038
|
+
const { provider: updateProvider, properties: updateProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
|
|
9039
|
+
let result;
|
|
9040
|
+
try {
|
|
9041
|
+
result = await this.withRetry(
|
|
9042
|
+
() => updateProvider.update(
|
|
9043
|
+
logicalId,
|
|
9044
|
+
currentResource.physicalId,
|
|
9045
|
+
resourceType,
|
|
9046
|
+
updateProps,
|
|
9047
|
+
currentProps
|
|
9048
|
+
),
|
|
8805
9049
|
logicalId
|
|
8806
9050
|
);
|
|
8807
|
-
|
|
8808
|
-
|
|
9051
|
+
} catch (updateError) {
|
|
9052
|
+
const msg = updateError instanceof Error ? updateError.message : String(updateError);
|
|
9053
|
+
if (msg.includes("UnsupportedActionException") || msg.includes("does not support UPDATE")) {
|
|
8809
9054
|
this.logger.info(
|
|
8810
|
-
`
|
|
9055
|
+
`UPDATE not supported for ${logicalId} (${resourceType}), replacing (DELETE \u2192 CREATE)`
|
|
8811
9056
|
);
|
|
8812
|
-
} else {
|
|
8813
|
-
this.logger.info(` Deleting old ${logicalId} (${currentResource.physicalId})...`);
|
|
8814
9057
|
try {
|
|
8815
9058
|
await provider.delete(
|
|
8816
9059
|
logicalId,
|
|
8817
9060
|
currentResource.physicalId,
|
|
8818
9061
|
resourceType,
|
|
8819
|
-
|
|
9062
|
+
currentProps,
|
|
8820
9063
|
{ expectedRegion: this.stackRegion }
|
|
8821
9064
|
);
|
|
8822
|
-
this.logger.info(` \u2713 Old resource deleted`);
|
|
8823
9065
|
} catch (deleteError) {
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
}
|
|
8829
|
-
stateResources[logicalId] = {
|
|
8830
|
-
physicalId: createResult.physicalId,
|
|
8831
|
-
resourceType,
|
|
8832
|
-
properties: resolvedProps,
|
|
8833
|
-
...createResult.attributes && { attributes: createResult.attributes },
|
|
8834
|
-
...dependencies && dependencies.length > 0 && { dependencies }
|
|
8835
|
-
};
|
|
8836
|
-
if (counts)
|
|
8837
|
-
counts.updated++;
|
|
8838
|
-
if (progress)
|
|
8839
|
-
progress.current++;
|
|
8840
|
-
const replacePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
8841
|
-
renderer.removeTask(logicalId);
|
|
8842
|
-
this.logger.info(`${replacePrefix}\u2705 ${logicalId} (${resourceType}) replaced`);
|
|
8843
|
-
} else {
|
|
8844
|
-
this.logger.debug(`Updating ${logicalId} (${resourceType})`);
|
|
8845
|
-
const { provider: updateProvider, properties: updateProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
|
|
8846
|
-
let result;
|
|
8847
|
-
try {
|
|
8848
|
-
result = await this.withRetry(
|
|
8849
|
-
() => updateProvider.update(
|
|
8850
|
-
logicalId,
|
|
8851
|
-
currentResource.physicalId,
|
|
8852
|
-
resourceType,
|
|
8853
|
-
updateProps,
|
|
8854
|
-
currentProps
|
|
8855
|
-
),
|
|
8856
|
-
logicalId
|
|
8857
|
-
);
|
|
8858
|
-
} catch (updateError) {
|
|
8859
|
-
const msg = updateError instanceof Error ? updateError.message : String(updateError);
|
|
8860
|
-
if (msg.includes("UnsupportedActionException") || msg.includes("does not support UPDATE")) {
|
|
8861
|
-
this.logger.info(
|
|
8862
|
-
`UPDATE not supported for ${logicalId} (${resourceType}), replacing (DELETE \u2192 CREATE)`
|
|
8863
|
-
);
|
|
8864
|
-
try {
|
|
8865
|
-
await provider.delete(
|
|
8866
|
-
logicalId,
|
|
8867
|
-
currentResource.physicalId,
|
|
8868
|
-
resourceType,
|
|
8869
|
-
currentProps,
|
|
8870
|
-
{ expectedRegion: this.stackRegion }
|
|
9066
|
+
const deleteMsg = deleteError instanceof Error ? deleteError.message : String(deleteError);
|
|
9067
|
+
if (deleteMsg.includes("does not exist") || deleteMsg.includes("not found") || deleteMsg.includes("NotFound")) {
|
|
9068
|
+
this.logger.debug(
|
|
9069
|
+
`Old resource ${logicalId} already gone, proceeding with CREATE`
|
|
8871
9070
|
);
|
|
8872
|
-
}
|
|
8873
|
-
|
|
8874
|
-
if (deleteMsg.includes("does not exist") || deleteMsg.includes("not found") || deleteMsg.includes("NotFound")) {
|
|
8875
|
-
this.logger.debug(
|
|
8876
|
-
`Old resource ${logicalId} already gone, proceeding with CREATE`
|
|
8877
|
-
);
|
|
8878
|
-
} else {
|
|
8879
|
-
throw deleteError;
|
|
8880
|
-
}
|
|
9071
|
+
} else {
|
|
9072
|
+
throw deleteError;
|
|
8881
9073
|
}
|
|
8882
|
-
const { provider: replProvider, properties: replProps } = this.selectProviderWithSafetyNet(
|
|
8883
|
-
provider,
|
|
8884
|
-
resourceType,
|
|
8885
|
-
resolvedProps,
|
|
8886
|
-
logicalId
|
|
8887
|
-
);
|
|
8888
|
-
const createResult = await this.withRetry(
|
|
8889
|
-
() => replProvider.create(logicalId, resourceType, replProps),
|
|
8890
|
-
logicalId
|
|
8891
|
-
);
|
|
8892
|
-
result = {
|
|
8893
|
-
physicalId: createResult.physicalId,
|
|
8894
|
-
attributes: createResult.attributes,
|
|
8895
|
-
wasReplaced: true
|
|
8896
|
-
};
|
|
8897
|
-
} else {
|
|
8898
|
-
throw updateError;
|
|
8899
9074
|
}
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
9075
|
+
const { provider: replProvider, properties: replProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
|
|
9076
|
+
const createResult = await this.withRetry(
|
|
9077
|
+
() => replProvider.create(logicalId, resourceType, replProps),
|
|
9078
|
+
logicalId
|
|
8904
9079
|
);
|
|
9080
|
+
result = {
|
|
9081
|
+
physicalId: createResult.physicalId,
|
|
9082
|
+
attributes: createResult.attributes,
|
|
9083
|
+
wasReplaced: true
|
|
9084
|
+
};
|
|
9085
|
+
} else {
|
|
9086
|
+
throw updateError;
|
|
8905
9087
|
}
|
|
8906
|
-
stateResources[logicalId] = {
|
|
8907
|
-
physicalId: result.physicalId,
|
|
8908
|
-
resourceType,
|
|
8909
|
-
properties: resolvedProps,
|
|
8910
|
-
...result.attributes && { attributes: result.attributes },
|
|
8911
|
-
...dependencies && dependencies.length > 0 && { dependencies }
|
|
8912
|
-
};
|
|
8913
|
-
if (counts)
|
|
8914
|
-
counts.updated++;
|
|
8915
|
-
if (progress)
|
|
8916
|
-
progress.current++;
|
|
8917
|
-
const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
8918
|
-
renderer.removeTask(logicalId);
|
|
8919
|
-
this.logger.info(`${updatePrefix}\u2705 ${logicalId} (${resourceType}) updated`);
|
|
8920
|
-
}
|
|
8921
|
-
break;
|
|
8922
|
-
}
|
|
8923
|
-
case "DELETE": {
|
|
8924
|
-
const currentResource = stateResources[logicalId];
|
|
8925
|
-
if (!currentResource) {
|
|
8926
|
-
throw new Error(`Cannot delete ${logicalId}: resource not found in state`);
|
|
8927
9088
|
}
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
delete stateResources[logicalId];
|
|
8932
|
-
break;
|
|
8933
|
-
}
|
|
8934
|
-
this.logger.debug(`Deleting ${logicalId} (${resourceType})`);
|
|
8935
|
-
try {
|
|
8936
|
-
await this.withRetry(
|
|
8937
|
-
() => provider.delete(
|
|
8938
|
-
logicalId,
|
|
8939
|
-
currentResource.physicalId,
|
|
8940
|
-
resourceType,
|
|
8941
|
-
currentResource.properties,
|
|
8942
|
-
{ expectedRegion: this.stackRegion }
|
|
8943
|
-
),
|
|
8944
|
-
logicalId,
|
|
8945
|
-
3,
|
|
8946
|
-
// fewer retries for DELETE
|
|
8947
|
-
5e3
|
|
9089
|
+
if (result.wasReplaced) {
|
|
9090
|
+
this.logger.info(
|
|
9091
|
+
`Resource ${logicalId} was replaced: ${currentResource.physicalId} -> ${result.physicalId}`
|
|
8948
9092
|
);
|
|
8949
|
-
} catch (deleteError) {
|
|
8950
|
-
const msg = deleteError instanceof Error ? deleteError.message : String(deleteError);
|
|
8951
|
-
if (msg.includes("does not exist") || msg.includes("was not found") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException") || msg.includes("ResourceNotFoundException")) {
|
|
8952
|
-
this.logger.debug(
|
|
8953
|
-
`Resource ${logicalId} already deleted (${msg}), removing from state`
|
|
8954
|
-
);
|
|
8955
|
-
} else {
|
|
8956
|
-
throw deleteError;
|
|
8957
|
-
}
|
|
8958
9093
|
}
|
|
8959
|
-
|
|
9094
|
+
stateResources[logicalId] = {
|
|
9095
|
+
physicalId: result.physicalId,
|
|
9096
|
+
resourceType,
|
|
9097
|
+
properties: resolvedProps,
|
|
9098
|
+
...result.attributes && { attributes: result.attributes },
|
|
9099
|
+
...dependencies && dependencies.length > 0 && { dependencies }
|
|
9100
|
+
};
|
|
8960
9101
|
if (counts)
|
|
8961
|
-
counts.
|
|
9102
|
+
counts.updated++;
|
|
8962
9103
|
if (progress)
|
|
8963
9104
|
progress.current++;
|
|
8964
|
-
const
|
|
9105
|
+
const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
8965
9106
|
renderer.removeTask(logicalId);
|
|
8966
|
-
this.logger.info(`${
|
|
9107
|
+
this.logger.info(`${updatePrefix}\u2705 ${logicalId} (${resourceType}) updated`);
|
|
9108
|
+
}
|
|
9109
|
+
break;
|
|
9110
|
+
}
|
|
9111
|
+
case "DELETE": {
|
|
9112
|
+
const currentResource = stateResources[logicalId];
|
|
9113
|
+
if (!currentResource) {
|
|
9114
|
+
throw new Error(`Cannot delete ${logicalId}: resource not found in state`);
|
|
9115
|
+
}
|
|
9116
|
+
const deletionPolicy = template?.Resources?.[logicalId]?.DeletionPolicy;
|
|
9117
|
+
if (deletionPolicy === "Retain") {
|
|
9118
|
+
this.logger.info(`Retaining ${logicalId} (${resourceType}) - DeletionPolicy: Retain`);
|
|
9119
|
+
delete stateResources[logicalId];
|
|
8967
9120
|
break;
|
|
8968
9121
|
}
|
|
9122
|
+
this.logger.debug(`Deleting ${logicalId} (${resourceType})`);
|
|
9123
|
+
try {
|
|
9124
|
+
await this.withRetry(
|
|
9125
|
+
() => provider.delete(
|
|
9126
|
+
logicalId,
|
|
9127
|
+
currentResource.physicalId,
|
|
9128
|
+
resourceType,
|
|
9129
|
+
currentResource.properties,
|
|
9130
|
+
{ expectedRegion: this.stackRegion }
|
|
9131
|
+
),
|
|
9132
|
+
logicalId,
|
|
9133
|
+
3,
|
|
9134
|
+
// fewer retries for DELETE
|
|
9135
|
+
5e3
|
|
9136
|
+
);
|
|
9137
|
+
} catch (deleteError) {
|
|
9138
|
+
const msg = deleteError instanceof Error ? deleteError.message : String(deleteError);
|
|
9139
|
+
if (msg.includes("does not exist") || msg.includes("was not found") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException") || msg.includes("ResourceNotFoundException")) {
|
|
9140
|
+
this.logger.debug(
|
|
9141
|
+
`Resource ${logicalId} already deleted (${msg}), removing from state`
|
|
9142
|
+
);
|
|
9143
|
+
} else {
|
|
9144
|
+
throw deleteError;
|
|
9145
|
+
}
|
|
9146
|
+
}
|
|
9147
|
+
delete stateResources[logicalId];
|
|
9148
|
+
if (counts)
|
|
9149
|
+
counts.deleted++;
|
|
9150
|
+
if (progress)
|
|
9151
|
+
progress.current++;
|
|
9152
|
+
const deletePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
|
|
9153
|
+
renderer.removeTask(logicalId);
|
|
9154
|
+
this.logger.info(`${deletePrefix}\u2705 ${logicalId} (${resourceType}) deleted`);
|
|
9155
|
+
break;
|
|
8969
9156
|
}
|
|
8970
|
-
} catch (error) {
|
|
8971
|
-
renderer.removeTask(logicalId);
|
|
8972
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
8973
|
-
this.logger.error(`Failed to ${change.changeType.toLowerCase()} ${logicalId}: ${message}`);
|
|
8974
|
-
throw new ProvisioningError(
|
|
8975
|
-
`Failed to ${change.changeType.toLowerCase()} resource ${logicalId}`,
|
|
8976
|
-
resourceType,
|
|
8977
|
-
logicalId,
|
|
8978
|
-
stateResources[logicalId]?.physicalId,
|
|
8979
|
-
error instanceof Error ? error : void 0
|
|
8980
|
-
);
|
|
8981
|
-
} finally {
|
|
8982
|
-
renderer.removeTask(logicalId);
|
|
8983
9157
|
}
|
|
8984
9158
|
}
|
|
8985
9159
|
/**
|