@terraforge/core 0.0.6 → 0.0.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/index.d.ts +3 -2
- package/dist/index.js +153 -86
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -58,12 +58,13 @@ declare const isDataSource: (obj: object) => obj is DataSource;
|
|
|
58
58
|
type ResourceConfig = Config & {
|
|
59
59
|
/** Import an existing resource instead of creating a new resource. */
|
|
60
60
|
import?: string;
|
|
61
|
-
/** If true the resource will be retained in the backing cloud provider during a
|
|
61
|
+
/** If true the resource will be retained in the backing cloud provider during a delete operation. */
|
|
62
62
|
retainOnDelete?: boolean;
|
|
63
|
-
/** Override the default create-after-delete behavior when replacing a resource. */
|
|
64
63
|
/** If set, the provider’s Delete method will not be called for this resource if the specified resource is being deleted as well. */
|
|
65
64
|
/** Declare that changes to certain properties should be treated as forcing a replacement. */
|
|
66
65
|
replaceOnChanges?: string[];
|
|
66
|
+
/** If true, create the replacement before deleting the existing resource. */
|
|
67
|
+
createBeforeReplace?: boolean;
|
|
67
68
|
};
|
|
68
69
|
type ResourceMeta<I extends State = State, O extends State = State> = Meta<'resource', I, O, ResourceConfig>;
|
|
69
70
|
type Resource<I extends State = State, O extends State = State> = O & {
|
package/dist/index.js
CHANGED
|
@@ -186,6 +186,40 @@ var findInputDeps = (props) => {
|
|
|
186
186
|
find(props);
|
|
187
187
|
return deps;
|
|
188
188
|
};
|
|
189
|
+
var formatPath = (path) => {
|
|
190
|
+
if (path.length === 0) {
|
|
191
|
+
return "<root>";
|
|
192
|
+
}
|
|
193
|
+
return path.map((part) => {
|
|
194
|
+
if (typeof part === "number") {
|
|
195
|
+
return `[${part}]`;
|
|
196
|
+
}
|
|
197
|
+
return `.${part}`;
|
|
198
|
+
}).join("").replace(/^\./, "");
|
|
199
|
+
};
|
|
200
|
+
var findInputDepsWithPaths = (props) => {
|
|
201
|
+
const deps = [];
|
|
202
|
+
const find = (value, path) => {
|
|
203
|
+
if (value instanceof Output) {
|
|
204
|
+
for (const meta of value.dependencies) {
|
|
205
|
+
deps.push({
|
|
206
|
+
meta,
|
|
207
|
+
path: formatPath(path)
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (Array.isArray(value)) {
|
|
213
|
+
value.map((item, index) => find(item, [...path, index]));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (value?.constructor === Object) {
|
|
217
|
+
Object.entries(value).map(([key, item]) => find(item, [...path, key]));
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
find(props, []);
|
|
221
|
+
return deps;
|
|
222
|
+
};
|
|
189
223
|
var resolveInputs = async (inputs) => {
|
|
190
224
|
const unresolved = [];
|
|
191
225
|
const find = (props, parent, key) => {
|
|
@@ -420,6 +454,9 @@ var DependencyGraph = class {
|
|
|
420
454
|
this.callbacks.set(urn, callback);
|
|
421
455
|
this.graph.mergeNode(urn);
|
|
422
456
|
for (const dep of deps) {
|
|
457
|
+
if (!dep) {
|
|
458
|
+
throw new Error(`Resource ${urn} has an undefined dependency.`);
|
|
459
|
+
}
|
|
423
460
|
if (willCreateCycle(this.graph, dep, urn)) {
|
|
424
461
|
throw new Error(`There is a circular dependency between ${urn} -> ${dep}`);
|
|
425
462
|
}
|
|
@@ -1030,6 +1067,7 @@ var deployApp = async (app, opt) => {
|
|
|
1030
1067
|
}
|
|
1031
1068
|
const queue = createConcurrencyQueue(opt.concurrency ?? 10);
|
|
1032
1069
|
const graph = new DependencyGraph();
|
|
1070
|
+
const replacementDeletes = /* @__PURE__ */ new Map();
|
|
1033
1071
|
const allNodes = {};
|
|
1034
1072
|
for (const stackState of Object.values(appState.stacks)) {
|
|
1035
1073
|
for (const [urn, nodeState] of entries(stackState.nodes)) {
|
|
@@ -1099,6 +1137,14 @@ var deployApp = async (app, opt) => {
|
|
|
1099
1137
|
for (const node of stack.nodes) {
|
|
1100
1138
|
const meta = getMeta(node);
|
|
1101
1139
|
const dependencies = [...meta.dependencies];
|
|
1140
|
+
for (let i = 0; i < dependencies.length; i++) {
|
|
1141
|
+
if (!dependencies[i]) {
|
|
1142
|
+
const depPaths = findInputDepsWithPaths(meta.input).map((dep) => `${dep.path} -> ${dep.meta?.urn ?? "undefined"}`).join(", ");
|
|
1143
|
+
throw new Error(
|
|
1144
|
+
`Resource ${meta.urn} has an undefined dependency at index ${i}. Check inputs for missing/undefined Output references. ` + (depPaths ? `Dependency sources: ${depPaths}` : "")
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1102
1148
|
const partialNewResourceState = {
|
|
1103
1149
|
dependencies,
|
|
1104
1150
|
lifecycle: isResource(node) ? {
|
|
@@ -1177,99 +1223,107 @@ var deployApp = async (app, opt) => {
|
|
|
1177
1223
|
let newResourceState;
|
|
1178
1224
|
const ignoreReplace = forcedUpdateDependents.has(meta2.urn);
|
|
1179
1225
|
if (!ignoreReplace && requiresReplacement(nodeState.input, input, meta2.config?.replaceOnChanges ?? [])) {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
if (!dependentMeta.dependencies.has(meta2.urn)) {
|
|
1186
|
-
continue;
|
|
1187
|
-
}
|
|
1188
|
-
if (plannedDependents.has(dependentUrn)) {
|
|
1189
|
-
continue;
|
|
1226
|
+
if (meta2.config?.createBeforeReplace) {
|
|
1227
|
+
const priorState = { ...nodeState };
|
|
1228
|
+
newResourceState = await createResource(node, appState.idempotentToken, input, opt);
|
|
1229
|
+
if (!meta2.config?.retainOnDelete) {
|
|
1230
|
+
replacementDeletes.set(meta2.urn, priorState);
|
|
1190
1231
|
}
|
|
1191
|
-
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
}
|
|
1196
|
-
const dependencyPaths = findDependencyPaths(dependentMeta.input, meta2.urn);
|
|
1197
|
-
if (dependencyPaths.length === 0) {
|
|
1198
|
-
continue;
|
|
1199
|
-
}
|
|
1200
|
-
const detachedInput = stripDependencyInputs(
|
|
1201
|
-
dependentState.input,
|
|
1202
|
-
dependentMeta.input,
|
|
1203
|
-
meta2.urn
|
|
1204
|
-
);
|
|
1205
|
-
if (compareState(dependentState.input, detachedInput)) {
|
|
1206
|
-
continue;
|
|
1207
|
-
}
|
|
1208
|
-
plannedDependents.add(dependentUrn);
|
|
1209
|
-
let dependentRequiresReplacement = false;
|
|
1210
|
-
const dependentProvider = findProvider(opt.providers, dependentMeta.provider);
|
|
1211
|
-
if (dependentProvider.planResourceChange) {
|
|
1212
|
-
try {
|
|
1213
|
-
const dependentPlan = await dependentProvider.planResourceChange({
|
|
1214
|
-
type: dependentMeta.type,
|
|
1215
|
-
priorState: dependentState.output,
|
|
1216
|
-
proposedState: detachedInput
|
|
1217
|
-
});
|
|
1218
|
-
dependentRequiresReplacement = dependentPlan.requiresReplacement;
|
|
1219
|
-
} catch (error) {
|
|
1220
|
-
throw ResourceError.wrap(
|
|
1221
|
-
dependentMeta.urn,
|
|
1222
|
-
dependentMeta.type,
|
|
1223
|
-
"update",
|
|
1224
|
-
error
|
|
1225
|
-
);
|
|
1232
|
+
} else {
|
|
1233
|
+
for (const [dependentUrn, dependentNode] of nodeByUrn.entries()) {
|
|
1234
|
+
if (!isResource(dependentNode)) {
|
|
1235
|
+
continue;
|
|
1226
1236
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
dependentMeta.config?.replaceOnChanges,
|
|
1231
|
-
dependencyPaths
|
|
1232
|
-
)) {
|
|
1233
|
-
throw ResourceError.wrap(
|
|
1234
|
-
dependentMeta.urn,
|
|
1235
|
-
dependentMeta.type,
|
|
1236
|
-
"update",
|
|
1237
|
-
new Error(
|
|
1238
|
-
`Replacing ${meta2.urn} requires ${dependentMeta.urn} to set replaceOnChanges for its dependency fields.`
|
|
1239
|
-
)
|
|
1240
|
-
);
|
|
1237
|
+
const dependentMeta = getMeta(dependentNode);
|
|
1238
|
+
if (!dependentMeta.dependencies.has(meta2.urn)) {
|
|
1239
|
+
continue;
|
|
1241
1240
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
)
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1241
|
+
if (plannedDependents.has(dependentUrn)) {
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
const dependentStackState = stackStates.get(dependentMeta.stack.urn);
|
|
1245
|
+
const dependentState = dependentStackState?.nodes[dependentUrn];
|
|
1246
|
+
if (!dependentStackState || !dependentState) {
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
const dependencyPaths = findDependencyPaths(dependentMeta.input, meta2.urn);
|
|
1250
|
+
if (dependencyPaths.length === 0) {
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
const detachedInput = stripDependencyInputs(
|
|
1253
1254
|
dependentState.input,
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
opt
|
|
1255
|
+
dependentMeta.input,
|
|
1256
|
+
meta2.urn
|
|
1257
1257
|
);
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1258
|
+
if (compareState(dependentState.input, detachedInput)) {
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
plannedDependents.add(dependentUrn);
|
|
1262
|
+
let dependentRequiresReplacement = false;
|
|
1263
|
+
const dependentProvider = findProvider(opt.providers, dependentMeta.provider);
|
|
1264
|
+
if (dependentProvider.planResourceChange) {
|
|
1265
|
+
try {
|
|
1266
|
+
const dependentPlan = await dependentProvider.planResourceChange({
|
|
1267
|
+
type: dependentMeta.type,
|
|
1268
|
+
priorState: dependentState.output,
|
|
1269
|
+
proposedState: detachedInput
|
|
1270
|
+
});
|
|
1271
|
+
dependentRequiresReplacement = dependentPlan.requiresReplacement;
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
throw ResourceError.wrap(
|
|
1274
|
+
dependentMeta.urn,
|
|
1275
|
+
dependentMeta.type,
|
|
1276
|
+
"update",
|
|
1277
|
+
error
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
if (dependentRequiresReplacement) {
|
|
1282
|
+
if (!allowsDependentReplace(
|
|
1283
|
+
dependentMeta.config?.replaceOnChanges,
|
|
1284
|
+
dependencyPaths
|
|
1285
|
+
)) {
|
|
1286
|
+
throw ResourceError.wrap(
|
|
1287
|
+
dependentMeta.urn,
|
|
1288
|
+
dependentMeta.type,
|
|
1289
|
+
"update",
|
|
1290
|
+
new Error(
|
|
1291
|
+
`Replacing ${meta2.urn} requires ${dependentMeta.urn} to set replaceOnChanges for its dependency fields.`
|
|
1292
|
+
)
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
await deleteResource(
|
|
1296
|
+
appState.idempotentToken,
|
|
1297
|
+
dependentUrn,
|
|
1298
|
+
dependentState,
|
|
1299
|
+
opt
|
|
1300
|
+
);
|
|
1301
|
+
delete dependentStackState.nodes[dependentUrn];
|
|
1302
|
+
} else {
|
|
1303
|
+
const updated = await updateResource(
|
|
1304
|
+
dependentNode,
|
|
1305
|
+
appState.idempotentToken,
|
|
1306
|
+
dependentState.input,
|
|
1307
|
+
dependentState.output,
|
|
1308
|
+
detachedInput,
|
|
1309
|
+
opt
|
|
1310
|
+
);
|
|
1311
|
+
Object.assign(dependentState, {
|
|
1312
|
+
input: detachedInput,
|
|
1313
|
+
...updated
|
|
1314
|
+
});
|
|
1315
|
+
forcedUpdateDependents.add(dependentUrn);
|
|
1316
|
+
}
|
|
1263
1317
|
}
|
|
1318
|
+
newResourceState = await replaceResource(
|
|
1319
|
+
node,
|
|
1320
|
+
appState.idempotentToken,
|
|
1321
|
+
nodeState.input,
|
|
1322
|
+
nodeState.output,
|
|
1323
|
+
input,
|
|
1324
|
+
opt
|
|
1325
|
+
);
|
|
1264
1326
|
}
|
|
1265
|
-
newResourceState = await replaceResource(
|
|
1266
|
-
node,
|
|
1267
|
-
appState.idempotentToken,
|
|
1268
|
-
nodeState.input,
|
|
1269
|
-
nodeState.output,
|
|
1270
|
-
input,
|
|
1271
|
-
opt
|
|
1272
|
-
);
|
|
1273
1327
|
} else {
|
|
1274
1328
|
newResourceState = await updateResource(
|
|
1275
1329
|
node,
|
|
@@ -1300,6 +1354,19 @@ var deployApp = async (app, opt) => {
|
|
|
1300
1354
|
}
|
|
1301
1355
|
}
|
|
1302
1356
|
const errors = await graph.run();
|
|
1357
|
+
if (errors.length === 0 && replacementDeletes.size > 0) {
|
|
1358
|
+
for (const [urn, nodeState] of replacementDeletes.entries()) {
|
|
1359
|
+
try {
|
|
1360
|
+
await deleteResource(appState.idempotentToken, urn, nodeState, opt);
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
if (error instanceof Error) {
|
|
1363
|
+
errors.push(error);
|
|
1364
|
+
} else {
|
|
1365
|
+
errors.push(new Error(`${error}`));
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1303
1370
|
removeEmptyStackStates(appState);
|
|
1304
1371
|
delete appState.idempotentToken;
|
|
1305
1372
|
await opt.backend.state.update(app.urn, appState);
|