diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts index 6259023476..a61e5de867 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/github.test.ts @@ -47,7 +47,8 @@ describe('GitHubPreparer', () => { generation: 1, }, spec: { - type: 'cookiecutter', + type: 'website', + templater: 'cookiecutter', path: './template', schema: { $schema: 'http://json-schema.org/draft-07/schema#', diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/helpers.test.ts index c5380fa5d1..8b174a1edf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/helpers.test.ts @@ -39,7 +39,8 @@ describe('Helpers', () => { generation: 1, }, spec: { - type: 'cookiecutter', + type: 'website', + templater: 'cookiecutter', path: './template', schema: { $schema: 'http://json-schema.org/draft-07/schema#', @@ -86,7 +87,8 @@ describe('Helpers', () => { generation: 1, }, spec: { - type: 'cookiecutter', + type: 'website', + templater: 'cookiecutter', path: './template', schema: { $schema: 'http://json-schema.org/draft-07/schema#', @@ -131,7 +133,8 @@ describe('Helpers', () => { generation: 1, }, spec: { - type: 'cookiecutter', + type: 'website', + templater: 'cookiecutter', path: './template', schema: { $schema: 'http://json-schema.org/draft-07/schema#', @@ -177,7 +180,8 @@ describe('Helpers', () => { generation: 1, }, spec: { - type: 'cookiecutter', + type: 'website', + templater: 'cookiecutter', path: './template', schema: { $schema: 'http://json-schema.org/draft-07/schema#', @@ -221,7 +225,8 @@ describe('Helpers', () => { generation: 1, }, spec: { - type: 'cookiecutter', + type: 'website', + templater: 'cookiecutter', path: './template', schema: { $schema: 'http://json-schema.org/draft-07/schema#', diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/preparers.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/preparers.test.ts index 9807c63c08..b79adffb18 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/prepare/preparers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/prepare/preparers.test.ts @@ -35,8 +35,9 @@ describe('Preparers', () => { generation: 1, }, spec: { - type: 'cookiecutter', + templater: 'cookiecutter', path: '.', + type: 'website', schema: { $schema: 'http://json-schema.org/draft-07/schema#', required: ['storePath', 'owner'], @@ -88,7 +89,8 @@ describe('Preparers', () => { generation: 1, }, spec: { - type: 'cookiecutter', + type: 'website', + templater: 'cookiecutter', path: '.', schema: { $schema: 'http://json-schema.org/draft-07/schema#', diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/index.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/index.ts index dcfd2c9c34..38a96480c2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export * from './github'; +export * from './types'; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts index 9ef85224d4..0267d5eeab 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts @@ -16,6 +16,8 @@ import { Writable, PassThrough } from 'stream'; import Docker from 'dockerode'; import fs from 'fs'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { InputError } from '@backstage/backend-common'; export type RunDockerContainerOptions = { imageName: string; @@ -26,6 +28,20 @@ export type RunDockerContainerOptions = { dockerClient: Docker; }; +/** + * Gets the templater key to use for templating from the entity + * @param entity Template entity + */ +export const getTemplaterKey = (entity: TemplateEntityV1alpha1): string => { + const { templater } = entity.spec; + + if (!templater) { + throw new InputError('Template does not have a required templating key'); + } + + return templater; +}; + /** * * @param options the options object diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts index 0813d0ab86..56358f5c5b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts @@ -16,3 +16,4 @@ export * from './cookiecutter'; export * from './types'; export * from './helpers'; +export * from './templaters'; diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/templaters.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/templaters.test.ts new file mode 100644 index 0000000000..bf2f2ed9c6 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/templaters.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { Templaters } from '.'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { CookieCutter } from './cookiecutter'; + +describe('Templaters', () => { + const mockTemplate: TemplateEntityV1alpha1 = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Template', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'file:/Users/blam/dev/spotify/backstage/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml', + }, + name: 'react-ssr-template', + title: 'React SSR Template', + description: + 'Next.js application skeleton for creating isomorphic web applications.', + uid: '7357f4c5-aa58-4a1e-9670-18931eef771f', + etag: 'YWUxZWQyY2EtZDkxMC00MDM0LWI0ODAtMDgwMWY0YzdlMWIw', + generation: 1, + }, + spec: { + templater: 'cookiecutter', + path: '.', + type: 'website', + schema: { + $schema: 'http://json-schema.org/draft-07/schema#', + required: ['storePath', 'owner'], + properties: { + owner: { + type: 'string', + title: 'Owner', + description: 'Who is going to own this component', + }, + storePath: { + type: 'string', + title: 'Store path', + description: 'GitHub store path in org/repo format', + }, + }, + }, + }, + }; + it('should throw an error when the templater is not registered', () => { + const templaters = new Templaters(); + + expect(() => templaters.get(mockTemplate)).toThrow( + expect.objectContaining({ + message: 'No templater registered for template: "cookiecutter"', + }), + ); + }); + it('should return the correct templater when the templater matches', () => { + const templaters = new Templaters(); + const templater = new CookieCutter(); + + templaters.register('cookiecutter', templater); + + expect(templaters.get(mockTemplate)).toBe(templater); + }); + + it('should throw an error if the templater does not exist in the entity', () => { + const brokenTemplate: TemplateEntityV1alpha1 = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Template', + metadata: { + annotations: {}, + name: 'react-ssr-template', + title: 'React SSR Template', + description: + 'Next.js application skeleton for creating isomorphic web applications.', + uid: '7357f4c5-aa58-4a1e-9670-18931eef771f', + etag: 'YWUxZWQyY2EtZDkxMC00MDM0LWI0ODAtMDgwMWY0YzdlMWIw', + generation: 1, + }, + spec: { + type: 'website', + path: '.', + templater: '', + schema: { + $schema: 'http://json-schema.org/draft-07/schema#', + required: ['storePath', 'owner'], + properties: { + owner: { + type: 'string', + title: 'Owner', + description: 'Who is going to own this component', + }, + storePath: { + type: 'string', + title: 'Store path', + description: 'GitHub store path in org/repo format', + }, + }, + }, + }, + }; + + const templaters = new Templaters(); + + expect(() => templaters.get(brokenTemplate)).toThrow( + expect.objectContaining({ + name: 'InputError', + message: expect.stringContaining( + 'Template does not have a required templating key', + ), + }), + ); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/templaters.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/templaters.ts new file mode 100644 index 0000000000..549b898af8 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/templaters.ts @@ -0,0 +1,45 @@ +/* + * 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 { + TemplaterBase, + SupportedTemplatingKey, + TemplaterBuilder, +} from './types'; + +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; +import { getTemplaterKey } from './helpers'; + +export class Templaters implements TemplaterBuilder { + private preparerMap = new Map(); + + register(templaterKey: SupportedTemplatingKey, templater: TemplaterBase) { + this.preparerMap.set(templaterKey, templater); + } + + get(template: TemplateEntityV1alpha1): TemplaterBase { + const templaterKey = getTemplaterKey(template); + const preparer = this.preparerMap.get(templaterKey); + + if (!preparer) { + throw new Error( + `No templater registered for template: "${templaterKey}"`, + ); + } + + return preparer; + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts index 519c24e381..6a0fe60858 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/types.ts @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import type { Writable } from 'stream'; import Docker from 'dockerode'; import { JsonValue } from '@backstage/config'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; /** * Currently the required template values. The owner @@ -55,3 +55,16 @@ export type TemplaterBase = { export type TemplaterConfig = { templater?: TemplaterBase; }; + +/** + * List of supported templating options + */ +export type SupportedTemplatingKey = 'cookiecutter' | string; + +/** + * The templater builder holds the templaters ready for run time + */ +export type TemplaterBuilder = { + register(protocol: SupportedTemplatingKey, templater: TemplaterBase): void; + get(template: TemplateEntityV1alpha1): TemplaterBase; +};