@highstate/backend 0.6.2 → 0.7.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/dist/index.d.ts +45 -6
- package/dist/index.mjs +377 -152
- package/dist/runner/source-resolution-worker.mjs +22 -0
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.mjs +2 -2
- package/dist/{terminal-NNJYvGqi.d.ts → terminal-Cm2WqcyB.d.ts} +192 -28
- package/dist/{terminal-C4MfopTF.mjs → terminal-CqIsctlZ.mjs} +92 -11
- package/package.json +6 -3
package/dist/index.mjs
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { z } from 'zod';
|
2
|
-
import { pickBy, mapKeys, mapValues,
|
2
|
+
import { pickBy, mapKeys, mapValues, omit, pick } from 'remeda';
|
3
3
|
import { BetterLock } from 'better-lock';
|
4
4
|
import { basename, relative, dirname, resolve as resolve$1 } from 'node:path';
|
5
5
|
import { findWorkspaceDir, readPackageJSON } from 'pkg-types';
|
@@ -10,7 +10,7 @@ import Watcher from 'watcher';
|
|
10
10
|
import { resolve } from 'import-meta-resolve';
|
11
11
|
import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
|
12
12
|
import { getInstanceId, isUnitModel, parseInstanceId } from '@highstate/contract';
|
13
|
-
import { h as hubModelSchema, i as instanceModelSchema, c as createInputResolver, a as createInputHashResolver, b as createInstanceState, d as instanceTerminalSchema, e as instancePageSchema, f as instanceFileSchema, g as instanceStatusFieldSchema, j as instanceTriggerSchema, k as compositeInstanceSchema, p as projectOperationSchema, l as instanceStateSchema, m as isFinalOperationStatus, t as terminalSessionSchema, n as
|
13
|
+
import { h as hubModelSchema, i as instanceModelSchema, c as createInputResolver, a as createInputHashResolver, b as createInstanceState, d as instanceTerminalSchema, e as instancePageSchema, f as instanceFileSchema, g as instanceStatusFieldSchema, j as instanceTriggerSchema, k as compositeInstanceSchema, p as projectOperationSchema, l as instanceStateSchema, m as isFinalOperationStatus, t as terminalSessionSchema, n as createInstanceStateFrontendPatch, o as applyPartialInstanceState } from './terminal-CqIsctlZ.mjs';
|
14
14
|
import { sha256 } from 'crypto-hash';
|
15
15
|
import 'ajv';
|
16
16
|
import { Readable, PassThrough } from 'node:stream';
|
@@ -57,11 +57,60 @@ function errorToString(error) {
|
|
57
57
|
}
|
58
58
|
return JSON.stringify(error);
|
59
59
|
}
|
60
|
-
function
|
61
|
-
|
62
|
-
|
60
|
+
function createAsyncBatcher(fn, { waitMs = 100, maxWaitTimeMs = 1e3 } = {}) {
|
61
|
+
let batch = [];
|
62
|
+
let activeTimeout = null;
|
63
|
+
let maxWaitTimeout = null;
|
64
|
+
let firstCallTimestamp = null;
|
65
|
+
async function processBatch() {
|
66
|
+
if (batch.length === 0) return;
|
67
|
+
const currentBatch = batch;
|
68
|
+
batch = [];
|
69
|
+
await fn(currentBatch);
|
70
|
+
if (maxWaitTimeout) {
|
71
|
+
clearTimeout(maxWaitTimeout);
|
72
|
+
maxWaitTimeout = null;
|
73
|
+
}
|
74
|
+
firstCallTimestamp = null;
|
75
|
+
}
|
76
|
+
function schedule() {
|
77
|
+
if (activeTimeout) clearTimeout(activeTimeout);
|
78
|
+
activeTimeout = setTimeout(() => {
|
79
|
+
activeTimeout = null;
|
80
|
+
void processBatch();
|
81
|
+
}, waitMs);
|
82
|
+
if (!firstCallTimestamp) {
|
83
|
+
firstCallTimestamp = Date.now();
|
84
|
+
maxWaitTimeout = setTimeout(() => {
|
85
|
+
if (activeTimeout) clearTimeout(activeTimeout);
|
86
|
+
activeTimeout = null;
|
87
|
+
void processBatch();
|
88
|
+
}, maxWaitTimeMs);
|
89
|
+
}
|
63
90
|
}
|
64
|
-
return
|
91
|
+
return {
|
92
|
+
/**
|
93
|
+
* Add an item to the batch.
|
94
|
+
*/
|
95
|
+
call(item) {
|
96
|
+
batch.push(item);
|
97
|
+
schedule();
|
98
|
+
},
|
99
|
+
/**
|
100
|
+
* Immediately flush the pending batch (if any).
|
101
|
+
*/
|
102
|
+
async flush() {
|
103
|
+
if (activeTimeout) {
|
104
|
+
clearTimeout(activeTimeout);
|
105
|
+
activeTimeout = null;
|
106
|
+
}
|
107
|
+
if (maxWaitTimeout) {
|
108
|
+
clearTimeout(maxWaitTimeout);
|
109
|
+
maxWaitTimeout = null;
|
110
|
+
}
|
111
|
+
await processBatch();
|
112
|
+
}
|
113
|
+
};
|
65
114
|
}
|
66
115
|
|
67
116
|
class LocalPulumiHost {
|
@@ -79,14 +128,15 @@ class LocalPulumiHost {
|
|
79
128
|
return null;
|
80
129
|
}
|
81
130
|
}
|
82
|
-
async
|
131
|
+
async runEmpty(options, fn) {
|
132
|
+
const { projectId, pulumiProjectName, pulumiStackName, envVars } = options;
|
83
133
|
return await this.lock.acquire(`${pulumiProjectName}.${pulumiStackName}`, async () => {
|
84
134
|
const { LocalWorkspace } = await import('@pulumi/pulumi/automation/index.js');
|
85
135
|
const stack = await LocalWorkspace.createOrSelectStack(
|
86
136
|
{
|
87
137
|
projectName: pulumiProjectName,
|
88
138
|
stackName: pulumiStackName,
|
89
|
-
program
|
139
|
+
program: () => Promise.resolve()
|
90
140
|
},
|
91
141
|
{
|
92
142
|
projectSettings: {
|
@@ -95,7 +145,8 @@ class LocalPulumiHost {
|
|
95
145
|
},
|
96
146
|
envVars: {
|
97
147
|
PULUMI_CONFIG_PASSPHRASE: this.getPassword(projectId),
|
98
|
-
PULUMI_K8S_AWAIT_ALL: "true"
|
148
|
+
PULUMI_K8S_AWAIT_ALL: "true",
|
149
|
+
...envVars
|
99
150
|
}
|
100
151
|
}
|
101
152
|
);
|
@@ -112,18 +163,14 @@ class LocalPulumiHost {
|
|
112
163
|
}
|
113
164
|
});
|
114
165
|
}
|
115
|
-
async
|
116
|
-
|
117
|
-
}, fn);
|
118
|
-
}
|
119
|
-
// TODO: extract args to options object
|
120
|
-
async runLocal(projectId, pulumiProjectName, pulumiStackName, programPathResolver, fn, stackConfig) {
|
166
|
+
async runLocal(options, fn) {
|
167
|
+
const { projectId, pulumiProjectName, pulumiStackName, projectPath, stackConfig, envVars } = options;
|
121
168
|
return await this.lock.acquire(`${pulumiProjectName}.${pulumiStackName}`, async () => {
|
122
169
|
const { LocalWorkspace } = await import('@pulumi/pulumi/automation/index.js');
|
123
170
|
const stack = await LocalWorkspace.createOrSelectStack(
|
124
171
|
{
|
125
172
|
stackName: pulumiStackName,
|
126
|
-
workDir:
|
173
|
+
workDir: projectPath
|
127
174
|
},
|
128
175
|
{
|
129
176
|
projectSettings: {
|
@@ -137,7 +184,8 @@ class LocalPulumiHost {
|
|
137
184
|
} : void 0,
|
138
185
|
envVars: {
|
139
186
|
PULUMI_CONFIG_PASSPHRASE: this.getPassword(projectId),
|
140
|
-
PULUMI_K8S_AWAIT_ALL: "true"
|
187
|
+
PULUMI_K8S_AWAIT_ALL: "true",
|
188
|
+
...envVars
|
141
189
|
}
|
142
190
|
}
|
143
191
|
);
|
@@ -251,10 +299,12 @@ class LocalSecretBackend {
|
|
251
299
|
this.pulumiProjectHost.setPassword(projectId, password);
|
252
300
|
try {
|
253
301
|
await this.pulumiProjectHost.runLocal(
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
302
|
+
{
|
303
|
+
projectId,
|
304
|
+
pulumiProjectName: this.projectName,
|
305
|
+
pulumiStackName: projectId,
|
306
|
+
projectPath: this.projectPath
|
307
|
+
},
|
258
308
|
async (stack) => {
|
259
309
|
this.logger.debug({ projectId }, "checking password");
|
260
310
|
await stack.info(true);
|
@@ -274,10 +324,12 @@ class LocalSecretBackend {
|
|
274
324
|
}
|
275
325
|
get(projectId, instanceId) {
|
276
326
|
return this.pulumiProjectHost.runLocal(
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
327
|
+
{
|
328
|
+
projectId,
|
329
|
+
pulumiProjectName: this.projectName,
|
330
|
+
pulumiStackName: projectId,
|
331
|
+
projectPath: this.projectPath
|
332
|
+
},
|
281
333
|
async (stack) => {
|
282
334
|
this.logger.debug({ projectId, instanceId }, "getting secrets");
|
283
335
|
const config = await stack.getAllConfig();
|
@@ -290,10 +342,12 @@ class LocalSecretBackend {
|
|
290
342
|
}
|
291
343
|
set(projectId, instanceId, values) {
|
292
344
|
return this.pulumiProjectHost.runLocal(
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
345
|
+
{
|
346
|
+
projectId,
|
347
|
+
pulumiProjectName: this.projectName,
|
348
|
+
pulumiStackName: projectId,
|
349
|
+
projectPath: this.projectPath
|
350
|
+
},
|
297
351
|
async (stack) => {
|
298
352
|
this.logger.debug({ projectId, instanceId }, "setting secrets");
|
299
353
|
const componentSecrets = mapValues(
|
@@ -768,6 +822,9 @@ class ProjectLock {
|
|
768
822
|
this.lock = lock;
|
769
823
|
this.projectId = projectId;
|
770
824
|
}
|
825
|
+
canImmediatelyAcquireLock(instanceId) {
|
826
|
+
return this.lock.canAcquire(`${this.projectId}/${instanceId}`);
|
827
|
+
}
|
771
828
|
lockInstance(instanceId, fn) {
|
772
829
|
return this.lock.acquire(`${this.projectId}/${instanceId}`, fn);
|
773
830
|
}
|
@@ -1180,17 +1237,10 @@ class TerminalManager {
|
|
1180
1237
|
logger.child({ service: "TerminalManager" })
|
1181
1238
|
);
|
1182
1239
|
}
|
1183
|
-
persistHistory =
|
1184
|
-
(entries)
|
1185
|
-
|
1186
|
-
|
1187
|
-
},
|
1188
|
-
{
|
1189
|
-
minQuietPeriodMs: 100,
|
1190
|
-
maxBurstDurationMs: 200,
|
1191
|
-
reducer: arrayAccumulator
|
1192
|
-
}
|
1193
|
-
);
|
1240
|
+
persistHistory = createAsyncBatcher(async (entries) => {
|
1241
|
+
this.logger.trace({ msg: "persisting history lines", count: entries.length });
|
1242
|
+
await this.stateBackend.appendTerminalSessionHistory(entries);
|
1243
|
+
});
|
1194
1244
|
}
|
1195
1245
|
|
1196
1246
|
class InvalidInstanceStatusError extends Error {
|
@@ -1206,14 +1256,16 @@ const localRunnerBackendConfig = z.object({
|
|
1206
1256
|
HIGHSTATE_BACKEND_RUNNER_LOCAL_SKIP_SOURCE_CHECK: z.boolean({ coerce: true }).default(false),
|
1207
1257
|
HIGHSTATE_BACKEND_RUNNER_LOCAL_SKIP_STATE_CHECK: z.boolean({ coerce: true }).default(false),
|
1208
1258
|
HIGHSTATE_BACKEND_RUNNER_LOCAL_PRINT_OUTPUT: z.boolean({ coerce: true }).default(true),
|
1209
|
-
HIGHSTATE_BACKEND_RUNNER_LOCAL_SOURCE_BASE_PATH: z.string().optional()
|
1259
|
+
HIGHSTATE_BACKEND_RUNNER_LOCAL_SOURCE_BASE_PATH: z.string().optional(),
|
1260
|
+
HIGHSTATE_BACKEND_RUNNER_LOCAL_CACHE_DIR: z.string().optional()
|
1210
1261
|
});
|
1211
1262
|
class LocalRunnerBackend {
|
1212
|
-
constructor(skipSourceCheck, skipStateCheck, printOutput, sourceBasePath, pulumiProjectHost) {
|
1263
|
+
constructor(skipSourceCheck, skipStateCheck, printOutput, sourceBasePath, cacheDir, pulumiProjectHost) {
|
1213
1264
|
this.skipSourceCheck = skipSourceCheck;
|
1214
1265
|
this.skipStateCheck = skipStateCheck;
|
1215
1266
|
this.printOutput = printOutput;
|
1216
1267
|
this.sourceBasePath = sourceBasePath;
|
1268
|
+
this.cacheDir = cacheDir;
|
1217
1269
|
this.pulumiProjectHost = pulumiProjectHost;
|
1218
1270
|
}
|
1219
1271
|
events = new EventEmitter();
|
@@ -1233,9 +1285,11 @@ class LocalRunnerBackend {
|
|
1233
1285
|
}
|
1234
1286
|
getState(options) {
|
1235
1287
|
return this.pulumiProjectHost.runEmpty(
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1288
|
+
{
|
1289
|
+
projectId: options.projectId,
|
1290
|
+
pulumiProjectName: options.instanceType,
|
1291
|
+
pulumiStackName: LocalRunnerBackend.getStackName(options)
|
1292
|
+
},
|
1239
1293
|
async (stack) => {
|
1240
1294
|
const info = await stack.info();
|
1241
1295
|
const instanceId = getInstanceId(options.instanceType, options.instanceName);
|
@@ -1262,9 +1316,11 @@ class LocalRunnerBackend {
|
|
1262
1316
|
}
|
1263
1317
|
getTerminalFactory(options, terminalName) {
|
1264
1318
|
return this.pulumiProjectHost.runEmpty(
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1319
|
+
{
|
1320
|
+
projectId: options.projectId,
|
1321
|
+
pulumiProjectName: options.instanceType,
|
1322
|
+
pulumiStackName: LocalRunnerBackend.getStackName(options)
|
1323
|
+
},
|
1268
1324
|
async (stack) => {
|
1269
1325
|
const outputs = await stack.outputs();
|
1270
1326
|
if (!outputs["$terminals"]) {
|
@@ -1281,9 +1337,11 @@ class LocalRunnerBackend {
|
|
1281
1337
|
}
|
1282
1338
|
getPageContent(options, pageName) {
|
1283
1339
|
return this.pulumiProjectHost.runEmpty(
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1340
|
+
{
|
1341
|
+
projectId: options.projectId,
|
1342
|
+
pulumiProjectName: options.instanceType,
|
1343
|
+
pulumiStackName: LocalRunnerBackend.getStackName(options)
|
1344
|
+
},
|
1287
1345
|
async (stack) => {
|
1288
1346
|
const outputs = await stack.outputs();
|
1289
1347
|
if (!outputs["$pages"]) {
|
@@ -1300,9 +1358,11 @@ class LocalRunnerBackend {
|
|
1300
1358
|
}
|
1301
1359
|
getFileContent(options, fileName) {
|
1302
1360
|
return this.pulumiProjectHost.runEmpty(
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1361
|
+
{
|
1362
|
+
projectId: options.projectId,
|
1363
|
+
pulumiProjectName: options.instanceType,
|
1364
|
+
pulumiStackName: LocalRunnerBackend.getStackName(options)
|
1365
|
+
},
|
1306
1366
|
async (stack) => {
|
1307
1367
|
const outputs = await stack.outputs();
|
1308
1368
|
if (!outputs["$files"]) {
|
@@ -1331,21 +1391,47 @@ class LocalRunnerBackend {
|
|
1331
1391
|
...mapValues(options.config, (value) => ({ value })),
|
1332
1392
|
...mapValues(options.secrets, (value) => ({ value, secret: true }))
|
1333
1393
|
};
|
1334
|
-
void this.updateWorker(options, configMap);
|
1394
|
+
void this.updateWorker(options, configMap, false);
|
1335
1395
|
}
|
1336
|
-
async
|
1396
|
+
async preview(options) {
|
1397
|
+
const currentStatus = await this.validateStatus(options, [
|
1398
|
+
"not_created",
|
1399
|
+
"previewing",
|
1400
|
+
"created",
|
1401
|
+
"error"
|
1402
|
+
]);
|
1403
|
+
if (currentStatus === "previewing") {
|
1404
|
+
return;
|
1405
|
+
}
|
1406
|
+
const configMap = {
|
1407
|
+
...mapValues(options.config, (value) => ({ value })),
|
1408
|
+
...mapValues(options.secrets, (value) => ({ value, secret: true }))
|
1409
|
+
};
|
1410
|
+
void this.updateWorker(options, configMap, true);
|
1411
|
+
}
|
1412
|
+
async updateWorker(options, configMap, preview) {
|
1337
1413
|
const instanceId = LocalRunnerBackend.getInstanceId(options);
|
1338
1414
|
try {
|
1415
|
+
const [projectPath, allowedDependencies] = await this.resolveProjectPath(
|
1416
|
+
options.instanceName,
|
1417
|
+
options.source
|
1418
|
+
);
|
1339
1419
|
await this.pulumiProjectHost.runLocal(
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1420
|
+
{
|
1421
|
+
projectId: options.projectId,
|
1422
|
+
pulumiProjectName: options.instanceType,
|
1423
|
+
pulumiStackName: LocalRunnerBackend.getStackName(options),
|
1424
|
+
projectPath,
|
1425
|
+
stackConfig: configMap,
|
1426
|
+
envVars: {
|
1427
|
+
HIGHSTATE_CACHE_DIR: this.cacheDir
|
1428
|
+
}
|
1429
|
+
},
|
1344
1430
|
async (stack) => {
|
1345
|
-
await
|
1431
|
+
await this.setStackConfig(stack, configMap);
|
1346
1432
|
this.updateState({
|
1347
1433
|
id: instanceId,
|
1348
|
-
status: "updating",
|
1434
|
+
status: preview ? "previewing" : "updating",
|
1349
1435
|
currentResourceCount: 0,
|
1350
1436
|
totalResourceCount: 0
|
1351
1437
|
});
|
@@ -1353,8 +1439,11 @@ class LocalRunnerBackend {
|
|
1353
1439
|
let totalResourceCount = 0;
|
1354
1440
|
await runWithRetryOnError(
|
1355
1441
|
async () => {
|
1356
|
-
await stack
|
1442
|
+
await stack[preview ? "preview" : "up"]({
|
1357
1443
|
color: "always",
|
1444
|
+
refresh: options.refresh,
|
1445
|
+
signal: options.signal,
|
1446
|
+
diff: preview,
|
1358
1447
|
onEvent: (event) => {
|
1359
1448
|
if (event.resourcePreEvent) {
|
1360
1449
|
totalResourceCount = updateResourceCount(
|
@@ -1378,8 +1467,7 @@ class LocalRunnerBackend {
|
|
1378
1467
|
if (this.printOutput) {
|
1379
1468
|
console.log(message);
|
1380
1469
|
}
|
1381
|
-
}
|
1382
|
-
signal: options.signal
|
1470
|
+
}
|
1383
1471
|
});
|
1384
1472
|
const extraOutputs = await this.getExtraOutputsStatePatch(stack);
|
1385
1473
|
this.updateState({
|
@@ -1392,13 +1480,15 @@ class LocalRunnerBackend {
|
|
1392
1480
|
async (error) => {
|
1393
1481
|
const isUnlocked = await this.pulumiProjectHost.tryUnlockStack(stack, error);
|
1394
1482
|
if (isUnlocked) return true;
|
1395
|
-
const isResolved = await this.tryInstallMissingDependencies(
|
1483
|
+
const isResolved = await this.tryInstallMissingDependencies(
|
1484
|
+
error,
|
1485
|
+
allowedDependencies
|
1486
|
+
);
|
1396
1487
|
if (isResolved) return true;
|
1397
1488
|
return false;
|
1398
1489
|
}
|
1399
1490
|
);
|
1400
|
-
}
|
1401
|
-
configMap
|
1491
|
+
}
|
1402
1492
|
);
|
1403
1493
|
} catch (error) {
|
1404
1494
|
this.updateState({
|
@@ -1408,6 +1498,12 @@ class LocalRunnerBackend {
|
|
1408
1498
|
});
|
1409
1499
|
}
|
1410
1500
|
}
|
1501
|
+
async setStackConfig(stack, configMap) {
|
1502
|
+
const currentConfig = await stack.getAllConfig();
|
1503
|
+
const currentConfigKeys = Object.keys(currentConfig);
|
1504
|
+
await stack.removeAllConfig(currentConfigKeys);
|
1505
|
+
await stack.setAllConfig(configMap);
|
1506
|
+
}
|
1411
1507
|
async destroy(options) {
|
1412
1508
|
const currentStatus = await this.validateStatus(options, [
|
1413
1509
|
"not_created",
|
@@ -1423,10 +1519,18 @@ class LocalRunnerBackend {
|
|
1423
1519
|
async destroyWorker(options) {
|
1424
1520
|
const instanceId = LocalRunnerBackend.getInstanceId(options);
|
1425
1521
|
try {
|
1426
|
-
await this.
|
1427
|
-
|
1428
|
-
|
1429
|
-
|
1522
|
+
const [projectPath] = await this.resolveProjectPath(options.instanceName, options.source);
|
1523
|
+
await this.pulumiProjectHost.runLocal(
|
1524
|
+
{
|
1525
|
+
projectId: options.projectId,
|
1526
|
+
pulumiProjectName: options.instanceType,
|
1527
|
+
pulumiStackName: LocalRunnerBackend.getStackName(options),
|
1528
|
+
projectPath,
|
1529
|
+
envVars: {
|
1530
|
+
HIGHSTATE_CACHE_DIR: this.cacheDir,
|
1531
|
+
PULUMI_K8S_DELETE_UNREACHABLE: options.deleteUnreachable ? "true" : ""
|
1532
|
+
}
|
1533
|
+
},
|
1430
1534
|
async (stack) => {
|
1431
1535
|
const summary = await stack.workspace.stack();
|
1432
1536
|
let currentResourceCount = summary?.resourceCount ?? 0;
|
@@ -1440,6 +1544,9 @@ class LocalRunnerBackend {
|
|
1440
1544
|
async () => {
|
1441
1545
|
await stack.destroy({
|
1442
1546
|
color: "always",
|
1547
|
+
refresh: options.refresh,
|
1548
|
+
remove: true,
|
1549
|
+
signal: options.signal,
|
1443
1550
|
onEvent: (event) => {
|
1444
1551
|
if (event.resOutputsEvent) {
|
1445
1552
|
currentResourceCount = updateResourceCount(
|
@@ -1455,8 +1562,7 @@ class LocalRunnerBackend {
|
|
1455
1562
|
if (this.printOutput) {
|
1456
1563
|
console.log(message);
|
1457
1564
|
}
|
1458
|
-
}
|
1459
|
-
signal: options.signal
|
1565
|
+
}
|
1460
1566
|
});
|
1461
1567
|
const extraOutputs = await this.getExtraOutputsStatePatch(stack);
|
1462
1568
|
this.updateState({
|
@@ -1471,6 +1577,16 @@ class LocalRunnerBackend {
|
|
1471
1577
|
}
|
1472
1578
|
);
|
1473
1579
|
} catch (error) {
|
1580
|
+
const { StackNotFoundError } = await import('@pulumi/pulumi/automation');
|
1581
|
+
if (error instanceof StackNotFoundError) {
|
1582
|
+
this.updateState({
|
1583
|
+
id: instanceId,
|
1584
|
+
status: "not_created",
|
1585
|
+
totalResourceCount: 0,
|
1586
|
+
currentResourceCount: 0
|
1587
|
+
});
|
1588
|
+
return;
|
1589
|
+
}
|
1474
1590
|
this.updateState({
|
1475
1591
|
id: instanceId,
|
1476
1592
|
status: "error",
|
@@ -1494,9 +1610,11 @@ class LocalRunnerBackend {
|
|
1494
1610
|
const instanceId = LocalRunnerBackend.getInstanceId(options);
|
1495
1611
|
try {
|
1496
1612
|
await this.pulumiProjectHost.runEmpty(
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1613
|
+
{
|
1614
|
+
projectId: options.projectId,
|
1615
|
+
pulumiProjectName: options.instanceType,
|
1616
|
+
pulumiStackName: LocalRunnerBackend.getStackName(options)
|
1617
|
+
},
|
1500
1618
|
async (stack) => {
|
1501
1619
|
const summary = await stack.workspace.stack();
|
1502
1620
|
let currentResourceCount = 0;
|
@@ -1618,24 +1736,33 @@ class LocalRunnerBackend {
|
|
1618
1736
|
static getInstanceId(options) {
|
1619
1737
|
return getInstanceId(options.instanceType, options.instanceName);
|
1620
1738
|
}
|
1621
|
-
async resolveProjectPath(source) {
|
1739
|
+
async resolveProjectPath(instanceType, source) {
|
1622
1740
|
if (source.type === "local") {
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
return resolve$1(this.sourceBasePath, source.path);
|
1741
|
+
const path = source.path ?? instanceType.replace(/\./g, "/");
|
1742
|
+
const projectPath = resolve$1(this.sourceBasePath, path);
|
1743
|
+
return [projectPath, []];
|
1627
1744
|
}
|
1628
1745
|
if (!this.skipSourceCheck) {
|
1629
1746
|
const packageName = source.version ? `${source.package}@${source.version}` : source.package;
|
1630
1747
|
await ensureDependencyInstalled(packageName);
|
1631
1748
|
}
|
1632
|
-
const
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1749
|
+
const workerPathUrl = resolve(
|
1750
|
+
`@highstate/backend/source-resolution-worker`,
|
1751
|
+
import.meta.url
|
1752
|
+
);
|
1753
|
+
const workerPath = fileURLToPath(workerPathUrl);
|
1754
|
+
const worker = new Worker(workerPath, {
|
1755
|
+
workerData: { source, skipSourceCheck: this.skipSourceCheck }
|
1756
|
+
});
|
1757
|
+
for await (const [event] of on(worker, "message")) {
|
1758
|
+
const eventData = event;
|
1759
|
+
if (eventData.type === "result") {
|
1760
|
+
return [eventData.projectPath, eventData.allowedDependencies];
|
1761
|
+
}
|
1762
|
+
}
|
1763
|
+
throw new Error("Worker ended without sending the result");
|
1637
1764
|
}
|
1638
|
-
async tryInstallMissingDependencies(error) {
|
1765
|
+
async tryInstallMissingDependencies(error, allowedDependencies) {
|
1639
1766
|
if (!(error instanceof Error)) {
|
1640
1767
|
return false;
|
1641
1768
|
}
|
@@ -1645,6 +1772,11 @@ class LocalRunnerBackend {
|
|
1645
1772
|
return false;
|
1646
1773
|
}
|
1647
1774
|
const packageName = match[1];
|
1775
|
+
if (!allowedDependencies.includes(packageName)) {
|
1776
|
+
throw new Error(
|
1777
|
+
`Dependency '${packageName}' was requested to be auto-installed, but it is not allowed. Please add it to the 'peerDependencies' in the package.json of the unit.`
|
1778
|
+
);
|
1779
|
+
}
|
1648
1780
|
await ensureDependencyInstalled(packageName);
|
1649
1781
|
return true;
|
1650
1782
|
}
|
@@ -1657,11 +1789,22 @@ class LocalRunnerBackend {
|
|
1657
1789
|
const [projectPath] = await resolveMainLocalProject();
|
1658
1790
|
sourceBasePath = resolve$1(projectPath, "units");
|
1659
1791
|
}
|
1792
|
+
let cacheDir = config.HIGHSTATE_BACKEND_RUNNER_LOCAL_CACHE_DIR;
|
1793
|
+
if (!cacheDir) {
|
1794
|
+
const homeDir = process.env.HOME ?? process.env.USERPROFILE;
|
1795
|
+
if (!homeDir) {
|
1796
|
+
throw new Error(
|
1797
|
+
"Failed to determine the home directory, please set HIGHSTATE_BACKEND_RUNNER_LOCAL_CACHE_DIR"
|
1798
|
+
);
|
1799
|
+
}
|
1800
|
+
cacheDir = resolve$1(homeDir, ".cache", "highstate");
|
1801
|
+
}
|
1660
1802
|
return new LocalRunnerBackend(
|
1661
1803
|
config.HIGHSTATE_BACKEND_RUNNER_LOCAL_SKIP_SOURCE_CHECK,
|
1662
1804
|
config.HIGHSTATE_BACKEND_RUNNER_LOCAL_SKIP_STATE_CHECK,
|
1663
1805
|
config.HIGHSTATE_BACKEND_RUNNER_LOCAL_PRINT_OUTPUT,
|
1664
1806
|
sourceBasePath,
|
1807
|
+
cacheDir,
|
1665
1808
|
pulumiProjectHost
|
1666
1809
|
);
|
1667
1810
|
}
|
@@ -1973,7 +2116,7 @@ class LocalStateBackend {
|
|
1973
2116
|
const currentUser = await localPulumiHost.getCurrentUser();
|
1974
2117
|
if (!currentUser) {
|
1975
2118
|
throw new Error(
|
1976
|
-
"The pulumi is not
|
2119
|
+
"The pulumi state is not specified, please run `pulumi login` or specify the state location manually before restarting the service"
|
1977
2120
|
);
|
1978
2121
|
}
|
1979
2122
|
if (!currentUser.url) {
|
@@ -2126,6 +2269,14 @@ class RuntimeOperation {
|
|
2126
2269
|
this.persistSecrets.flush()
|
2127
2270
|
]);
|
2128
2271
|
this.logger.debug("operation finished, all entries persisted");
|
2272
|
+
if (this.operation.type === "preview") {
|
2273
|
+
const states = await this.stateBackend.getInstanceStates(this.operation.projectId);
|
2274
|
+
for (const state of states) {
|
2275
|
+
if (this.operation.instanceIds.includes(state.id)) {
|
2276
|
+
this.stateEE.emit(this.operation.projectId, createInstanceStateFrontendPatch(state));
|
2277
|
+
}
|
2278
|
+
}
|
2279
|
+
}
|
2129
2280
|
}
|
2130
2281
|
}
|
2131
2282
|
resetPendingStateStatuses() {
|
@@ -2203,9 +2354,9 @@ class RuntimeOperation {
|
|
2203
2354
|
{ promiseCache: this.inputHashPromiseCache }
|
2204
2355
|
);
|
2205
2356
|
if (this.operation.type === "update") {
|
2206
|
-
await this.
|
2357
|
+
await this.extendWithDependencies();
|
2207
2358
|
await this.updateOperation();
|
2208
|
-
} else if (this.operation.type === "destroy") {
|
2359
|
+
} else if (this.operation.type === "destroy" && this.operation.options.destroyDependentInstances !== false) {
|
2209
2360
|
this.extendWithCreatedDependents();
|
2210
2361
|
await this.updateOperation();
|
2211
2362
|
}
|
@@ -2252,7 +2403,8 @@ class RuntimeOperation {
|
|
2252
2403
|
}
|
2253
2404
|
async getUnitPromise(instance, component) {
|
2254
2405
|
switch (this.operation.type) {
|
2255
|
-
case "update":
|
2406
|
+
case "update":
|
2407
|
+
case "preview": {
|
2256
2408
|
return this.updateUnit(instance, component);
|
2257
2409
|
}
|
2258
2410
|
case "recreate": {
|
@@ -2340,11 +2492,12 @@ class RuntimeOperation {
|
|
2340
2492
|
const secrets = await this.secretBackend.get(this.operation.projectId, instance.id);
|
2341
2493
|
this.abortController.signal.throwIfAborted();
|
2342
2494
|
logger.debug("secrets loaded", { count: Object.keys(secrets).length });
|
2343
|
-
await this.runnerBackend.update({
|
2495
|
+
await this.runnerBackend[this.operation.type === "preview" ? "preview" : "update"]({
|
2344
2496
|
projectId: this.operation.projectId,
|
2345
2497
|
instanceType: instance.type,
|
2346
2498
|
instanceName: instance.name,
|
2347
2499
|
config: await this.prepareUnitConfig(instance),
|
2500
|
+
refresh: this.operation.options.refresh,
|
2348
2501
|
secrets: mapValues(secrets, (value) => valueToString(value)),
|
2349
2502
|
source: this.resolveUnitSource(component),
|
2350
2503
|
signal: this.abortController.signal
|
@@ -2357,23 +2510,34 @@ class RuntimeOperation {
|
|
2357
2510
|
finalStatuses: ["created", "error"]
|
2358
2511
|
});
|
2359
2512
|
await this.watchStateStream(stream);
|
2360
|
-
const inputHash = await this.
|
2361
|
-
|
2362
|
-
|
2363
|
-
|
2364
|
-
|
2365
|
-
sourceHash: void 0
|
2366
|
-
// TODO: implement source hash
|
2367
|
-
});
|
2368
|
-
this.inputHashPromiseCache.clear();
|
2369
|
-
return await this.resolveInputHash(instance.id);
|
2513
|
+
const inputHash = await this.getUpToDateInputHash(instance);
|
2514
|
+
this.updateInstanceState({
|
2515
|
+
id: instance.id,
|
2516
|
+
inputHash,
|
2517
|
+
dependencyIds: dependencies.map((dependency) => dependency.id)
|
2370
2518
|
});
|
2371
|
-
this.updateInstanceState({ id: instance.id, inputHash });
|
2372
2519
|
logger.debug("input hash after update", { inputHash });
|
2373
2520
|
logger.info("unit updated");
|
2374
2521
|
});
|
2375
2522
|
}
|
2523
|
+
async getUpToDateInputHash(instance) {
|
2524
|
+
return await this.inputHashLock.acquire(async () => {
|
2525
|
+
this.inputHashNodes.set(instance.id, {
|
2526
|
+
instance,
|
2527
|
+
resolvedInputs: this.resolvedInstanceInputs.get(instance.id),
|
2528
|
+
state: this.stateMap.get(instance.id),
|
2529
|
+
sourceHash: void 0
|
2530
|
+
// TODO: implement source hash
|
2531
|
+
});
|
2532
|
+
this.inputHashPromiseCache.clear();
|
2533
|
+
return await this.resolveInputHash(instance.id);
|
2534
|
+
});
|
2535
|
+
}
|
2376
2536
|
async processBeforeDestroyTriggers(state, component, logger) {
|
2537
|
+
if (!this.operation.options.invokeDestroyTriggers) {
|
2538
|
+
logger.debug("destroy triggers are disabled for the operation");
|
2539
|
+
return;
|
2540
|
+
}
|
2377
2541
|
const instance = this.instanceMap.get(state.id);
|
2378
2542
|
if (!instance) {
|
2379
2543
|
throw new Error(`Instance not found: ${state.id}`);
|
@@ -2386,12 +2550,13 @@ class RuntimeOperation {
|
|
2386
2550
|
logger.info("updating unit to process before-destroy triggers...");
|
2387
2551
|
const secrets = await this.secretBackend.get(this.operation.projectId, instance.id);
|
2388
2552
|
this.abortController.signal.throwIfAborted();
|
2389
|
-
logger.debug(
|
2553
|
+
logger.debug({ count: Object.keys(secrets).length }, "secrets loaded");
|
2390
2554
|
await this.runnerBackend.update({
|
2391
2555
|
projectId: this.operation.projectId,
|
2392
2556
|
instanceType: instance.type,
|
2393
2557
|
instanceName: instance.name,
|
2394
2558
|
config: await this.prepareUnitConfig(instance, invokedTriggers),
|
2559
|
+
refresh: this.operation.options.refresh,
|
2395
2560
|
secrets: mapValues(secrets, (value) => valueToString(value)),
|
2396
2561
|
source: this.resolveUnitSource(component),
|
2397
2562
|
signal: this.abortController.signal
|
@@ -2435,7 +2600,10 @@ class RuntimeOperation {
|
|
2435
2600
|
projectId: this.operation.projectId,
|
2436
2601
|
instanceType: type,
|
2437
2602
|
instanceName: name,
|
2438
|
-
|
2603
|
+
refresh: this.operation.options.refresh,
|
2604
|
+
signal: this.abortController.signal,
|
2605
|
+
source: this.resolveUnitSource(component),
|
2606
|
+
deleteUnreachable: this.operation.options.deleteUnreachableResources
|
2439
2607
|
});
|
2440
2608
|
this.logger.debug("destroy request sent");
|
2441
2609
|
const stream = this.runnerBackend.watch({
|
@@ -2469,7 +2637,10 @@ class RuntimeOperation {
|
|
2469
2637
|
projectId: this.operation.projectId,
|
2470
2638
|
instanceType: instance.type,
|
2471
2639
|
instanceName: instance.name,
|
2472
|
-
|
2640
|
+
refresh: this.operation.options.refresh,
|
2641
|
+
signal: this.abortController.signal,
|
2642
|
+
source: this.resolveUnitSource(component),
|
2643
|
+
deleteUnreachable: this.operation.options.deleteUnreachableResources
|
2473
2644
|
});
|
2474
2645
|
logger.debug("destroy request sent");
|
2475
2646
|
const destroyStream = this.runnerBackend.watch({
|
@@ -2490,6 +2661,7 @@ class RuntimeOperation {
|
|
2490
2661
|
instanceType: instance.type,
|
2491
2662
|
instanceName: instance.name,
|
2492
2663
|
config: await this.prepareUnitConfig(instance),
|
2664
|
+
refresh: this.operation.options.refresh,
|
2493
2665
|
secrets: mapValues(secrets, (value) => valueToString(value)),
|
2494
2666
|
source: this.resolveUnitSource(component),
|
2495
2667
|
signal: this.abortController.signal
|
@@ -2501,7 +2673,13 @@ class RuntimeOperation {
|
|
2501
2673
|
instanceName: instance.name,
|
2502
2674
|
finalStatuses: ["created", "error"]
|
2503
2675
|
});
|
2504
|
-
this.
|
2676
|
+
const inputHash = await this.getUpToDateInputHash(instance);
|
2677
|
+
const dependencies = this.getInstanceDependencies(instance);
|
2678
|
+
this.updateInstanceState({
|
2679
|
+
id: instance.id,
|
2680
|
+
inputHash,
|
2681
|
+
dependencyIds: dependencies.map((dependency) => dependency.id)
|
2682
|
+
});
|
2505
2683
|
await this.watchStateStream(updateStream);
|
2506
2684
|
logger.info("unit recreated");
|
2507
2685
|
});
|
@@ -2639,9 +2817,11 @@ class RuntimeOperation {
|
|
2639
2817
|
return;
|
2640
2818
|
}
|
2641
2819
|
const state = applyPartialInstanceState(this.stateMap, patch);
|
2642
|
-
this.
|
2643
|
-
|
2644
|
-
|
2820
|
+
if (this.operation.type !== "preview") {
|
2821
|
+
this.persistStates.call(state);
|
2822
|
+
if (patch.secrets) {
|
2823
|
+
this.persistSecrets.call([patch.id, patch.secrets]);
|
2824
|
+
}
|
2645
2825
|
}
|
2646
2826
|
this.stateEE.emit(this.operation.projectId, createInstanceStateFrontendPatch(patch));
|
2647
2827
|
}
|
@@ -2653,7 +2833,29 @@ class RuntimeOperation {
|
|
2653
2833
|
children.push(state);
|
2654
2834
|
this.childrenStateMap.set(state.parentId, children);
|
2655
2835
|
}
|
2836
|
+
removeStateFromParent(state) {
|
2837
|
+
if (!state.parentId) {
|
2838
|
+
return;
|
2839
|
+
}
|
2840
|
+
const children = this.childrenStateMap.get(state.parentId) ?? [];
|
2841
|
+
const index = children.findIndex((child) => child.id === state.id);
|
2842
|
+
if (index !== -1) {
|
2843
|
+
children.splice(index, 1);
|
2844
|
+
this.childrenStateMap.set(state.parentId, children);
|
2845
|
+
}
|
2846
|
+
}
|
2656
2847
|
addDependentState(state) {
|
2848
|
+
const instance = this.instanceMap.get(state.id);
|
2849
|
+
if (!instance) {
|
2850
|
+
return;
|
2851
|
+
}
|
2852
|
+
for (const dependency of state.dependencyIds) {
|
2853
|
+
const dependents = this.dependentStateMap.get(dependency) ?? [];
|
2854
|
+
dependents.push(state);
|
2855
|
+
this.dependentStateMap.set(dependency, dependents);
|
2856
|
+
}
|
2857
|
+
}
|
2858
|
+
removeDependentState(state) {
|
2657
2859
|
const instance = this.instanceMap.get(state.id);
|
2658
2860
|
if (!instance) {
|
2659
2861
|
return;
|
@@ -2662,12 +2864,15 @@ class RuntimeOperation {
|
|
2662
2864
|
for (const inputs of Object.values(instanceInputs)) {
|
2663
2865
|
for (const input of inputs) {
|
2664
2866
|
const dependents = this.dependentStateMap.get(input.input.instanceId) ?? [];
|
2665
|
-
dependents.
|
2666
|
-
|
2867
|
+
const index = dependents.findIndex((dependent) => dependent.id === state.id);
|
2868
|
+
if (index !== -1) {
|
2869
|
+
dependents.splice(index, 1);
|
2870
|
+
this.dependentStateMap.set(input.input.instanceId, dependents);
|
2871
|
+
}
|
2667
2872
|
}
|
2668
2873
|
}
|
2669
2874
|
}
|
2670
|
-
async
|
2875
|
+
async extendWithDependencies() {
|
2671
2876
|
const instanceIdsSet = /* @__PURE__ */ new Set();
|
2672
2877
|
const traverse = async (instanceId) => {
|
2673
2878
|
if (instanceIdsSet.has(instanceId)) {
|
@@ -2685,6 +2890,10 @@ class RuntimeOperation {
|
|
2685
2890
|
}
|
2686
2891
|
const state = this.stateMap.get(instance.id);
|
2687
2892
|
const expectedInputHash = await this.resolveInputHash(instance.id);
|
2893
|
+
if (this.operation.options.forceUpdateDependencies) {
|
2894
|
+
instanceIdsSet.add(instanceId);
|
2895
|
+
return;
|
2896
|
+
}
|
2688
2897
|
if (state?.status !== "created" || state.inputHash !== expectedInputHash) {
|
2689
2898
|
instanceIdsSet.add(instanceId);
|
2690
2899
|
}
|
@@ -2726,7 +2935,15 @@ class RuntimeOperation {
|
|
2726
2935
|
if (instancePromise) {
|
2727
2936
|
return instancePromise;
|
2728
2937
|
}
|
2729
|
-
instancePromise =
|
2938
|
+
instancePromise = (async () => {
|
2939
|
+
const wasImmediatelyAcquired = this.projectLock.canImmediatelyAcquireLock(instanceId);
|
2940
|
+
await this.projectLock.lockInstance(instanceId, async () => {
|
2941
|
+
if (!wasImmediatelyAcquired) {
|
2942
|
+
await this.refetchState(instanceId);
|
2943
|
+
}
|
2944
|
+
return await fn();
|
2945
|
+
});
|
2946
|
+
})();
|
2730
2947
|
instancePromise = instancePromise.finally(() => this.instancePromiseMap.delete(instanceId));
|
2731
2948
|
this.instancePromiseMap.set(instanceId, instancePromise);
|
2732
2949
|
return instancePromise;
|
@@ -2735,6 +2952,8 @@ class RuntimeOperation {
|
|
2735
2952
|
switch (this.operation.type) {
|
2736
2953
|
case "update":
|
2737
2954
|
return "updating";
|
2955
|
+
case "preview":
|
2956
|
+
return "previewing";
|
2738
2957
|
case "recreate":
|
2739
2958
|
return "updating";
|
2740
2959
|
case "destroy":
|
@@ -2768,47 +2987,44 @@ class RuntimeOperation {
|
|
2768
2987
|
}
|
2769
2988
|
return component.source;
|
2770
2989
|
}
|
2771
|
-
|
2772
|
-
(
|
2773
|
-
|
2774
|
-
|
2775
|
-
|
2776
|
-
|
2777
|
-
|
2778
|
-
|
2779
|
-
|
2780
|
-
|
2781
|
-
|
2782
|
-
|
2783
|
-
|
2784
|
-
|
2785
|
-
|
2786
|
-
|
2787
|
-
persistLogs = funnel(
|
2788
|
-
(entries) => {
|
2789
|
-
this.logger.trace({ msg: "persisting logs", count: entries.length });
|
2790
|
-
void this.stateBackend.appendInstanceLogs(this.operation.id, entries);
|
2791
|
-
},
|
2792
|
-
{
|
2793
|
-
minQuietPeriodMs: 100,
|
2794
|
-
maxBurstDurationMs: 200,
|
2795
|
-
reducer: arrayAccumulator
|
2990
|
+
async refetchState(instanceId) {
|
2991
|
+
this.logger.info({ instanceId }, "refetching state since the lock was not immediately acquired");
|
2992
|
+
const state = await this.stateBackend.getInstanceState(this.operation.projectId, instanceId);
|
2993
|
+
const oldState = this.stateMap.get(instanceId);
|
2994
|
+
if (oldState) {
|
2995
|
+
this.removeStateFromParent(oldState);
|
2996
|
+
this.removeDependentState(oldState);
|
2997
|
+
}
|
2998
|
+
if (state) {
|
2999
|
+
this.stateMap.set(instanceId, state);
|
3000
|
+
this.initialStatusMap.set(instanceId, state.status);
|
3001
|
+
this.tryAddStateToParent(state);
|
3002
|
+
this.addDependentState(state);
|
3003
|
+
} else {
|
3004
|
+
this.stateMap.delete(instanceId);
|
3005
|
+
this.initialStatusMap.delete(instanceId);
|
2796
3006
|
}
|
2797
|
-
|
2798
|
-
|
2799
|
-
(
|
3007
|
+
}
|
3008
|
+
persistStates = createAsyncBatcher(async (states) => {
|
3009
|
+
this.logger.debug({ msg: "persisting states", count: states.length });
|
3010
|
+
await this.stateBackend.putAffectedInstanceStates(
|
3011
|
+
this.operation.projectId,
|
3012
|
+
this.operation.id,
|
3013
|
+
states
|
3014
|
+
);
|
3015
|
+
});
|
3016
|
+
persistLogs = createAsyncBatcher(async (entries) => {
|
3017
|
+
this.logger.trace({ msg: "persisting logs", count: entries.length });
|
3018
|
+
await this.stateBackend.appendInstanceLogs(this.operation.id, entries);
|
3019
|
+
});
|
3020
|
+
persistSecrets = createAsyncBatcher(
|
3021
|
+
async (entries) => {
|
2800
3022
|
this.logger.debug({ msg: "persisting secrets", count: entries.length });
|
2801
3023
|
for (const [instanceId, secrets] of entries) {
|
2802
|
-
|
2803
|
-
|
2804
|
-
|
2805
|
-
});
|
3024
|
+
const existingSecrets = await this.secretBackend.get(this.operation.projectId, instanceId);
|
3025
|
+
Object.assign(existingSecrets, secrets);
|
3026
|
+
await this.secretBackend.set(this.operation.projectId, instanceId, existingSecrets);
|
2806
3027
|
}
|
2807
|
-
},
|
2808
|
-
{
|
2809
|
-
minQuietPeriodMs: 100,
|
2810
|
-
maxBurstDurationMs: 200,
|
2811
|
-
reducer: arrayAccumulator
|
2812
3028
|
}
|
2813
3029
|
);
|
2814
3030
|
static abortMessagePatterns = [
|
@@ -2901,12 +3117,21 @@ class OperationManager {
|
|
2901
3117
|
type: request.type,
|
2902
3118
|
instanceIds: request.instanceIds,
|
2903
3119
|
status: "pending",
|
3120
|
+
options: {
|
3121
|
+
forceUpdateDependencies: request.options?.forceUpdateDependencies ?? false,
|
3122
|
+
destroyDependentInstances: request.options?.destroyDependentInstances ?? true,
|
3123
|
+
invokeDestroyTriggers: request.options?.invokeDestroyTriggers ?? false,
|
3124
|
+
deleteUnreachableResources: request.options?.deleteUnreachableResources ?? false,
|
3125
|
+
refresh: request.options?.refresh ?? false
|
3126
|
+
},
|
2904
3127
|
error: null,
|
2905
3128
|
startedAt: Date.now(),
|
2906
3129
|
completedAt: null
|
2907
3130
|
};
|
3131
|
+
this.logger.info({ operation }, "launching operation");
|
2908
3132
|
await this.stateBackend.putOperation(operation);
|
2909
3133
|
this.startOperation(operation);
|
3134
|
+
return operation;
|
2910
3135
|
}
|
2911
3136
|
/**
|
2912
3137
|
* Cancels the current operation.
|