@backstage/plugin-scaffolder-backend 0.15.4 → 0.15.8
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 +43 -0
- package/dist/index.cjs.js +319 -67
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/package.json +14 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @backstage/plugin-scaffolder-backend
|
|
2
2
|
|
|
3
|
+
## 0.15.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 42c618abf6: Use `resolveSafeChildPath` in the `fetchContents` function to forbid reading files outside the base directory when a template is registered from a `file:` location.
|
|
8
|
+
- 18083d1821: Introduce the new `scaffolder.backstage.io/v1beta3` template kind with nunjucks support 🥋
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/integration@0.6.8
|
|
11
|
+
- @backstage/plugin-catalog-backend@0.17.0
|
|
12
|
+
|
|
13
|
+
## 0.15.7
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- ca3086a7ad: Fixed a bug where the `catalog:register` action would not return any entity when running towards recent versions of the catalog.
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- @backstage/catalog-model@0.9.4
|
|
20
|
+
- @backstage/backend-common@0.9.6
|
|
21
|
+
- @backstage/catalog-client@0.5.0
|
|
22
|
+
- @backstage/integration@0.6.7
|
|
23
|
+
|
|
24
|
+
## 0.15.6
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Updated dependencies
|
|
29
|
+
- @backstage/integration@0.6.5
|
|
30
|
+
- @backstage/catalog-client@0.4.0
|
|
31
|
+
- @backstage/catalog-model@0.9.3
|
|
32
|
+
- @backstage/backend-common@0.9.4
|
|
33
|
+
- @backstage/config@0.1.10
|
|
34
|
+
|
|
35
|
+
## 0.15.5
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- 618143c3c7: Action needed: If you are using the templates located at https://github.com/backstage/backstage/tree/master/ in your Backstage app directly using the URL via the `app-config.yaml`, you should copy over the templates inside your org and import from there. The templates have now been moved to https://github.com/backstage/software-templates. See https://github.com/backstage/backstage/issues/6415 for explanation.
|
|
40
|
+
- cfade02127: Change hardcoded branch `master` to \$defaultBranch in GitLab provider
|
|
41
|
+
- 96fef17a18: Upgrade git-parse-url to v11.6.0
|
|
42
|
+
- Updated dependencies
|
|
43
|
+
- @backstage/backend-common@0.9.3
|
|
44
|
+
- @backstage/integration@0.6.4
|
|
45
|
+
|
|
3
46
|
## 0.15.4
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
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;
|
|
@@ -108,6 +111,7 @@ function createCatalogRegisterAction(options) {
|
|
|
108
111
|
}
|
|
109
112
|
},
|
|
110
113
|
async handler(ctx) {
|
|
114
|
+
var _a;
|
|
111
115
|
const {input} = ctx;
|
|
112
116
|
let catalogInfoUrl;
|
|
113
117
|
if ("catalogInfoUrl" in input) {
|
|
@@ -124,15 +128,21 @@ function createCatalogRegisterAction(options) {
|
|
|
124
128
|
});
|
|
125
129
|
}
|
|
126
130
|
ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`);
|
|
131
|
+
await catalogClient.addLocation({
|
|
132
|
+
type: "url",
|
|
133
|
+
target: catalogInfoUrl
|
|
134
|
+
}, ctx.token ? {token: ctx.token} : {});
|
|
127
135
|
const result = await catalogClient.addLocation({
|
|
136
|
+
dryRun: true,
|
|
128
137
|
type: "url",
|
|
129
138
|
target: catalogInfoUrl
|
|
130
139
|
}, ctx.token ? {token: ctx.token} : {});
|
|
131
|
-
if (result.entities.length
|
|
132
|
-
const {
|
|
133
|
-
|
|
134
|
-
ctx.output("
|
|
140
|
+
if (result.entities.length > 0) {
|
|
141
|
+
const {entities} = result;
|
|
142
|
+
const entity = (_a = entities.find((e) => !e.metadata.name.startsWith("generated-"))) != null ? _a : entities[0];
|
|
143
|
+
ctx.output("entityRef", catalogModel.stringifyEntityRef(entity));
|
|
135
144
|
}
|
|
145
|
+
ctx.output("catalogInfoUrl", catalogInfoUrl);
|
|
136
146
|
}
|
|
137
147
|
});
|
|
138
148
|
}
|
|
@@ -176,12 +186,16 @@ function createDebugLogAction() {
|
|
|
176
186
|
listWorkspace: {
|
|
177
187
|
title: "List all files in the workspace, if true.",
|
|
178
188
|
type: "boolean"
|
|
189
|
+
},
|
|
190
|
+
extra: {
|
|
191
|
+
title: "Extra info"
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
194
|
}
|
|
182
195
|
},
|
|
183
196
|
async handler(ctx) {
|
|
184
197
|
var _a, _b;
|
|
198
|
+
ctx.logger.info(JSON.stringify(ctx.input, null, 2));
|
|
185
199
|
if ((_a = ctx.input) == null ? void 0 : _a.message) {
|
|
186
200
|
ctx.logStream.write(ctx.input.message);
|
|
187
201
|
}
|
|
@@ -220,10 +234,7 @@ async function fetchContents({
|
|
|
220
234
|
}
|
|
221
235
|
if (!fetchUrlIsAbsolute && (baseUrl == null ? void 0 : baseUrl.startsWith("file://"))) {
|
|
222
236
|
const basePath = baseUrl.slice("file://".length);
|
|
223
|
-
|
|
224
|
-
throw new errors.InputError(`Fetch URL may not be absolute for file locations, ${fetchUrl}`);
|
|
225
|
-
}
|
|
226
|
-
const srcDir = path.resolve(basePath, "..", fetchUrl);
|
|
237
|
+
const srcDir = backendCommon.resolveSafeChildPath(path__namespace.dirname(basePath), fetchUrl);
|
|
227
238
|
await fs__default['default'].copy(srcDir, outputPath);
|
|
228
239
|
} else {
|
|
229
240
|
let readUrl;
|
|
@@ -1462,7 +1473,7 @@ function createPublishGitlabAction(options) {
|
|
|
1462
1473
|
visibility: repoVisibility
|
|
1463
1474
|
});
|
|
1464
1475
|
const remoteUrl = http_url_to_repo.replace(/\.git$/, "");
|
|
1465
|
-
const repoContentsUrl = `${remoteUrl}/-/blob
|
|
1476
|
+
const repoContentsUrl = `${remoteUrl}/-/blob/${defaultBranch}`;
|
|
1466
1477
|
const gitAuthorInfo = {
|
|
1467
1478
|
name: config.getOptionalString("scaffolder.defaultAuthor.name"),
|
|
1468
1479
|
email: config.getOptionalString("scaffolder.defaultAuthor.email")
|
|
@@ -2028,39 +2039,90 @@ class StorageTaskBroker {
|
|
|
2028
2039
|
}
|
|
2029
2040
|
}
|
|
2030
2041
|
|
|
2042
|
+
class TaskWorker {
|
|
2043
|
+
constructor(options) {
|
|
2044
|
+
this.options = options;
|
|
2045
|
+
}
|
|
2046
|
+
start() {
|
|
2047
|
+
(async () => {
|
|
2048
|
+
for (; ; ) {
|
|
2049
|
+
const task = await this.options.taskBroker.claim();
|
|
2050
|
+
await this.runOneTask(task);
|
|
2051
|
+
}
|
|
2052
|
+
})();
|
|
2053
|
+
}
|
|
2054
|
+
async runOneTask(task) {
|
|
2055
|
+
try {
|
|
2056
|
+
const {output} = task.spec.apiVersion === "scaffolder.backstage.io/v1beta3" ? await this.options.runners.workflowRunner.execute(task) : await this.options.runners.legacyWorkflowRunner.execute(task);
|
|
2057
|
+
await task.complete("completed", {output});
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
await task.complete("failed", {
|
|
2060
|
+
error: {name: error.name, message: error.message}
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
async function getWorkingDirectory(config, logger) {
|
|
2067
|
+
if (!config.has("backend.workingDirectory")) {
|
|
2068
|
+
return os__default['default'].tmpdir();
|
|
2069
|
+
}
|
|
2070
|
+
const workingDirectory = config.getString("backend.workingDirectory");
|
|
2071
|
+
try {
|
|
2072
|
+
await fs__default['default'].access(workingDirectory, fs__default['default'].constants.F_OK | fs__default['default'].constants.W_OK);
|
|
2073
|
+
logger.info(`using working directory: ${workingDirectory}`);
|
|
2074
|
+
} catch (err) {
|
|
2075
|
+
logger.error(`working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`);
|
|
2076
|
+
throw err;
|
|
2077
|
+
}
|
|
2078
|
+
return workingDirectory;
|
|
2079
|
+
}
|
|
2080
|
+
function getEntityBaseUrl(entity) {
|
|
2081
|
+
var _a, _b;
|
|
2082
|
+
let location = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.SOURCE_LOCATION_ANNOTATION];
|
|
2083
|
+
if (!location) {
|
|
2084
|
+
location = (_b = entity.metadata.annotations) == null ? void 0 : _b[catalogModel.LOCATION_ANNOTATION];
|
|
2085
|
+
}
|
|
2086
|
+
if (!location) {
|
|
2087
|
+
return void 0;
|
|
2088
|
+
}
|
|
2089
|
+
const {type, target} = catalogModel.parseLocationReference(location);
|
|
2090
|
+
if (type === "url") {
|
|
2091
|
+
return target;
|
|
2092
|
+
} else if (type === "file") {
|
|
2093
|
+
return `file://${target}`;
|
|
2094
|
+
}
|
|
2095
|
+
return void 0;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2031
2098
|
function isTruthy(value) {
|
|
2032
2099
|
return lodash.isArray(value) ? value.length > 0 : !!value;
|
|
2033
2100
|
}
|
|
2034
2101
|
|
|
2035
|
-
|
|
2102
|
+
const isValidTaskSpec$1 = (taskSpec) => taskSpec.apiVersion === "backstage.io/v1beta2";
|
|
2103
|
+
class LegacyWorkflowRunner {
|
|
2036
2104
|
constructor(options) {
|
|
2037
2105
|
this.options = options;
|
|
2038
2106
|
this.handlebars = Handlebars__namespace.create();
|
|
2039
2107
|
this.handlebars.registerHelper("parseRepoUrl", (repoUrl) => {
|
|
2040
|
-
return JSON.stringify(parseRepoUrl(repoUrl, options.integrations));
|
|
2108
|
+
return JSON.stringify(parseRepoUrl(repoUrl, this.options.integrations));
|
|
2041
2109
|
});
|
|
2042
2110
|
this.handlebars.registerHelper("projectSlug", (repoUrl) => {
|
|
2043
|
-
const {owner, repo} = parseRepoUrl(repoUrl, options.integrations);
|
|
2111
|
+
const {owner, repo} = parseRepoUrl(repoUrl, this.options.integrations);
|
|
2044
2112
|
return `${owner}/${repo}`;
|
|
2045
2113
|
});
|
|
2046
2114
|
this.handlebars.registerHelper("json", (obj) => JSON.stringify(obj));
|
|
2047
2115
|
this.handlebars.registerHelper("not", (value) => !isTruthy(value));
|
|
2048
2116
|
this.handlebars.registerHelper("eq", (a, b) => a === b);
|
|
2049
2117
|
}
|
|
2050
|
-
|
|
2051
|
-
(async () => {
|
|
2052
|
-
for (; ; ) {
|
|
2053
|
-
const task = await this.options.taskBroker.claim();
|
|
2054
|
-
await this.runOneTask(task);
|
|
2055
|
-
}
|
|
2056
|
-
})();
|
|
2057
|
-
}
|
|
2058
|
-
async runOneTask(task) {
|
|
2118
|
+
async execute(task) {
|
|
2059
2119
|
var _a, _b;
|
|
2060
|
-
|
|
2120
|
+
if (!isValidTaskSpec$1(task.spec)) {
|
|
2121
|
+
throw new errors.InputError(`Task spec is not a valid v1beta2 task spec`);
|
|
2122
|
+
}
|
|
2123
|
+
const {actionRegistry} = this.options;
|
|
2124
|
+
const workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
|
|
2061
2125
|
try {
|
|
2062
|
-
const {actionRegistry} = this.options;
|
|
2063
|
-
workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
|
|
2064
2126
|
await fs__default['default'].ensureDir(workspacePath);
|
|
2065
2127
|
await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
|
|
2066
2128
|
const templateCtx = {parameters: task.spec.values, steps: {}};
|
|
@@ -2202,11 +2264,7 @@ class TaskWorker {
|
|
|
2202
2264
|
}
|
|
2203
2265
|
return value;
|
|
2204
2266
|
});
|
|
2205
|
-
|
|
2206
|
-
} catch (error) {
|
|
2207
|
-
await task.complete("failed", {
|
|
2208
|
-
error: {name: error.name, message: error.message}
|
|
2209
|
-
});
|
|
2267
|
+
return {output};
|
|
2210
2268
|
} finally {
|
|
2211
2269
|
if (workspacePath) {
|
|
2212
2270
|
await fs__default['default'].remove(workspacePath);
|
|
@@ -2215,40 +2273,159 @@ class TaskWorker {
|
|
|
2215
2273
|
}
|
|
2216
2274
|
}
|
|
2217
2275
|
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2276
|
+
const isValidTaskSpec = (taskSpec) => {
|
|
2277
|
+
return taskSpec.apiVersion === "scaffolder.backstage.io/v1beta3";
|
|
2278
|
+
};
|
|
2279
|
+
const createStepLogger = ({task, step}) => {
|
|
2280
|
+
const metadata = {stepId: step.id};
|
|
2281
|
+
const taskLogger = winston__namespace.createLogger({
|
|
2282
|
+
level: process.env.LOG_LEVEL || "info",
|
|
2283
|
+
format: winston__namespace.format.combine(winston__namespace.format.colorize(), winston__namespace.format.timestamp(), winston__namespace.format.simple()),
|
|
2284
|
+
defaultMeta: {}
|
|
2285
|
+
});
|
|
2286
|
+
const streamLogger = new stream.PassThrough();
|
|
2287
|
+
streamLogger.on("data", async (data) => {
|
|
2288
|
+
const message = data.toString().trim();
|
|
2289
|
+
if ((message == null ? void 0 : message.length) > 1) {
|
|
2290
|
+
await task.emitLog(message, metadata);
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
taskLogger.add(new winston__namespace.transports.Stream({stream: streamLogger}));
|
|
2294
|
+
return {taskLogger, streamLogger};
|
|
2295
|
+
};
|
|
2296
|
+
class DefaultWorkflowRunner {
|
|
2297
|
+
constructor(options) {
|
|
2298
|
+
this.options = options;
|
|
2299
|
+
this.nunjucksOptions = {
|
|
2300
|
+
autoescape: false,
|
|
2301
|
+
tags: {
|
|
2302
|
+
variableStart: "${{",
|
|
2303
|
+
variableEnd: "}}"
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
this.nunjucks = nunjucks__default['default'].configure(this.nunjucksOptions);
|
|
2307
|
+
this.nunjucks.addFilter("parseRepoUrl", (repoUrl) => {
|
|
2308
|
+
return parseRepoUrl(repoUrl, this.options.integrations);
|
|
2309
|
+
});
|
|
2310
|
+
this.nunjucks.addFilter("projectSlug", (repoUrl) => {
|
|
2311
|
+
const {owner, repo} = parseRepoUrl(repoUrl, this.options.integrations);
|
|
2312
|
+
return `${owner}/${repo}`;
|
|
2313
|
+
});
|
|
2229
2314
|
}
|
|
2230
|
-
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
let location = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.SOURCE_LOCATION_ANNOTATION];
|
|
2235
|
-
if (!location) {
|
|
2236
|
-
location = (_b = entity.metadata.annotations) == null ? void 0 : _b[catalogModel.LOCATION_ANNOTATION];
|
|
2315
|
+
isSingleTemplateString(input) {
|
|
2316
|
+
const {parser, nodes} = require("nunjucks");
|
|
2317
|
+
const parsed = parser.parse(input, {}, this.nunjucksOptions);
|
|
2318
|
+
return parsed.children.length === 1 && !(parsed.children[0] instanceof nodes.TemplateData);
|
|
2237
2319
|
}
|
|
2238
|
-
|
|
2239
|
-
return
|
|
2320
|
+
render(input, context) {
|
|
2321
|
+
return JSON.parse(JSON.stringify(input), (_key, value) => {
|
|
2322
|
+
try {
|
|
2323
|
+
if (typeof value === "string") {
|
|
2324
|
+
try {
|
|
2325
|
+
if (this.isSingleTemplateString(value)) {
|
|
2326
|
+
const wrappedDumped = value.replace(/\${{(.+)}}/g, "${{ ( $1 ) | dump }}");
|
|
2327
|
+
const templated2 = this.nunjucks.renderString(wrappedDumped, context);
|
|
2328
|
+
if (templated2 === "") {
|
|
2329
|
+
return void 0;
|
|
2330
|
+
}
|
|
2331
|
+
return JSON.parse(templated2);
|
|
2332
|
+
}
|
|
2333
|
+
} catch (ex) {
|
|
2334
|
+
this.options.logger.error(`Failed to parse template string: ${value} with error ${ex.message}`);
|
|
2335
|
+
}
|
|
2336
|
+
const templated = this.nunjucks.renderString(value, context);
|
|
2337
|
+
if (templated === "") {
|
|
2338
|
+
return void 0;
|
|
2339
|
+
}
|
|
2340
|
+
return templated;
|
|
2341
|
+
}
|
|
2342
|
+
} catch {
|
|
2343
|
+
return value;
|
|
2344
|
+
}
|
|
2345
|
+
return value;
|
|
2346
|
+
});
|
|
2240
2347
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2348
|
+
async execute(task) {
|
|
2349
|
+
var _a, _b;
|
|
2350
|
+
if (!isValidTaskSpec(task.spec)) {
|
|
2351
|
+
throw new errors.InputError("Wrong template version executed with the workflow engine");
|
|
2352
|
+
}
|
|
2353
|
+
const workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
|
|
2354
|
+
try {
|
|
2355
|
+
await fs__default['default'].ensureDir(workspacePath);
|
|
2356
|
+
await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
|
|
2357
|
+
const context = {
|
|
2358
|
+
parameters: task.spec.parameters,
|
|
2359
|
+
steps: {}
|
|
2360
|
+
};
|
|
2361
|
+
for (const step of task.spec.steps) {
|
|
2362
|
+
try {
|
|
2363
|
+
if (step.if) {
|
|
2364
|
+
const ifResult = await this.render(step.if, context);
|
|
2365
|
+
if (!isTruthy(ifResult)) {
|
|
2366
|
+
await task.emitLog(`Skipping step ${step.id} because it's if condition was false`, {stepId: step.id, status: "skipped"});
|
|
2367
|
+
continue;
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
await task.emitLog(`Beginning step ${step.name}`, {
|
|
2371
|
+
stepId: step.id,
|
|
2372
|
+
status: "processing"
|
|
2373
|
+
});
|
|
2374
|
+
const action = this.options.actionRegistry.get(step.action);
|
|
2375
|
+
const {taskLogger, streamLogger} = createStepLogger({task, step});
|
|
2376
|
+
const input = (_a = step.input && this.render(step.input, context)) != null ? _a : {};
|
|
2377
|
+
if ((_b = action.schema) == null ? void 0 : _b.input) {
|
|
2378
|
+
const validateResult = jsonschema.validate(input, action.schema.input);
|
|
2379
|
+
if (!validateResult.valid) {
|
|
2380
|
+
const errors$1 = validateResult.errors.join(", ");
|
|
2381
|
+
throw new errors.InputError(`Invalid input passed to action ${action.id}, ${errors$1}`);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
const tmpDirs = new Array();
|
|
2385
|
+
const stepOutput = {};
|
|
2386
|
+
await action.handler({
|
|
2387
|
+
baseUrl: task.spec.baseUrl,
|
|
2388
|
+
input,
|
|
2389
|
+
logger: taskLogger,
|
|
2390
|
+
logStream: streamLogger,
|
|
2391
|
+
workspacePath,
|
|
2392
|
+
createTemporaryDirectory: async () => {
|
|
2393
|
+
const tmpDir = await fs__default['default'].mkdtemp(`${workspacePath}_step-${step.id}-`);
|
|
2394
|
+
tmpDirs.push(tmpDir);
|
|
2395
|
+
return tmpDir;
|
|
2396
|
+
},
|
|
2397
|
+
output(name, value) {
|
|
2398
|
+
stepOutput[name] = value;
|
|
2399
|
+
}
|
|
2400
|
+
});
|
|
2401
|
+
for (const tmpDir of tmpDirs) {
|
|
2402
|
+
await fs__default['default'].remove(tmpDir);
|
|
2403
|
+
}
|
|
2404
|
+
context.steps[step.id] = {output: stepOutput};
|
|
2405
|
+
await task.emitLog(`Finished step ${step.name}`, {
|
|
2406
|
+
stepId: step.id,
|
|
2407
|
+
status: "completed"
|
|
2408
|
+
});
|
|
2409
|
+
} catch (err) {
|
|
2410
|
+
await task.emitLog(String(err.stack), {
|
|
2411
|
+
stepId: step.id,
|
|
2412
|
+
status: "failed"
|
|
2413
|
+
});
|
|
2414
|
+
throw err;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
const output = this.render(task.spec.output, context);
|
|
2418
|
+
return {output};
|
|
2419
|
+
} finally {
|
|
2420
|
+
if (workspacePath) {
|
|
2421
|
+
await fs__default['default'].remove(workspacePath);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2246
2424
|
}
|
|
2247
|
-
return void 0;
|
|
2248
2425
|
}
|
|
2249
2426
|
|
|
2250
|
-
function
|
|
2251
|
-
return entity.apiVersion === "backstage.io/v1beta2";
|
|
2427
|
+
function isSupportedTemplate(entity) {
|
|
2428
|
+
return entity.apiVersion === "backstage.io/v1beta2" || entity.apiVersion === "scaffolder.backstage.io/v1beta3";
|
|
2252
2429
|
}
|
|
2253
2430
|
async function createRouter(options) {
|
|
2254
2431
|
const router = Router__default['default']();
|
|
@@ -2270,14 +2447,26 @@ async function createRouter(options) {
|
|
|
2270
2447
|
const databaseTaskStore = await DatabaseTaskStore.create(await database.getClient());
|
|
2271
2448
|
const taskBroker = new StorageTaskBroker(databaseTaskStore, logger);
|
|
2272
2449
|
const actionRegistry = new TemplateActionRegistry();
|
|
2450
|
+
const legacyWorkflowRunner = new LegacyWorkflowRunner({
|
|
2451
|
+
logger,
|
|
2452
|
+
actionRegistry,
|
|
2453
|
+
integrations,
|
|
2454
|
+
workingDirectory
|
|
2455
|
+
});
|
|
2456
|
+
const workflowRunner = new DefaultWorkflowRunner({
|
|
2457
|
+
actionRegistry,
|
|
2458
|
+
integrations,
|
|
2459
|
+
logger,
|
|
2460
|
+
workingDirectory
|
|
2461
|
+
});
|
|
2273
2462
|
const workers = [];
|
|
2274
2463
|
for (let i = 0; i < (taskWorkers || 1); i++) {
|
|
2275
2464
|
const worker = new TaskWorker({
|
|
2276
|
-
logger,
|
|
2277
2465
|
taskBroker,
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2466
|
+
runners: {
|
|
2467
|
+
legacyWorkflowRunner,
|
|
2468
|
+
workflowRunner
|
|
2469
|
+
}
|
|
2281
2470
|
});
|
|
2282
2471
|
workers.push(worker);
|
|
2283
2472
|
}
|
|
@@ -2302,7 +2491,7 @@ async function createRouter(options) {
|
|
|
2302
2491
|
const template = await entityClient.findTemplate(name, {
|
|
2303
2492
|
token: getBearerToken(req.headers.authorization)
|
|
2304
2493
|
});
|
|
2305
|
-
if (
|
|
2494
|
+
if (isSupportedTemplate(template)) {
|
|
2306
2495
|
const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
|
|
2307
2496
|
res.json({
|
|
2308
2497
|
title: (_b = template.metadata.title) != null ? _b : template.metadata.name,
|
|
@@ -2327,7 +2516,7 @@ async function createRouter(options) {
|
|
|
2327
2516
|
});
|
|
2328
2517
|
res.json(actionsList);
|
|
2329
2518
|
}).post("/v2/tasks", async (req, res) => {
|
|
2330
|
-
var _a, _b;
|
|
2519
|
+
var _a, _b, _c;
|
|
2331
2520
|
const templateName = req.body.templateName;
|
|
2332
2521
|
const values = req.body.values;
|
|
2333
2522
|
const token = getBearerToken(req.headers.authorization);
|
|
@@ -2335,7 +2524,7 @@ async function createRouter(options) {
|
|
|
2335
2524
|
token
|
|
2336
2525
|
});
|
|
2337
2526
|
let taskSpec;
|
|
2338
|
-
if (
|
|
2527
|
+
if (isSupportedTemplate(template)) {
|
|
2339
2528
|
for (const parameters of [(_a = template.spec.parameters) != null ? _a : []].flat()) {
|
|
2340
2529
|
const result2 = jsonschema.validate(values, parameters);
|
|
2341
2530
|
if (!result2.valid) {
|
|
@@ -2344,7 +2533,8 @@ async function createRouter(options) {
|
|
|
2344
2533
|
}
|
|
2345
2534
|
}
|
|
2346
2535
|
const baseUrl = getEntityBaseUrl(template);
|
|
2347
|
-
taskSpec = {
|
|
2536
|
+
taskSpec = template.apiVersion === "backstage.io/v1beta2" ? {
|
|
2537
|
+
apiVersion: template.apiVersion,
|
|
2348
2538
|
baseUrl,
|
|
2349
2539
|
values,
|
|
2350
2540
|
steps: template.spec.steps.map((step, index) => {
|
|
@@ -2356,6 +2546,19 @@ async function createRouter(options) {
|
|
|
2356
2546
|
};
|
|
2357
2547
|
}),
|
|
2358
2548
|
output: (_b = template.spec.output) != null ? _b : {}
|
|
2549
|
+
} : {
|
|
2550
|
+
apiVersion: template.apiVersion,
|
|
2551
|
+
baseUrl,
|
|
2552
|
+
parameters: values,
|
|
2553
|
+
steps: template.spec.steps.map((step, index) => {
|
|
2554
|
+
var _a2, _b2;
|
|
2555
|
+
return {
|
|
2556
|
+
...step,
|
|
2557
|
+
id: (_a2 = step.id) != null ? _a2 : `step-${index + 1}`,
|
|
2558
|
+
name: (_b2 = step.name) != null ? _b2 : step.action
|
|
2559
|
+
};
|
|
2560
|
+
}),
|
|
2561
|
+
output: (_c = template.spec.output) != null ? _c : {}
|
|
2359
2562
|
};
|
|
2360
2563
|
} else {
|
|
2361
2564
|
throw new errors.InputError(`Unsupported apiVersion field in schema entity, ${template.apiVersion}`);
|
|
@@ -2414,6 +2617,54 @@ function getBearerToken(header) {
|
|
|
2414
2617
|
return (_a = header == null ? void 0 : header.match(/Bearer\s+(\S+)/i)) == null ? void 0 : _a[1];
|
|
2415
2618
|
}
|
|
2416
2619
|
|
|
2620
|
+
class ScaffolderEntitiesProcessor {
|
|
2621
|
+
constructor() {
|
|
2622
|
+
this.validators = [
|
|
2623
|
+
catalogModel.entityKindSchemaValidator(pluginScaffolderCommon.templateEntityV1beta3Schema)
|
|
2624
|
+
];
|
|
2625
|
+
}
|
|
2626
|
+
async validateEntityKind(entity) {
|
|
2627
|
+
for (const validator of this.validators) {
|
|
2628
|
+
if (validator(entity)) {
|
|
2629
|
+
return true;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2634
|
+
async postProcessEntity(entity, _location, emit) {
|
|
2635
|
+
const selfRef = catalogModel.getEntityName(entity);
|
|
2636
|
+
if (entity.apiVersion === "scaffolder.backstage.io/v1beta3" && entity.kind === "Template") {
|
|
2637
|
+
const template = entity;
|
|
2638
|
+
const target = template.spec.owner;
|
|
2639
|
+
if (target) {
|
|
2640
|
+
const targetRef = catalogModel.parseEntityRef(target, {
|
|
2641
|
+
defaultKind: "Group",
|
|
2642
|
+
defaultNamespace: selfRef.namespace
|
|
2643
|
+
});
|
|
2644
|
+
emit(pluginCatalogBackend.results.relation({
|
|
2645
|
+
source: selfRef,
|
|
2646
|
+
type: catalogModel.RELATION_OWNED_BY,
|
|
2647
|
+
target: {
|
|
2648
|
+
kind: targetRef.kind,
|
|
2649
|
+
namespace: targetRef.namespace,
|
|
2650
|
+
name: targetRef.name
|
|
2651
|
+
}
|
|
2652
|
+
}));
|
|
2653
|
+
emit(pluginCatalogBackend.results.relation({
|
|
2654
|
+
source: {
|
|
2655
|
+
kind: targetRef.kind,
|
|
2656
|
+
namespace: targetRef.namespace,
|
|
2657
|
+
name: targetRef.name
|
|
2658
|
+
},
|
|
2659
|
+
type: catalogModel.RELATION_OWNER_OF,
|
|
2660
|
+
target: selfRef
|
|
2661
|
+
}));
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
return entity;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2417
2668
|
Object.defineProperty(exports, 'createFetchCookiecutterAction', {
|
|
2418
2669
|
enumerable: true,
|
|
2419
2670
|
get: function () {
|
|
@@ -2422,6 +2673,7 @@ Object.defineProperty(exports, 'createFetchCookiecutterAction', {
|
|
|
2422
2673
|
});
|
|
2423
2674
|
exports.CatalogEntityClient = CatalogEntityClient;
|
|
2424
2675
|
exports.OctokitProvider = OctokitProvider;
|
|
2676
|
+
exports.ScaffolderEntitiesProcessor = ScaffolderEntitiesProcessor;
|
|
2425
2677
|
exports.TemplateActionRegistry = TemplateActionRegistry;
|
|
2426
2678
|
exports.createBuiltinActions = createBuiltinActions;
|
|
2427
2679
|
exports.createCatalogRegisterAction = createCatalogRegisterAction;
|