Validate template values on calls to the scaffolder API

Calls to the scaffolder API should use the schema from the template
entity to validate input values.
This commit is contained in:
Iain Billett
2020-09-15 18:06:44 +01:00
parent 3e50f8ab74
commit ef9117f50a
4 changed files with 107 additions and 0 deletions
+1
View File
@@ -36,6 +36,7 @@
"git-url-parse": "^11.1.2",
"globby": "^11.0.0",
"helmet": "^4.0.0",
"jsonschema": "^1.2.6",
"morgan": "^1.10.0",
"nodegit": "0.26.5",
"uuid": "^8.2.0",
@@ -0,0 +1,94 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import express from 'express';
import request from 'supertest';
import { createRouter } from './router';
import { Templaters, Preparers, PublisherBase } from '../scaffolder';
import Docker from 'dockerode';
jest.mock('dockerode');
describe('createRouter', () => {
let app: express.Express;
const publisher: jest.Mocked<PublisherBase> = { publish: jest.fn() };
beforeAll(async () => {
const router = await createRouter({
logger: getVoidLogger(),
preparers: new Preparers(),
templaters: new Templaters(),
publisher: publisher,
dockerClient: new Docker(),
});
app = express().use(router);
});
beforeEach(() => {
jest.resetAllMocks();
});
describe('POST /v1/jobs', () => {
const template = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Template',
metadata: {
description: 'Create a new CRA website project',
name: 'create-react-app-template',
tags: ['experimental', 'react', 'cra'],
title: 'Create React App Template',
},
spec: {
owner: 'web@example.com',
path: '.',
schema: {
type: 'string',
properties: {
component_id: {
description: 'Unique name of the component',
title: 'Name',
type: 'string',
},
description: {
description: 'Description of the component',
title: 'Description',
type: 'string',
},
use_typescript: {
default: true,
description: 'Include typescript',
title: 'Use Typescript',
type: 'boolean',
},
},
required: ['component_id', 'use_typescript'],
},
templater: 'cra',
type: 'website',
},
};
it('rejects template values which do not match the template schema definition', async () => {
const response = await request(app).post('/v1/jobs').send({
template,
values: {},
});
expect(response.status).toEqual(400);
});
});
});
@@ -28,6 +28,7 @@ import {
TemplaterBuilder,
PublisherBase,
} from '../scaffolder';
import {validate, ValidatorResult} from 'jsonschema';
export interface RouterOptions {
preparers: PreparerBuilder;
@@ -84,6 +85,12 @@ export async function createRouter(
const values: RequiredTemplateValues & Record<string, JsonValue> =
req.body.values;
const validationResult: ValidatorResult = validate(values, template.spec.schema);
if (!validationResult.valid) {
res.status(400).json({ errors: validationResult.errors });
return;
}
const job = jobProcessor.create({
entity: template,
values,
+5
View File
@@ -13288,6 +13288,11 @@ jsonpointer@^4.0.1:
resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk=
jsonschema@^1.2.6:
version "1.2.6"
resolved "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.6.tgz#52b0a8e9dc06bbae7295249d03e4b9faee8a0c0b"
integrity sha512-SqhURKZG07JyKKeo/ir24QnS4/BV7a6gQy93bUSe4lUdNp0QNpIz2c9elWJQ9dpc5cQYY6cvCzgRwy0MQCLyqA==
jspdf-autotable@3.5.3:
version "3.5.3"
resolved "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.5.3.tgz#2f73adb07f340e7dbf22950e3e6c8bf853991479"