scaffolder-backend: switch jsonify flag for cookiecutter compat render option

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-11-19 18:53:34 +01:00
parent 7b5430fc82
commit 07daffed47
2 changed files with 68 additions and 23 deletions
@@ -43,16 +43,35 @@ describe('SecureTemplater', () => {
);
});
it('should make jsonify available when requested', async () => {
const templaterWith = new SecureTemplater({ jsonify: true });
await templaterWith.initializeIfNeeded();
const templaterWithout = new SecureTemplater();
await templaterWithout.initializeIfNeeded();
it('should make cookiecutter compatibility available when requested', async () => {
const templater = new SecureTemplater();
await templater.initializeIfNeeded();
expect(templaterWith.render('${{ 1 | jsonify }}', {})).toBe('1');
expect(() => templaterWithout.render('${{ 1 | jsonify }}', {})).toThrow(
// Same two tests repeated to make sure switching back and forth works
expect(
templater.render('${{ 1 | jsonify }}', {}, { cookiecutterCompat: true }),
).toBe('1');
expect(
templater.render('${{ 1 | jsonify }}', {}, { cookiecutterCompat: true }),
).toBe('1');
expect(() => templater.render('${{ 1 | jsonify }}', {})).toThrow(
'(unknown path)\n Error: filter not found: jsonify',
);
expect(
templater.render('${{ 1 | jsonify }}', {}, { cookiecutterCompat: true }),
).toBe('1');
expect(() => templater.render('${{ 1 | jsonify }}', {})).toThrow(
'(unknown path)\n Error: filter not found: jsonify',
);
expect(() => templater.render('${{ 1 | jsonify }}', {})).toThrow(
'(unknown path)\n Error: filter not found: jsonify',
);
expect(() => templater.render('${{ 1 | jsonify }}', {})).toThrow(
'(unknown path)\n Error: filter not found: jsonify',
);
expect(
templater.render('${{ 1 | jsonify }}', {}, { cookiecutterCompat: true }),
).toBe('1');
});
it('should make parseRepoUrl available when requested', async () => {
@@ -20,8 +20,9 @@ import fs from 'fs-extra';
import { RepoSpec } from '../../scaffolder/actions/builtin/publish/util';
const mkScript = (nunjucksSource: string) => `
const render = (() => {
const { render, renderCompat } = (() => {
const module = {};
const process = { env: {} };
const require = (pkg) => { if (pkg === 'events') { return function (){}; }};
${nunjucksSource}
@@ -34,10 +35,6 @@ const render = (() => {
},
});
if (typeof jsonify !== 'undefined' && jsonify) {
env.addFilter('jsonify', env.getFilter('dump'));
}
if (typeof parseRepoUrl !== 'undefined') {
env.addFilter('parseRepoUrl', repoUrl => {
return JSON.parse(parseRepoUrl(repoUrl))
@@ -48,53 +45,82 @@ const render = (() => {
});
}
return function render(str, values) {
let uninstallCompat = undefined;
function render(str, values) {
try {
if (uninstallCompat) {
uninstallCompat();
uninstallCompat = undefined;
delete env.filters.jsonify;
}
return env.renderString(str, JSON.parse(values));
} catch (error) {
// Make sure errors don't leak anything
throw new Error(String(error.message));
}
}
function renderCompat(str, values) {
try {
if (!uninstallCompat) {
uninstallCompat = module.exports.installJinjaCompat();
env.filters.jsonify = env.filters.dump;
}
return env.renderString(str, JSON.parse(values));
} catch (error) {
// Make sure errors don't leak anything
throw new Error(String(error.message));
}
}
return { render, renderCompat };
})();
`;
export interface SecureTemplaterRenderOptions {
/* Enables jinja compatibility and the "jsonify" filter */
cookiecutterCompat?: boolean;
}
export interface SecureTemplaterOptions {
jsonify?: true;
parseRepoUrl?(repoUrl: string): RepoSpec;
}
export class SecureTemplater {
#vm?: VM;
#jsonify?: true;
#parseRepoUrl?: (repoUrl: string) => RepoSpec;
constructor(options?: SecureTemplaterOptions) {
this.#jsonify = options?.jsonify;
this.#parseRepoUrl = options?.parseRepoUrl;
}
render(template: string, values: unknown): string {
render(
template: string,
values: unknown,
options?: SecureTemplaterRenderOptions,
): string {
if (!this.#vm) {
throw new Error('SecureTemplater has not been initialized');
}
this.#vm.setGlobal('templateStr', template);
this.#vm.setGlobal('templateValues', JSON.stringify(values));
const result = this.#vm.run(`render(templateStr, templateValues)`);
return result;
if (options?.cookiecutterCompat) {
return this.#vm.run(`renderCompat(templateStr, templateValues)`);
}
return this.#vm.run(`render(templateStr, templateValues)`);
}
async initializeIfNeeded() {
if (!this.#vm) {
let sandbox = undefined;
if (this.#jsonify) {
sandbox = { jsonify: true };
}
if (this.#parseRepoUrl) {
const parseRepoUrl = this.#parseRepoUrl;
sandbox = {
...sandbox,
parseRepoUrl: (url: string) => JSON.stringify(parseRepoUrl(url)),
};
}