@backstage/plugin-scaffolder-backend 1.12.0-next.2 → 1.12.1-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/alpha/package.json +1 -1
- package/dist/alpha.cjs.js +402 -164
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/index.cjs.js +403 -164
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +35 -9
- package/package.json +19 -16
package/dist/alpha.cjs.js
CHANGED
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
6
6
|
var alpha = require('@backstage/plugin-catalog-node/alpha');
|
|
7
7
|
var catalogModel = require('@backstage/catalog-model');
|
|
8
|
-
var
|
|
8
|
+
var pluginCatalogNode = require('@backstage/plugin-catalog-node');
|
|
9
9
|
var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
|
|
10
10
|
var backendCommon = require('@backstage/backend-common');
|
|
11
11
|
var integration = require('@backstage/integration');
|
|
@@ -15,6 +15,7 @@ var yaml = require('yaml');
|
|
|
15
15
|
var fs = require('fs-extra');
|
|
16
16
|
var zod = require('zod');
|
|
17
17
|
var path = require('path');
|
|
18
|
+
var luxon = require('luxon');
|
|
18
19
|
var globby = require('globby');
|
|
19
20
|
var isbinaryfile = require('isbinaryfile');
|
|
20
21
|
var vm2 = require('vm2');
|
|
@@ -30,7 +31,6 @@ var fs$1 = require('fs');
|
|
|
30
31
|
var limiterFactory = require('p-limit');
|
|
31
32
|
var node = require('@gitbeaker/node');
|
|
32
33
|
var uuid = require('uuid');
|
|
33
|
-
var luxon = require('luxon');
|
|
34
34
|
var ObservableImpl = require('zen-observable');
|
|
35
35
|
var PQueue = require('p-queue');
|
|
36
36
|
var winston = require('winston');
|
|
@@ -105,7 +105,7 @@ class ScaffolderEntitiesProcessor {
|
|
|
105
105
|
defaultNamespace: selfRef.namespace
|
|
106
106
|
});
|
|
107
107
|
emit(
|
|
108
|
-
|
|
108
|
+
pluginCatalogNode.processingResult.relation({
|
|
109
109
|
source: selfRef,
|
|
110
110
|
type: catalogModel.RELATION_OWNED_BY,
|
|
111
111
|
target: {
|
|
@@ -116,7 +116,7 @@ class ScaffolderEntitiesProcessor {
|
|
|
116
116
|
})
|
|
117
117
|
);
|
|
118
118
|
emit(
|
|
119
|
-
|
|
119
|
+
pluginCatalogNode.processingResult.relation({
|
|
120
120
|
source: {
|
|
121
121
|
kind: targetRef.kind,
|
|
122
122
|
namespace: targetRef.namespace,
|
|
@@ -147,14 +147,14 @@ const catalogModuleTemplateKind = backendPluginApi.createBackendModule({
|
|
|
147
147
|
}
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
const id$
|
|
151
|
-
const examples$
|
|
150
|
+
const id$4 = "catalog:register";
|
|
151
|
+
const examples$4 = [
|
|
152
152
|
{
|
|
153
153
|
description: "Register with the catalog",
|
|
154
154
|
example: yaml__default["default"].stringify({
|
|
155
155
|
steps: [
|
|
156
156
|
{
|
|
157
|
-
action: id$
|
|
157
|
+
action: id$4,
|
|
158
158
|
id: "register-with-catalog",
|
|
159
159
|
name: "Register with the catalog",
|
|
160
160
|
input: {
|
|
@@ -168,9 +168,9 @@ const examples$3 = [
|
|
|
168
168
|
function createCatalogRegisterAction(options) {
|
|
169
169
|
const { catalogClient, integrations } = options;
|
|
170
170
|
return pluginScaffolderNode.createTemplateAction({
|
|
171
|
-
id: id$
|
|
171
|
+
id: id$4,
|
|
172
172
|
description: "Registers entities from a catalog descriptor file in the workspace into the software catalog.",
|
|
173
|
-
examples: examples$
|
|
173
|
+
examples: examples$4,
|
|
174
174
|
schema: {
|
|
175
175
|
input: {
|
|
176
176
|
oneOf: [
|
|
@@ -288,14 +288,14 @@ function createCatalogRegisterAction(options) {
|
|
|
288
288
|
});
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
const id$
|
|
292
|
-
const examples$
|
|
291
|
+
const id$3 = "catalog:write";
|
|
292
|
+
const examples$3 = [
|
|
293
293
|
{
|
|
294
294
|
description: "Write a catalog yaml file",
|
|
295
295
|
example: yaml__namespace.stringify({
|
|
296
296
|
steps: [
|
|
297
297
|
{
|
|
298
|
-
action: id$
|
|
298
|
+
action: id$3,
|
|
299
299
|
id: "create-catalog-info-file",
|
|
300
300
|
name: "Create catalog file",
|
|
301
301
|
input: {
|
|
@@ -320,7 +320,7 @@ const examples$2 = [
|
|
|
320
320
|
];
|
|
321
321
|
function createCatalogWriteAction() {
|
|
322
322
|
return pluginScaffolderNode.createTemplateAction({
|
|
323
|
-
id: id$
|
|
323
|
+
id: id$3,
|
|
324
324
|
description: "Writes the catalog-info.yaml for your template",
|
|
325
325
|
schema: {
|
|
326
326
|
input: zod.z.object({
|
|
@@ -331,7 +331,7 @@ function createCatalogWriteAction() {
|
|
|
331
331
|
)
|
|
332
332
|
})
|
|
333
333
|
},
|
|
334
|
-
examples: examples$
|
|
334
|
+
examples: examples$3,
|
|
335
335
|
supportsDryRun: true,
|
|
336
336
|
async handler(ctx) {
|
|
337
337
|
ctx.logStream.write(`Writing catalog-info.yaml`);
|
|
@@ -345,14 +345,14 @@ function createCatalogWriteAction() {
|
|
|
345
345
|
});
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
const id$
|
|
349
|
-
const examples$
|
|
348
|
+
const id$2 = "catalog:fetch";
|
|
349
|
+
const examples$2 = [
|
|
350
350
|
{
|
|
351
351
|
description: "Fetch entity by reference",
|
|
352
352
|
example: yaml__default["default"].stringify({
|
|
353
353
|
steps: [
|
|
354
354
|
{
|
|
355
|
-
action: id$
|
|
355
|
+
action: id$2,
|
|
356
356
|
id: "fetch",
|
|
357
357
|
name: "Fetch catalog entity",
|
|
358
358
|
input: {
|
|
@@ -361,17 +361,31 @@ const examples$1 = [
|
|
|
361
361
|
}
|
|
362
362
|
]
|
|
363
363
|
})
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
description: "Fetch multiple entities by referencse",
|
|
367
|
+
example: yaml__default["default"].stringify({
|
|
368
|
+
steps: [
|
|
369
|
+
{
|
|
370
|
+
action: id$2,
|
|
371
|
+
id: "fetchMultiple",
|
|
372
|
+
name: "Fetch catalog entities",
|
|
373
|
+
input: {
|
|
374
|
+
entityRefs: ["component:default/name"]
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
]
|
|
378
|
+
})
|
|
364
379
|
}
|
|
365
380
|
];
|
|
366
381
|
function createFetchCatalogEntityAction(options) {
|
|
367
382
|
const { catalogClient } = options;
|
|
368
383
|
return pluginScaffolderNode.createTemplateAction({
|
|
369
|
-
id: id$
|
|
370
|
-
description: "Returns entity from the catalog by entity reference",
|
|
371
|
-
examples: examples$
|
|
384
|
+
id: id$2,
|
|
385
|
+
description: "Returns entity or entities from the catalog by entity reference(s)",
|
|
386
|
+
examples: examples$2,
|
|
372
387
|
schema: {
|
|
373
388
|
input: {
|
|
374
|
-
required: ["entityRef"],
|
|
375
389
|
type: "object",
|
|
376
390
|
properties: {
|
|
377
391
|
entityRef: {
|
|
@@ -379,9 +393,14 @@ function createFetchCatalogEntityAction(options) {
|
|
|
379
393
|
title: "Entity reference",
|
|
380
394
|
description: "Entity reference of the entity to get"
|
|
381
395
|
},
|
|
396
|
+
entityRefs: {
|
|
397
|
+
type: "array",
|
|
398
|
+
title: "Entity references",
|
|
399
|
+
description: "Entity references of the entities to get"
|
|
400
|
+
},
|
|
382
401
|
optional: {
|
|
383
402
|
title: "Optional",
|
|
384
|
-
description: "
|
|
403
|
+
description: "Allow the entity or entities to optionally exist. Default: false",
|
|
385
404
|
type: "boolean"
|
|
386
405
|
}
|
|
387
406
|
}
|
|
@@ -392,40 +411,62 @@ function createFetchCatalogEntityAction(options) {
|
|
|
392
411
|
entity: {
|
|
393
412
|
title: "Entity found by the entity reference",
|
|
394
413
|
type: "object",
|
|
395
|
-
description: "Object containing same values used in the Entity schema."
|
|
414
|
+
description: "Object containing same values used in the Entity schema. Only when used with `entityRef` parameter."
|
|
415
|
+
},
|
|
416
|
+
entities: {
|
|
417
|
+
title: "Entities found by the entity references",
|
|
418
|
+
type: "array",
|
|
419
|
+
items: { type: "object" },
|
|
420
|
+
description: "Array containing objects with same values used in the Entity schema. Only when used with `entityRefs` parameter."
|
|
396
421
|
}
|
|
397
422
|
}
|
|
398
423
|
}
|
|
399
424
|
},
|
|
400
425
|
async handler(ctx) {
|
|
401
|
-
var _a;
|
|
402
|
-
const { entityRef, optional } = ctx.input;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
426
|
+
var _a, _b;
|
|
427
|
+
const { entityRef, entityRefs, optional } = ctx.input;
|
|
428
|
+
if (!entityRef && !entityRefs) {
|
|
429
|
+
if (optional) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
throw new Error("Missing entity reference or references");
|
|
433
|
+
}
|
|
434
|
+
if (entityRef) {
|
|
435
|
+
const entity = await catalogClient.getEntityByRef(entityRef, {
|
|
406
436
|
token: (_a = ctx.secrets) == null ? void 0 : _a.backstageToken
|
|
407
437
|
});
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
throw e;
|
|
438
|
+
if (!entity && !optional) {
|
|
439
|
+
throw new Error(`Entity ${entityRef} not found`);
|
|
411
440
|
}
|
|
441
|
+
ctx.output("entity", entity != null ? entity : null);
|
|
412
442
|
}
|
|
413
|
-
if (
|
|
414
|
-
|
|
443
|
+
if (entityRefs) {
|
|
444
|
+
const entities = await catalogClient.getEntitiesByRefs(
|
|
445
|
+
{ entityRefs },
|
|
446
|
+
{
|
|
447
|
+
token: (_b = ctx.secrets) == null ? void 0 : _b.backstageToken
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
const finalEntities = entities.items.map((e, i) => {
|
|
451
|
+
if (!e && !optional) {
|
|
452
|
+
throw new Error(`Entity ${entityRefs[i]} not found`);
|
|
453
|
+
}
|
|
454
|
+
return e != null ? e : null;
|
|
455
|
+
});
|
|
456
|
+
ctx.output("entities", finalEntities);
|
|
415
457
|
}
|
|
416
|
-
ctx.output("entity", entity != null ? entity : null);
|
|
417
458
|
}
|
|
418
459
|
});
|
|
419
460
|
}
|
|
420
461
|
|
|
421
|
-
const id = "debug:log";
|
|
422
|
-
const examples = [
|
|
462
|
+
const id$1 = "debug:log";
|
|
463
|
+
const examples$1 = [
|
|
423
464
|
{
|
|
424
465
|
description: "Write a debug message",
|
|
425
466
|
example: yaml__default["default"].stringify({
|
|
426
467
|
steps: [
|
|
427
468
|
{
|
|
428
|
-
action: id,
|
|
469
|
+
action: id$1,
|
|
429
470
|
id: "write-debug-line",
|
|
430
471
|
name: 'Write "Hello Backstage!" log line',
|
|
431
472
|
input: {
|
|
@@ -440,7 +481,7 @@ const examples = [
|
|
|
440
481
|
example: yaml__default["default"].stringify({
|
|
441
482
|
steps: [
|
|
442
483
|
{
|
|
443
|
-
action: id,
|
|
484
|
+
action: id$1,
|
|
444
485
|
id: "write-workspace-directory",
|
|
445
486
|
name: "List the workspace directory",
|
|
446
487
|
input: {
|
|
@@ -453,9 +494,9 @@ const examples = [
|
|
|
453
494
|
];
|
|
454
495
|
function createDebugLogAction() {
|
|
455
496
|
return pluginScaffolderNode.createTemplateAction({
|
|
456
|
-
id,
|
|
497
|
+
id: id$1,
|
|
457
498
|
description: "Writes a message into the log or lists all files in the workspace.",
|
|
458
|
-
examples,
|
|
499
|
+
examples: examples$1,
|
|
459
500
|
schema: {
|
|
460
501
|
input: {
|
|
461
502
|
type: "object",
|
|
@@ -502,6 +543,98 @@ async function recursiveReadDir(dir) {
|
|
|
502
543
|
return files.reduce((a, f) => a.concat(f), []);
|
|
503
544
|
}
|
|
504
545
|
|
|
546
|
+
const id = "debug:wait";
|
|
547
|
+
const MAX_WAIT_TIME_IN_ISO = "T00:00:30";
|
|
548
|
+
const examples = [
|
|
549
|
+
{
|
|
550
|
+
description: "Waiting for 5 seconds",
|
|
551
|
+
example: yaml__default["default"].stringify({
|
|
552
|
+
steps: [
|
|
553
|
+
{
|
|
554
|
+
action: id,
|
|
555
|
+
id: "wait-5sec",
|
|
556
|
+
name: "Waiting for 5 seconds",
|
|
557
|
+
input: {
|
|
558
|
+
seconds: 5
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
]
|
|
562
|
+
})
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
description: "Waiting for 5 minutes",
|
|
566
|
+
example: yaml__default["default"].stringify({
|
|
567
|
+
steps: [
|
|
568
|
+
{
|
|
569
|
+
action: id,
|
|
570
|
+
id: "wait-5min",
|
|
571
|
+
name: "Waiting for 5 minutes",
|
|
572
|
+
input: {
|
|
573
|
+
minutes: 5
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
]
|
|
577
|
+
})
|
|
578
|
+
}
|
|
579
|
+
];
|
|
580
|
+
function createWaitAction(options) {
|
|
581
|
+
const toDuration = (maxWaitTime) => {
|
|
582
|
+
if (maxWaitTime) {
|
|
583
|
+
if (maxWaitTime instanceof luxon.Duration) {
|
|
584
|
+
return maxWaitTime;
|
|
585
|
+
}
|
|
586
|
+
return luxon.Duration.fromObject(maxWaitTime);
|
|
587
|
+
}
|
|
588
|
+
return luxon.Duration.fromISOTime(MAX_WAIT_TIME_IN_ISO);
|
|
589
|
+
};
|
|
590
|
+
return pluginScaffolderNode.createTemplateAction({
|
|
591
|
+
id,
|
|
592
|
+
description: "Waits for a certain period of time.",
|
|
593
|
+
examples,
|
|
594
|
+
schema: {
|
|
595
|
+
input: {
|
|
596
|
+
type: "object",
|
|
597
|
+
properties: {
|
|
598
|
+
minutes: {
|
|
599
|
+
title: "Waiting period in minutes.",
|
|
600
|
+
type: "number"
|
|
601
|
+
},
|
|
602
|
+
seconds: {
|
|
603
|
+
title: "Waiting period in seconds.",
|
|
604
|
+
type: "number"
|
|
605
|
+
},
|
|
606
|
+
milliseconds: {
|
|
607
|
+
title: "Waiting period in milliseconds.",
|
|
608
|
+
type: "number"
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
async handler(ctx) {
|
|
614
|
+
const delayTime = luxon.Duration.fromObject(ctx.input);
|
|
615
|
+
const maxWait = toDuration(options == null ? void 0 : options.maxWaitTime);
|
|
616
|
+
if (delayTime.minus(maxWait).toMillis() > 0) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
`Waiting duration is longer than the maximum threshold of ${maxWait.toHuman()}`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
await new Promise((resolve) => {
|
|
622
|
+
var _a;
|
|
623
|
+
const controller = new AbortController();
|
|
624
|
+
const timeoutHandle = setTimeout(abort, delayTime.toMillis());
|
|
625
|
+
(_a = ctx.signal) == null ? void 0 : _a.addEventListener("abort", abort);
|
|
626
|
+
function abort() {
|
|
627
|
+
var _a2;
|
|
628
|
+
(_a2 = ctx.signal) == null ? void 0 : _a2.removeEventListener("abort", abort);
|
|
629
|
+
clearTimeout(timeoutHandle);
|
|
630
|
+
controller.abort();
|
|
631
|
+
resolve("finished");
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
505
638
|
async function fetchContents(options) {
|
|
506
639
|
const { reader, integrations, baseUrl, fetchUrl = ".", outputPath } = options;
|
|
507
640
|
let fetchUrlIsAbsolute = false;
|
|
@@ -3970,7 +4103,7 @@ const createPublishGitlabMergeRequestAction = (options) => {
|
|
|
3970
4103
|
title: "Gitlab Project path",
|
|
3971
4104
|
type: "string"
|
|
3972
4105
|
},
|
|
3973
|
-
|
|
4106
|
+
mergeRequestUrl: {
|
|
3974
4107
|
title: "MergeRequest(MR) URL",
|
|
3975
4108
|
type: "string",
|
|
3976
4109
|
description: "Link to the merge request in GitLab"
|
|
@@ -4142,6 +4275,7 @@ const createBuiltinActions = (options) => {
|
|
|
4142
4275
|
config
|
|
4143
4276
|
}),
|
|
4144
4277
|
createDebugLogAction(),
|
|
4278
|
+
createWaitAction(),
|
|
4145
4279
|
createCatalogRegisterAction({ catalogClient, integrations }),
|
|
4146
4280
|
createFetchCatalogEntityAction({ catalogClient }),
|
|
4147
4281
|
createCatalogWriteAction(),
|
|
@@ -4308,7 +4442,7 @@ class DatabaseTaskStore {
|
|
|
4308
4442
|
const updateCount = await tx("tasks").where({ id: task.id, status: "open" }).update({
|
|
4309
4443
|
status: "processing",
|
|
4310
4444
|
last_heartbeat_at: this.db.fn.now(),
|
|
4311
|
-
// remove the secrets when moving
|
|
4445
|
+
// remove the secrets when moving to processing state.
|
|
4312
4446
|
secrets: null
|
|
4313
4447
|
});
|
|
4314
4448
|
if (updateCount < 1) {
|
|
@@ -4356,7 +4490,7 @@ class DatabaseTaskStore {
|
|
|
4356
4490
|
async completeTask(options) {
|
|
4357
4491
|
const { taskId, status, eventBody } = options;
|
|
4358
4492
|
let oldStatus;
|
|
4359
|
-
if (
|
|
4493
|
+
if (["failed", "completed", "cancelled"].includes(status)) {
|
|
4360
4494
|
oldStatus = "processing";
|
|
4361
4495
|
} else {
|
|
4362
4496
|
throw new Error(
|
|
@@ -4367,6 +4501,30 @@ class DatabaseTaskStore {
|
|
|
4367
4501
|
const [task] = await tx("tasks").where({
|
|
4368
4502
|
id: taskId
|
|
4369
4503
|
}).limit(1).select();
|
|
4504
|
+
const updateTask = async (criteria) => {
|
|
4505
|
+
const updateCount = await tx("tasks").where(criteria).update({
|
|
4506
|
+
status
|
|
4507
|
+
});
|
|
4508
|
+
if (updateCount !== 1) {
|
|
4509
|
+
throw new errors.ConflictError(
|
|
4510
|
+
`Failed to update status to '${status}' for taskId ${taskId}`
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
await tx("task_events").insert({
|
|
4514
|
+
task_id: taskId,
|
|
4515
|
+
event_type: "completion",
|
|
4516
|
+
body: JSON.stringify(eventBody)
|
|
4517
|
+
});
|
|
4518
|
+
};
|
|
4519
|
+
if (status === "cancelled") {
|
|
4520
|
+
await updateTask({
|
|
4521
|
+
id: taskId
|
|
4522
|
+
});
|
|
4523
|
+
return;
|
|
4524
|
+
}
|
|
4525
|
+
if (task.status === "cancelled") {
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4370
4528
|
if (!task) {
|
|
4371
4529
|
throw new Error(`No task with taskId ${taskId} found`);
|
|
4372
4530
|
}
|
|
@@ -4375,21 +4533,9 @@ class DatabaseTaskStore {
|
|
|
4375
4533
|
`Refusing to update status of run '${taskId}' to status '${status}' as it is currently '${task.status}', expected '${oldStatus}'`
|
|
4376
4534
|
);
|
|
4377
4535
|
}
|
|
4378
|
-
|
|
4536
|
+
await updateTask({
|
|
4379
4537
|
id: taskId,
|
|
4380
4538
|
status: oldStatus
|
|
4381
|
-
}).update({
|
|
4382
|
-
status
|
|
4383
|
-
});
|
|
4384
|
-
if (updateCount !== 1) {
|
|
4385
|
-
throw new errors.ConflictError(
|
|
4386
|
-
`Failed to update status to '${status}' for taskId ${taskId}`
|
|
4387
|
-
);
|
|
4388
|
-
}
|
|
4389
|
-
await tx("task_events").insert({
|
|
4390
|
-
task_id: taskId,
|
|
4391
|
-
event_type: "completion",
|
|
4392
|
-
body: JSON.stringify(eventBody)
|
|
4393
4539
|
});
|
|
4394
4540
|
});
|
|
4395
4541
|
}
|
|
@@ -4457,24 +4603,37 @@ class DatabaseTaskStore {
|
|
|
4457
4603
|
}
|
|
4458
4604
|
});
|
|
4459
4605
|
}
|
|
4606
|
+
async cancelTask(options) {
|
|
4607
|
+
const { taskId, body } = options;
|
|
4608
|
+
const serializedBody = JSON.stringify(body);
|
|
4609
|
+
await this.db("task_events").insert({
|
|
4610
|
+
task_id: taskId,
|
|
4611
|
+
event_type: "cancelled",
|
|
4612
|
+
body: serializedBody
|
|
4613
|
+
});
|
|
4614
|
+
}
|
|
4460
4615
|
}
|
|
4461
4616
|
|
|
4462
4617
|
class TaskManager {
|
|
4463
4618
|
// Runs heartbeat internally
|
|
4464
|
-
constructor(task, storage, logger) {
|
|
4619
|
+
constructor(task, storage, signal, logger) {
|
|
4465
4620
|
this.task = task;
|
|
4466
4621
|
this.storage = storage;
|
|
4622
|
+
this.signal = signal;
|
|
4467
4623
|
this.logger = logger;
|
|
4468
4624
|
this.isDone = false;
|
|
4469
4625
|
}
|
|
4470
|
-
static create(task, storage, logger) {
|
|
4471
|
-
const agent = new TaskManager(task, storage, logger);
|
|
4626
|
+
static create(task, storage, abortSignal, logger) {
|
|
4627
|
+
const agent = new TaskManager(task, storage, abortSignal, logger);
|
|
4472
4628
|
agent.startTimeout();
|
|
4473
4629
|
return agent;
|
|
4474
4630
|
}
|
|
4475
4631
|
get spec() {
|
|
4476
4632
|
return this.task.spec;
|
|
4477
4633
|
}
|
|
4634
|
+
get cancelSignal() {
|
|
4635
|
+
return this.signal;
|
|
4636
|
+
}
|
|
4478
4637
|
get secrets() {
|
|
4479
4638
|
return this.task.secrets;
|
|
4480
4639
|
}
|
|
@@ -4544,6 +4703,28 @@ class StorageTaskBroker {
|
|
|
4544
4703
|
}
|
|
4545
4704
|
return await this.storage.list({ createdBy: options == null ? void 0 : options.createdBy });
|
|
4546
4705
|
}
|
|
4706
|
+
async registerCancellable(taskId, abortController) {
|
|
4707
|
+
let shouldUnsubscribe = false;
|
|
4708
|
+
const subscription = this.event$({ taskId, after: void 0 }).subscribe({
|
|
4709
|
+
error: (_) => {
|
|
4710
|
+
subscription.unsubscribe();
|
|
4711
|
+
},
|
|
4712
|
+
next: ({ events }) => {
|
|
4713
|
+
for (const event of events) {
|
|
4714
|
+
if (event.type === "cancelled") {
|
|
4715
|
+
abortController.abort();
|
|
4716
|
+
shouldUnsubscribe = true;
|
|
4717
|
+
}
|
|
4718
|
+
if (event.type === "completion") {
|
|
4719
|
+
shouldUnsubscribe = true;
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
if (shouldUnsubscribe) {
|
|
4723
|
+
subscription.unsubscribe();
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
});
|
|
4727
|
+
}
|
|
4547
4728
|
/**
|
|
4548
4729
|
* {@inheritdoc TaskBroker.claim}
|
|
4549
4730
|
*/
|
|
@@ -4551,6 +4732,8 @@ class StorageTaskBroker {
|
|
|
4551
4732
|
for (; ; ) {
|
|
4552
4733
|
const pendingTask = await this.storage.claimTask();
|
|
4553
4734
|
if (pendingTask) {
|
|
4735
|
+
const abortController = new AbortController();
|
|
4736
|
+
await this.registerCancellable(pendingTask.id, abortController);
|
|
4554
4737
|
return TaskManager.create(
|
|
4555
4738
|
{
|
|
4556
4739
|
taskId: pendingTask.id,
|
|
@@ -4559,6 +4742,7 @@ class StorageTaskBroker {
|
|
|
4559
4742
|
createdBy: pendingTask.createdBy
|
|
4560
4743
|
},
|
|
4561
4744
|
this.storage,
|
|
4745
|
+
abortController.signal,
|
|
4562
4746
|
this.logger
|
|
4563
4747
|
);
|
|
4564
4748
|
}
|
|
@@ -4633,6 +4817,19 @@ class StorageTaskBroker {
|
|
|
4633
4817
|
this.deferredDispatch.resolve();
|
|
4634
4818
|
this.deferredDispatch = defer();
|
|
4635
4819
|
}
|
|
4820
|
+
async cancel(taskId) {
|
|
4821
|
+
var _a, _b;
|
|
4822
|
+
const { events } = await this.storage.listEvents({ taskId });
|
|
4823
|
+
const currentStepId = events.length > 0 ? events.filter(({ body }) => body == null ? void 0 : body.stepId).reduce((prev, curr) => prev.id > curr.id ? prev : curr).body.stepId : 0;
|
|
4824
|
+
await ((_b = (_a = this.storage).cancelTask) == null ? void 0 : _b.call(_a, {
|
|
4825
|
+
taskId,
|
|
4826
|
+
body: {
|
|
4827
|
+
message: `Step ${currentStepId} has been cancelled.`,
|
|
4828
|
+
stepId: currentStepId,
|
|
4829
|
+
status: "cancelled"
|
|
4830
|
+
}
|
|
4831
|
+
}));
|
|
4832
|
+
}
|
|
4636
4833
|
}
|
|
4637
4834
|
|
|
4638
4835
|
function isTruthy(value) {
|
|
@@ -4764,8 +4961,107 @@ class NunjucksWorkflowRunner {
|
|
|
4764
4961
|
return value;
|
|
4765
4962
|
});
|
|
4766
4963
|
}
|
|
4767
|
-
async
|
|
4964
|
+
async executeStep(task, step, context, renderTemplate, taskTrack, workspacePath) {
|
|
4768
4965
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
4966
|
+
const stepTrack = await this.tracker.stepStart(task, step);
|
|
4967
|
+
if (task.cancelSignal.aborted) {
|
|
4968
|
+
throw new Error(`Step ${step.name} has been cancelled.`);
|
|
4969
|
+
}
|
|
4970
|
+
try {
|
|
4971
|
+
if (step.if) {
|
|
4972
|
+
const ifResult = await this.render(step.if, context, renderTemplate);
|
|
4973
|
+
if (!isTruthy(ifResult)) {
|
|
4974
|
+
await stepTrack.skipFalsy();
|
|
4975
|
+
return;
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
const action = this.options.actionRegistry.get(step.action);
|
|
4979
|
+
const { taskLogger, streamLogger } = createStepLogger({ task, step });
|
|
4980
|
+
if (task.isDryRun) {
|
|
4981
|
+
const redactedSecrets = Object.fromEntries(
|
|
4982
|
+
Object.entries((_a = task.secrets) != null ? _a : {}).map((secret) => [
|
|
4983
|
+
secret[0],
|
|
4984
|
+
"[REDACTED]"
|
|
4985
|
+
])
|
|
4986
|
+
);
|
|
4987
|
+
const debugInput = (_b = step.input && this.render(
|
|
4988
|
+
step.input,
|
|
4989
|
+
{
|
|
4990
|
+
...context,
|
|
4991
|
+
secrets: redactedSecrets
|
|
4992
|
+
},
|
|
4993
|
+
renderTemplate
|
|
4994
|
+
)) != null ? _b : {};
|
|
4995
|
+
taskLogger.info(
|
|
4996
|
+
`Running ${action.id} in dry-run mode with inputs (secrets redacted): ${JSON.stringify(
|
|
4997
|
+
debugInput,
|
|
4998
|
+
void 0,
|
|
4999
|
+
2
|
|
5000
|
+
)}`
|
|
5001
|
+
);
|
|
5002
|
+
if (!action.supportsDryRun) {
|
|
5003
|
+
await taskTrack.skipDryRun(step, action);
|
|
5004
|
+
const outputSchema = (_c = action.schema) == null ? void 0 : _c.output;
|
|
5005
|
+
if (outputSchema) {
|
|
5006
|
+
context.steps[step.id] = {
|
|
5007
|
+
output: generateExampleOutput(outputSchema)
|
|
5008
|
+
};
|
|
5009
|
+
} else {
|
|
5010
|
+
context.steps[step.id] = { output: {} };
|
|
5011
|
+
}
|
|
5012
|
+
return;
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
const input = (_e = step.input && this.render(
|
|
5016
|
+
step.input,
|
|
5017
|
+
{ ...context, secrets: (_d = task.secrets) != null ? _d : {} },
|
|
5018
|
+
renderTemplate
|
|
5019
|
+
)) != null ? _e : {};
|
|
5020
|
+
if ((_f = action.schema) == null ? void 0 : _f.input) {
|
|
5021
|
+
const validateResult = jsonschema.validate(input, action.schema.input);
|
|
5022
|
+
if (!validateResult.valid) {
|
|
5023
|
+
const errors$1 = validateResult.errors.join(", ");
|
|
5024
|
+
throw new errors.InputError(
|
|
5025
|
+
`Invalid input passed to action ${action.id}, ${errors$1}`
|
|
5026
|
+
);
|
|
5027
|
+
}
|
|
5028
|
+
}
|
|
5029
|
+
const tmpDirs = new Array();
|
|
5030
|
+
const stepOutput = {};
|
|
5031
|
+
await action.handler({
|
|
5032
|
+
input,
|
|
5033
|
+
secrets: (_g = task.secrets) != null ? _g : {},
|
|
5034
|
+
logger: taskLogger,
|
|
5035
|
+
logStream: streamLogger,
|
|
5036
|
+
workspacePath,
|
|
5037
|
+
createTemporaryDirectory: async () => {
|
|
5038
|
+
const tmpDir = await fs__default["default"].mkdtemp(`${workspacePath}_step-${step.id}-`);
|
|
5039
|
+
tmpDirs.push(tmpDir);
|
|
5040
|
+
return tmpDir;
|
|
5041
|
+
},
|
|
5042
|
+
output(name, value) {
|
|
5043
|
+
stepOutput[name] = value;
|
|
5044
|
+
},
|
|
5045
|
+
templateInfo: task.spec.templateInfo,
|
|
5046
|
+
user: task.spec.user,
|
|
5047
|
+
isDryRun: task.isDryRun,
|
|
5048
|
+
signal: task.cancelSignal
|
|
5049
|
+
});
|
|
5050
|
+
for (const tmpDir of tmpDirs) {
|
|
5051
|
+
await fs__default["default"].remove(tmpDir);
|
|
5052
|
+
}
|
|
5053
|
+
context.steps[step.id] = { output: stepOutput };
|
|
5054
|
+
if (task.cancelSignal.aborted) {
|
|
5055
|
+
throw new Error(`Step ${step.name} has been cancelled.`);
|
|
5056
|
+
}
|
|
5057
|
+
await stepTrack.markSuccessful();
|
|
5058
|
+
} catch (err) {
|
|
5059
|
+
await taskTrack.markFailed(step, err);
|
|
5060
|
+
await stepTrack.markFailed();
|
|
5061
|
+
throw err;
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
async execute(task) {
|
|
4769
5065
|
if (!isValidTaskSpec(task.spec)) {
|
|
4770
5066
|
throw new errors.InputError(
|
|
4771
5067
|
"Wrong template version executed with the workflow engine"
|
|
@@ -4775,7 +5071,11 @@ class NunjucksWorkflowRunner {
|
|
|
4775
5071
|
this.options.workingDirectory,
|
|
4776
5072
|
await task.getWorkspaceName()
|
|
4777
5073
|
);
|
|
4778
|
-
const {
|
|
5074
|
+
const {
|
|
5075
|
+
additionalTemplateFilters,
|
|
5076
|
+
additionalTemplateGlobals,
|
|
5077
|
+
integrations
|
|
5078
|
+
} = this.options;
|
|
4779
5079
|
const renderTemplate = await SecureTemplater.loadRenderer({
|
|
4780
5080
|
// TODO(blam): let's work out how we can deprecate this.
|
|
4781
5081
|
// We shouldn't really need to be exposing these now we can deal with
|
|
@@ -4784,8 +5084,8 @@ class NunjucksWorkflowRunner {
|
|
|
4784
5084
|
parseRepoUrl(url) {
|
|
4785
5085
|
return parseRepoUrl(url, integrations);
|
|
4786
5086
|
},
|
|
4787
|
-
additionalTemplateFilters
|
|
4788
|
-
additionalTemplateGlobals
|
|
5087
|
+
additionalTemplateFilters,
|
|
5088
|
+
additionalTemplateGlobals
|
|
4789
5089
|
});
|
|
4790
5090
|
try {
|
|
4791
5091
|
const taskTrack = await this.tracker.taskStart(task);
|
|
@@ -4796,105 +5096,14 @@ class NunjucksWorkflowRunner {
|
|
|
4796
5096
|
user: task.spec.user
|
|
4797
5097
|
};
|
|
4798
5098
|
for (const step of task.spec.steps) {
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
if (!isTruthy(ifResult)) {
|
|
4808
|
-
await stepTrack.skipFalsy();
|
|
4809
|
-
continue;
|
|
4810
|
-
}
|
|
4811
|
-
}
|
|
4812
|
-
const action = this.options.actionRegistry.get(step.action);
|
|
4813
|
-
const { taskLogger, streamLogger } = createStepLogger({ task, step });
|
|
4814
|
-
if (task.isDryRun) {
|
|
4815
|
-
const redactedSecrets = Object.fromEntries(
|
|
4816
|
-
Object.entries((_a = task.secrets) != null ? _a : {}).map((secret) => [
|
|
4817
|
-
secret[0],
|
|
4818
|
-
"[REDACTED]"
|
|
4819
|
-
])
|
|
4820
|
-
);
|
|
4821
|
-
const debugInput = (_b = step.input && this.render(
|
|
4822
|
-
step.input,
|
|
4823
|
-
{
|
|
4824
|
-
...context,
|
|
4825
|
-
secrets: redactedSecrets
|
|
4826
|
-
},
|
|
4827
|
-
renderTemplate
|
|
4828
|
-
)) != null ? _b : {};
|
|
4829
|
-
taskLogger.info(
|
|
4830
|
-
`Running ${action.id} in dry-run mode with inputs (secrets redacted): ${JSON.stringify(
|
|
4831
|
-
debugInput,
|
|
4832
|
-
void 0,
|
|
4833
|
-
2
|
|
4834
|
-
)}`
|
|
4835
|
-
);
|
|
4836
|
-
if (!action.supportsDryRun) {
|
|
4837
|
-
await taskTrack.skipDryRun(step, action);
|
|
4838
|
-
const outputSchema = (_c = action.schema) == null ? void 0 : _c.output;
|
|
4839
|
-
if (outputSchema) {
|
|
4840
|
-
context.steps[step.id] = {
|
|
4841
|
-
output: generateExampleOutput(outputSchema)
|
|
4842
|
-
};
|
|
4843
|
-
} else {
|
|
4844
|
-
context.steps[step.id] = { output: {} };
|
|
4845
|
-
}
|
|
4846
|
-
continue;
|
|
4847
|
-
}
|
|
4848
|
-
}
|
|
4849
|
-
const input = (_e = step.input && this.render(
|
|
4850
|
-
step.input,
|
|
4851
|
-
{ ...context, secrets: (_d = task.secrets) != null ? _d : {} },
|
|
4852
|
-
renderTemplate
|
|
4853
|
-
)) != null ? _e : {};
|
|
4854
|
-
if ((_f = action.schema) == null ? void 0 : _f.input) {
|
|
4855
|
-
const validateResult = jsonschema.validate(
|
|
4856
|
-
input,
|
|
4857
|
-
action.schema.input
|
|
4858
|
-
);
|
|
4859
|
-
if (!validateResult.valid) {
|
|
4860
|
-
const errors$1 = validateResult.errors.join(", ");
|
|
4861
|
-
throw new errors.InputError(
|
|
4862
|
-
`Invalid input passed to action ${action.id}, ${errors$1}`
|
|
4863
|
-
);
|
|
4864
|
-
}
|
|
4865
|
-
}
|
|
4866
|
-
const tmpDirs = new Array();
|
|
4867
|
-
const stepOutput = {};
|
|
4868
|
-
await action.handler({
|
|
4869
|
-
input,
|
|
4870
|
-
secrets: (_g = task.secrets) != null ? _g : {},
|
|
4871
|
-
logger: taskLogger,
|
|
4872
|
-
logStream: streamLogger,
|
|
4873
|
-
workspacePath,
|
|
4874
|
-
createTemporaryDirectory: async () => {
|
|
4875
|
-
const tmpDir = await fs__default["default"].mkdtemp(
|
|
4876
|
-
`${workspacePath}_step-${step.id}-`
|
|
4877
|
-
);
|
|
4878
|
-
tmpDirs.push(tmpDir);
|
|
4879
|
-
return tmpDir;
|
|
4880
|
-
},
|
|
4881
|
-
output(name, value) {
|
|
4882
|
-
stepOutput[name] = value;
|
|
4883
|
-
},
|
|
4884
|
-
templateInfo: task.spec.templateInfo,
|
|
4885
|
-
user: task.spec.user,
|
|
4886
|
-
isDryRun: task.isDryRun
|
|
4887
|
-
});
|
|
4888
|
-
for (const tmpDir of tmpDirs) {
|
|
4889
|
-
await fs__default["default"].remove(tmpDir);
|
|
4890
|
-
}
|
|
4891
|
-
context.steps[step.id] = { output: stepOutput };
|
|
4892
|
-
await stepTrack.markSuccessful();
|
|
4893
|
-
} catch (err) {
|
|
4894
|
-
await taskTrack.markFailed(step, err);
|
|
4895
|
-
await stepTrack.markFailed();
|
|
4896
|
-
throw err;
|
|
4897
|
-
}
|
|
5099
|
+
await this.executeStep(
|
|
5100
|
+
task,
|
|
5101
|
+
step,
|
|
5102
|
+
context,
|
|
5103
|
+
renderTemplate,
|
|
5104
|
+
taskTrack,
|
|
5105
|
+
workspacePath
|
|
5106
|
+
);
|
|
4898
5107
|
}
|
|
4899
5108
|
const output = this.render(task.spec.output, context, renderTemplate);
|
|
4900
5109
|
await taskTrack.markSuccessful();
|
|
@@ -4961,8 +5170,21 @@ function scaffoldingTracker() {
|
|
|
4961
5170
|
});
|
|
4962
5171
|
taskTimer({ result: "failed" });
|
|
4963
5172
|
}
|
|
5173
|
+
async function markCancelled(step) {
|
|
5174
|
+
await task.emitLog(`Step ${step.id} has been cancelled.`, {
|
|
5175
|
+
stepId: step.id,
|
|
5176
|
+
status: "cancelled"
|
|
5177
|
+
});
|
|
5178
|
+
taskCount.inc({
|
|
5179
|
+
template,
|
|
5180
|
+
user,
|
|
5181
|
+
result: "cancelled"
|
|
5182
|
+
});
|
|
5183
|
+
taskTimer({ result: "cancelled" });
|
|
5184
|
+
}
|
|
4964
5185
|
return {
|
|
4965
5186
|
skipDryRun,
|
|
5187
|
+
markCancelled,
|
|
4966
5188
|
markSuccessful,
|
|
4967
5189
|
markFailed
|
|
4968
5190
|
};
|
|
@@ -4990,6 +5212,14 @@ function scaffoldingTracker() {
|
|
|
4990
5212
|
});
|
|
4991
5213
|
stepTimer({ result: "ok" });
|
|
4992
5214
|
}
|
|
5215
|
+
async function markCancelled() {
|
|
5216
|
+
stepCount.inc({
|
|
5217
|
+
template,
|
|
5218
|
+
step: step.name,
|
|
5219
|
+
result: "cancelled"
|
|
5220
|
+
});
|
|
5221
|
+
stepTimer({ result: "cancelled" });
|
|
5222
|
+
}
|
|
4993
5223
|
async function markFailed() {
|
|
4994
5224
|
stepCount.inc({
|
|
4995
5225
|
template,
|
|
@@ -5006,8 +5236,9 @@ function scaffoldingTracker() {
|
|
|
5006
5236
|
stepTimer({ result: "skipped" });
|
|
5007
5237
|
}
|
|
5008
5238
|
return {
|
|
5009
|
-
|
|
5239
|
+
markCancelled,
|
|
5010
5240
|
markFailed,
|
|
5241
|
+
markSuccessful,
|
|
5011
5242
|
skipFalsy
|
|
5012
5243
|
};
|
|
5013
5244
|
}
|
|
@@ -5131,6 +5362,7 @@ function createDryRunner(options) {
|
|
|
5131
5362
|
);
|
|
5132
5363
|
try {
|
|
5133
5364
|
await deserializeDirectoryContents(contentsPath, input.directoryContents);
|
|
5365
|
+
const abortSignal = new AbortController().signal;
|
|
5134
5366
|
const result = await workflowRunner.execute({
|
|
5135
5367
|
spec: {
|
|
5136
5368
|
...input.spec,
|
|
@@ -5154,6 +5386,7 @@ function createDryRunner(options) {
|
|
|
5154
5386
|
done: false,
|
|
5155
5387
|
isDryRun: true,
|
|
5156
5388
|
getWorkspaceName: async () => `dry-run-${dryRunId}`,
|
|
5389
|
+
cancelSignal: abortSignal,
|
|
5157
5390
|
async emitLog(message, logMetadata) {
|
|
5158
5391
|
if ((logMetadata == null ? void 0 : logMetadata.stepId) === dryRunId) {
|
|
5159
5392
|
return;
|
|
@@ -5165,7 +5398,7 @@ function createDryRunner(options) {
|
|
|
5165
5398
|
}
|
|
5166
5399
|
});
|
|
5167
5400
|
},
|
|
5168
|
-
async
|
|
5401
|
+
complete: async () => {
|
|
5169
5402
|
throw new Error("Not implemented");
|
|
5170
5403
|
}
|
|
5171
5404
|
});
|
|
@@ -5496,6 +5729,11 @@ async function createRouter(options) {
|
|
|
5496
5729
|
}
|
|
5497
5730
|
delete task.secrets;
|
|
5498
5731
|
res.status(200).json(task);
|
|
5732
|
+
}).post("/v2/tasks/:taskId/cancel", async (req, res) => {
|
|
5733
|
+
var _a;
|
|
5734
|
+
const { taskId } = req.params;
|
|
5735
|
+
await ((_a = taskBroker.cancel) == null ? void 0 : _a.call(taskBroker, taskId));
|
|
5736
|
+
res.status(200).json({ status: "cancelled" });
|
|
5499
5737
|
}).get("/v2/tasks/:taskId/eventstream", async (req, res) => {
|
|
5500
5738
|
const { taskId } = req.params;
|
|
5501
5739
|
const after = req.query.after !== void 0 ? Number(req.query.after) : void 0;
|