@backstage/plugin-scaffolder-backend 1.25.0 → 1.26.0-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 +50 -0
- package/alpha/package.json +1 -1
- package/dist/alpha.cjs.js +9 -3
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/cjs/{router-L9rspgmo.cjs.js → router-CC-UhVkG.cjs.js} +331 -91
- package/dist/cjs/router-CC-UhVkG.cjs.js.map +1 -0
- package/dist/index.cjs.js +3 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +79 -1
- package/package.json +22 -21
- package/dist/cjs/router-L9rspgmo.cjs.js.map +0 -1
|
@@ -32,6 +32,7 @@ var gitlab = require('@backstage/plugin-scaffolder-backend-module-gitlab');
|
|
|
32
32
|
var pluginScaffolderBackendModuleGitea = require('@backstage/plugin-scaffolder-backend-module-gitea');
|
|
33
33
|
var uuid = require('uuid');
|
|
34
34
|
var alpha = require('@backstage/plugin-scaffolder-node/alpha');
|
|
35
|
+
var os = require('os');
|
|
35
36
|
var ObservableImpl = require('zen-observable');
|
|
36
37
|
var lodash = require('lodash');
|
|
37
38
|
var PQueue = require('p-queue');
|
|
@@ -45,7 +46,6 @@ var pluginPermissionCommon = require('@backstage/plugin-permission-common');
|
|
|
45
46
|
var Transport = require('winston-transport');
|
|
46
47
|
var tripleBeam = require('triple-beam');
|
|
47
48
|
var url = require('url');
|
|
48
|
-
var os = require('os');
|
|
49
49
|
|
|
50
50
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
51
51
|
|
|
@@ -75,14 +75,14 @@ var path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
|
75
75
|
var fs__default$1 = /*#__PURE__*/_interopDefaultCompat(fs$1);
|
|
76
76
|
var globby__default = /*#__PURE__*/_interopDefaultCompat(globby);
|
|
77
77
|
var get__default = /*#__PURE__*/_interopDefaultCompat(get);
|
|
78
|
+
var os__default = /*#__PURE__*/_interopDefaultCompat(os);
|
|
78
79
|
var ObservableImpl__default = /*#__PURE__*/_interopDefaultCompat(ObservableImpl);
|
|
79
80
|
var PQueue__default = /*#__PURE__*/_interopDefaultCompat(PQueue);
|
|
80
81
|
var winston__namespace = /*#__PURE__*/_interopNamespaceCompat(winston);
|
|
81
82
|
var nunjucks__default = /*#__PURE__*/_interopDefaultCompat(nunjucks);
|
|
82
83
|
var Transport__default = /*#__PURE__*/_interopDefaultCompat(Transport);
|
|
83
|
-
var os__default = /*#__PURE__*/_interopDefaultCompat(os);
|
|
84
84
|
|
|
85
|
-
const examples$
|
|
85
|
+
const examples$a = [
|
|
86
86
|
{
|
|
87
87
|
description: "Register with the catalog",
|
|
88
88
|
example: yaml__namespace.default.stringify({
|
|
@@ -106,7 +106,7 @@ function createCatalogRegisterAction(options) {
|
|
|
106
106
|
return pluginScaffolderNode.createTemplateAction({
|
|
107
107
|
id: id$4,
|
|
108
108
|
description: "Registers entities from a catalog descriptor file in the workspace into the software catalog.",
|
|
109
|
-
examples: examples$
|
|
109
|
+
examples: examples$a,
|
|
110
110
|
schema: {
|
|
111
111
|
input: {
|
|
112
112
|
oneOf: [
|
|
@@ -233,7 +233,7 @@ function createCatalogRegisterAction(options) {
|
|
|
233
233
|
});
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
const examples$
|
|
236
|
+
const examples$9 = [
|
|
237
237
|
{
|
|
238
238
|
description: "Write a catalog yaml file",
|
|
239
239
|
example: yaml__namespace.stringify({
|
|
@@ -277,7 +277,7 @@ function createCatalogWriteAction() {
|
|
|
277
277
|
)
|
|
278
278
|
})
|
|
279
279
|
},
|
|
280
|
-
examples: examples$
|
|
280
|
+
examples: examples$9,
|
|
281
281
|
supportsDryRun: true,
|
|
282
282
|
async handler(ctx) {
|
|
283
283
|
const { filePath, entity } = ctx.input;
|
|
@@ -303,7 +303,7 @@ function createCatalogWriteAction() {
|
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
const examples$
|
|
306
|
+
const examples$8 = [
|
|
307
307
|
{
|
|
308
308
|
description: "Fetch entity by reference",
|
|
309
309
|
example: yaml__namespace.default.stringify({
|
|
@@ -342,7 +342,7 @@ function createFetchCatalogEntityAction(options) {
|
|
|
342
342
|
return pluginScaffolderNode.createTemplateAction({
|
|
343
343
|
id: id$2,
|
|
344
344
|
description: "Returns entity or entities from the catalog by entity reference(s)",
|
|
345
|
-
examples: examples$
|
|
345
|
+
examples: examples$8,
|
|
346
346
|
supportsDryRun: true,
|
|
347
347
|
schema: {
|
|
348
348
|
input: zod.z.object({
|
|
@@ -420,7 +420,7 @@ function createFetchCatalogEntityAction(options) {
|
|
|
420
420
|
});
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
-
const examples$
|
|
423
|
+
const examples$7 = [
|
|
424
424
|
{
|
|
425
425
|
description: "Write a debug message",
|
|
426
426
|
example: yaml__namespace.default.stringify({
|
|
@@ -473,7 +473,7 @@ function createDebugLogAction() {
|
|
|
473
473
|
return pluginScaffolderNode.createTemplateAction({
|
|
474
474
|
id: id$1,
|
|
475
475
|
description: "Writes a message into the log and/or lists all files in the workspace.",
|
|
476
|
-
examples: examples$
|
|
476
|
+
examples: examples$7,
|
|
477
477
|
schema: {
|
|
478
478
|
input: zod.z.object({
|
|
479
479
|
message: zod.z.string({ description: "Message to output." }).optional(),
|
|
@@ -518,7 +518,7 @@ async function recursiveReadDir(dir) {
|
|
|
518
518
|
return files.reduce((a, f) => a.concat(f), []);
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
-
const examples$
|
|
521
|
+
const examples$6 = [
|
|
522
522
|
{
|
|
523
523
|
description: "Waiting for 50 milliseconds",
|
|
524
524
|
example: yaml__namespace.default.stringify({
|
|
@@ -581,7 +581,7 @@ function createWaitAction(options) {
|
|
|
581
581
|
return pluginScaffolderNode.createTemplateAction({
|
|
582
582
|
id,
|
|
583
583
|
description: "Waits for a certain period of time.",
|
|
584
|
-
examples: examples$
|
|
584
|
+
examples: examples$6,
|
|
585
585
|
schema: {
|
|
586
586
|
input: {
|
|
587
587
|
type: "object",
|
|
@@ -624,7 +624,7 @@ function createWaitAction(options) {
|
|
|
624
624
|
});
|
|
625
625
|
}
|
|
626
626
|
|
|
627
|
-
const examples$
|
|
627
|
+
const examples$5 = [
|
|
628
628
|
{
|
|
629
629
|
description: "Downloads content and places it in the workspace.",
|
|
630
630
|
example: yaml__namespace.default.stringify({
|
|
@@ -663,7 +663,7 @@ function createFetchPlainAction(options) {
|
|
|
663
663
|
const { reader, integrations } = options;
|
|
664
664
|
return pluginScaffolderNode.createTemplateAction({
|
|
665
665
|
id: ACTION_ID,
|
|
666
|
-
examples: examples$
|
|
666
|
+
examples: examples$5,
|
|
667
667
|
description: "Downloads content and places it in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.",
|
|
668
668
|
schema: {
|
|
669
669
|
input: {
|
|
@@ -705,7 +705,7 @@ function createFetchPlainAction(options) {
|
|
|
705
705
|
});
|
|
706
706
|
}
|
|
707
707
|
|
|
708
|
-
const examples$
|
|
708
|
+
const examples$4 = [
|
|
709
709
|
{
|
|
710
710
|
description: "Downloads a file and places it in the workspace.",
|
|
711
711
|
example: yaml__namespace.default.stringify({
|
|
@@ -729,7 +729,7 @@ function createFetchPlainFileAction(options) {
|
|
|
729
729
|
return pluginScaffolderNode.createTemplateAction({
|
|
730
730
|
id: "fetch:plain:file",
|
|
731
731
|
description: "Downloads single file and places it in the workspace.",
|
|
732
|
-
examples: examples$
|
|
732
|
+
examples: examples$4,
|
|
733
733
|
schema: {
|
|
734
734
|
input: {
|
|
735
735
|
type: "object",
|
|
@@ -950,7 +950,7 @@ const createDefaultFilters = ({
|
|
|
950
950
|
};
|
|
951
951
|
};
|
|
952
952
|
|
|
953
|
-
const examples$
|
|
953
|
+
const examples$3 = [
|
|
954
954
|
{
|
|
955
955
|
description: "Downloads a skeleton directory that lives alongside the template file and fill it out with values.",
|
|
956
956
|
example: yaml__namespace.default.stringify({
|
|
@@ -986,7 +986,7 @@ function createFetchTemplateAction(options) {
|
|
|
986
986
|
return pluginScaffolderNode.createTemplateAction({
|
|
987
987
|
id: "fetch:template",
|
|
988
988
|
description: "Downloads a skeleton, templates variables into file and directory names and content, and places the result in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.",
|
|
989
|
-
examples: examples$
|
|
989
|
+
examples: examples$3,
|
|
990
990
|
schema: {
|
|
991
991
|
input: {
|
|
992
992
|
type: "object",
|
|
@@ -1196,6 +1196,132 @@ function containsSkippedContent(localOutputPath) {
|
|
|
1196
1196
|
return localOutputPath === "" || localOutputPath.startsWith("/") || localOutputPath.includes("//");
|
|
1197
1197
|
}
|
|
1198
1198
|
|
|
1199
|
+
const examples$2 = [
|
|
1200
|
+
{
|
|
1201
|
+
description: "Downloads a template file and fill it out with values.",
|
|
1202
|
+
example: yaml__namespace.default.stringify({
|
|
1203
|
+
steps: [
|
|
1204
|
+
{
|
|
1205
|
+
action: "fetch:template:file",
|
|
1206
|
+
id: "fetch-template-file",
|
|
1207
|
+
name: "Fetch template file",
|
|
1208
|
+
input: {
|
|
1209
|
+
url: "./skeleton.txt",
|
|
1210
|
+
targetPath: "./target/skeleton.txt",
|
|
1211
|
+
values: {
|
|
1212
|
+
name: "test-project",
|
|
1213
|
+
count: 1234,
|
|
1214
|
+
itemList: ["first", "second", "third"]
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
]
|
|
1219
|
+
})
|
|
1220
|
+
}
|
|
1221
|
+
];
|
|
1222
|
+
|
|
1223
|
+
function createFetchTemplateFileAction(options) {
|
|
1224
|
+
const {
|
|
1225
|
+
reader,
|
|
1226
|
+
integrations,
|
|
1227
|
+
additionalTemplateFilters,
|
|
1228
|
+
additionalTemplateGlobals
|
|
1229
|
+
} = options;
|
|
1230
|
+
const defaultTemplateFilters = createDefaultFilters({ integrations });
|
|
1231
|
+
return pluginScaffolderNode.createTemplateAction({
|
|
1232
|
+
id: "fetch:template:file",
|
|
1233
|
+
description: "Downloads single file and places it in the workspace.",
|
|
1234
|
+
examples: examples$2,
|
|
1235
|
+
schema: {
|
|
1236
|
+
input: {
|
|
1237
|
+
type: "object",
|
|
1238
|
+
required: ["url", "targetPath"],
|
|
1239
|
+
properties: {
|
|
1240
|
+
url: {
|
|
1241
|
+
title: "Fetch URL",
|
|
1242
|
+
description: "Relative path or absolute URL pointing to the single file to fetch.",
|
|
1243
|
+
type: "string"
|
|
1244
|
+
},
|
|
1245
|
+
targetPath: {
|
|
1246
|
+
title: "Target Path",
|
|
1247
|
+
description: "Target path within the working directory to download the file as.",
|
|
1248
|
+
type: "string"
|
|
1249
|
+
},
|
|
1250
|
+
values: {
|
|
1251
|
+
title: "Template Values",
|
|
1252
|
+
description: "Values to pass on to the templating engine",
|
|
1253
|
+
type: "object"
|
|
1254
|
+
},
|
|
1255
|
+
cookiecutterCompat: {
|
|
1256
|
+
title: "Cookiecutter compatibility mode",
|
|
1257
|
+
description: "Enable features to maximise compatibility with templates built for fetch:cookiecutter",
|
|
1258
|
+
type: "boolean"
|
|
1259
|
+
},
|
|
1260
|
+
replace: {
|
|
1261
|
+
title: "Replace file",
|
|
1262
|
+
description: "If set, replace file in targetPath instead of overwriting existing one.",
|
|
1263
|
+
type: "boolean"
|
|
1264
|
+
},
|
|
1265
|
+
token: {
|
|
1266
|
+
title: "Token",
|
|
1267
|
+
description: "An optional token to use for authentication when reading the resources.",
|
|
1268
|
+
type: "string"
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
supportsDryRun: true,
|
|
1274
|
+
async handler(ctx) {
|
|
1275
|
+
ctx.logger.info("Fetching template file content from remote URL");
|
|
1276
|
+
const workDir = await ctx.createTemporaryDirectory();
|
|
1277
|
+
const tmpFilePath = path__default.default.join(workDir, "tmp");
|
|
1278
|
+
const outputPath = backendPluginApi.resolveSafeChildPath(
|
|
1279
|
+
ctx.workspacePath,
|
|
1280
|
+
ctx.input.targetPath
|
|
1281
|
+
);
|
|
1282
|
+
if (fs__default.default.existsSync(outputPath) && !ctx.input.replace) {
|
|
1283
|
+
ctx.logger.info(
|
|
1284
|
+
`File ${ctx.input.targetPath} already exists in workspace, not replacing.`
|
|
1285
|
+
);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
await pluginScaffolderNode.fetchFile({
|
|
1289
|
+
reader,
|
|
1290
|
+
integrations,
|
|
1291
|
+
baseUrl: ctx.templateInfo?.baseUrl,
|
|
1292
|
+
fetchUrl: ctx.input.url,
|
|
1293
|
+
outputPath: tmpFilePath,
|
|
1294
|
+
token: ctx.input.token
|
|
1295
|
+
});
|
|
1296
|
+
const { cookiecutterCompat, values } = ctx.input;
|
|
1297
|
+
const context = {
|
|
1298
|
+
[cookiecutterCompat ? "cookiecutter" : "values"]: values
|
|
1299
|
+
};
|
|
1300
|
+
ctx.logger.info(
|
|
1301
|
+
`Processing template file with input values`,
|
|
1302
|
+
ctx.input.values
|
|
1303
|
+
);
|
|
1304
|
+
const renderTemplate = await SecureTemplater.loadRenderer({
|
|
1305
|
+
cookiecutterCompat,
|
|
1306
|
+
templateFilters: {
|
|
1307
|
+
...defaultTemplateFilters,
|
|
1308
|
+
...additionalTemplateFilters
|
|
1309
|
+
},
|
|
1310
|
+
templateGlobals: additionalTemplateGlobals,
|
|
1311
|
+
nunjucksConfigs: {
|
|
1312
|
+
trimBlocks: ctx.input.trimBlocks,
|
|
1313
|
+
lstripBlocks: ctx.input.lstripBlocks
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
const contents = await fs__default.default.readFile(tmpFilePath, "utf-8");
|
|
1317
|
+
const result = renderTemplate(contents, context);
|
|
1318
|
+
await fs__default.default.ensureDir(path__default.default.dirname(outputPath));
|
|
1319
|
+
await fs__default.default.outputFile(outputPath, result);
|
|
1320
|
+
ctx.logger.info(`Template file has been written to ${outputPath}`);
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1199
1325
|
const examples$1 = [
|
|
1200
1326
|
{
|
|
1201
1327
|
description: "Delete specified files",
|
|
@@ -1371,6 +1497,12 @@ const createBuiltinActions = (options) => {
|
|
|
1371
1497
|
additionalTemplateFilters,
|
|
1372
1498
|
additionalTemplateGlobals
|
|
1373
1499
|
}),
|
|
1500
|
+
createFetchTemplateFileAction({
|
|
1501
|
+
integrations,
|
|
1502
|
+
reader,
|
|
1503
|
+
additionalTemplateFilters,
|
|
1504
|
+
additionalTemplateGlobals
|
|
1505
|
+
}),
|
|
1374
1506
|
gerrit.createPublishGerritAction({
|
|
1375
1507
|
integrations,
|
|
1376
1508
|
config
|
|
@@ -1524,6 +1656,79 @@ const intervalFromNowTill = (timeoutS, knex) => {
|
|
|
1524
1656
|
return heartbeatInterval;
|
|
1525
1657
|
};
|
|
1526
1658
|
|
|
1659
|
+
async function getWorkingDirectory(config, logger) {
|
|
1660
|
+
if (!config.has("backend.workingDirectory")) {
|
|
1661
|
+
return os__default.default.tmpdir();
|
|
1662
|
+
}
|
|
1663
|
+
const workingDirectory = config.getString("backend.workingDirectory");
|
|
1664
|
+
try {
|
|
1665
|
+
await fs__default.default.access(workingDirectory, fs__default.default.constants.F_OK | fs__default.default.constants.W_OK);
|
|
1666
|
+
logger.info(`using working directory: ${workingDirectory}`);
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
errors.assertError(err);
|
|
1669
|
+
logger.error(
|
|
1670
|
+
`working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`
|
|
1671
|
+
);
|
|
1672
|
+
throw err;
|
|
1673
|
+
}
|
|
1674
|
+
return workingDirectory;
|
|
1675
|
+
}
|
|
1676
|
+
function getEntityBaseUrl(entity) {
|
|
1677
|
+
let location = entity.metadata.annotations?.[catalogModel.ANNOTATION_SOURCE_LOCATION];
|
|
1678
|
+
if (!location) {
|
|
1679
|
+
location = entity.metadata.annotations?.[catalogModel.ANNOTATION_LOCATION];
|
|
1680
|
+
}
|
|
1681
|
+
if (!location) {
|
|
1682
|
+
return void 0;
|
|
1683
|
+
}
|
|
1684
|
+
const { type, target } = catalogModel.parseLocationRef(location);
|
|
1685
|
+
if (type === "url") {
|
|
1686
|
+
return target;
|
|
1687
|
+
} else if (type === "file") {
|
|
1688
|
+
return `file://${target}`;
|
|
1689
|
+
}
|
|
1690
|
+
return void 0;
|
|
1691
|
+
}
|
|
1692
|
+
async function findTemplate(options) {
|
|
1693
|
+
const { entityRef, token, catalogApi } = options;
|
|
1694
|
+
if (entityRef.kind.toLocaleLowerCase("en-US") !== "template") {
|
|
1695
|
+
throw new errors.InputError(`Invalid kind, only 'Template' kind is supported`);
|
|
1696
|
+
}
|
|
1697
|
+
const template = await catalogApi.getEntityByRef(entityRef, { token });
|
|
1698
|
+
if (!template) {
|
|
1699
|
+
throw new errors.NotFoundError(
|
|
1700
|
+
`Template ${catalogModel.stringifyEntityRef(entityRef)} not found`
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
return template;
|
|
1704
|
+
}
|
|
1705
|
+
function parseStringsParam(param, paramName) {
|
|
1706
|
+
if (param === void 0) {
|
|
1707
|
+
return void 0;
|
|
1708
|
+
}
|
|
1709
|
+
const array = [param].flat();
|
|
1710
|
+
if (array.some((p) => typeof p !== "string")) {
|
|
1711
|
+
throw new errors.InputError(
|
|
1712
|
+
`Invalid ${paramName}, not a string or array of strings`
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
return array;
|
|
1716
|
+
}
|
|
1717
|
+
function parseNumberParam(param, paramName) {
|
|
1718
|
+
return parseStringsParam(param, paramName)?.map((val) => {
|
|
1719
|
+
const ret = Number.parseInt(val, 10);
|
|
1720
|
+
if (isNaN(ret)) {
|
|
1721
|
+
throw new errors.InputError(
|
|
1722
|
+
`Invalid ${paramName} parameter "${val}", expected a number or array of numbers`
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
return ret;
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
function flattenParams(...params) {
|
|
1729
|
+
return [...params].flat().filter(Boolean);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1527
1732
|
const migrationsDir = backendPluginApi.resolvePackagePath(
|
|
1528
1733
|
"@backstage/plugin-scaffolder-backend",
|
|
1529
1734
|
"migrations"
|
|
@@ -1595,16 +1800,41 @@ class DatabaseTaskStore {
|
|
|
1595
1800
|
this.db = client;
|
|
1596
1801
|
}
|
|
1597
1802
|
async list(options) {
|
|
1803
|
+
const { createdBy, status, pagination, order, filters } = options ?? {};
|
|
1598
1804
|
const queryBuilder = this.db("tasks");
|
|
1599
|
-
if (
|
|
1600
|
-
|
|
1601
|
-
|
|
1805
|
+
if (createdBy || filters?.createdBy) {
|
|
1806
|
+
const arr = flattenParams(
|
|
1807
|
+
createdBy,
|
|
1808
|
+
filters?.createdBy
|
|
1809
|
+
);
|
|
1810
|
+
queryBuilder.whereIn("created_by", [...new Set(arr)]);
|
|
1811
|
+
}
|
|
1812
|
+
if (status || filters?.status) {
|
|
1813
|
+
const arr = flattenParams(
|
|
1814
|
+
status,
|
|
1815
|
+
filters?.status
|
|
1816
|
+
);
|
|
1817
|
+
queryBuilder.whereIn("status", [...new Set(arr)]);
|
|
1818
|
+
}
|
|
1819
|
+
if (order) {
|
|
1820
|
+
order.forEach((f) => {
|
|
1821
|
+
queryBuilder.orderBy(f.field, f.order);
|
|
1602
1822
|
});
|
|
1823
|
+
} else {
|
|
1824
|
+
queryBuilder.orderBy("created_at", "desc");
|
|
1825
|
+
}
|
|
1826
|
+
const countQuery = queryBuilder.clone();
|
|
1827
|
+
countQuery.count("tasks.id", { as: "count" });
|
|
1828
|
+
if (pagination?.limit !== void 0) {
|
|
1829
|
+
queryBuilder.limit(pagination.limit);
|
|
1603
1830
|
}
|
|
1604
|
-
if (
|
|
1605
|
-
queryBuilder.
|
|
1831
|
+
if (pagination?.offset !== void 0) {
|
|
1832
|
+
queryBuilder.offset(pagination.offset);
|
|
1606
1833
|
}
|
|
1607
|
-
const results = await
|
|
1834
|
+
const [results, [{ count }]] = await Promise.all([
|
|
1835
|
+
queryBuilder.select(),
|
|
1836
|
+
countQuery
|
|
1837
|
+
]);
|
|
1608
1838
|
const tasks = results.map((result) => ({
|
|
1609
1839
|
id: result.id,
|
|
1610
1840
|
spec: JSON.parse(result.spec),
|
|
@@ -1613,7 +1843,7 @@ class DatabaseTaskStore {
|
|
|
1613
1843
|
lastHeartbeatAt: parseSqlDateToIsoString(result.last_heartbeat_at),
|
|
1614
1844
|
createdAt: parseSqlDateToIsoString(result.created_at)
|
|
1615
1845
|
}));
|
|
1616
|
-
return { tasks };
|
|
1846
|
+
return { tasks, totalTasks: count };
|
|
1617
1847
|
}
|
|
1618
1848
|
async getTask(taskId) {
|
|
1619
1849
|
const [result] = await this.db("tasks").where({ id: taskId }).select();
|
|
@@ -1782,7 +2012,7 @@ class DatabaseTaskStore {
|
|
|
1782
2012
|
}
|
|
1783
2013
|
}
|
|
1784
2014
|
async listEvents(options) {
|
|
1785
|
-
const { taskId, after } = options;
|
|
2015
|
+
const { isTaskRecoverable, taskId, after } = options;
|
|
1786
2016
|
const rawEvents = await this.db("task_events").where({
|
|
1787
2017
|
task_id: taskId
|
|
1788
2018
|
}).andWhere((builder) => {
|
|
@@ -1795,6 +2025,7 @@ class DatabaseTaskStore {
|
|
|
1795
2025
|
const body = JSON.parse(event.body);
|
|
1796
2026
|
return {
|
|
1797
2027
|
id: Number(event.id),
|
|
2028
|
+
isTaskRecoverable,
|
|
1798
2029
|
taskId,
|
|
1799
2030
|
body,
|
|
1800
2031
|
type: event.event_type,
|
|
@@ -1865,6 +2096,28 @@ class DatabaseTaskStore {
|
|
|
1865
2096
|
body: serializedBody
|
|
1866
2097
|
});
|
|
1867
2098
|
}
|
|
2099
|
+
async retryTask(options) {
|
|
2100
|
+
await this.db.transaction(async (tx) => {
|
|
2101
|
+
const result = await tx("tasks").where("id", options.taskId).update(
|
|
2102
|
+
{
|
|
2103
|
+
status: "open",
|
|
2104
|
+
last_heartbeat_at: this.db.fn.now()
|
|
2105
|
+
},
|
|
2106
|
+
["id", "spec"]
|
|
2107
|
+
);
|
|
2108
|
+
for (const { id, spec } of result) {
|
|
2109
|
+
const taskSpec = JSON.parse(spec);
|
|
2110
|
+
await tx("task_events").where("task_id", id).andWhere((q) => q.whereIn("event_type", ["cancelled", "completion"])).del();
|
|
2111
|
+
await tx("task_events").insert({
|
|
2112
|
+
task_id: id,
|
|
2113
|
+
event_type: "recovered",
|
|
2114
|
+
body: JSON.stringify({
|
|
2115
|
+
recoverStrategy: taskSpec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy ?? "none"
|
|
2116
|
+
})
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
1868
2121
|
async recoverTasks(options) {
|
|
1869
2122
|
const taskIdsToRecover = [];
|
|
1870
2123
|
const timeoutS = luxon.Duration.fromObject(options.timeout).as("seconds");
|
|
@@ -1976,6 +2229,7 @@ class DefaultWorkspaceService {
|
|
|
1976
2229
|
}
|
|
1977
2230
|
async rehydrateWorkspace(options) {
|
|
1978
2231
|
if (this.isWorkspaceSerializationEnabled()) {
|
|
2232
|
+
await fs__default.default.mkdirp(options.targetPath);
|
|
1979
2233
|
await this.workspaceProvider.rehydrateWorkspace(options);
|
|
1980
2234
|
}
|
|
1981
2235
|
}
|
|
@@ -2127,10 +2381,7 @@ class StorageTaskBroker {
|
|
|
2127
2381
|
"TaskStore does not implement the list method. Please implement the list method to be able to list tasks"
|
|
2128
2382
|
);
|
|
2129
2383
|
}
|
|
2130
|
-
return await this.storage.list({
|
|
2131
|
-
createdBy: options?.createdBy,
|
|
2132
|
-
status: options?.status
|
|
2133
|
-
});
|
|
2384
|
+
return await this.storage.list(options ?? {});
|
|
2134
2385
|
}
|
|
2135
2386
|
deferredDispatch = defer();
|
|
2136
2387
|
async registerCancellable(taskId, abortController) {
|
|
@@ -2145,7 +2396,7 @@ class StorageTaskBroker {
|
|
|
2145
2396
|
abortController.abort();
|
|
2146
2397
|
shouldUnsubscribe = true;
|
|
2147
2398
|
}
|
|
2148
|
-
if (event.type === "completion") {
|
|
2399
|
+
if (event.type === "completion" && !event.isTaskRecoverable) {
|
|
2149
2400
|
shouldUnsubscribe = true;
|
|
2150
2401
|
}
|
|
2151
2402
|
}
|
|
@@ -2227,8 +2478,14 @@ class StorageTaskBroker {
|
|
|
2227
2478
|
let after = options.after;
|
|
2228
2479
|
let cancelled = false;
|
|
2229
2480
|
(async () => {
|
|
2481
|
+
const task = await this.storage.getTask(taskId);
|
|
2482
|
+
const isTaskRecoverable = task.spec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy === "startOver";
|
|
2230
2483
|
while (!cancelled) {
|
|
2231
|
-
const result = await this.storage.listEvents({
|
|
2484
|
+
const result = await this.storage.listEvents({
|
|
2485
|
+
isTaskRecoverable,
|
|
2486
|
+
taskId,
|
|
2487
|
+
after
|
|
2488
|
+
});
|
|
2232
2489
|
const { events } = result;
|
|
2233
2490
|
if (events.length) {
|
|
2234
2491
|
after = events[events.length - 1].id;
|
|
@@ -2282,6 +2539,10 @@ class StorageTaskBroker {
|
|
|
2282
2539
|
}
|
|
2283
2540
|
});
|
|
2284
2541
|
}
|
|
2542
|
+
async retry(taskId) {
|
|
2543
|
+
await this.storage.retryTask?.({ taskId });
|
|
2544
|
+
this.signalDispatch();
|
|
2545
|
+
}
|
|
2285
2546
|
}
|
|
2286
2547
|
|
|
2287
2548
|
function createCounterMetric(config) {
|
|
@@ -3220,53 +3481,6 @@ function createDryRunner(options) {
|
|
|
3220
3481
|
};
|
|
3221
3482
|
}
|
|
3222
3483
|
|
|
3223
|
-
async function getWorkingDirectory(config, logger) {
|
|
3224
|
-
if (!config.has("backend.workingDirectory")) {
|
|
3225
|
-
return os__default.default.tmpdir();
|
|
3226
|
-
}
|
|
3227
|
-
const workingDirectory = config.getString("backend.workingDirectory");
|
|
3228
|
-
try {
|
|
3229
|
-
await fs__default.default.access(workingDirectory, fs__default.default.constants.F_OK | fs__default.default.constants.W_OK);
|
|
3230
|
-
logger.info(`using working directory: ${workingDirectory}`);
|
|
3231
|
-
} catch (err) {
|
|
3232
|
-
errors.assertError(err);
|
|
3233
|
-
logger.error(
|
|
3234
|
-
`working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`
|
|
3235
|
-
);
|
|
3236
|
-
throw err;
|
|
3237
|
-
}
|
|
3238
|
-
return workingDirectory;
|
|
3239
|
-
}
|
|
3240
|
-
function getEntityBaseUrl(entity) {
|
|
3241
|
-
let location = entity.metadata.annotations?.[catalogModel.ANNOTATION_SOURCE_LOCATION];
|
|
3242
|
-
if (!location) {
|
|
3243
|
-
location = entity.metadata.annotations?.[catalogModel.ANNOTATION_LOCATION];
|
|
3244
|
-
}
|
|
3245
|
-
if (!location) {
|
|
3246
|
-
return void 0;
|
|
3247
|
-
}
|
|
3248
|
-
const { type, target } = catalogModel.parseLocationRef(location);
|
|
3249
|
-
if (type === "url") {
|
|
3250
|
-
return target;
|
|
3251
|
-
} else if (type === "file") {
|
|
3252
|
-
return `file://${target}`;
|
|
3253
|
-
}
|
|
3254
|
-
return void 0;
|
|
3255
|
-
}
|
|
3256
|
-
async function findTemplate(options) {
|
|
3257
|
-
const { entityRef, token, catalogApi } = options;
|
|
3258
|
-
if (entityRef.kind.toLocaleLowerCase("en-US") !== "template") {
|
|
3259
|
-
throw new errors.InputError(`Invalid kind, only 'Template' kind is supported`);
|
|
3260
|
-
}
|
|
3261
|
-
const template = await catalogApi.getEntityByRef(entityRef, { token });
|
|
3262
|
-
if (!template) {
|
|
3263
|
-
throw new errors.NotFoundError(
|
|
3264
|
-
`Template ${catalogModel.stringifyEntityRef(entityRef)} not found`
|
|
3265
|
-
);
|
|
3266
|
-
}
|
|
3267
|
-
return template;
|
|
3268
|
-
}
|
|
3269
|
-
|
|
3270
3484
|
async function checkPermission(options) {
|
|
3271
3485
|
const { permissions, permissionService, credentials } = options;
|
|
3272
3486
|
if (permissionService) {
|
|
@@ -3601,22 +3815,37 @@ async function createRouter(options) {
|
|
|
3601
3815
|
permissions: [alpha$1.taskReadPermission],
|
|
3602
3816
|
permissionService: permissions
|
|
3603
3817
|
});
|
|
3604
|
-
const [userEntityRef] = [req.query.createdBy].flat();
|
|
3605
|
-
if (typeof userEntityRef !== "string" && typeof userEntityRef !== "undefined") {
|
|
3606
|
-
throw new errors.InputError("createdBy query parameter must be a string");
|
|
3607
|
-
}
|
|
3608
3818
|
if (!taskBroker.list) {
|
|
3609
3819
|
throw new Error(
|
|
3610
3820
|
"TaskBroker does not support listing tasks, please implement the list method on the TaskBroker."
|
|
3611
3821
|
);
|
|
3612
3822
|
}
|
|
3613
|
-
const
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3823
|
+
const createdBy = parseStringsParam(req.query.createdBy, "createdBy");
|
|
3824
|
+
const status = parseStringsParam(req.query.status, "status");
|
|
3825
|
+
const order = parseStringsParam(req.query.order, "order")?.map((item) => {
|
|
3826
|
+
const match = item.match(/^(asc|desc):(.+)$/);
|
|
3827
|
+
if (!match) {
|
|
3828
|
+
throw new errors.InputError(
|
|
3829
|
+
`Invalid order parameter "${item}", expected "<asc or desc>:<field name>"`
|
|
3830
|
+
);
|
|
3831
|
+
}
|
|
3832
|
+
return {
|
|
3833
|
+
order: match[1],
|
|
3834
|
+
field: match[2]
|
|
3835
|
+
};
|
|
3836
|
+
});
|
|
3837
|
+
const limit = parseNumberParam(req.query.limit, "limit");
|
|
3838
|
+
const offset = parseNumberParam(req.query.offset, "offset");
|
|
3617
3839
|
const tasks = await taskBroker.list({
|
|
3618
|
-
|
|
3619
|
-
|
|
3840
|
+
filters: {
|
|
3841
|
+
createdBy,
|
|
3842
|
+
status: status ? status : void 0
|
|
3843
|
+
},
|
|
3844
|
+
order,
|
|
3845
|
+
pagination: {
|
|
3846
|
+
limit: limit ? limit[0] : void 0,
|
|
3847
|
+
offset: offset ? offset[0] : void 0
|
|
3848
|
+
}
|
|
3620
3849
|
});
|
|
3621
3850
|
res.status(200).json(tasks);
|
|
3622
3851
|
}).get("/v2/tasks/:taskId", async (req, res) => {
|
|
@@ -3643,6 +3872,16 @@ async function createRouter(options) {
|
|
|
3643
3872
|
const { taskId } = req.params;
|
|
3644
3873
|
await taskBroker.cancel?.(taskId);
|
|
3645
3874
|
res.status(200).json({ status: "cancelled" });
|
|
3875
|
+
}).post("/v2/tasks/:taskId/retry", async (req, res) => {
|
|
3876
|
+
const credentials = await httpAuth.credentials(req);
|
|
3877
|
+
await checkPermission({
|
|
3878
|
+
credentials,
|
|
3879
|
+
permissions: [alpha$1.taskCreatePermission, alpha$1.taskReadPermission],
|
|
3880
|
+
permissionService: permissions
|
|
3881
|
+
});
|
|
3882
|
+
const { taskId } = req.params;
|
|
3883
|
+
await taskBroker.retry?.(taskId);
|
|
3884
|
+
res.status(201).json({ id: taskId });
|
|
3646
3885
|
}).get("/v2/tasks/:taskId/eventstream", async (req, res) => {
|
|
3647
3886
|
const credentials = await httpAuth.credentials(req);
|
|
3648
3887
|
await checkPermission({
|
|
@@ -3674,7 +3913,7 @@ data: ${JSON.stringify(event)}
|
|
|
3674
3913
|
|
|
3675
3914
|
`
|
|
3676
3915
|
);
|
|
3677
|
-
if (event.type === "completion") {
|
|
3916
|
+
if (event.type === "completion" && !event.isTaskRecoverable) {
|
|
3678
3917
|
shouldUnsubscribe = true;
|
|
3679
3918
|
}
|
|
3680
3919
|
}
|
|
@@ -3852,10 +4091,11 @@ exports.createFetchCatalogEntityAction = createFetchCatalogEntityAction;
|
|
|
3852
4091
|
exports.createFetchPlainAction = createFetchPlainAction;
|
|
3853
4092
|
exports.createFetchPlainFileAction = createFetchPlainFileAction;
|
|
3854
4093
|
exports.createFetchTemplateAction = createFetchTemplateAction;
|
|
4094
|
+
exports.createFetchTemplateFileAction = createFetchTemplateFileAction;
|
|
3855
4095
|
exports.createFilesystemDeleteAction = createFilesystemDeleteAction;
|
|
3856
4096
|
exports.createFilesystemRenameAction = createFilesystemRenameAction;
|
|
3857
4097
|
exports.createRouter = createRouter;
|
|
3858
4098
|
exports.createWaitAction = createWaitAction;
|
|
3859
4099
|
exports.scaffolderActionRules = scaffolderActionRules;
|
|
3860
4100
|
exports.scaffolderTemplateRules = scaffolderTemplateRules;
|
|
3861
|
-
//# sourceMappingURL=router-
|
|
4101
|
+
//# sourceMappingURL=router-CC-UhVkG.cjs.js.map
|