diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 47bfcccfdf..9e1a3f29db 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -21,22 +21,9 @@ import { ScmIntegrations } from '@backstage/integration'; import { fetchContents } from './helpers'; import { createTemplateAction } from '../../createTemplateAction'; import globby from 'globby'; -import nunjucks from 'nunjucks'; import fs from 'fs-extra'; import { isBinaryFile } from 'isbinaryfile'; - -/* - * Maximise compatibility with Jinja (and therefore cookiecutter) - * using nunjucks jinja compat mode. Since this method mutates - * the global nunjucks instance, we can't enable this per-template, - * or only for templates with cookiecutter compat enabled, so the - * next best option is to explicitly enable it globally and allow - * folks to rely on jinja compatibility behaviour in fetch:template - * templates if they wish. - * - * cf. https://mozilla.github.io/nunjucks/api.html#installjinjacompat - */ -nunjucks.installJinjaCompat(); +import { SecureTemplater } from '../../../../lib/templating/SecureTemplater'; type CookieCompatInput = { copyWithoutRender?: string[]; @@ -60,6 +47,8 @@ export function createFetchTemplateAction(options: { }) { const { reader, integrations } = options; + const templater = new SecureTemplater(); + return createTemplateAction({ id: 'fetch:template', description: @@ -113,6 +102,8 @@ export function createFetchTemplateAction(options: { async handler(ctx) { ctx.logger.info('Fetching template content from remote URL'); + await templater.initializeIfNeeded(); + const workDir = await ctx.createTemporaryDirectory(); const templateDir = resolvePath(workDir, 'template'); @@ -179,36 +170,6 @@ export function createFetchTemplateAction(options: { ).flat(), ); - // Create a templater - const templater = nunjucks.configure({ - ...(ctx.input.cookiecutterCompat - ? {} - : { - tags: { - // TODO(mtlewis/orkohunter): Document Why we are changing the literals? Not here, but on scaffolder docs. ADR? - variableStart: '${{', - variableEnd: '}}', - }, - }), - // We don't want this builtin auto-escaping, since uses HTML escape sequences - // like `"` - the correct way to escape strings in our case depends on - // the file type. - autoescape: false, - }); - - if (ctx.input.cookiecutterCompat) { - // The "jsonify" filter built into cookiecutter is common - // in fetch:cookiecutter templates, so when compat mode - // is enabled we alias the "dump" filter from nunjucks as - // jsonify. Dump accepts an optional `spaces` parameter - // which enables indented output, but when this parameter - // is not supplied it works identically to jsonify. - // - // cf. https://cookiecutter.readthedocs.io/en/latest/advanced/template_extensions.html?highlight=jsonify#jsonify-extension - // cf. https://mozilla.github.io/nunjucks/templating.html#dump - templater.addFilter('jsonify', templater.getFilter('dump')); - } - // Cookiecutter prefixes all parameters in templates with // `cookiecutter.`. To replicate this, we wrap our parameters // in an object with a `cookiecutter` property when compat @@ -238,7 +199,9 @@ export function createFetchTemplateAction(options: { renderFilename = renderContents = !nonTemplatedEntries.has(location); } if (renderFilename) { - localOutputPath = templater.renderString(localOutputPath, context); + localOutputPath = templater.render(localOutputPath, context, { + cookiecutterCompat: ctx.input.cookiecutterCompat, + }); } const outputPath = resolvePath(outputDir, localOutputPath); // variables have been expanded to make an empty file name @@ -275,7 +238,9 @@ export function createFetchTemplateAction(options: { await fs.outputFile( outputPath, renderContents - ? templater.renderString(inputFileContents, context) + ? templater.render(inputFileContents, context, { + cookiecutterCompat: ctx.input.cookiecutterCompat, + }) : inputFileContents, { mode: statsObj.mode }, );