@backstage/plugin-scaffolder-backend 1.26.0-next.0 → 1.26.0-next.2
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/CHANGELOG.md +62 -0
- package/alpha/package.json +1 -1
- package/dist/ScaffolderPlugin.cjs.js +168 -0
- package/dist/ScaffolderPlugin.cjs.js.map +1 -0
- package/dist/alpha.cjs.js +7 -196
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/deprecated.cjs.js +15 -0
- package/dist/deprecated.cjs.js.map +1 -0
- package/dist/index.cjs.js +57 -134
- package/dist/index.cjs.js.map +1 -1
- package/dist/lib/templating/SecureTemplater.cjs.js +169 -0
- package/dist/lib/templating/SecureTemplater.cjs.js.map +1 -0
- package/dist/lib/templating/filters.cjs.js +26 -0
- package/dist/lib/templating/filters.cjs.js.map +1 -0
- package/dist/lib/templating/helpers.cjs.js +13 -0
- package/dist/lib/templating/helpers.cjs.js.map +1 -0
- package/dist/scaffolder/actions/TemplateActionRegistry.cjs.js +30 -0
- package/dist/scaffolder/actions/TemplateActionRegistry.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/catalog/fetch.cjs.js +93 -0
- package/dist/scaffolder/actions/builtin/catalog/fetch.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/catalog/fetch.examples.cjs.js +43 -0
- package/dist/scaffolder/actions/builtin/catalog/fetch.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/catalog/register.cjs.js +142 -0
- package/dist/scaffolder/actions/builtin/catalog/register.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/catalog/register.examples.cjs.js +28 -0
- package/dist/scaffolder/actions/builtin/catalog/register.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/catalog/write.cjs.js +74 -0
- package/dist/scaffolder/actions/builtin/catalog/write.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/catalog/write.examples.cjs.js +56 -0
- package/dist/scaffolder/actions/builtin/catalog/write.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/createBuiltinActions.cjs.js +156 -0
- package/dist/scaffolder/actions/builtin/createBuiltinActions.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/debug/log.cjs.js +66 -0
- package/dist/scaffolder/actions/builtin/debug/log.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/debug/log.examples.cjs.js +58 -0
- package/dist/scaffolder/actions/builtin/debug/log.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/debug/wait.cjs.js +66 -0
- package/dist/scaffolder/actions/builtin/debug/wait.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/debug/wait.examples.cjs.js +58 -0
- package/dist/scaffolder/actions/builtin/debug/wait.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/fetch/plain.cjs.js +56 -0
- package/dist/scaffolder/actions/builtin/fetch/plain.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/fetch/plain.examples.cjs.js +44 -0
- package/dist/scaffolder/actions/builtin/fetch/plain.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/fetch/plainFile.cjs.js +56 -0
- package/dist/scaffolder/actions/builtin/fetch/plainFile.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/fetch/plainFile.examples.cjs.js +29 -0
- package/dist/scaffolder/actions/builtin/fetch/plainFile.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/fetch/template.cjs.js +241 -0
- package/dist/scaffolder/actions/builtin/fetch/template.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/fetch/template.examples.cjs.js +35 -0
- package/dist/scaffolder/actions/builtin/fetch/template.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/fetch/templateFile.cjs.js +119 -0
- package/dist/scaffolder/actions/builtin/fetch/templateFile.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/fetch/templateFile.examples.cjs.js +34 -0
- package/dist/scaffolder/actions/builtin/fetch/templateFile.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/filesystem/delete.cjs.js +54 -0
- package/dist/scaffolder/actions/builtin/filesystem/delete.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/filesystem/delete.examples.cjs.js +44 -0
- package/dist/scaffolder/actions/builtin/filesystem/delete.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/filesystem/rename.cjs.js +83 -0
- package/dist/scaffolder/actions/builtin/filesystem/rename.cjs.js.map +1 -0
- package/dist/scaffolder/actions/builtin/filesystem/rename.examples.cjs.js +48 -0
- package/dist/scaffolder/actions/builtin/filesystem/rename.examples.cjs.js.map +1 -0
- package/dist/scaffolder/actions/deprecated.cjs.js +74 -0
- package/dist/scaffolder/actions/deprecated.cjs.js.map +1 -0
- package/dist/scaffolder/dryrun/DecoratedActionsRegistry.cjs.js +57 -0
- package/dist/scaffolder/dryrun/DecoratedActionsRegistry.cjs.js.map +1 -0
- package/dist/scaffolder/dryrun/createDryRunner.cjs.js +97 -0
- package/dist/scaffolder/dryrun/createDryRunner.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/DatabaseTaskStore.cjs.js +430 -0
- package/dist/scaffolder/tasks/DatabaseTaskStore.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/DatabaseWorkspaceProvider.cjs.js +22 -0
- package/dist/scaffolder/tasks/DatabaseWorkspaceProvider.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/NunjucksWorkflowRunner.cjs.js +545 -0
- package/dist/scaffolder/tasks/NunjucksWorkflowRunner.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/StorageTaskBroker.cjs.js +318 -0
- package/dist/scaffolder/tasks/StorageTaskBroker.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/TaskWorker.cjs.js +110 -0
- package/dist/scaffolder/tasks/TaskWorker.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/WorkspaceService.cjs.js +50 -0
- package/dist/scaffolder/tasks/WorkspaceService.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/dbUtil.cjs.js +20 -0
- package/dist/scaffolder/tasks/dbUtil.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/helper.cjs.js +46 -0
- package/dist/scaffolder/tasks/helper.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/logger.cjs.js +156 -0
- package/dist/scaffolder/tasks/logger.cjs.js.map +1 -0
- package/dist/scaffolder/tasks/taskRecoveryHelper.cjs.js +18 -0
- package/dist/scaffolder/tasks/taskRecoveryHelper.cjs.js.map +1 -0
- package/dist/service/conditionExports.cjs.js +26 -0
- package/dist/service/conditionExports.cjs.js.map +1 -0
- package/dist/service/helpers.cjs.js +92 -0
- package/dist/service/helpers.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +640 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/dist/service/rules.cjs.js +97 -0
- package/dist/service/rules.cjs.js.map +1 -0
- package/dist/util/checkPermissions.cjs.js +25 -0
- package/dist/util/checkPermissions.cjs.js.map +1 -0
- package/dist/util/metrics.cjs.js +24 -0
- package/dist/util/metrics.cjs.js.map +1 -0
- package/package.json +30 -30
- package/dist/cjs/router-CC-UhVkG.cjs.js +0 -4101
- package/dist/cjs/router-CC-UhVkG.cjs.js.map +0 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ObservableImpl = require('zen-observable');
|
|
4
|
+
var helper = require('./helper.cjs.js');
|
|
5
|
+
var WorkspaceService = require('./WorkspaceService.cjs.js');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var ObservableImpl__default = /*#__PURE__*/_interopDefaultCompat(ObservableImpl);
|
|
10
|
+
|
|
11
|
+
class TaskManager {
|
|
12
|
+
// Runs heartbeat internally
|
|
13
|
+
constructor(task, storage, signal, logger, workspaceService, auth) {
|
|
14
|
+
this.task = task;
|
|
15
|
+
this.storage = storage;
|
|
16
|
+
this.signal = signal;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
this.workspaceService = workspaceService;
|
|
19
|
+
this.auth = auth;
|
|
20
|
+
}
|
|
21
|
+
isDone = false;
|
|
22
|
+
heartbeatTimeoutId;
|
|
23
|
+
static create(task, storage, abortSignal, logger, auth, config, additionalWorkspaceProviders) {
|
|
24
|
+
const workspaceService = WorkspaceService.DefaultWorkspaceService.create(
|
|
25
|
+
task,
|
|
26
|
+
storage,
|
|
27
|
+
additionalWorkspaceProviders,
|
|
28
|
+
config
|
|
29
|
+
);
|
|
30
|
+
const agent = new TaskManager(
|
|
31
|
+
task,
|
|
32
|
+
storage,
|
|
33
|
+
abortSignal,
|
|
34
|
+
logger,
|
|
35
|
+
workspaceService,
|
|
36
|
+
auth
|
|
37
|
+
);
|
|
38
|
+
agent.startTimeout();
|
|
39
|
+
return agent;
|
|
40
|
+
}
|
|
41
|
+
get spec() {
|
|
42
|
+
return this.task.spec;
|
|
43
|
+
}
|
|
44
|
+
get cancelSignal() {
|
|
45
|
+
return this.signal;
|
|
46
|
+
}
|
|
47
|
+
get secrets() {
|
|
48
|
+
return this.task.secrets;
|
|
49
|
+
}
|
|
50
|
+
get createdBy() {
|
|
51
|
+
return this.task.createdBy;
|
|
52
|
+
}
|
|
53
|
+
async getWorkspaceName() {
|
|
54
|
+
return this.task.taskId;
|
|
55
|
+
}
|
|
56
|
+
async rehydrateWorkspace(options) {
|
|
57
|
+
await this.workspaceService.rehydrateWorkspace(options);
|
|
58
|
+
}
|
|
59
|
+
get done() {
|
|
60
|
+
return this.isDone;
|
|
61
|
+
}
|
|
62
|
+
async emitLog(message, logMetadata) {
|
|
63
|
+
await this.storage.emitLogEvent({
|
|
64
|
+
taskId: this.task.taskId,
|
|
65
|
+
body: { message, ...logMetadata }
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async getTaskState() {
|
|
69
|
+
return this.storage.getTaskState?.({ taskId: this.task.taskId });
|
|
70
|
+
}
|
|
71
|
+
async updateCheckpoint(options) {
|
|
72
|
+
const { key, ...value } = options;
|
|
73
|
+
if (this.task.state) {
|
|
74
|
+
this.task.state.checkpoints[key] = value;
|
|
75
|
+
} else {
|
|
76
|
+
this.task.state = { checkpoints: { [key]: value } };
|
|
77
|
+
}
|
|
78
|
+
await this.storage.saveTaskState?.({
|
|
79
|
+
taskId: this.task.taskId,
|
|
80
|
+
state: this.task.state
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async serializeWorkspace(options) {
|
|
84
|
+
await this.workspaceService.serializeWorkspace(options);
|
|
85
|
+
}
|
|
86
|
+
async cleanWorkspace() {
|
|
87
|
+
await this.workspaceService.cleanWorkspace();
|
|
88
|
+
}
|
|
89
|
+
async complete(result, metadata) {
|
|
90
|
+
await this.storage.completeTask({
|
|
91
|
+
taskId: this.task.taskId,
|
|
92
|
+
status: result === "failed" ? "failed" : "completed",
|
|
93
|
+
eventBody: {
|
|
94
|
+
message: `Run completed with status: ${result}`,
|
|
95
|
+
...metadata
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
this.isDone = true;
|
|
99
|
+
if (this.heartbeatTimeoutId) {
|
|
100
|
+
clearTimeout(this.heartbeatTimeoutId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
startTimeout() {
|
|
104
|
+
this.heartbeatTimeoutId = setTimeout(async () => {
|
|
105
|
+
try {
|
|
106
|
+
await this.storage.heartbeatTask(this.task.taskId);
|
|
107
|
+
this.startTimeout();
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.isDone = true;
|
|
110
|
+
this.logger.error(
|
|
111
|
+
`Heartbeat for task ${this.task.taskId} failed`,
|
|
112
|
+
error
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}, 1e3);
|
|
116
|
+
}
|
|
117
|
+
async getInitiatorCredentials() {
|
|
118
|
+
const secrets = this.task.secrets;
|
|
119
|
+
if (secrets && secrets.__initiatorCredentials) {
|
|
120
|
+
return JSON.parse(secrets.__initiatorCredentials);
|
|
121
|
+
}
|
|
122
|
+
if (!this.auth) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
"Failed to create none credentials in scaffolder task. The TaskManager has not been initialized with an auth service implementation"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return this.auth.getNoneCredentials();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function defer() {
|
|
131
|
+
let resolve = () => {
|
|
132
|
+
};
|
|
133
|
+
const promise = new Promise((_resolve) => {
|
|
134
|
+
resolve = _resolve;
|
|
135
|
+
});
|
|
136
|
+
return { promise, resolve };
|
|
137
|
+
}
|
|
138
|
+
class StorageTaskBroker {
|
|
139
|
+
constructor(storage, logger, config, auth, additionalWorkspaceProviders) {
|
|
140
|
+
this.storage = storage;
|
|
141
|
+
this.logger = logger;
|
|
142
|
+
this.config = config;
|
|
143
|
+
this.auth = auth;
|
|
144
|
+
this.additionalWorkspaceProviders = additionalWorkspaceProviders;
|
|
145
|
+
}
|
|
146
|
+
async list(options) {
|
|
147
|
+
if (!this.storage.list) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
"TaskStore does not implement the list method. Please implement the list method to be able to list tasks"
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return await this.storage.list(options ?? {});
|
|
153
|
+
}
|
|
154
|
+
deferredDispatch = defer();
|
|
155
|
+
async registerCancellable(taskId, abortController) {
|
|
156
|
+
let shouldUnsubscribe = false;
|
|
157
|
+
const subscription = this.event$({ taskId, after: void 0 }).subscribe({
|
|
158
|
+
error: (_) => {
|
|
159
|
+
subscription.unsubscribe();
|
|
160
|
+
},
|
|
161
|
+
next: ({ events }) => {
|
|
162
|
+
for (const event of events) {
|
|
163
|
+
if (event.type === "cancelled") {
|
|
164
|
+
abortController.abort();
|
|
165
|
+
shouldUnsubscribe = true;
|
|
166
|
+
}
|
|
167
|
+
if (event.type === "completion" && !event.isTaskRecoverable) {
|
|
168
|
+
shouldUnsubscribe = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (shouldUnsubscribe) {
|
|
172
|
+
subscription.unsubscribe();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async recoverTasks() {
|
|
178
|
+
const enabled = (this.config && this.config.getOptionalBoolean(
|
|
179
|
+
"scaffolder.EXPERIMENTAL_recoverTasks"
|
|
180
|
+
)) ?? false;
|
|
181
|
+
if (enabled) {
|
|
182
|
+
const defaultTimeout = { seconds: 30 };
|
|
183
|
+
const timeout = helper.readDuration(
|
|
184
|
+
this.config,
|
|
185
|
+
"scaffolder.EXPERIMENTAL_recoverTasksTimeout",
|
|
186
|
+
defaultTimeout
|
|
187
|
+
);
|
|
188
|
+
const { ids: recoveredTaskIds } = await this.storage.recoverTasks?.({
|
|
189
|
+
timeout
|
|
190
|
+
}) ?? { ids: [] };
|
|
191
|
+
if (recoveredTaskIds.length > 0) {
|
|
192
|
+
this.signalDispatch();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* {@inheritdoc TaskBroker.claim}
|
|
198
|
+
*/
|
|
199
|
+
async claim() {
|
|
200
|
+
for (; ; ) {
|
|
201
|
+
const pendingTask = await this.storage.claimTask();
|
|
202
|
+
if (pendingTask) {
|
|
203
|
+
const abortController = new AbortController();
|
|
204
|
+
await this.registerCancellable(pendingTask.id, abortController);
|
|
205
|
+
return TaskManager.create(
|
|
206
|
+
{
|
|
207
|
+
taskId: pendingTask.id,
|
|
208
|
+
spec: pendingTask.spec,
|
|
209
|
+
secrets: pendingTask.secrets,
|
|
210
|
+
createdBy: pendingTask.createdBy,
|
|
211
|
+
state: pendingTask.state
|
|
212
|
+
},
|
|
213
|
+
this.storage,
|
|
214
|
+
abortController.signal,
|
|
215
|
+
this.logger,
|
|
216
|
+
this.auth,
|
|
217
|
+
this.config,
|
|
218
|
+
this.additionalWorkspaceProviders
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
await this.waitForDispatch();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* {@inheritdoc TaskBroker.dispatch}
|
|
226
|
+
*/
|
|
227
|
+
async dispatch(options) {
|
|
228
|
+
const taskRow = await this.storage.createTask(options);
|
|
229
|
+
this.signalDispatch();
|
|
230
|
+
return {
|
|
231
|
+
taskId: taskRow.taskId
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* {@inheritdoc TaskBroker.get}
|
|
236
|
+
*/
|
|
237
|
+
async get(taskId) {
|
|
238
|
+
return this.storage.getTask(taskId);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* {@inheritdoc TaskBroker.event$}
|
|
242
|
+
*/
|
|
243
|
+
event$(options) {
|
|
244
|
+
return new ObservableImpl__default.default((observer) => {
|
|
245
|
+
const { taskId } = options;
|
|
246
|
+
let after = options.after;
|
|
247
|
+
let cancelled = false;
|
|
248
|
+
(async () => {
|
|
249
|
+
const task = await this.storage.getTask(taskId);
|
|
250
|
+
const isTaskRecoverable = task.spec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy === "startOver";
|
|
251
|
+
while (!cancelled) {
|
|
252
|
+
const result = await this.storage.listEvents({
|
|
253
|
+
isTaskRecoverable,
|
|
254
|
+
taskId,
|
|
255
|
+
after
|
|
256
|
+
});
|
|
257
|
+
const { events } = result;
|
|
258
|
+
if (events.length) {
|
|
259
|
+
after = events[events.length - 1].id;
|
|
260
|
+
observer.next(result);
|
|
261
|
+
}
|
|
262
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
263
|
+
}
|
|
264
|
+
})();
|
|
265
|
+
return () => {
|
|
266
|
+
cancelled = true;
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* {@inheritdoc TaskBroker.vacuumTasks}
|
|
272
|
+
*/
|
|
273
|
+
async vacuumTasks(options) {
|
|
274
|
+
const { tasks } = await this.storage.listStaleTasks(options);
|
|
275
|
+
await Promise.all(
|
|
276
|
+
tasks.map(async (task) => {
|
|
277
|
+
try {
|
|
278
|
+
await this.storage.completeTask({
|
|
279
|
+
taskId: task.taskId,
|
|
280
|
+
status: "failed",
|
|
281
|
+
eventBody: {
|
|
282
|
+
message: "The task was cancelled because the task worker lost connection to the task broker"
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
} catch (error) {
|
|
286
|
+
this.logger.warn(`Failed to cancel task '${task.taskId}', ${error}`);
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
waitForDispatch() {
|
|
292
|
+
return this.deferredDispatch.promise;
|
|
293
|
+
}
|
|
294
|
+
signalDispatch() {
|
|
295
|
+
this.deferredDispatch.resolve();
|
|
296
|
+
this.deferredDispatch = defer();
|
|
297
|
+
}
|
|
298
|
+
async cancel(taskId) {
|
|
299
|
+
const { events } = await this.storage.listEvents({ taskId });
|
|
300
|
+
const currentStepId = events.length > 0 ? events.filter(({ body }) => body?.stepId).reduce((prev, curr) => prev.id > curr.id ? prev : curr).body.stepId : 0;
|
|
301
|
+
await this.storage.cancelTask?.({
|
|
302
|
+
taskId,
|
|
303
|
+
body: {
|
|
304
|
+
message: `Step ${currentStepId} has been cancelled.`,
|
|
305
|
+
stepId: currentStepId,
|
|
306
|
+
status: "cancelled"
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
async retry(taskId) {
|
|
311
|
+
await this.storage.retryTask?.({ taskId });
|
|
312
|
+
this.signalDispatch();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
exports.StorageTaskBroker = StorageTaskBroker;
|
|
317
|
+
exports.TaskManager = TaskManager;
|
|
318
|
+
//# sourceMappingURL=StorageTaskBroker.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageTaskBroker.cjs.js","sources":["../../../src/scaffolder/tasks/StorageTaskBroker.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { TaskSpec } from '@backstage/plugin-scaffolder-common';\nimport { JsonObject, JsonValue, Observable } from '@backstage/types';\nimport { Logger } from 'winston';\nimport ObservableImpl from 'zen-observable';\nimport {\n SerializedTask,\n SerializedTaskEvent,\n TaskBroker,\n TaskBrokerDispatchOptions,\n TaskCompletionState,\n TaskContext,\n TaskSecrets,\n TaskStatus,\n} from '@backstage/plugin-scaffolder-node';\nimport { InternalTaskSecrets, TaskStore } from './types';\nimport { readDuration } from './helper';\nimport {\n AuthService,\n BackstageCredentials,\n} from '@backstage/backend-plugin-api';\nimport { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService';\nimport { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha';\n\ntype TaskState = {\n checkpoints: {\n [key: string]:\n | {\n status: 'failed';\n reason: string;\n }\n | {\n status: 'success';\n value: JsonValue;\n };\n };\n};\n/**\n * TaskManager\n *\n * @public\n */\nexport class TaskManager implements TaskContext {\n private isDone = false;\n\n private heartbeatTimeoutId?: ReturnType<typeof setInterval>;\n\n static create(\n task: CurrentClaimedTask,\n storage: TaskStore,\n abortSignal: AbortSignal,\n logger: Logger,\n auth?: AuthService,\n config?: Config,\n additionalWorkspaceProviders?: Record<string, WorkspaceProvider>,\n ) {\n const workspaceService = DefaultWorkspaceService.create(\n task,\n storage,\n additionalWorkspaceProviders,\n config,\n );\n\n const agent = new TaskManager(\n task,\n storage,\n abortSignal,\n logger,\n workspaceService,\n auth,\n );\n agent.startTimeout();\n return agent;\n }\n\n // Runs heartbeat internally\n private constructor(\n private readonly task: CurrentClaimedTask,\n private readonly storage: TaskStore,\n private readonly signal: AbortSignal,\n private readonly logger: Logger,\n private readonly workspaceService: WorkspaceService,\n private readonly auth?: AuthService,\n ) {}\n\n get spec() {\n return this.task.spec;\n }\n\n get cancelSignal() {\n return this.signal;\n }\n\n get secrets() {\n return this.task.secrets;\n }\n\n get createdBy() {\n return this.task.createdBy;\n }\n\n async getWorkspaceName() {\n return this.task.taskId;\n }\n\n async rehydrateWorkspace?(options: {\n taskId: string;\n targetPath: string;\n }): Promise<void> {\n await this.workspaceService.rehydrateWorkspace(options);\n }\n\n get done() {\n return this.isDone;\n }\n\n async emitLog(message: string, logMetadata?: JsonObject): Promise<void> {\n await this.storage.emitLogEvent({\n taskId: this.task.taskId,\n body: { message, ...logMetadata },\n });\n }\n\n async getTaskState?(): Promise<\n | {\n state?: JsonObject;\n }\n | undefined\n > {\n return this.storage.getTaskState?.({ taskId: this.task.taskId });\n }\n\n async updateCheckpoint?(\n options:\n | {\n key: string;\n status: 'success';\n value: JsonValue;\n }\n | {\n key: string;\n status: 'failed';\n reason: string;\n },\n ): Promise<void> {\n const { key, ...value } = options;\n if (this.task.state) {\n (this.task.state as TaskState).checkpoints[key] = value;\n } else {\n this.task.state = { checkpoints: { [key]: value } };\n }\n await this.storage.saveTaskState?.({\n taskId: this.task.taskId,\n state: this.task.state,\n });\n }\n\n async serializeWorkspace?(options: { path: string }): Promise<void> {\n await this.workspaceService.serializeWorkspace(options);\n }\n\n async cleanWorkspace?(): Promise<void> {\n await this.workspaceService.cleanWorkspace();\n }\n\n async complete(\n result: TaskCompletionState,\n metadata?: JsonObject,\n ): Promise<void> {\n await this.storage.completeTask({\n taskId: this.task.taskId,\n status: result === 'failed' ? 'failed' : 'completed',\n eventBody: {\n message: `Run completed with status: ${result}`,\n ...metadata,\n },\n });\n this.isDone = true;\n if (this.heartbeatTimeoutId) {\n clearTimeout(this.heartbeatTimeoutId);\n }\n }\n\n private startTimeout() {\n this.heartbeatTimeoutId = setTimeout(async () => {\n try {\n await this.storage.heartbeatTask(this.task.taskId);\n this.startTimeout();\n } catch (error) {\n this.isDone = true;\n\n this.logger.error(\n `Heartbeat for task ${this.task.taskId} failed`,\n error,\n );\n }\n }, 1000);\n }\n\n async getInitiatorCredentials(): Promise<BackstageCredentials> {\n const secrets = this.task.secrets as InternalTaskSecrets;\n\n if (secrets && secrets.__initiatorCredentials) {\n return JSON.parse(secrets.__initiatorCredentials);\n }\n if (!this.auth) {\n throw new Error(\n 'Failed to create none credentials in scaffolder task. The TaskManager has not been initialized with an auth service implementation',\n );\n }\n return this.auth.getNoneCredentials();\n }\n}\n\n/**\n * Stores the state of the current claimed task passed to the TaskContext\n *\n * @public\n */\nexport interface CurrentClaimedTask {\n /**\n * The TaskSpec of the current claimed task.\n */\n spec: TaskSpec;\n /**\n * The uuid of the current claimed task.\n */\n taskId: string;\n /**\n * The secrets that are stored with the task.\n */\n secrets?: TaskSecrets;\n /**\n * The state of checkpoints of the task.\n */\n state?: JsonObject;\n /**\n * The creator of the task.\n */\n createdBy?: string;\n /**\n * The workspace of the task.\n */\n workspace?: Promise<Buffer>;\n}\n\nfunction defer() {\n let resolve = () => {};\n const promise = new Promise<void>(_resolve => {\n resolve = _resolve;\n });\n return { promise, resolve };\n}\n\nexport class StorageTaskBroker implements TaskBroker {\n constructor(\n private readonly storage: TaskStore,\n private readonly logger: Logger,\n private readonly config?: Config,\n private readonly auth?: AuthService,\n private readonly additionalWorkspaceProviders?: Record<\n string,\n WorkspaceProvider\n >,\n ) {}\n\n async list(options?: {\n createdBy?: string;\n status?: TaskStatus;\n filters?: {\n createdBy?: string | string[];\n status?: TaskStatus | TaskStatus[];\n };\n pagination?: {\n limit?: number;\n offset?: number;\n };\n order?: { order: 'asc' | 'desc'; field: string }[];\n }): Promise<{ tasks: SerializedTask[]; totalTasks?: number }> {\n if (!this.storage.list) {\n throw new Error(\n 'TaskStore does not implement the list method. Please implement the list method to be able to list tasks',\n );\n }\n return await this.storage.list(options ?? {});\n }\n\n private deferredDispatch = defer();\n\n private async registerCancellable(\n taskId: string,\n abortController: AbortController,\n ) {\n let shouldUnsubscribe = false;\n const subscription = this.event$({ taskId, after: undefined }).subscribe({\n error: _ => {\n subscription.unsubscribe();\n },\n next: ({ events }) => {\n for (const event of events) {\n if (event.type === 'cancelled') {\n abortController.abort();\n shouldUnsubscribe = true;\n }\n\n if (event.type === 'completion' && !event.isTaskRecoverable) {\n shouldUnsubscribe = true;\n }\n }\n if (shouldUnsubscribe) {\n subscription.unsubscribe();\n }\n },\n });\n }\n\n public async recoverTasks(): Promise<void> {\n const enabled =\n (this.config &&\n this.config.getOptionalBoolean(\n 'scaffolder.EXPERIMENTAL_recoverTasks',\n )) ??\n false;\n\n if (enabled) {\n const defaultTimeout = { seconds: 30 };\n const timeout = readDuration(\n this.config,\n 'scaffolder.EXPERIMENTAL_recoverTasksTimeout',\n defaultTimeout,\n );\n const { ids: recoveredTaskIds } = (await this.storage.recoverTasks?.({\n timeout,\n })) ?? { ids: [] };\n if (recoveredTaskIds.length > 0) {\n this.signalDispatch();\n }\n }\n }\n\n /**\n * {@inheritdoc TaskBroker.claim}\n */\n async claim(): Promise<TaskContext> {\n for (;;) {\n const pendingTask = await this.storage.claimTask();\n if (pendingTask) {\n const abortController = new AbortController();\n await this.registerCancellable(pendingTask.id, abortController);\n return TaskManager.create(\n {\n taskId: pendingTask.id,\n spec: pendingTask.spec,\n secrets: pendingTask.secrets,\n createdBy: pendingTask.createdBy,\n state: pendingTask.state,\n },\n this.storage,\n abortController.signal,\n this.logger,\n this.auth,\n this.config,\n this.additionalWorkspaceProviders,\n );\n }\n\n await this.waitForDispatch();\n }\n }\n\n /**\n * {@inheritdoc TaskBroker.dispatch}\n */\n async dispatch(\n options: TaskBrokerDispatchOptions,\n ): Promise<{ taskId: string }> {\n const taskRow = await this.storage.createTask(options);\n this.signalDispatch();\n return {\n taskId: taskRow.taskId,\n };\n }\n\n /**\n * {@inheritdoc TaskBroker.get}\n */\n async get(taskId: string): Promise<SerializedTask> {\n return this.storage.getTask(taskId);\n }\n\n /**\n * {@inheritdoc TaskBroker.event$}\n */\n event$(options: {\n taskId: string;\n after?: number;\n }): Observable<{ events: SerializedTaskEvent[] }> {\n return new ObservableImpl(observer => {\n const { taskId } = options;\n\n let after = options.after;\n let cancelled = false;\n\n (async () => {\n const task = await this.storage.getTask(taskId);\n const isTaskRecoverable =\n task.spec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy ===\n 'startOver';\n\n while (!cancelled) {\n const result = await this.storage.listEvents({\n isTaskRecoverable,\n taskId,\n after,\n });\n const { events } = result;\n if (events.length) {\n after = events[events.length - 1].id;\n observer.next(result);\n }\n\n await new Promise(resolve => setTimeout(resolve, 1000));\n }\n })();\n\n return () => {\n cancelled = true;\n };\n });\n }\n\n /**\n * {@inheritdoc TaskBroker.vacuumTasks}\n */\n async vacuumTasks(options: { timeoutS: number }): Promise<void> {\n const { tasks } = await this.storage.listStaleTasks(options);\n await Promise.all(\n tasks.map(async task => {\n try {\n await this.storage.completeTask({\n taskId: task.taskId,\n status: 'failed',\n eventBody: {\n message:\n 'The task was cancelled because the task worker lost connection to the task broker',\n },\n });\n } catch (error) {\n this.logger.warn(`Failed to cancel task '${task.taskId}', ${error}`);\n }\n }),\n );\n }\n\n private waitForDispatch() {\n return this.deferredDispatch.promise;\n }\n\n private signalDispatch() {\n this.deferredDispatch.resolve();\n this.deferredDispatch = defer();\n }\n\n async cancel(taskId: string) {\n const { events } = await this.storage.listEvents({ taskId });\n const currentStepId =\n events.length > 0\n ? events\n .filter(({ body }) => body?.stepId)\n .reduce((prev, curr) => (prev.id > curr.id ? prev : curr)).body\n .stepId\n : 0;\n\n await this.storage.cancelTask?.({\n taskId,\n body: {\n message: `Step ${currentStepId} has been cancelled.`,\n stepId: currentStepId,\n status: 'cancelled',\n },\n });\n }\n\n async retry?(taskId: string): Promise<void> {\n await this.storage.retryTask?.({ taskId });\n this.signalDispatch();\n }\n}\n"],"names":["DefaultWorkspaceService","readDuration","ObservableImpl"],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var PQueue = require('p-queue');
|
|
4
|
+
var NunjucksWorkflowRunner = require('./NunjucksWorkflowRunner.cjs.js');
|
|
5
|
+
var errors = require('@backstage/errors');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var PQueue__default = /*#__PURE__*/_interopDefaultCompat(PQueue);
|
|
10
|
+
|
|
11
|
+
class TaskWorker {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
this.stopWorkers = false;
|
|
15
|
+
this.logger = options.logger;
|
|
16
|
+
this.taskQueue = new PQueue__default.default({
|
|
17
|
+
concurrency: options.concurrentTasksLimit
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
taskQueue;
|
|
21
|
+
logger;
|
|
22
|
+
stopWorkers;
|
|
23
|
+
static async create(options) {
|
|
24
|
+
const {
|
|
25
|
+
taskBroker,
|
|
26
|
+
logger,
|
|
27
|
+
actionRegistry,
|
|
28
|
+
integrations,
|
|
29
|
+
workingDirectory,
|
|
30
|
+
additionalTemplateFilters,
|
|
31
|
+
concurrentTasksLimit = 10,
|
|
32
|
+
// from 1 to Infinity
|
|
33
|
+
additionalTemplateGlobals,
|
|
34
|
+
permissions
|
|
35
|
+
} = options;
|
|
36
|
+
const workflowRunner = new NunjucksWorkflowRunner.NunjucksWorkflowRunner({
|
|
37
|
+
actionRegistry,
|
|
38
|
+
integrations,
|
|
39
|
+
logger,
|
|
40
|
+
workingDirectory,
|
|
41
|
+
additionalTemplateFilters,
|
|
42
|
+
additionalTemplateGlobals,
|
|
43
|
+
permissions
|
|
44
|
+
});
|
|
45
|
+
return new TaskWorker({
|
|
46
|
+
taskBroker,
|
|
47
|
+
runners: { workflowRunner },
|
|
48
|
+
concurrentTasksLimit,
|
|
49
|
+
permissions
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async recoverTasks() {
|
|
53
|
+
try {
|
|
54
|
+
await this.options.taskBroker.recoverTasks?.();
|
|
55
|
+
} catch (err) {
|
|
56
|
+
this.logger?.error(errors.stringifyError(err));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
start() {
|
|
60
|
+
(async () => {
|
|
61
|
+
while (!this.stopWorkers) {
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
63
|
+
await this.recoverTasks();
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
(async () => {
|
|
67
|
+
while (!this.stopWorkers) {
|
|
68
|
+
await this.onReadyToClaimTask();
|
|
69
|
+
if (!this.stopWorkers) {
|
|
70
|
+
const task = await this.options.taskBroker.claim();
|
|
71
|
+
void this.taskQueue.add(() => this.runOneTask(task));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})();
|
|
75
|
+
}
|
|
76
|
+
stop() {
|
|
77
|
+
this.stopWorkers = true;
|
|
78
|
+
}
|
|
79
|
+
onReadyToClaimTask() {
|
|
80
|
+
if (this.taskQueue.pending < this.options.concurrentTasksLimit) {
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
}
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
this.taskQueue.once("next", () => {
|
|
85
|
+
resolve();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async runOneTask(task) {
|
|
90
|
+
try {
|
|
91
|
+
if (task.spec.apiVersion !== "scaffolder.backstage.io/v1beta3") {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Unsupported Template apiVersion ${task.spec.apiVersion}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const { output } = await this.options.runners.workflowRunner.execute(
|
|
97
|
+
task
|
|
98
|
+
);
|
|
99
|
+
await task.complete("completed", { output });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
errors.assertError(error);
|
|
102
|
+
await task.complete("failed", {
|
|
103
|
+
error: { name: error.name, message: error.message }
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
exports.TaskWorker = TaskWorker;
|
|
110
|
+
//# sourceMappingURL=TaskWorker.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskWorker.cjs.js","sources":["../../../src/scaffolder/tasks/TaskWorker.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WorkflowRunner } from './types';\nimport {\n TaskContext,\n TaskBroker,\n TemplateFilter,\n TemplateGlobal,\n} from '@backstage/plugin-scaffolder-node';\nimport PQueue from 'p-queue';\nimport { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner';\nimport { Logger } from 'winston';\nimport { TemplateActionRegistry } from '../actions';\nimport { ScmIntegrations } from '@backstage/integration';\nimport { assertError, stringifyError } from '@backstage/errors';\nimport { PermissionEvaluator } from '@backstage/plugin-permission-common';\n\n/**\n * TaskWorkerOptions\n *\n * @public\n */\nexport type TaskWorkerOptions = {\n taskBroker: TaskBroker;\n runners: {\n workflowRunner: WorkflowRunner;\n };\n concurrentTasksLimit: number;\n permissions?: PermissionEvaluator;\n logger?: Logger;\n};\n\n/**\n * CreateWorkerOptions\n *\n * @public\n */\nexport type CreateWorkerOptions = {\n taskBroker: TaskBroker;\n actionRegistry: TemplateActionRegistry;\n integrations: ScmIntegrations;\n workingDirectory: string;\n logger: Logger;\n additionalTemplateFilters?: Record<string, TemplateFilter>;\n /**\n * The number of tasks that can be executed at the same time by the worker\n * @defaultValue 10\n * @example\n * ```\n * {\n * concurrentTasksLimit: 1,\n * // OR\n * concurrentTasksLimit: Infinity\n * }\n * ```\n */\n concurrentTasksLimit?: number;\n additionalTemplateGlobals?: Record<string, TemplateGlobal>;\n permissions?: PermissionEvaluator;\n};\n\n/**\n * TaskWorker\n *\n * @public\n */\nexport class TaskWorker {\n private taskQueue: PQueue;\n private logger: Logger | undefined;\n private stopWorkers: boolean;\n\n private constructor(private readonly options: TaskWorkerOptions) {\n this.stopWorkers = false;\n this.logger = options.logger;\n this.taskQueue = new PQueue({\n concurrency: options.concurrentTasksLimit,\n });\n }\n\n static async create(options: CreateWorkerOptions): Promise<TaskWorker> {\n const {\n taskBroker,\n logger,\n actionRegistry,\n integrations,\n workingDirectory,\n additionalTemplateFilters,\n concurrentTasksLimit = 10, // from 1 to Infinity\n additionalTemplateGlobals,\n permissions,\n } = options;\n\n const workflowRunner = new NunjucksWorkflowRunner({\n actionRegistry,\n integrations,\n logger,\n workingDirectory,\n additionalTemplateFilters,\n additionalTemplateGlobals,\n permissions,\n });\n\n return new TaskWorker({\n taskBroker: taskBroker,\n runners: { workflowRunner },\n concurrentTasksLimit,\n permissions,\n });\n }\n\n async recoverTasks() {\n try {\n await this.options.taskBroker.recoverTasks?.();\n } catch (err) {\n this.logger?.error(stringifyError(err));\n }\n }\n\n start() {\n (async () => {\n while (!this.stopWorkers) {\n await new Promise(resolve => setTimeout(resolve, 10000));\n await this.recoverTasks();\n }\n })();\n (async () => {\n while (!this.stopWorkers) {\n await this.onReadyToClaimTask();\n if (!this.stopWorkers) {\n const task = await this.options.taskBroker.claim();\n void this.taskQueue.add(() => this.runOneTask(task));\n }\n }\n })();\n }\n\n stop() {\n this.stopWorkers = true;\n }\n\n protected onReadyToClaimTask(): Promise<void> {\n if (this.taskQueue.pending < this.options.concurrentTasksLimit) {\n return Promise.resolve();\n }\n return new Promise(resolve => {\n // \"next\" event emits when a task completes\n // https://github.com/sindresorhus/p-queue#next\n this.taskQueue.once('next', () => {\n resolve();\n });\n });\n }\n\n async runOneTask(task: TaskContext) {\n try {\n if (task.spec.apiVersion !== 'scaffolder.backstage.io/v1beta3') {\n throw new Error(\n `Unsupported Template apiVersion ${task.spec.apiVersion}`,\n );\n }\n\n const { output } = await this.options.runners.workflowRunner.execute(\n task,\n );\n\n await task.complete('completed', { output });\n } catch (error) {\n assertError(error);\n await task.complete('failed', {\n error: { name: error.name, message: error.message },\n });\n }\n }\n}\n"],"names":["PQueue","NunjucksWorkflowRunner","stringifyError","assertError"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var DatabaseWorkspaceProvider = require('./DatabaseWorkspaceProvider.cjs.js');
|
|
4
|
+
var fs = require('fs-extra');
|
|
5
|
+
|
|
6
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
9
|
+
|
|
10
|
+
class DefaultWorkspaceService {
|
|
11
|
+
constructor(task, workspaceProvider, config) {
|
|
12
|
+
this.task = task;
|
|
13
|
+
this.workspaceProvider = workspaceProvider;
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
static create(task, storage, additionalWorkspaceProviders, config) {
|
|
17
|
+
const workspaceProviderName = config?.getOptionalString(
|
|
18
|
+
"scaffolder.EXPERIMENTAL_workspaceSerializationProvider"
|
|
19
|
+
) ?? "database";
|
|
20
|
+
const workspaceProvider = additionalWorkspaceProviders?.[workspaceProviderName] ?? DatabaseWorkspaceProvider.DatabaseWorkspaceProvider.create(storage);
|
|
21
|
+
return new DefaultWorkspaceService(task, workspaceProvider, config);
|
|
22
|
+
}
|
|
23
|
+
async serializeWorkspace(options) {
|
|
24
|
+
if (this.isWorkspaceSerializationEnabled()) {
|
|
25
|
+
await this.workspaceProvider.serializeWorkspace({
|
|
26
|
+
path: options.path,
|
|
27
|
+
taskId: this.task.taskId
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async cleanWorkspace() {
|
|
32
|
+
if (this.isWorkspaceSerializationEnabled()) {
|
|
33
|
+
await this.workspaceProvider.cleanWorkspace({ taskId: this.task.taskId });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async rehydrateWorkspace(options) {
|
|
37
|
+
if (this.isWorkspaceSerializationEnabled()) {
|
|
38
|
+
await fs__default.default.mkdirp(options.targetPath);
|
|
39
|
+
await this.workspaceProvider.rehydrateWorkspace(options);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
isWorkspaceSerializationEnabled() {
|
|
43
|
+
return this.config?.getOptionalBoolean(
|
|
44
|
+
"scaffolder.EXPERIMENTAL_workspaceSerialization"
|
|
45
|
+
) ?? false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
exports.DefaultWorkspaceService = DefaultWorkspaceService;
|
|
50
|
+
//# sourceMappingURL=WorkspaceService.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkspaceService.cjs.js","sources":["../../../src/scaffolder/tasks/WorkspaceService.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { CurrentClaimedTask } from './StorageTaskBroker';\nimport { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha';\nimport { DatabaseWorkspaceProvider } from './DatabaseWorkspaceProvider';\nimport { TaskStore } from './types';\nimport fs from 'fs-extra';\n\nexport interface WorkspaceService {\n serializeWorkspace(options: { path: string }): Promise<void>;\n\n cleanWorkspace(): Promise<void>;\n\n rehydrateWorkspace(options: {\n taskId: string;\n targetPath: string;\n }): Promise<void>;\n}\n\nexport class DefaultWorkspaceService implements WorkspaceService {\n static create(\n task: CurrentClaimedTask,\n storage: TaskStore,\n additionalWorkspaceProviders?: Record<string, WorkspaceProvider>,\n config?: Config,\n ) {\n const workspaceProviderName =\n config?.getOptionalString(\n 'scaffolder.EXPERIMENTAL_workspaceSerializationProvider',\n ) ?? 'database';\n const workspaceProvider =\n additionalWorkspaceProviders?.[workspaceProviderName] ??\n DatabaseWorkspaceProvider.create(storage);\n return new DefaultWorkspaceService(task, workspaceProvider, config);\n }\n\n private constructor(\n private readonly task: CurrentClaimedTask,\n private readonly workspaceProvider: WorkspaceProvider,\n private readonly config?: Config,\n ) {}\n\n public async serializeWorkspace(options: { path: string }): Promise<void> {\n if (this.isWorkspaceSerializationEnabled()) {\n await this.workspaceProvider.serializeWorkspace({\n path: options.path,\n taskId: this.task.taskId,\n });\n }\n }\n\n public async cleanWorkspace(): Promise<void> {\n if (this.isWorkspaceSerializationEnabled()) {\n await this.workspaceProvider.cleanWorkspace({ taskId: this.task.taskId });\n }\n }\n\n public async rehydrateWorkspace(options: {\n taskId: string;\n targetPath: string;\n }): Promise<void> {\n if (this.isWorkspaceSerializationEnabled()) {\n await fs.mkdirp(options.targetPath);\n await this.workspaceProvider.rehydrateWorkspace(options);\n }\n }\n\n private isWorkspaceSerializationEnabled(): boolean {\n return (\n this.config?.getOptionalBoolean(\n 'scaffolder.EXPERIMENTAL_workspaceSerialization',\n ) ?? false\n );\n }\n}\n"],"names":["DatabaseWorkspaceProvider","fs"],"mappings":";;;;;;;;;AAkCO,MAAM,uBAAoD,CAAA;AAAA,EAiBvD,WAAA,CACW,IACA,EAAA,iBAAA,EACA,MACjB,EAAA;AAHiB,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA,CAAA;AACA,IAAA,IAAA,CAAA,iBAAA,GAAA,iBAAA,CAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA,CAAA;AAAA,GAChB;AAAA,EApBH,OAAO,MAAA,CACL,IACA,EAAA,OAAA,EACA,8BACA,MACA,EAAA;AACA,IAAA,MAAM,wBACJ,MAAQ,EAAA,iBAAA;AAAA,MACN,wDAAA;AAAA,KACG,IAAA,UAAA,CAAA;AACP,IAAA,MAAM,oBACJ,4BAA+B,GAAA,qBAAqB,CACpD,IAAAA,mDAAA,CAA0B,OAAO,OAAO,CAAA,CAAA;AAC1C,IAAA,OAAO,IAAI,uBAAA,CAAwB,IAAM,EAAA,iBAAA,EAAmB,MAAM,CAAA,CAAA;AAAA,GACpE;AAAA,EAQA,MAAa,mBAAmB,OAA0C,EAAA;AACxE,IAAI,IAAA,IAAA,CAAK,iCAAmC,EAAA;AAC1C,MAAM,MAAA,IAAA,CAAK,kBAAkB,kBAAmB,CAAA;AAAA,QAC9C,MAAM,OAAQ,CAAA,IAAA;AAAA,QACd,MAAA,EAAQ,KAAK,IAAK,CAAA,MAAA;AAAA,OACnB,CAAA,CAAA;AAAA,KACH;AAAA,GACF;AAAA,EAEA,MAAa,cAAgC,GAAA;AAC3C,IAAI,IAAA,IAAA,CAAK,iCAAmC,EAAA;AAC1C,MAAM,MAAA,IAAA,CAAK,kBAAkB,cAAe,CAAA,EAAE,QAAQ,IAAK,CAAA,IAAA,CAAK,QAAQ,CAAA,CAAA;AAAA,KAC1E;AAAA,GACF;AAAA,EAEA,MAAa,mBAAmB,OAGd,EAAA;AAChB,IAAI,IAAA,IAAA,CAAK,iCAAmC,EAAA;AAC1C,MAAM,MAAAC,mBAAA,CAAG,MAAO,CAAA,OAAA,CAAQ,UAAU,CAAA,CAAA;AAClC,MAAM,MAAA,IAAA,CAAK,iBAAkB,CAAA,kBAAA,CAAmB,OAAO,CAAA,CAAA;AAAA,KACzD;AAAA,GACF;AAAA,EAEQ,+BAA2C,GAAA;AACjD,IAAA,OACE,KAAK,MAAQ,EAAA,kBAAA;AAAA,MACX,gDAAA;AAAA,KACG,IAAA,KAAA,CAAA;AAAA,GAET;AACF;;;;"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const intervalFromNowTill = (timeoutS, knex) => {
|
|
4
|
+
let heartbeatInterval = knex.raw(`? - interval '${timeoutS} seconds'`, [
|
|
5
|
+
knex.fn.now()
|
|
6
|
+
]);
|
|
7
|
+
if (knex.client.config.client.includes("mysql")) {
|
|
8
|
+
heartbeatInterval = knex.raw(
|
|
9
|
+
`date_sub(now(), interval ${timeoutS} second)`
|
|
10
|
+
);
|
|
11
|
+
} else if (knex.client.config.client.includes("sqlite3")) {
|
|
12
|
+
heartbeatInterval = knex.raw(`datetime('now', ?)`, [
|
|
13
|
+
`-${timeoutS} seconds`
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
16
|
+
return heartbeatInterval;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
exports.intervalFromNowTill = intervalFromNowTill;
|
|
20
|
+
//# sourceMappingURL=dbUtil.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dbUtil.cjs.js","sources":["../../../src/scaffolder/tasks/dbUtil.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Knex } from 'knex';\n\nexport const intervalFromNowTill = (timeoutS: number, knex: Knex) => {\n let heartbeatInterval = knex.raw(`? - interval '${timeoutS} seconds'`, [\n knex.fn.now(),\n ]);\n if (knex.client.config.client.includes('mysql')) {\n heartbeatInterval = knex.raw(\n `date_sub(now(), interval ${timeoutS} second)`,\n );\n } else if (knex.client.config.client.includes('sqlite3')) {\n heartbeatInterval = knex.raw(`datetime('now', ?)`, [\n `-${timeoutS} seconds`,\n ]);\n }\n return heartbeatInterval;\n};\n"],"names":[],"mappings":";;AAiBa,MAAA,mBAAA,GAAsB,CAAC,QAAA,EAAkB,IAAe,KAAA;AACnE,EAAA,IAAI,iBAAoB,GAAA,IAAA,CAAK,GAAI,CAAA,CAAA,cAAA,EAAiB,QAAQ,CAAa,SAAA,CAAA,EAAA;AAAA,IACrE,IAAA,CAAK,GAAG,GAAI,EAAA;AAAA,GACb,CAAA,CAAA;AACD,EAAA,IAAI,KAAK,MAAO,CAAA,MAAA,CAAO,MAAO,CAAA,QAAA,CAAS,OAAO,CAAG,EAAA;AAC/C,IAAA,iBAAA,GAAoB,IAAK,CAAA,GAAA;AAAA,MACvB,4BAA4B,QAAQ,CAAA,QAAA,CAAA;AAAA,KACtC,CAAA;AAAA,aACS,IAAK,CAAA,MAAA,CAAO,OAAO,MAAO,CAAA,QAAA,CAAS,SAAS,CAAG,EAAA;AACxD,IAAoB,iBAAA,GAAA,IAAA,CAAK,IAAI,CAAsB,kBAAA,CAAA,EAAA;AAAA,MACjD,IAAI,QAAQ,CAAA,QAAA,CAAA;AAAA,KACb,CAAA,CAAA;AAAA,GACH;AACA,EAAO,OAAA,iBAAA,CAAA;AACT;;;;"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var config = require('@backstage/config');
|
|
4
|
+
var lodash = require('lodash');
|
|
5
|
+
|
|
6
|
+
function isTruthy(value) {
|
|
7
|
+
return lodash.isArray(value) ? value.length > 0 : !!value;
|
|
8
|
+
}
|
|
9
|
+
function generateExampleOutput(schema) {
|
|
10
|
+
const { examples } = schema;
|
|
11
|
+
if (examples && Array.isArray(examples)) {
|
|
12
|
+
return examples[0];
|
|
13
|
+
}
|
|
14
|
+
if (schema.type === "object") {
|
|
15
|
+
return Object.fromEntries(
|
|
16
|
+
Object.entries(schema.properties ?? {}).map(([key, value]) => [
|
|
17
|
+
key,
|
|
18
|
+
generateExampleOutput(value)
|
|
19
|
+
])
|
|
20
|
+
);
|
|
21
|
+
} else if (schema.type === "array") {
|
|
22
|
+
const [firstSchema] = [schema.items]?.flat();
|
|
23
|
+
if (firstSchema) {
|
|
24
|
+
return [generateExampleOutput(firstSchema)];
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
} else if (schema.type === "string") {
|
|
28
|
+
return "<example>";
|
|
29
|
+
} else if (schema.type === "number") {
|
|
30
|
+
return 0;
|
|
31
|
+
} else if (schema.type === "boolean") {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return "<unknown>";
|
|
35
|
+
}
|
|
36
|
+
const readDuration = (config$1, key, defaultValue) => {
|
|
37
|
+
if (config$1?.has(key)) {
|
|
38
|
+
return config.readDurationFromConfig(config$1, { key });
|
|
39
|
+
}
|
|
40
|
+
return defaultValue;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
exports.generateExampleOutput = generateExampleOutput;
|
|
44
|
+
exports.isTruthy = isTruthy;
|
|
45
|
+
exports.readDuration = readDuration;
|
|
46
|
+
//# sourceMappingURL=helper.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.cjs.js","sources":["../../../src/scaffolder/tasks/helper.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config, readDurationFromConfig } from '@backstage/config';\nimport { HumanDuration } from '@backstage/types';\n\nimport { isArray } from 'lodash';\nimport { Schema } from 'jsonschema';\n\n/**\n * Returns true if the input is not `false`, `undefined`, `null`, `\"\"`, `0`, or\n * `[]`. This behavior is based on the behavior of handlebars, see\n * https://handlebarsjs.com/guide/builtin-helpers.html#if\n */\nexport function isTruthy(value: any): boolean {\n return isArray(value) ? value.length > 0 : !!value;\n}\n\nexport function generateExampleOutput(schema: Schema): unknown {\n const { examples } = schema as { examples?: unknown };\n if (examples && Array.isArray(examples)) {\n return examples[0];\n }\n if (schema.type === 'object') {\n return Object.fromEntries(\n Object.entries(schema.properties ?? {}).map(([key, value]) => [\n key,\n generateExampleOutput(value),\n ]),\n );\n } else if (schema.type === 'array') {\n const [firstSchema] = [schema.items]?.flat();\n if (firstSchema) {\n return [generateExampleOutput(firstSchema)];\n }\n return [];\n } else if (schema.type === 'string') {\n return '<example>';\n } else if (schema.type === 'number') {\n return 0;\n } else if (schema.type === 'boolean') {\n return false;\n }\n return '<unknown>';\n}\n\nexport const readDuration = (\n config: Config | undefined,\n key: string,\n defaultValue: HumanDuration,\n) => {\n if (config?.has(key)) {\n return readDurationFromConfig(config, { key });\n }\n return defaultValue;\n};\n"],"names":["isArray","config","readDurationFromConfig"],"mappings":";;;;;AA2BO,SAAS,SAAS,KAAqB,EAAA;AAC5C,EAAA,OAAOA,eAAQ,KAAK,CAAA,GAAI,MAAM,MAAS,GAAA,CAAA,GAAI,CAAC,CAAC,KAAA,CAAA;AAC/C,CAAA;AAEO,SAAS,sBAAsB,MAAyB,EAAA;AAC7D,EAAM,MAAA,EAAE,UAAa,GAAA,MAAA,CAAA;AACrB,EAAA,IAAI,QAAY,IAAA,KAAA,CAAM,OAAQ,CAAA,QAAQ,CAAG,EAAA;AACvC,IAAA,OAAO,SAAS,CAAC,CAAA,CAAA;AAAA,GACnB;AACA,EAAI,IAAA,MAAA,CAAO,SAAS,QAAU,EAAA;AAC5B,IAAA,OAAO,MAAO,CAAA,WAAA;AAAA,MACZ,MAAO,CAAA,OAAA,CAAQ,MAAO,CAAA,UAAA,IAAc,EAAE,CAAE,CAAA,GAAA,CAAI,CAAC,CAAC,GAAK,EAAA,KAAK,CAAM,KAAA;AAAA,QAC5D,GAAA;AAAA,QACA,sBAAsB,KAAK,CAAA;AAAA,OAC5B,CAAA;AAAA,KACH,CAAA;AAAA,GACF,MAAA,IAAW,MAAO,CAAA,IAAA,KAAS,OAAS,EAAA;AAClC,IAAA,MAAM,CAAC,WAAW,CAAA,GAAI,CAAC,MAAO,CAAA,KAAK,GAAG,IAAK,EAAA,CAAA;AAC3C,IAAA,IAAI,WAAa,EAAA;AACf,MAAO,OAAA,CAAC,qBAAsB,CAAA,WAAW,CAAC,CAAA,CAAA;AAAA,KAC5C;AACA,IAAA,OAAO,EAAC,CAAA;AAAA,GACV,MAAA,IAAW,MAAO,CAAA,IAAA,KAAS,QAAU,EAAA;AACnC,IAAO,OAAA,WAAA,CAAA;AAAA,GACT,MAAA,IAAW,MAAO,CAAA,IAAA,KAAS,QAAU,EAAA;AACnC,IAAO,OAAA,CAAA,CAAA;AAAA,GACT,MAAA,IAAW,MAAO,CAAA,IAAA,KAAS,SAAW,EAAA;AACpC,IAAO,OAAA,KAAA,CAAA;AAAA,GACT;AACA,EAAO,OAAA,WAAA,CAAA;AACT,CAAA;AAEO,MAAM,YAAe,GAAA,CAC1BC,QACA,EAAA,GAAA,EACA,YACG,KAAA;AACH,EAAI,IAAAA,QAAA,EAAQ,GAAI,CAAA,GAAG,CAAG,EAAA;AACpB,IAAA,OAAOC,6BAAuB,CAAAD,QAAA,EAAQ,EAAE,GAAA,EAAK,CAAA,CAAA;AAAA,GAC/C;AACA,EAAO,OAAA,YAAA,CAAA;AACT;;;;;;"}
|