@backstage/plugin-scaffolder-backend 0.15.7 → 0.15.11
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 +46 -0
- package/dist/index.cjs.js +360 -75
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +14 -5
- package/package.json +15 -12
package/dist/index.cjs.js
CHANGED
|
@@ -27,9 +27,11 @@ var Router = require('express-promise-router');
|
|
|
27
27
|
var jsonschema = require('jsonschema');
|
|
28
28
|
var uuid = require('uuid');
|
|
29
29
|
var luxon = require('luxon');
|
|
30
|
+
var os = require('os');
|
|
30
31
|
var Handlebars = require('handlebars');
|
|
31
32
|
var winston = require('winston');
|
|
32
|
-
var
|
|
33
|
+
var pluginCatalogBackend = require('@backstage/plugin-catalog-backend');
|
|
34
|
+
var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
|
|
33
35
|
|
|
34
36
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
35
37
|
|
|
@@ -54,6 +56,7 @@ function _interopNamespace(e) {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
59
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
57
60
|
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
58
61
|
var yaml__namespace = /*#__PURE__*/_interopNamespace(yaml);
|
|
59
62
|
var globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
|
|
@@ -61,9 +64,9 @@ var nunjucks__default = /*#__PURE__*/_interopDefaultLegacy(nunjucks);
|
|
|
61
64
|
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
62
65
|
var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
|
|
63
66
|
var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
|
|
67
|
+
var os__default = /*#__PURE__*/_interopDefaultLegacy(os);
|
|
64
68
|
var Handlebars__namespace = /*#__PURE__*/_interopNamespace(Handlebars);
|
|
65
69
|
var winston__namespace = /*#__PURE__*/_interopNamespace(winston);
|
|
66
|
-
var os__default = /*#__PURE__*/_interopDefaultLegacy(os);
|
|
67
70
|
|
|
68
71
|
const createTemplateAction = (templateAction) => {
|
|
69
72
|
return templateAction;
|
|
@@ -85,6 +88,11 @@ function createCatalogRegisterAction(options) {
|
|
|
85
88
|
title: "Catalog Info URL",
|
|
86
89
|
description: "An absolute URL pointing to the catalog info file location",
|
|
87
90
|
type: "string"
|
|
91
|
+
},
|
|
92
|
+
optional: {
|
|
93
|
+
title: "Optional",
|
|
94
|
+
description: "Permit the registered location to optionally exist. Default: false",
|
|
95
|
+
type: "boolean"
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
},
|
|
@@ -101,6 +109,11 @@ function createCatalogRegisterAction(options) {
|
|
|
101
109
|
title: "Fetch URL",
|
|
102
110
|
description: "A relative path from the repo root pointing to the catalog info file, defaults to /catalog-info.yaml",
|
|
103
111
|
type: "string"
|
|
112
|
+
},
|
|
113
|
+
optional: {
|
|
114
|
+
title: "Optional",
|
|
115
|
+
description: "Permit the registered location to optionally exist. Default: false",
|
|
116
|
+
type: "boolean"
|
|
104
117
|
}
|
|
105
118
|
}
|
|
106
119
|
}
|
|
@@ -129,15 +142,21 @@ function createCatalogRegisterAction(options) {
|
|
|
129
142
|
type: "url",
|
|
130
143
|
target: catalogInfoUrl
|
|
131
144
|
}, ctx.token ? {token: ctx.token} : {});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
145
|
+
try {
|
|
146
|
+
const result = await catalogClient.addLocation({
|
|
147
|
+
dryRun: true,
|
|
148
|
+
type: "url",
|
|
149
|
+
target: catalogInfoUrl
|
|
150
|
+
}, ctx.token ? {token: ctx.token} : {});
|
|
151
|
+
if (result.entities.length > 0) {
|
|
152
|
+
const {entities} = result;
|
|
153
|
+
const entity = (_a = entities.find((e) => !e.metadata.name.startsWith("generated-"))) != null ? _a : entities[0];
|
|
154
|
+
ctx.output("entityRef", catalogModel.stringifyEntityRef(entity));
|
|
155
|
+
}
|
|
156
|
+
} catch (e) {
|
|
157
|
+
if (!input.optional) {
|
|
158
|
+
throw e;
|
|
159
|
+
}
|
|
141
160
|
}
|
|
142
161
|
ctx.output("catalogInfoUrl", catalogInfoUrl);
|
|
143
162
|
}
|
|
@@ -183,12 +202,16 @@ function createDebugLogAction() {
|
|
|
183
202
|
listWorkspace: {
|
|
184
203
|
title: "List all files in the workspace, if true.",
|
|
185
204
|
type: "boolean"
|
|
205
|
+
},
|
|
206
|
+
extra: {
|
|
207
|
+
title: "Extra info"
|
|
186
208
|
}
|
|
187
209
|
}
|
|
188
210
|
}
|
|
189
211
|
},
|
|
190
212
|
async handler(ctx) {
|
|
191
213
|
var _a, _b;
|
|
214
|
+
ctx.logger.info(JSON.stringify(ctx.input, null, 2));
|
|
192
215
|
if ((_a = ctx.input) == null ? void 0 : _a.message) {
|
|
193
216
|
ctx.logStream.write(ctx.input.message);
|
|
194
217
|
}
|
|
@@ -227,10 +250,7 @@ async function fetchContents({
|
|
|
227
250
|
}
|
|
228
251
|
if (!fetchUrlIsAbsolute && (baseUrl == null ? void 0 : baseUrl.startsWith("file://"))) {
|
|
229
252
|
const basePath = baseUrl.slice("file://".length);
|
|
230
|
-
|
|
231
|
-
throw new errors.InputError(`Fetch URL may not be absolute for file locations, ${fetchUrl}`);
|
|
232
|
-
}
|
|
233
|
-
const srcDir = path.resolve(basePath, "..", fetchUrl);
|
|
253
|
+
const srcDir = backendCommon.resolveSafeChildPath(path__namespace.dirname(basePath), fetchUrl);
|
|
234
254
|
await fs__default['default'].copy(srcDir, outputPath);
|
|
235
255
|
} else {
|
|
236
256
|
let readUrl;
|
|
@@ -627,6 +647,7 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
|
|
|
627
647
|
}
|
|
628
648
|
});
|
|
629
649
|
} catch (e) {
|
|
650
|
+
errors.assertError(e);
|
|
630
651
|
if (e.message.includes("Upgrade to GitHub Pro or make this repository public to enable this feature")) {
|
|
631
652
|
logger.warn("Branch protection was not enabled as it requires GitHub Pro for private repositories");
|
|
632
653
|
} else {
|
|
@@ -648,7 +669,11 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
|
|
|
648
669
|
const getRepoSourceDirectory = (workspacePath, sourcePath) => {
|
|
649
670
|
if (sourcePath) {
|
|
650
671
|
const safeSuffix = path.normalize(sourcePath).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
651
|
-
|
|
672
|
+
const path$1 = path.join(workspacePath, safeSuffix);
|
|
673
|
+
if (!backendCommon.isChildPath(workspacePath, path$1)) {
|
|
674
|
+
throw new Error("Invalid source path");
|
|
675
|
+
}
|
|
676
|
+
return path$1;
|
|
652
677
|
}
|
|
653
678
|
return workspacePath;
|
|
654
679
|
};
|
|
@@ -1208,6 +1233,7 @@ function createPublishGithubAction(options) {
|
|
|
1208
1233
|
permission
|
|
1209
1234
|
});
|
|
1210
1235
|
} catch (e) {
|
|
1236
|
+
errors.assertError(e);
|
|
1211
1237
|
ctx.logger.warn(`Skipping ${permission} access for ${team_slug}, ${e.message}`);
|
|
1212
1238
|
}
|
|
1213
1239
|
}
|
|
@@ -1220,6 +1246,7 @@ function createPublishGithubAction(options) {
|
|
|
1220
1246
|
names: topics.map((t) => t.toLowerCase())
|
|
1221
1247
|
});
|
|
1222
1248
|
} catch (e) {
|
|
1249
|
+
errors.assertError(e);
|
|
1223
1250
|
ctx.logger.warn(`Skipping topics ${topics.join(" ")}, ${e.message}`);
|
|
1224
1251
|
}
|
|
1225
1252
|
}
|
|
@@ -1251,6 +1278,7 @@ function createPublishGithubAction(options) {
|
|
|
1251
1278
|
requireCodeOwnerReviews
|
|
1252
1279
|
});
|
|
1253
1280
|
} catch (e) {
|
|
1281
|
+
errors.assertError(e);
|
|
1254
1282
|
ctx.logger.warn(`Skipping: default branch protection on '${newRepo.name}', ${e.message}`);
|
|
1255
1283
|
}
|
|
1256
1284
|
ctx.output("remoteUrl", remoteUrl);
|
|
@@ -1357,19 +1385,31 @@ const createPublishGithubPullRequestAction = ({
|
|
|
1357
1385
|
throw new errors.InputError(`No owner provided for host: ${host}, and repo ${repo}`);
|
|
1358
1386
|
}
|
|
1359
1387
|
const client = await clientFactory({integrations, host, owner, repo});
|
|
1360
|
-
const fileRoot = sourcePath ?
|
|
1388
|
+
const fileRoot = sourcePath ? backendCommon.resolveSafeChildPath(ctx.workspacePath, sourcePath) : ctx.workspacePath;
|
|
1361
1389
|
const localFilePaths = await globby__default['default'](["./**", "./**/.*", "!.git"], {
|
|
1362
1390
|
cwd: fileRoot,
|
|
1363
1391
|
gitignore: true,
|
|
1364
1392
|
dot: true
|
|
1365
1393
|
});
|
|
1366
|
-
const fileContents = await Promise.all(localFilePaths.map((
|
|
1394
|
+
const fileContents = await Promise.all(localFilePaths.map((filePath) => {
|
|
1395
|
+
const absPath = path__default['default'].resolve(fileRoot, filePath);
|
|
1396
|
+
const base64EncodedContent = fs__default['default'].readFileSync(absPath).toString("base64");
|
|
1397
|
+
const fileStat = fs__default['default'].statSync(absPath);
|
|
1398
|
+
const isExecutable = fileStat.mode === 33277;
|
|
1399
|
+
const githubTreeItemMode = isExecutable ? "100755" : "100644";
|
|
1400
|
+
const encoding = "base64";
|
|
1401
|
+
return {
|
|
1402
|
+
encoding,
|
|
1403
|
+
content: base64EncodedContent,
|
|
1404
|
+
mode: githubTreeItemMode
|
|
1405
|
+
};
|
|
1406
|
+
}));
|
|
1367
1407
|
const repoFilePaths = localFilePaths.map((repoFilePath) => {
|
|
1368
1408
|
return targetPath ? `${targetPath}/${repoFilePath}` : repoFilePath;
|
|
1369
1409
|
});
|
|
1370
1410
|
const changes = [
|
|
1371
1411
|
{
|
|
1372
|
-
files: lodash.zipObject(repoFilePaths, fileContents
|
|
1412
|
+
files: lodash.zipObject(repoFilePaths, fileContents),
|
|
1373
1413
|
commit: title
|
|
1374
1414
|
}
|
|
1375
1415
|
];
|
|
@@ -1629,6 +1669,7 @@ function createGithubWebhookAction(options) {
|
|
|
1629
1669
|
});
|
|
1630
1670
|
ctx.logger.info(`Webhook '${webhookUrl}' created successfully`);
|
|
1631
1671
|
} catch (e) {
|
|
1672
|
+
errors.assertError(e);
|
|
1632
1673
|
ctx.logger.warn(`Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`);
|
|
1633
1674
|
}
|
|
1634
1675
|
}
|
|
@@ -2002,6 +2043,7 @@ class StorageTaskBroker {
|
|
|
2002
2043
|
try {
|
|
2003
2044
|
callback(void 0, result);
|
|
2004
2045
|
} catch (error) {
|
|
2046
|
+
errors.assertError(error);
|
|
2005
2047
|
callback(error, {events: []});
|
|
2006
2048
|
}
|
|
2007
2049
|
}
|
|
@@ -2035,39 +2077,92 @@ class StorageTaskBroker {
|
|
|
2035
2077
|
}
|
|
2036
2078
|
}
|
|
2037
2079
|
|
|
2080
|
+
class TaskWorker {
|
|
2081
|
+
constructor(options) {
|
|
2082
|
+
this.options = options;
|
|
2083
|
+
}
|
|
2084
|
+
start() {
|
|
2085
|
+
(async () => {
|
|
2086
|
+
for (; ; ) {
|
|
2087
|
+
const task = await this.options.taskBroker.claim();
|
|
2088
|
+
await this.runOneTask(task);
|
|
2089
|
+
}
|
|
2090
|
+
})();
|
|
2091
|
+
}
|
|
2092
|
+
async runOneTask(task) {
|
|
2093
|
+
try {
|
|
2094
|
+
const {output} = task.spec.apiVersion === "scaffolder.backstage.io/v1beta3" ? await this.options.runners.workflowRunner.execute(task) : await this.options.runners.legacyWorkflowRunner.execute(task);
|
|
2095
|
+
await task.complete("completed", {output});
|
|
2096
|
+
} catch (error) {
|
|
2097
|
+
errors.assertError(error);
|
|
2098
|
+
await task.complete("failed", {
|
|
2099
|
+
error: {name: error.name, message: error.message}
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
async function getWorkingDirectory(config, logger) {
|
|
2106
|
+
if (!config.has("backend.workingDirectory")) {
|
|
2107
|
+
return os__default['default'].tmpdir();
|
|
2108
|
+
}
|
|
2109
|
+
const workingDirectory = config.getString("backend.workingDirectory");
|
|
2110
|
+
try {
|
|
2111
|
+
await fs__default['default'].access(workingDirectory, fs__default['default'].constants.F_OK | fs__default['default'].constants.W_OK);
|
|
2112
|
+
logger.info(`using working directory: ${workingDirectory}`);
|
|
2113
|
+
} catch (err) {
|
|
2114
|
+
errors.assertError(err);
|
|
2115
|
+
logger.error(`working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`);
|
|
2116
|
+
throw err;
|
|
2117
|
+
}
|
|
2118
|
+
return workingDirectory;
|
|
2119
|
+
}
|
|
2120
|
+
function getEntityBaseUrl(entity) {
|
|
2121
|
+
var _a, _b;
|
|
2122
|
+
let location = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.SOURCE_LOCATION_ANNOTATION];
|
|
2123
|
+
if (!location) {
|
|
2124
|
+
location = (_b = entity.metadata.annotations) == null ? void 0 : _b[catalogModel.LOCATION_ANNOTATION];
|
|
2125
|
+
}
|
|
2126
|
+
if (!location) {
|
|
2127
|
+
return void 0;
|
|
2128
|
+
}
|
|
2129
|
+
const {type, target} = catalogModel.parseLocationReference(location);
|
|
2130
|
+
if (type === "url") {
|
|
2131
|
+
return target;
|
|
2132
|
+
} else if (type === "file") {
|
|
2133
|
+
return `file://${target}`;
|
|
2134
|
+
}
|
|
2135
|
+
return void 0;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2038
2138
|
function isTruthy(value) {
|
|
2039
2139
|
return lodash.isArray(value) ? value.length > 0 : !!value;
|
|
2040
2140
|
}
|
|
2041
2141
|
|
|
2042
|
-
|
|
2142
|
+
const isValidTaskSpec$1 = (taskSpec) => taskSpec.apiVersion === "backstage.io/v1beta2";
|
|
2143
|
+
class LegacyWorkflowRunner {
|
|
2043
2144
|
constructor(options) {
|
|
2044
2145
|
this.options = options;
|
|
2045
2146
|
this.handlebars = Handlebars__namespace.create();
|
|
2046
2147
|
this.handlebars.registerHelper("parseRepoUrl", (repoUrl) => {
|
|
2047
|
-
return JSON.stringify(parseRepoUrl(repoUrl, options.integrations));
|
|
2148
|
+
return JSON.stringify(parseRepoUrl(repoUrl, this.options.integrations));
|
|
2048
2149
|
});
|
|
2049
2150
|
this.handlebars.registerHelper("projectSlug", (repoUrl) => {
|
|
2050
|
-
const {owner, repo} = parseRepoUrl(repoUrl, options.integrations);
|
|
2151
|
+
const {owner, repo} = parseRepoUrl(repoUrl, this.options.integrations);
|
|
2051
2152
|
return `${owner}/${repo}`;
|
|
2052
2153
|
});
|
|
2053
2154
|
this.handlebars.registerHelper("json", (obj) => JSON.stringify(obj));
|
|
2054
2155
|
this.handlebars.registerHelper("not", (value) => !isTruthy(value));
|
|
2055
2156
|
this.handlebars.registerHelper("eq", (a, b) => a === b);
|
|
2056
2157
|
}
|
|
2057
|
-
|
|
2058
|
-
(async () => {
|
|
2059
|
-
for (; ; ) {
|
|
2060
|
-
const task = await this.options.taskBroker.claim();
|
|
2061
|
-
await this.runOneTask(task);
|
|
2062
|
-
}
|
|
2063
|
-
})();
|
|
2064
|
-
}
|
|
2065
|
-
async runOneTask(task) {
|
|
2158
|
+
async execute(task) {
|
|
2066
2159
|
var _a, _b;
|
|
2067
|
-
|
|
2160
|
+
if (!isValidTaskSpec$1(task.spec)) {
|
|
2161
|
+
throw new errors.InputError(`Task spec is not a valid v1beta2 task spec`);
|
|
2162
|
+
}
|
|
2163
|
+
const {actionRegistry} = this.options;
|
|
2164
|
+
const workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
|
|
2068
2165
|
try {
|
|
2069
|
-
const {actionRegistry} = this.options;
|
|
2070
|
-
workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
|
|
2071
2166
|
await fs__default['default'].ensureDir(workspacePath);
|
|
2072
2167
|
await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
|
|
2073
2168
|
const templateCtx = {parameters: task.spec.values, steps: {}};
|
|
@@ -2209,11 +2304,7 @@ class TaskWorker {
|
|
|
2209
2304
|
}
|
|
2210
2305
|
return value;
|
|
2211
2306
|
});
|
|
2212
|
-
|
|
2213
|
-
} catch (error) {
|
|
2214
|
-
await task.complete("failed", {
|
|
2215
|
-
error: {name: error.name, message: error.message}
|
|
2216
|
-
});
|
|
2307
|
+
return {output};
|
|
2217
2308
|
} finally {
|
|
2218
2309
|
if (workspacePath) {
|
|
2219
2310
|
await fs__default['default'].remove(workspacePath);
|
|
@@ -2222,40 +2313,159 @@ class TaskWorker {
|
|
|
2222
2313
|
}
|
|
2223
2314
|
}
|
|
2224
2315
|
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
const
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2316
|
+
const isValidTaskSpec = (taskSpec) => {
|
|
2317
|
+
return taskSpec.apiVersion === "scaffolder.backstage.io/v1beta3";
|
|
2318
|
+
};
|
|
2319
|
+
const createStepLogger = ({task, step}) => {
|
|
2320
|
+
const metadata = {stepId: step.id};
|
|
2321
|
+
const taskLogger = winston__namespace.createLogger({
|
|
2322
|
+
level: process.env.LOG_LEVEL || "info",
|
|
2323
|
+
format: winston__namespace.format.combine(winston__namespace.format.colorize(), winston__namespace.format.timestamp(), winston__namespace.format.simple()),
|
|
2324
|
+
defaultMeta: {}
|
|
2325
|
+
});
|
|
2326
|
+
const streamLogger = new stream.PassThrough();
|
|
2327
|
+
streamLogger.on("data", async (data) => {
|
|
2328
|
+
const message = data.toString().trim();
|
|
2329
|
+
if ((message == null ? void 0 : message.length) > 1) {
|
|
2330
|
+
await task.emitLog(message, metadata);
|
|
2331
|
+
}
|
|
2332
|
+
});
|
|
2333
|
+
taskLogger.add(new winston__namespace.transports.Stream({stream: streamLogger}));
|
|
2334
|
+
return {taskLogger, streamLogger};
|
|
2335
|
+
};
|
|
2336
|
+
class DefaultWorkflowRunner {
|
|
2337
|
+
constructor(options) {
|
|
2338
|
+
this.options = options;
|
|
2339
|
+
this.nunjucksOptions = {
|
|
2340
|
+
autoescape: false,
|
|
2341
|
+
tags: {
|
|
2342
|
+
variableStart: "${{",
|
|
2343
|
+
variableEnd: "}}"
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
this.nunjucks = nunjucks__default['default'].configure(this.nunjucksOptions);
|
|
2347
|
+
this.nunjucks.addFilter("parseRepoUrl", (repoUrl) => {
|
|
2348
|
+
return parseRepoUrl(repoUrl, this.options.integrations);
|
|
2349
|
+
});
|
|
2350
|
+
this.nunjucks.addFilter("projectSlug", (repoUrl) => {
|
|
2351
|
+
const {owner, repo} = parseRepoUrl(repoUrl, this.options.integrations);
|
|
2352
|
+
return `${owner}/${repo}`;
|
|
2353
|
+
});
|
|
2236
2354
|
}
|
|
2237
|
-
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
let location = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.SOURCE_LOCATION_ANNOTATION];
|
|
2242
|
-
if (!location) {
|
|
2243
|
-
location = (_b = entity.metadata.annotations) == null ? void 0 : _b[catalogModel.LOCATION_ANNOTATION];
|
|
2355
|
+
isSingleTemplateString(input) {
|
|
2356
|
+
const {parser, nodes} = require("nunjucks");
|
|
2357
|
+
const parsed = parser.parse(input, {}, this.nunjucksOptions);
|
|
2358
|
+
return parsed.children.length === 1 && !(parsed.children[0] instanceof nodes.TemplateData);
|
|
2244
2359
|
}
|
|
2245
|
-
|
|
2246
|
-
return
|
|
2360
|
+
render(input, context) {
|
|
2361
|
+
return JSON.parse(JSON.stringify(input), (_key, value) => {
|
|
2362
|
+
try {
|
|
2363
|
+
if (typeof value === "string") {
|
|
2364
|
+
try {
|
|
2365
|
+
if (this.isSingleTemplateString(value)) {
|
|
2366
|
+
const wrappedDumped = value.replace(/\${{(.+)}}/g, "${{ ( $1 ) | dump }}");
|
|
2367
|
+
const templated2 = this.nunjucks.renderString(wrappedDumped, context);
|
|
2368
|
+
if (templated2 === "") {
|
|
2369
|
+
return void 0;
|
|
2370
|
+
}
|
|
2371
|
+
return JSON.parse(templated2);
|
|
2372
|
+
}
|
|
2373
|
+
} catch (ex) {
|
|
2374
|
+
this.options.logger.error(`Failed to parse template string: ${value} with error ${ex.message}`);
|
|
2375
|
+
}
|
|
2376
|
+
const templated = this.nunjucks.renderString(value, context);
|
|
2377
|
+
if (templated === "") {
|
|
2378
|
+
return void 0;
|
|
2379
|
+
}
|
|
2380
|
+
return templated;
|
|
2381
|
+
}
|
|
2382
|
+
} catch {
|
|
2383
|
+
return value;
|
|
2384
|
+
}
|
|
2385
|
+
return value;
|
|
2386
|
+
});
|
|
2247
2387
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2388
|
+
async execute(task) {
|
|
2389
|
+
var _a, _b;
|
|
2390
|
+
if (!isValidTaskSpec(task.spec)) {
|
|
2391
|
+
throw new errors.InputError("Wrong template version executed with the workflow engine");
|
|
2392
|
+
}
|
|
2393
|
+
const workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
|
|
2394
|
+
try {
|
|
2395
|
+
await fs__default['default'].ensureDir(workspacePath);
|
|
2396
|
+
await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
|
|
2397
|
+
const context = {
|
|
2398
|
+
parameters: task.spec.parameters,
|
|
2399
|
+
steps: {}
|
|
2400
|
+
};
|
|
2401
|
+
for (const step of task.spec.steps) {
|
|
2402
|
+
try {
|
|
2403
|
+
if (step.if) {
|
|
2404
|
+
const ifResult = await this.render(step.if, context);
|
|
2405
|
+
if (!isTruthy(ifResult)) {
|
|
2406
|
+
await task.emitLog(`Skipping step ${step.id} because it's if condition was false`, {stepId: step.id, status: "skipped"});
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
await task.emitLog(`Beginning step ${step.name}`, {
|
|
2411
|
+
stepId: step.id,
|
|
2412
|
+
status: "processing"
|
|
2413
|
+
});
|
|
2414
|
+
const action = this.options.actionRegistry.get(step.action);
|
|
2415
|
+
const {taskLogger, streamLogger} = createStepLogger({task, step});
|
|
2416
|
+
const input = (_a = step.input && this.render(step.input, context)) != null ? _a : {};
|
|
2417
|
+
if ((_b = action.schema) == null ? void 0 : _b.input) {
|
|
2418
|
+
const validateResult = jsonschema.validate(input, action.schema.input);
|
|
2419
|
+
if (!validateResult.valid) {
|
|
2420
|
+
const errors$1 = validateResult.errors.join(", ");
|
|
2421
|
+
throw new errors.InputError(`Invalid input passed to action ${action.id}, ${errors$1}`);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
const tmpDirs = new Array();
|
|
2425
|
+
const stepOutput = {};
|
|
2426
|
+
await action.handler({
|
|
2427
|
+
baseUrl: task.spec.baseUrl,
|
|
2428
|
+
input,
|
|
2429
|
+
logger: taskLogger,
|
|
2430
|
+
logStream: streamLogger,
|
|
2431
|
+
workspacePath,
|
|
2432
|
+
createTemporaryDirectory: async () => {
|
|
2433
|
+
const tmpDir = await fs__default['default'].mkdtemp(`${workspacePath}_step-${step.id}-`);
|
|
2434
|
+
tmpDirs.push(tmpDir);
|
|
2435
|
+
return tmpDir;
|
|
2436
|
+
},
|
|
2437
|
+
output(name, value) {
|
|
2438
|
+
stepOutput[name] = value;
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
2441
|
+
for (const tmpDir of tmpDirs) {
|
|
2442
|
+
await fs__default['default'].remove(tmpDir);
|
|
2443
|
+
}
|
|
2444
|
+
context.steps[step.id] = {output: stepOutput};
|
|
2445
|
+
await task.emitLog(`Finished step ${step.name}`, {
|
|
2446
|
+
stepId: step.id,
|
|
2447
|
+
status: "completed"
|
|
2448
|
+
});
|
|
2449
|
+
} catch (err) {
|
|
2450
|
+
await task.emitLog(String(err.stack), {
|
|
2451
|
+
stepId: step.id,
|
|
2452
|
+
status: "failed"
|
|
2453
|
+
});
|
|
2454
|
+
throw err;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
const output = this.render(task.spec.output, context);
|
|
2458
|
+
return {output};
|
|
2459
|
+
} finally {
|
|
2460
|
+
if (workspacePath) {
|
|
2461
|
+
await fs__default['default'].remove(workspacePath);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2253
2464
|
}
|
|
2254
|
-
return void 0;
|
|
2255
2465
|
}
|
|
2256
2466
|
|
|
2257
|
-
function
|
|
2258
|
-
return entity.apiVersion === "backstage.io/v1beta2";
|
|
2467
|
+
function isSupportedTemplate(entity) {
|
|
2468
|
+
return entity.apiVersion === "backstage.io/v1beta2" || entity.apiVersion === "scaffolder.backstage.io/v1beta3";
|
|
2259
2469
|
}
|
|
2260
2470
|
async function createRouter(options) {
|
|
2261
2471
|
const router = Router__default['default']();
|
|
@@ -2277,14 +2487,26 @@ async function createRouter(options) {
|
|
|
2277
2487
|
const databaseTaskStore = await DatabaseTaskStore.create(await database.getClient());
|
|
2278
2488
|
const taskBroker = new StorageTaskBroker(databaseTaskStore, logger);
|
|
2279
2489
|
const actionRegistry = new TemplateActionRegistry();
|
|
2490
|
+
const legacyWorkflowRunner = new LegacyWorkflowRunner({
|
|
2491
|
+
logger,
|
|
2492
|
+
actionRegistry,
|
|
2493
|
+
integrations,
|
|
2494
|
+
workingDirectory
|
|
2495
|
+
});
|
|
2496
|
+
const workflowRunner = new DefaultWorkflowRunner({
|
|
2497
|
+
actionRegistry,
|
|
2498
|
+
integrations,
|
|
2499
|
+
logger,
|
|
2500
|
+
workingDirectory
|
|
2501
|
+
});
|
|
2280
2502
|
const workers = [];
|
|
2281
2503
|
for (let i = 0; i < (taskWorkers || 1); i++) {
|
|
2282
2504
|
const worker = new TaskWorker({
|
|
2283
|
-
logger,
|
|
2284
2505
|
taskBroker,
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2506
|
+
runners: {
|
|
2507
|
+
legacyWorkflowRunner,
|
|
2508
|
+
workflowRunner
|
|
2509
|
+
}
|
|
2288
2510
|
});
|
|
2289
2511
|
workers.push(worker);
|
|
2290
2512
|
}
|
|
@@ -2309,7 +2531,7 @@ async function createRouter(options) {
|
|
|
2309
2531
|
const template = await entityClient.findTemplate(name, {
|
|
2310
2532
|
token: getBearerToken(req.headers.authorization)
|
|
2311
2533
|
});
|
|
2312
|
-
if (
|
|
2534
|
+
if (isSupportedTemplate(template)) {
|
|
2313
2535
|
const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
|
|
2314
2536
|
res.json({
|
|
2315
2537
|
title: (_b = template.metadata.title) != null ? _b : template.metadata.name,
|
|
@@ -2334,7 +2556,7 @@ async function createRouter(options) {
|
|
|
2334
2556
|
});
|
|
2335
2557
|
res.json(actionsList);
|
|
2336
2558
|
}).post("/v2/tasks", async (req, res) => {
|
|
2337
|
-
var _a, _b;
|
|
2559
|
+
var _a, _b, _c;
|
|
2338
2560
|
const templateName = req.body.templateName;
|
|
2339
2561
|
const values = req.body.values;
|
|
2340
2562
|
const token = getBearerToken(req.headers.authorization);
|
|
@@ -2342,7 +2564,7 @@ async function createRouter(options) {
|
|
|
2342
2564
|
token
|
|
2343
2565
|
});
|
|
2344
2566
|
let taskSpec;
|
|
2345
|
-
if (
|
|
2567
|
+
if (isSupportedTemplate(template)) {
|
|
2346
2568
|
for (const parameters of [(_a = template.spec.parameters) != null ? _a : []].flat()) {
|
|
2347
2569
|
const result2 = jsonschema.validate(values, parameters);
|
|
2348
2570
|
if (!result2.valid) {
|
|
@@ -2351,7 +2573,8 @@ async function createRouter(options) {
|
|
|
2351
2573
|
}
|
|
2352
2574
|
}
|
|
2353
2575
|
const baseUrl = getEntityBaseUrl(template);
|
|
2354
|
-
taskSpec = {
|
|
2576
|
+
taskSpec = template.apiVersion === "backstage.io/v1beta2" ? {
|
|
2577
|
+
apiVersion: template.apiVersion,
|
|
2355
2578
|
baseUrl,
|
|
2356
2579
|
values,
|
|
2357
2580
|
steps: template.spec.steps.map((step, index) => {
|
|
@@ -2363,6 +2586,19 @@ async function createRouter(options) {
|
|
|
2363
2586
|
};
|
|
2364
2587
|
}),
|
|
2365
2588
|
output: (_b = template.spec.output) != null ? _b : {}
|
|
2589
|
+
} : {
|
|
2590
|
+
apiVersion: template.apiVersion,
|
|
2591
|
+
baseUrl,
|
|
2592
|
+
parameters: values,
|
|
2593
|
+
steps: template.spec.steps.map((step, index) => {
|
|
2594
|
+
var _a2, _b2;
|
|
2595
|
+
return {
|
|
2596
|
+
...step,
|
|
2597
|
+
id: (_a2 = step.id) != null ? _a2 : `step-${index + 1}`,
|
|
2598
|
+
name: (_b2 = step.name) != null ? _b2 : step.action
|
|
2599
|
+
};
|
|
2600
|
+
}),
|
|
2601
|
+
output: (_c = template.spec.output) != null ? _c : {}
|
|
2366
2602
|
};
|
|
2367
2603
|
} else {
|
|
2368
2604
|
throw new errors.InputError(`Unsupported apiVersion field in schema entity, ${template.apiVersion}`);
|
|
@@ -2421,6 +2657,54 @@ function getBearerToken(header) {
|
|
|
2421
2657
|
return (_a = header == null ? void 0 : header.match(/Bearer\s+(\S+)/i)) == null ? void 0 : _a[1];
|
|
2422
2658
|
}
|
|
2423
2659
|
|
|
2660
|
+
class ScaffolderEntitiesProcessor {
|
|
2661
|
+
constructor() {
|
|
2662
|
+
this.validators = [
|
|
2663
|
+
catalogModel.entityKindSchemaValidator(pluginScaffolderCommon.templateEntityV1beta3Schema)
|
|
2664
|
+
];
|
|
2665
|
+
}
|
|
2666
|
+
async validateEntityKind(entity) {
|
|
2667
|
+
for (const validator of this.validators) {
|
|
2668
|
+
if (validator(entity)) {
|
|
2669
|
+
return true;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
return false;
|
|
2673
|
+
}
|
|
2674
|
+
async postProcessEntity(entity, _location, emit) {
|
|
2675
|
+
const selfRef = catalogModel.getEntityName(entity);
|
|
2676
|
+
if (entity.apiVersion === "scaffolder.backstage.io/v1beta3" && entity.kind === "Template") {
|
|
2677
|
+
const template = entity;
|
|
2678
|
+
const target = template.spec.owner;
|
|
2679
|
+
if (target) {
|
|
2680
|
+
const targetRef = catalogModel.parseEntityRef(target, {
|
|
2681
|
+
defaultKind: "Group",
|
|
2682
|
+
defaultNamespace: selfRef.namespace
|
|
2683
|
+
});
|
|
2684
|
+
emit(pluginCatalogBackend.results.relation({
|
|
2685
|
+
source: selfRef,
|
|
2686
|
+
type: catalogModel.RELATION_OWNED_BY,
|
|
2687
|
+
target: {
|
|
2688
|
+
kind: targetRef.kind,
|
|
2689
|
+
namespace: targetRef.namespace,
|
|
2690
|
+
name: targetRef.name
|
|
2691
|
+
}
|
|
2692
|
+
}));
|
|
2693
|
+
emit(pluginCatalogBackend.results.relation({
|
|
2694
|
+
source: {
|
|
2695
|
+
kind: targetRef.kind,
|
|
2696
|
+
namespace: targetRef.namespace,
|
|
2697
|
+
name: targetRef.name
|
|
2698
|
+
},
|
|
2699
|
+
type: catalogModel.RELATION_OWNER_OF,
|
|
2700
|
+
target: selfRef
|
|
2701
|
+
}));
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
return entity;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2424
2708
|
Object.defineProperty(exports, 'createFetchCookiecutterAction', {
|
|
2425
2709
|
enumerable: true,
|
|
2426
2710
|
get: function () {
|
|
@@ -2429,6 +2713,7 @@ Object.defineProperty(exports, 'createFetchCookiecutterAction', {
|
|
|
2429
2713
|
});
|
|
2430
2714
|
exports.CatalogEntityClient = CatalogEntityClient;
|
|
2431
2715
|
exports.OctokitProvider = OctokitProvider;
|
|
2716
|
+
exports.ScaffolderEntitiesProcessor = ScaffolderEntitiesProcessor;
|
|
2432
2717
|
exports.TemplateActionRegistry = TemplateActionRegistry;
|
|
2433
2718
|
exports.createBuiltinActions = createBuiltinActions;
|
|
2434
2719
|
exports.createCatalogRegisterAction = createCatalogRegisterAction;
|