@go-to-k/cdkd 0.1.0 → 0.2.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 +5 -4
- package/dist/cli.js +198 -108
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.2.0.tgz +0 -0
- package/dist/index.js +197 -107
- package/dist/index.js.map +4 -4
- package/package.json +3 -2
- package/dist/go-to-k-cdkd-0.1.0.tgz +0 -0
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -6993,8 +6993,96 @@ var IAMRoleProvider = class {
|
|
|
6993
6993
|
}
|
|
6994
6994
|
};
|
|
6995
6995
|
|
|
6996
|
+
// src/deployment/dag-executor.ts
|
|
6997
|
+
var DagExecutor = class {
|
|
6998
|
+
nodes = /* @__PURE__ */ new Map();
|
|
6999
|
+
logger = getLogger().child("DagExecutor");
|
|
7000
|
+
add(node) {
|
|
7001
|
+
this.nodes.set(node.id, node);
|
|
7002
|
+
}
|
|
7003
|
+
has(id) {
|
|
7004
|
+
return this.nodes.has(id);
|
|
7005
|
+
}
|
|
7006
|
+
size() {
|
|
7007
|
+
return this.nodes.size;
|
|
7008
|
+
}
|
|
7009
|
+
values() {
|
|
7010
|
+
return this.nodes.values();
|
|
7011
|
+
}
|
|
7012
|
+
async execute(concurrency, fn, cancelled = () => false) {
|
|
7013
|
+
let active = 0;
|
|
7014
|
+
const errors = [];
|
|
7015
|
+
return new Promise((resolve4, reject) => {
|
|
7016
|
+
const dispatch = () => {
|
|
7017
|
+
let changed = true;
|
|
7018
|
+
while (changed) {
|
|
7019
|
+
changed = false;
|
|
7020
|
+
for (const node of this.nodes.values()) {
|
|
7021
|
+
if (node.state !== "pending")
|
|
7022
|
+
continue;
|
|
7023
|
+
const hasFailedDep = [...node.dependencies].some((depId) => {
|
|
7024
|
+
const dep = this.nodes.get(depId);
|
|
7025
|
+
return dep && (dep.state === "failed" || dep.state === "skipped");
|
|
7026
|
+
});
|
|
7027
|
+
if (hasFailedDep) {
|
|
7028
|
+
node.state = "skipped";
|
|
7029
|
+
changed = true;
|
|
7030
|
+
this.logger.debug(`Skipped ${node.id}: dependency failed or was skipped`);
|
|
7031
|
+
}
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
const ready = [];
|
|
7035
|
+
for (const node of this.nodes.values()) {
|
|
7036
|
+
if (node.state !== "pending")
|
|
7037
|
+
continue;
|
|
7038
|
+
const depsReady = [...node.dependencies].every((depId) => {
|
|
7039
|
+
const dep = this.nodes.get(depId);
|
|
7040
|
+
return !dep || dep.state === "completed";
|
|
7041
|
+
});
|
|
7042
|
+
if (depsReady)
|
|
7043
|
+
ready.push(node);
|
|
7044
|
+
}
|
|
7045
|
+
if (!cancelled()) {
|
|
7046
|
+
for (const node of ready) {
|
|
7047
|
+
if (active >= concurrency)
|
|
7048
|
+
break;
|
|
7049
|
+
node.state = "running";
|
|
7050
|
+
active++;
|
|
7051
|
+
fn(node).then(() => {
|
|
7052
|
+
node.state = "completed";
|
|
7053
|
+
}).catch((error) => {
|
|
7054
|
+
node.state = "failed";
|
|
7055
|
+
errors.push({ id: node.id, error });
|
|
7056
|
+
}).finally(() => {
|
|
7057
|
+
active--;
|
|
7058
|
+
dispatch();
|
|
7059
|
+
});
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
if (active === 0) {
|
|
7063
|
+
if (errors.length > 0) {
|
|
7064
|
+
reject(errors[0].error);
|
|
7065
|
+
return;
|
|
7066
|
+
}
|
|
7067
|
+
const stillPending = [...this.nodes.values()].some((n) => n.state === "pending");
|
|
7068
|
+
if (stillPending && !cancelled()) {
|
|
7069
|
+
const pending = [...this.nodes.values()].filter((n) => n.state === "pending").map((n) => n.id);
|
|
7070
|
+
reject(
|
|
7071
|
+
new Error(
|
|
7072
|
+
`Deadlock detected: ${pending.length} node(s) stuck with unresolvable dependencies (${pending.join(", ")})`
|
|
7073
|
+
)
|
|
7074
|
+
);
|
|
7075
|
+
return;
|
|
7076
|
+
}
|
|
7077
|
+
resolve4();
|
|
7078
|
+
}
|
|
7079
|
+
};
|
|
7080
|
+
dispatch();
|
|
7081
|
+
});
|
|
7082
|
+
}
|
|
7083
|
+
};
|
|
7084
|
+
|
|
6996
7085
|
// src/deployment/deploy-engine.ts
|
|
6997
|
-
import pLimit from "p-limit";
|
|
6998
7086
|
var InterruptedError = class extends Error {
|
|
6999
7087
|
constructor() {
|
|
7000
7088
|
super("Deployment interrupted by user (Ctrl+C)");
|
|
@@ -7127,6 +7215,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
7127
7215
|
template,
|
|
7128
7216
|
currentState,
|
|
7129
7217
|
changes,
|
|
7218
|
+
dag,
|
|
7130
7219
|
executionLevels,
|
|
7131
7220
|
stackName,
|
|
7132
7221
|
parameterValues,
|
|
@@ -7159,13 +7248,16 @@ var DeployEngine = class _DeployEngine {
|
|
|
7159
7248
|
}
|
|
7160
7249
|
}
|
|
7161
7250
|
/**
|
|
7162
|
-
* Execute deployment by processing resources
|
|
7251
|
+
* Execute deployment by processing resources via event-driven DAG dispatch.
|
|
7163
7252
|
*
|
|
7164
|
-
*
|
|
7165
|
-
*
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7253
|
+
* - CREATE/UPDATE follow forward dependency order (a node starts as soon as
|
|
7254
|
+
* ALL of its dependencies are completed — does not wait for unrelated
|
|
7255
|
+
* siblings in the same "level")
|
|
7256
|
+
* - DELETE follows reverse dependency order (a node starts as soon as all
|
|
7257
|
+
* resources that depend ON it have finished deleting)
|
|
7258
|
+
*/
|
|
7259
|
+
async executeDeployment(template, currentState, changes, dag, executionLevels, stackName, parameterValues, conditions, currentEtag, progress) {
|
|
7260
|
+
const concurrency = this.options.concurrency;
|
|
7169
7261
|
const newResources = { ...currentState.resources };
|
|
7170
7262
|
const actualCounts = { created: 0, updated: 0, deleted: 0, skipped: 0 };
|
|
7171
7263
|
const completedOperations = [];
|
|
@@ -7196,32 +7288,36 @@ var DeployEngine = class _DeployEngine {
|
|
|
7196
7288
|
Array.from(changes.entries()).filter(([_, change]) => change.changeType === "DELETE").map(([logicalId]) => logicalId)
|
|
7197
7289
|
);
|
|
7198
7290
|
try {
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
}
|
|
7203
|
-
const levelNodes = executionLevels[levelIndex];
|
|
7204
|
-
if (!levelNodes)
|
|
7291
|
+
const createUpdateIds = [];
|
|
7292
|
+
for (const [id, change] of changes.entries()) {
|
|
7293
|
+
if (deleteChanges.has(id))
|
|
7205
7294
|
continue;
|
|
7206
|
-
|
|
7207
|
-
if (deleteChanges.has(id))
|
|
7208
|
-
return false;
|
|
7209
|
-
const change = changes.get(id);
|
|
7210
|
-
return !!change && change.changeType !== "NO_CHANGE";
|
|
7211
|
-
});
|
|
7212
|
-
if (level.length === 0)
|
|
7295
|
+
if (change.changeType === "NO_CHANGE")
|
|
7213
7296
|
continue;
|
|
7297
|
+
createUpdateIds.push(id);
|
|
7298
|
+
}
|
|
7299
|
+
if (createUpdateIds.length > 0) {
|
|
7214
7300
|
this.logger.info(
|
|
7215
|
-
`
|
|
7301
|
+
`Deploying ${createUpdateIds.length} resource(s) (DAG: ${executionLevels.length} levels, max parallel: ${concurrency})`
|
|
7216
7302
|
);
|
|
7217
|
-
const
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7303
|
+
const createUpdateExecutor = new DagExecutor();
|
|
7304
|
+
const provisionable = new Set(createUpdateIds);
|
|
7305
|
+
for (const id of createUpdateIds) {
|
|
7306
|
+
const allDeps = this.dagBuilder.getDirectDependencies(dag, id);
|
|
7307
|
+
const deps = new Set(allDeps.filter((d) => provisionable.has(d)));
|
|
7308
|
+
createUpdateExecutor.add({
|
|
7309
|
+
id,
|
|
7310
|
+
dependencies: deps,
|
|
7311
|
+
state: "pending",
|
|
7312
|
+
data: changes.get(id)
|
|
7313
|
+
});
|
|
7314
|
+
}
|
|
7315
|
+
try {
|
|
7316
|
+
await createUpdateExecutor.execute(
|
|
7317
|
+
concurrency,
|
|
7318
|
+
async (node) => {
|
|
7319
|
+
const logicalId = node.id;
|
|
7320
|
+
const change = node.data;
|
|
7225
7321
|
const previousState = currentState.resources[logicalId] ? { ...currentState.resources[logicalId] } : void 0;
|
|
7226
7322
|
try {
|
|
7227
7323
|
await this.provisionResource(
|
|
@@ -7248,30 +7344,36 @@ var DeployEngine = class _DeployEngine {
|
|
|
7248
7344
|
properties: newResources[logicalId]?.properties
|
|
7249
7345
|
});
|
|
7250
7346
|
saveStateAfterResource(logicalId);
|
|
7251
|
-
}
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7347
|
+
},
|
|
7348
|
+
() => this.interrupted
|
|
7349
|
+
);
|
|
7350
|
+
} finally {
|
|
7351
|
+
await saveChain;
|
|
7352
|
+
}
|
|
7353
|
+
if (this.interrupted && this.hasPending(createUpdateExecutor)) {
|
|
7354
|
+
throw new InterruptedError();
|
|
7258
7355
|
}
|
|
7259
7356
|
}
|
|
7260
7357
|
if (deleteChanges.size > 0) {
|
|
7261
7358
|
this.logger.info(`Deleting ${deleteChanges.size} resource(s)`);
|
|
7262
|
-
const
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7359
|
+
const deleteDeps = this.buildDeletionDependencies(deleteChanges, currentState);
|
|
7360
|
+
const deleteExecutor = new DagExecutor();
|
|
7361
|
+
for (const id of deleteChanges) {
|
|
7362
|
+
deleteExecutor.add({
|
|
7363
|
+
id,
|
|
7364
|
+
dependencies: deleteDeps.get(id) ?? /* @__PURE__ */ new Set(),
|
|
7365
|
+
state: "pending",
|
|
7366
|
+
data: changes.get(id)
|
|
7367
|
+
});
|
|
7368
|
+
}
|
|
7369
|
+
try {
|
|
7370
|
+
await deleteExecutor.execute(
|
|
7371
|
+
concurrency,
|
|
7372
|
+
async (node) => {
|
|
7373
|
+
const logicalId = node.id;
|
|
7374
|
+
const change = node.data;
|
|
7375
|
+
const previousState = currentState.resources[logicalId] ? { ...currentState.resources[logicalId] } : void 0;
|
|
7376
|
+
try {
|
|
7275
7377
|
await this.provisionResource(
|
|
7276
7378
|
logicalId,
|
|
7277
7379
|
change,
|
|
@@ -7283,23 +7385,25 @@ var DeployEngine = class _DeployEngine {
|
|
|
7283
7385
|
actualCounts,
|
|
7284
7386
|
progress
|
|
7285
7387
|
);
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7388
|
+
} catch (provisionError) {
|
|
7389
|
+
this.interrupted = true;
|
|
7390
|
+
throw provisionError;
|
|
7391
|
+
}
|
|
7392
|
+
completedOperations.push({
|
|
7393
|
+
logicalId,
|
|
7394
|
+
changeType: "DELETE",
|
|
7395
|
+
resourceType: change.resourceType,
|
|
7396
|
+
previousState
|
|
7397
|
+
});
|
|
7398
|
+
saveStateAfterResource(logicalId);
|
|
7399
|
+
},
|
|
7400
|
+
() => this.interrupted
|
|
7295
7401
|
);
|
|
7402
|
+
} finally {
|
|
7296
7403
|
await saveChain;
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
);
|
|
7300
|
-
if (deleteFailures.length > 0) {
|
|
7301
|
-
throw deleteFailures[0].reason;
|
|
7302
|
-
}
|
|
7404
|
+
}
|
|
7405
|
+
if (this.interrupted && this.hasPending(deleteExecutor)) {
|
|
7406
|
+
throw new InterruptedError();
|
|
7303
7407
|
}
|
|
7304
7408
|
}
|
|
7305
7409
|
} catch (error) {
|
|
@@ -7393,12 +7497,12 @@ var DeployEngine = class _DeployEngine {
|
|
|
7393
7497
|
* - UPDATE → update back to previous properties
|
|
7394
7498
|
* - DELETE → cannot rollback (resource already deleted), log warning
|
|
7395
7499
|
*
|
|
7396
|
-
* Resources
|
|
7397
|
-
* (e.g., IAM Policy depends on IAM Role). When rolling back
|
|
7398
|
-
* dependent resources must be deleted before their
|
|
7399
|
-
* sorts CREATE rollback operations using dependency
|
|
7400
|
-
* then processes UPDATE/DELETE rollbacks, and finally
|
|
7401
|
-
* rollback deletions.
|
|
7500
|
+
* Resources completed concurrently in the dispatcher may have dependencies
|
|
7501
|
+
* between them (e.g., IAM Policy depends on IAM Role). When rolling back
|
|
7502
|
+
* CREATEs (deleting), dependent resources must be deleted before their
|
|
7503
|
+
* dependencies. This method sorts CREATE rollback operations using dependency
|
|
7504
|
+
* information from state, then processes UPDATE/DELETE rollbacks, and finally
|
|
7505
|
+
* processes sorted CREATE rollback deletions.
|
|
7402
7506
|
*/
|
|
7403
7507
|
async performRollback(completedOperations, stateResources, _stackName) {
|
|
7404
7508
|
if (completedOperations.length === 0) {
|
|
@@ -7431,7 +7535,7 @@ var DeployEngine = class _DeployEngine {
|
|
|
7431
7535
|
* Sort CREATE rollback operations so that resources depending on others
|
|
7432
7536
|
* are deleted first (reverse dependency order).
|
|
7433
7537
|
*
|
|
7434
|
-
* Uses state dependencies to
|
|
7538
|
+
* Uses state dependencies to determine reverse-dependency order, similar to buildDeletionDependencies.
|
|
7435
7539
|
*/
|
|
7436
7540
|
sortRollbackCreates(createOps, stateResources) {
|
|
7437
7541
|
const opMap = /* @__PURE__ */ new Map();
|
|
@@ -7853,17 +7957,30 @@ var DeployEngine = class _DeployEngine {
|
|
|
7853
7957
|
"AWS::EC2::SecurityGroup": ["AWS::EC2::SecurityGroupIngress", "AWS::EC2::SecurityGroupEgress"]
|
|
7854
7958
|
};
|
|
7855
7959
|
/**
|
|
7856
|
-
* Build
|
|
7857
|
-
*
|
|
7960
|
+
* Build a per-resource map of "must be deleted before me" dependencies for
|
|
7961
|
+
* the DELETE phase, derived from state-recorded dependencies plus implicit
|
|
7962
|
+
* type-based ordering rules.
|
|
7963
|
+
*
|
|
7964
|
+
* For a resource X, the returned set contains every resource Y such that Y
|
|
7965
|
+
* must finish deleting before X starts — i.e., Y depends on X (or is otherwise
|
|
7966
|
+
* required to vanish first per implicit type rules).
|
|
7967
|
+
*/
|
|
7968
|
+
/**
|
|
7969
|
+
* Returns true if the executor still has un-started pending nodes —
|
|
7970
|
+
* used to distinguish "SIGINT cancelled real work" from "SIGINT landed
|
|
7971
|
+
* after all nodes already completed" (the latter should not error).
|
|
7858
7972
|
*/
|
|
7859
|
-
|
|
7973
|
+
hasPending(executor) {
|
|
7974
|
+
for (const node of executor.values()) {
|
|
7975
|
+
if (node.state === "pending")
|
|
7976
|
+
return true;
|
|
7977
|
+
}
|
|
7978
|
+
return false;
|
|
7979
|
+
}
|
|
7980
|
+
buildDeletionDependencies(deleteIds, state) {
|
|
7860
7981
|
const dependedBy = /* @__PURE__ */ new Map();
|
|
7861
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
7862
7982
|
for (const id of deleteIds) {
|
|
7863
|
-
|
|
7864
|
-
dependedBy.set(id, /* @__PURE__ */ new Set());
|
|
7865
|
-
if (!inDegree.has(id))
|
|
7866
|
-
inDegree.set(id, 0);
|
|
7983
|
+
dependedBy.set(id, /* @__PURE__ */ new Set());
|
|
7867
7984
|
}
|
|
7868
7985
|
for (const id of deleteIds) {
|
|
7869
7986
|
const resource = state.resources[id];
|
|
@@ -7872,38 +7989,11 @@ var DeployEngine = class _DeployEngine {
|
|
|
7872
7989
|
for (const dep of resource.dependencies) {
|
|
7873
7990
|
if (!deleteIds.has(dep))
|
|
7874
7991
|
continue;
|
|
7875
|
-
if (!dependedBy.has(dep))
|
|
7876
|
-
dependedBy.set(dep, /* @__PURE__ */ new Set());
|
|
7877
7992
|
dependedBy.get(dep).add(id);
|
|
7878
|
-
inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
|
|
7879
7993
|
}
|
|
7880
7994
|
}
|
|
7881
7995
|
this.addImplicitDeleteDependencies(deleteIds, state, dependedBy);
|
|
7882
|
-
|
|
7883
|
-
let remaining = new Set(deleteIds);
|
|
7884
|
-
while (remaining.size > 0) {
|
|
7885
|
-
const level = [];
|
|
7886
|
-
for (const id of remaining) {
|
|
7887
|
-
const dependents = dependedBy.get(id);
|
|
7888
|
-
const hasPendingDependents = dependents ? [...dependents].some((d) => remaining.has(d)) : false;
|
|
7889
|
-
if (!hasPendingDependents) {
|
|
7890
|
-
level.push(id);
|
|
7891
|
-
}
|
|
7892
|
-
}
|
|
7893
|
-
if (level.length === 0) {
|
|
7894
|
-
this.logger.warn(
|
|
7895
|
-
`Circular dependency detected in delete order, deleting remaining ${remaining.size} resources`
|
|
7896
|
-
);
|
|
7897
|
-
levels.push([...remaining]);
|
|
7898
|
-
break;
|
|
7899
|
-
}
|
|
7900
|
-
levels.push(level);
|
|
7901
|
-
remaining = new Set([...remaining].filter((id) => !level.includes(id)));
|
|
7902
|
-
}
|
|
7903
|
-
this.logger.debug(
|
|
7904
|
-
`Delete order: ${levels.length} levels - ${levels.map((l, i) => `L${i + 1}(${l.length})`).join(", ")}`
|
|
7905
|
-
);
|
|
7906
|
-
return levels;
|
|
7996
|
+
return dependedBy;
|
|
7907
7997
|
}
|
|
7908
7998
|
/**
|
|
7909
7999
|
* Add implicit delete dependency edges based on resource type relationships.
|