@backstage/plugin-scaffolder-backend 0.15.13 → 0.15.14
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 +13 -0
- package/assets/nunjucks.js.txt +10385 -0
- package/dist/index.cjs.js +129 -47
- package/dist/index.cjs.js.map +1 -1
- package/package.json +15 -11
package/dist/index.cjs.js
CHANGED
|
@@ -5,12 +5,12 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var errors = require('@backstage/errors');
|
|
6
6
|
var catalogModel = require('@backstage/catalog-model');
|
|
7
7
|
var fs = require('fs-extra');
|
|
8
|
-
var path = require('path');
|
|
9
8
|
var yaml = require('yaml');
|
|
10
9
|
var backendCommon = require('@backstage/backend-common');
|
|
10
|
+
var path = require('path');
|
|
11
11
|
var globby = require('globby');
|
|
12
|
-
var nunjucks = require('nunjucks');
|
|
13
12
|
var isbinaryfile = require('isbinaryfile');
|
|
13
|
+
var vm2 = require('vm2');
|
|
14
14
|
var pluginScaffolderBackendModuleCookiecutter = require('@backstage/plugin-scaffolder-backend-module-cookiecutter');
|
|
15
15
|
var child_process = require('child_process');
|
|
16
16
|
var stream = require('stream');
|
|
@@ -27,6 +27,7 @@ var luxon = require('luxon');
|
|
|
27
27
|
var Handlebars = require('handlebars');
|
|
28
28
|
var winston = require('winston');
|
|
29
29
|
var jsonschema = require('jsonschema');
|
|
30
|
+
var nunjucks = require('nunjucks');
|
|
30
31
|
var express = require('express');
|
|
31
32
|
var Router = require('express-promise-router');
|
|
32
33
|
var os = require('os');
|
|
@@ -56,14 +57,13 @@ function _interopNamespace(e) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
59
|
-
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
60
|
-
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
61
60
|
var yaml__namespace = /*#__PURE__*/_interopNamespace(yaml);
|
|
61
|
+
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
62
62
|
var globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
|
|
63
|
-
var nunjucks__default = /*#__PURE__*/_interopDefaultLegacy(nunjucks);
|
|
64
63
|
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
65
64
|
var Handlebars__namespace = /*#__PURE__*/_interopNamespace(Handlebars);
|
|
66
65
|
var winston__namespace = /*#__PURE__*/_interopNamespace(winston);
|
|
66
|
+
var nunjucks__default = /*#__PURE__*/_interopDefaultLegacy(nunjucks);
|
|
67
67
|
var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
|
|
68
68
|
var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
|
|
69
69
|
var os__default = /*#__PURE__*/_interopDefaultLegacy(os);
|
|
@@ -182,7 +182,7 @@ function createCatalogWriteAction() {
|
|
|
182
182
|
async handler(ctx) {
|
|
183
183
|
ctx.logStream.write(`Writing catalog-info.yaml`);
|
|
184
184
|
const {entity} = ctx.input;
|
|
185
|
-
await fs__default['default'].writeFile(
|
|
185
|
+
await fs__default['default'].writeFile(backendCommon.resolveSafeChildPath(ctx.workspacePath, "catalog-info.yaml"), yaml__namespace.stringify(entity));
|
|
186
186
|
}
|
|
187
187
|
});
|
|
188
188
|
}
|
|
@@ -226,7 +226,7 @@ ${files.map((f) => ` - ${path.relative(ctx.workspacePath, f)}`).join("\n")}`);
|
|
|
226
226
|
async function recursiveReadDir(dir) {
|
|
227
227
|
const subdirs = await fs.readdir(dir);
|
|
228
228
|
const files = await Promise.all(subdirs.map(async (subdir) => {
|
|
229
|
-
const res = path.
|
|
229
|
+
const res = path.join(dir, subdir);
|
|
230
230
|
return (await fs.stat(res)).isDirectory() ? recursiveReadDir(res) : [res];
|
|
231
231
|
}));
|
|
232
232
|
return files.reduce((a, f) => a.concat(f), []);
|
|
@@ -250,7 +250,7 @@ async function fetchContents({
|
|
|
250
250
|
}
|
|
251
251
|
if (!fetchUrlIsAbsolute && (baseUrl == null ? void 0 : baseUrl.startsWith("file://"))) {
|
|
252
252
|
const basePath = baseUrl.slice("file://".length);
|
|
253
|
-
const srcDir = backendCommon.resolveSafeChildPath(
|
|
253
|
+
const srcDir = backendCommon.resolveSafeChildPath(path__default['default'].dirname(basePath), fetchUrl);
|
|
254
254
|
await fs__default['default'].copy(srcDir, outputPath);
|
|
255
255
|
} else {
|
|
256
256
|
let readUrl;
|
|
@@ -313,7 +313,100 @@ function createFetchPlainAction(options) {
|
|
|
313
313
|
});
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
const mkScript = (nunjucksSource) => `
|
|
317
|
+
const { render, renderCompat } = (() => {
|
|
318
|
+
const module = {};
|
|
319
|
+
const process = { env: {} };
|
|
320
|
+
const require = (pkg) => { if (pkg === 'events') { return function (){}; }};
|
|
321
|
+
|
|
322
|
+
${nunjucksSource}
|
|
323
|
+
|
|
324
|
+
const env = module.exports.configure({
|
|
325
|
+
autoescape: false,
|
|
326
|
+
tags: {
|
|
327
|
+
variableStart: '\${{',
|
|
328
|
+
variableEnd: '}}',
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const compatEnv = module.exports.configure({
|
|
333
|
+
autoescape: false,
|
|
334
|
+
tags: {
|
|
335
|
+
variableStart: '{{',
|
|
336
|
+
variableEnd: '}}',
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
compatEnv.addFilter('jsonify', compatEnv.getFilter('dump'));
|
|
340
|
+
|
|
341
|
+
if (typeof parseRepoUrl !== 'undefined') {
|
|
342
|
+
const safeHelperRef = parseRepoUrl;
|
|
343
|
+
|
|
344
|
+
env.addFilter('parseRepoUrl', repoUrl => {
|
|
345
|
+
return JSON.parse(safeHelperRef(repoUrl))
|
|
346
|
+
});
|
|
347
|
+
env.addFilter('projectSlug', repoUrl => {
|
|
348
|
+
const { owner, repo } = JSON.parse(safeHelperRef(repoUrl));
|
|
349
|
+
return owner + '/' + repo;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let uninstallCompat = undefined;
|
|
354
|
+
|
|
355
|
+
function render(str, values) {
|
|
356
|
+
try {
|
|
357
|
+
if (uninstallCompat) {
|
|
358
|
+
uninstallCompat();
|
|
359
|
+
uninstallCompat = undefined;
|
|
360
|
+
}
|
|
361
|
+
return env.renderString(str, JSON.parse(values));
|
|
362
|
+
} catch (error) {
|
|
363
|
+
// Make sure errors don't leak anything
|
|
364
|
+
throw new Error(String(error.message));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function renderCompat(str, values) {
|
|
369
|
+
try {
|
|
370
|
+
if (!uninstallCompat) {
|
|
371
|
+
uninstallCompat = module.exports.installJinjaCompat();
|
|
372
|
+
}
|
|
373
|
+
return compatEnv.renderString(str, JSON.parse(values));
|
|
374
|
+
} catch (error) {
|
|
375
|
+
// Make sure errors don't leak anything
|
|
376
|
+
throw new Error(String(error.message));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return { render, renderCompat };
|
|
381
|
+
})();
|
|
382
|
+
`;
|
|
383
|
+
class SecureTemplater {
|
|
384
|
+
static async loadRenderer(options = {}) {
|
|
385
|
+
const {parseRepoUrl, cookiecutterCompat} = options;
|
|
386
|
+
let sandbox = void 0;
|
|
387
|
+
if (parseRepoUrl) {
|
|
388
|
+
sandbox = {
|
|
389
|
+
parseRepoUrl: (url) => JSON.stringify(parseRepoUrl(url))
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
const vm = new vm2.VM({sandbox});
|
|
393
|
+
const nunjucksSource = await fs__default['default'].readFile(backendCommon.resolvePackagePath("@backstage/plugin-scaffolder-backend", "assets/nunjucks.js.txt"), "utf-8");
|
|
394
|
+
vm.run(mkScript(nunjucksSource));
|
|
395
|
+
const render = (template, values) => {
|
|
396
|
+
if (!vm) {
|
|
397
|
+
throw new Error("SecureTemplater has not been initialized");
|
|
398
|
+
}
|
|
399
|
+
vm.setGlobal("templateStr", template);
|
|
400
|
+
vm.setGlobal("templateValues", JSON.stringify(values));
|
|
401
|
+
if (cookiecutterCompat) {
|
|
402
|
+
return vm.run(`renderCompat(templateStr, templateValues)`);
|
|
403
|
+
}
|
|
404
|
+
return vm.run(`render(templateStr, templateValues)`);
|
|
405
|
+
};
|
|
406
|
+
return render;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
317
410
|
function createFetchTemplateAction(options) {
|
|
318
411
|
const {reader, integrations} = options;
|
|
319
412
|
return createTemplateAction({
|
|
@@ -364,7 +457,7 @@ function createFetchTemplateAction(options) {
|
|
|
364
457
|
var _a;
|
|
365
458
|
ctx.logger.info("Fetching template content from remote URL");
|
|
366
459
|
const workDir = await ctx.createTemporaryDirectory();
|
|
367
|
-
const templateDir =
|
|
460
|
+
const templateDir = backendCommon.resolveSafeChildPath(workDir, "template");
|
|
368
461
|
const targetPath = (_a = ctx.input.targetPath) != null ? _a : "./";
|
|
369
462
|
const outputDir = backendCommon.resolveSafeChildPath(ctx.workspacePath, targetPath);
|
|
370
463
|
if (ctx.input.copyWithoutRender && !Array.isArray(ctx.input.copyWithoutRender)) {
|
|
@@ -400,23 +493,14 @@ function createFetchTemplateAction(options) {
|
|
|
400
493
|
onlyFiles: false,
|
|
401
494
|
markDirectories: true
|
|
402
495
|
})))).flat());
|
|
403
|
-
const templater = nunjucks__default['default'].configure({
|
|
404
|
-
...ctx.input.cookiecutterCompat ? {} : {
|
|
405
|
-
tags: {
|
|
406
|
-
variableStart: "${{",
|
|
407
|
-
variableEnd: "}}"
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
autoescape: false
|
|
411
|
-
});
|
|
412
|
-
if (ctx.input.cookiecutterCompat) {
|
|
413
|
-
templater.addFilter("jsonify", templater.getFilter("dump"));
|
|
414
|
-
}
|
|
415
496
|
const {cookiecutterCompat, values} = ctx.input;
|
|
416
497
|
const context = {
|
|
417
498
|
[cookiecutterCompat ? "cookiecutter" : "values"]: values
|
|
418
499
|
};
|
|
419
500
|
ctx.logger.info(`Processing ${allEntriesInTemplate.length} template files/directories with input values`, ctx.input.values);
|
|
501
|
+
const renderTemplate = await SecureTemplater.loadRenderer({
|
|
502
|
+
cookiecutterCompat: ctx.input.cookiecutterCompat
|
|
503
|
+
});
|
|
420
504
|
for (const location of allEntriesInTemplate) {
|
|
421
505
|
let renderFilename;
|
|
422
506
|
let renderContents;
|
|
@@ -431,9 +515,9 @@ function createFetchTemplateAction(options) {
|
|
|
431
515
|
renderFilename = renderContents = !nonTemplatedEntries.has(location);
|
|
432
516
|
}
|
|
433
517
|
if (renderFilename) {
|
|
434
|
-
localOutputPath =
|
|
518
|
+
localOutputPath = renderTemplate(localOutputPath, context);
|
|
435
519
|
}
|
|
436
|
-
const outputPath =
|
|
520
|
+
const outputPath = backendCommon.resolveSafeChildPath(outputDir, localOutputPath);
|
|
437
521
|
if (outputDir === outputPath) {
|
|
438
522
|
continue;
|
|
439
523
|
}
|
|
@@ -444,7 +528,7 @@ function createFetchTemplateAction(options) {
|
|
|
444
528
|
ctx.logger.info(`Writing directory ${location} to template output path.`);
|
|
445
529
|
await fs__default['default'].ensureDir(outputPath);
|
|
446
530
|
} else {
|
|
447
|
-
const inputFilePath =
|
|
531
|
+
const inputFilePath = backendCommon.resolveSafeChildPath(templateDir, location);
|
|
448
532
|
if (await isbinaryfile.isBinaryFile(inputFilePath)) {
|
|
449
533
|
ctx.logger.info(`Copying binary file ${location} to template output path.`);
|
|
450
534
|
await fs__default['default'].copy(inputFilePath, outputPath);
|
|
@@ -452,7 +536,7 @@ function createFetchTemplateAction(options) {
|
|
|
452
536
|
const statsObj = await fs__default['default'].stat(inputFilePath);
|
|
453
537
|
ctx.logger.info(`Writing file ${location} to template output path with mode ${statsObj.mode}.`);
|
|
454
538
|
const inputFileContents = await fs__default['default'].readFile(inputFilePath, "utf-8");
|
|
455
|
-
await fs__default['default'].outputFile(outputPath, renderContents ?
|
|
539
|
+
await fs__default['default'].outputFile(outputPath, renderContents ? renderTemplate(inputFileContents, context) : inputFileContents, {mode: statsObj.mode});
|
|
456
540
|
}
|
|
457
541
|
}
|
|
458
542
|
}
|
|
@@ -1400,7 +1484,7 @@ const createPublishGithubPullRequestAction = ({
|
|
|
1400
1484
|
dot: true
|
|
1401
1485
|
});
|
|
1402
1486
|
const fileContents = await Promise.all(localFilePaths.map((filePath) => {
|
|
1403
|
-
const absPath =
|
|
1487
|
+
const absPath = backendCommon.resolveSafeChildPath(fileRoot, filePath);
|
|
1404
1488
|
const base64EncodedContent = fs__default['default'].readFileSync(absPath).toString("base64");
|
|
1405
1489
|
const fileStat = fs__default['default'].statSync(absPath);
|
|
1406
1490
|
const githubTreeItemMode = isExecutable(fileStat.mode) ? "100755" : "100644";
|
|
@@ -2270,35 +2354,27 @@ const createStepLogger = ({
|
|
|
2270
2354
|
class NunjucksWorkflowRunner {
|
|
2271
2355
|
constructor(options) {
|
|
2272
2356
|
this.options = options;
|
|
2273
|
-
|
|
2357
|
+
}
|
|
2358
|
+
isSingleTemplateString(input) {
|
|
2359
|
+
var _a, _b;
|
|
2360
|
+
const {parser, nodes} = nunjucks__default['default'];
|
|
2361
|
+
const parsed = parser.parse(input, {}, {
|
|
2274
2362
|
autoescape: false,
|
|
2275
2363
|
tags: {
|
|
2276
2364
|
variableStart: "${{",
|
|
2277
2365
|
variableEnd: "}}"
|
|
2278
2366
|
}
|
|
2279
|
-
};
|
|
2280
|
-
this.nunjucks = nunjucks__default['default'].configure(this.nunjucksOptions);
|
|
2281
|
-
this.nunjucks.addFilter("parseRepoUrl", (repoUrl) => {
|
|
2282
|
-
return parseRepoUrl(repoUrl, this.options.integrations);
|
|
2283
|
-
});
|
|
2284
|
-
this.nunjucks.addFilter("projectSlug", (repoUrl) => {
|
|
2285
|
-
const {owner, repo} = parseRepoUrl(repoUrl, this.options.integrations);
|
|
2286
|
-
return `${owner}/${repo}`;
|
|
2287
2367
|
});
|
|
2368
|
+
return parsed.children.length === 1 && !(((_b = (_a = parsed.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b[0]) instanceof nodes.TemplateData);
|
|
2288
2369
|
}
|
|
2289
|
-
|
|
2290
|
-
const {parser, nodes} = require("nunjucks");
|
|
2291
|
-
const parsed = parser.parse(input, {}, this.nunjucksOptions);
|
|
2292
|
-
return parsed.children.length === 1 && !(parsed.children[0] instanceof nodes.TemplateData);
|
|
2293
|
-
}
|
|
2294
|
-
render(input, context) {
|
|
2370
|
+
render(input, context, renderTemplate) {
|
|
2295
2371
|
return JSON.parse(JSON.stringify(input), (_key, value) => {
|
|
2296
2372
|
try {
|
|
2297
2373
|
if (typeof value === "string") {
|
|
2298
2374
|
try {
|
|
2299
2375
|
if (this.isSingleTemplateString(value)) {
|
|
2300
2376
|
const wrappedDumped = value.replace(/\${{(.+)}}/g, "${{ ( $1 ) | dump }}");
|
|
2301
|
-
const templated2 =
|
|
2377
|
+
const templated2 = renderTemplate(wrappedDumped, context);
|
|
2302
2378
|
if (templated2 === "") {
|
|
2303
2379
|
return void 0;
|
|
2304
2380
|
}
|
|
@@ -2307,7 +2383,7 @@ class NunjucksWorkflowRunner {
|
|
|
2307
2383
|
} catch (ex) {
|
|
2308
2384
|
this.options.logger.error(`Failed to parse template string: ${value} with error ${ex.message}`);
|
|
2309
2385
|
}
|
|
2310
|
-
const templated =
|
|
2386
|
+
const templated = renderTemplate(value, context);
|
|
2311
2387
|
if (templated === "") {
|
|
2312
2388
|
return void 0;
|
|
2313
2389
|
}
|
|
@@ -2325,6 +2401,12 @@ class NunjucksWorkflowRunner {
|
|
|
2325
2401
|
throw new errors.InputError("Wrong template version executed with the workflow engine");
|
|
2326
2402
|
}
|
|
2327
2403
|
const workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
|
|
2404
|
+
const {integrations} = this.options;
|
|
2405
|
+
const renderTemplate = await SecureTemplater.loadRenderer({
|
|
2406
|
+
parseRepoUrl(url) {
|
|
2407
|
+
return parseRepoUrl(url, integrations);
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2328
2410
|
try {
|
|
2329
2411
|
await fs__default['default'].ensureDir(workspacePath);
|
|
2330
2412
|
await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
|
|
@@ -2335,7 +2417,7 @@ class NunjucksWorkflowRunner {
|
|
|
2335
2417
|
for (const step of task.spec.steps) {
|
|
2336
2418
|
try {
|
|
2337
2419
|
if (step.if) {
|
|
2338
|
-
const ifResult = await this.render(step.if, context);
|
|
2420
|
+
const ifResult = await this.render(step.if, context, renderTemplate);
|
|
2339
2421
|
if (!isTruthy(ifResult)) {
|
|
2340
2422
|
await task.emitLog(`Skipping step ${step.id} because it's if condition was false`, {stepId: step.id, status: "skipped"});
|
|
2341
2423
|
continue;
|
|
@@ -2347,7 +2429,7 @@ class NunjucksWorkflowRunner {
|
|
|
2347
2429
|
});
|
|
2348
2430
|
const action = this.options.actionRegistry.get(step.action);
|
|
2349
2431
|
const {taskLogger, streamLogger} = createStepLogger({task, step});
|
|
2350
|
-
const input = (_a = step.input && this.render(step.input, context)) != null ? _a : {};
|
|
2432
|
+
const input = (_a = step.input && this.render(step.input, context, renderTemplate)) != null ? _a : {};
|
|
2351
2433
|
if ((_b = action.schema) == null ? void 0 : _b.input) {
|
|
2352
2434
|
const validateResult = jsonschema.validate(input, action.schema.input);
|
|
2353
2435
|
if (!validateResult.valid) {
|
|
@@ -2392,7 +2474,7 @@ class NunjucksWorkflowRunner {
|
|
|
2392
2474
|
throw err;
|
|
2393
2475
|
}
|
|
2394
2476
|
}
|
|
2395
|
-
const output = this.render(task.spec.output, context);
|
|
2477
|
+
const output = this.render(task.spec.output, context, renderTemplate);
|
|
2396
2478
|
return {output};
|
|
2397
2479
|
} finally {
|
|
2398
2480
|
if (workspacePath) {
|