scaffolder-backend: jsonify + repoUrl helpers for SecureTemplater + error wrapping
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -41,4 +41,58 @@ describe('SecureTemplater', () => {
|
||||
'(unknown path) [Line 1, Column 13]\n expected name as lookup value, got .',
|
||||
);
|
||||
});
|
||||
|
||||
it('should make jsonify available when requested', async () => {
|
||||
const templaterWith = new SecureTemplater({ jsonify: true });
|
||||
const templaterWithout = new SecureTemplater();
|
||||
|
||||
await expect(templaterWith.render('${{ 1 | jsonify }}', {})).resolves.toBe(
|
||||
'1',
|
||||
);
|
||||
await expect(
|
||||
templaterWithout.render('${{ 1 | jsonify }}', {}),
|
||||
).rejects.toThrow('(unknown path)\n Error: filter not found: jsonify');
|
||||
});
|
||||
|
||||
it('should make parseRepoUrl available when requested', async () => {
|
||||
const parseRepoUrl = jest.fn(() => ({
|
||||
repo: 'my-repo',
|
||||
owner: 'my-owner',
|
||||
host: 'my-host.com',
|
||||
}));
|
||||
const templaterWith = new SecureTemplater({
|
||||
parseRepoUrl,
|
||||
});
|
||||
const templaterWithout = new SecureTemplater();
|
||||
|
||||
const ctx = {
|
||||
repoUrl: 'https://my-host.com/my-owner/my-repo',
|
||||
};
|
||||
|
||||
await expect(
|
||||
templaterWith.render('${{ repoUrl | parseRepoUrl | dump }}', ctx),
|
||||
).resolves.toBe(
|
||||
JSON.stringify({
|
||||
repo: 'my-repo',
|
||||
owner: 'my-owner',
|
||||
host: 'my-host.com',
|
||||
}),
|
||||
);
|
||||
await expect(
|
||||
templaterWith.render('${{ repoUrl | projectSlug }}', ctx),
|
||||
).resolves.toBe('my-owner/my-repo');
|
||||
await expect(
|
||||
templaterWithout.render('${{ repoUrl | parseRepoUrl | dump }}', ctx),
|
||||
).rejects.toThrow(
|
||||
'(unknown path)\n Error: filter not found: parseRepoUrl',
|
||||
);
|
||||
await expect(
|
||||
templaterWithout.render('${{ repoUrl | projectSlug }}', ctx),
|
||||
).rejects.toThrow('(unknown path)\n Error: filter not found: projectSlug');
|
||||
|
||||
expect(parseRepoUrl.mock.calls).toEqual([
|
||||
['https://my-host.com/my-owner/my-repo'],
|
||||
['https://my-host.com/my-owner/my-repo'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { VM } from 'vm2';
|
||||
import { resolvePackagePath } from '@backstage/backend-common';
|
||||
import fs from 'fs-extra';
|
||||
import { RepoSpec } from '../../scaffolder/actions/builtin/publish/util';
|
||||
|
||||
const mkScript = (nunjucksSource: string) => `
|
||||
const render = (() => {
|
||||
@@ -33,15 +34,47 @@ 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))
|
||||
});
|
||||
env.addFilter('projectSlug', repoUrl => {
|
||||
const { owner, repo } = JSON.parse(parseRepoUrl(repoUrl));
|
||||
return owner + '/' + repo;
|
||||
});
|
||||
}
|
||||
|
||||
return function render(str, values) {
|
||||
return env.renderString(str, JSON.parse(values));
|
||||
try {
|
||||
return env.renderString(str, JSON.parse(values));
|
||||
} catch (error) {
|
||||
// Make sure errors don't leak anything
|
||||
throw new Error(String(error.message));
|
||||
}
|
||||
}
|
||||
})();
|
||||
`;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
async render(template: string, values: unknown) {
|
||||
const vm = await this.getVm();
|
||||
vm.setGlobal('templateStr', template);
|
||||
@@ -52,9 +85,19 @@ export class SecureTemplater {
|
||||
|
||||
private async getVm() {
|
||||
if (!this.#vm) {
|
||||
this.#vm = new VM({
|
||||
timeout: 1000,
|
||||
});
|
||||
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)),
|
||||
};
|
||||
}
|
||||
|
||||
this.#vm = new VM({ timeout: 1000, sandbox });
|
||||
|
||||
const nunjucksSource = await fs.readFile(
|
||||
resolvePackagePath(
|
||||
|
||||
Reference in New Issue
Block a user