From 12db9bc694dba19bf992e91632f8283ac1738ca3 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Tue, 23 Jun 2020 14:17:28 +0200 Subject: [PATCH] chore: add tests, merge cookiecutter.json Co-authored-by: Ben Lambert --- plugins/scaffolder-backend/package.json | 1 + .../react-ssr-template/cookiecutter.json | 3 + .../scaffolder/templater/cookiecutter.test.ts | 119 ++++++++++++++++++ .../src/scaffolder/templater/cookiecutter.ts | 42 +++++-- 4 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 plugins/scaffolder-backend/sample-templates/react-ssr-template/cookiecutter.json create mode 100644 plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.test.ts diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index e85a65e744..b356f026e5 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -23,6 +23,7 @@ "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.9", "@backstage/catalog-model": "^0.1.1-alpha.9", + "@backstage/config": "^0.1.1-alpha.9", "@types/express": "^4.17.6", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/cookiecutter.json b/plugins/scaffolder-backend/sample-templates/react-ssr-template/cookiecutter.json new file mode 100644 index 0000000000..bc6e4b5347 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/cookiecutter.json @@ -0,0 +1,3 @@ +{ + "_copy_without_render": [".github/workflows/*"] +} diff --git a/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.test.ts b/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.test.ts new file mode 100644 index 0000000000..8362a759bf --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.test.ts @@ -0,0 +1,119 @@ +/* + * 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 { CookieCutter } from './cookiecutter'; +import fs from 'fs-extra'; +import os from 'os'; + +const mockDocker = { run: jest.fn() }; +jest.mock( + 'dockerode', + () => + class { + constructor() { + return mockDocker; + } + }, +); + +describe('CookieCutter Templater', () => { + const cookie = new CookieCutter(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should write a cookiecutter.json file with the values from the entitiy', async () => { + const tempdir = os.tmpdir(); + + const values = { + component_id: 'test', + description: 'description', + }; + + await cookie.run({ directory: tempdir, values }); + + const cookieCutterJson = await fs.readJSON(`${tempdir}/cookiecutter.json`); + + expect(cookieCutterJson).toEqual(expect.objectContaining(values)); + }); + + it('should merge any value that is in the cookiecutter.json path already', async () => { + const tempdir = os.tmpdir(); + const existingJson = { + _copy_without_render: ['./github/workflows/*'], + }; + await fs.writeJSON(`${tempdir}/cookiecutter.json`, existingJson); + + const values = { + component_id: 'hello', + description: 'im something cool', + }; + + await cookie.run({ directory: tempdir, values }); + + const cookieCutterJson = await fs.readJSON(`${tempdir}/cookiecutter.json`); + + expect(cookieCutterJson).toEqual({ ...existingJson, ...values }); + }); + + it('should run the correct docker container with the correct bindings for the volumes', async () => { + const tempdir = os.tmpdir(); + + const values = { + component_id: 'test', + description: 'description', + }; + + await cookie.run({ directory: tempdir, values }); + + const realpath = await fs.realpath(tempdir); + + // TODO(blam): This might change when we publish our own cookiecutter image + // to @backstage/cookiecutter in docker hub. + expect(mockDocker.run).toHaveBeenCalledWith( + 'backstage/cookiecutter', + ['cookiecutter', '--no-input', '-o', '/result', '/template', '--verbose'], + process.stdout, + expect.objectContaining({ + HostConfig: { + Binds: expect.arrayContaining([ + `${realpath}:/template`, + `${realpath}/result:/result`, + ]), + }, + Volumes: { + '/template': {}, + '/result': {}, + }, + }), + ); + }); + + it('should return the result path to the end templated folder', async () => { + const tempdir = os.tmpdir(); + + const values = { + component_id: 'test', + description: 'description', + }; + + const path = await cookie.run({ directory: tempdir, values }); + + const realpath = await fs.realpath(tempdir); + + expect(path).toBe(`${realpath}/result`); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.ts index cabb1d4f1e..e927108dc4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/templater/cookiecutter.ts @@ -17,32 +17,52 @@ import { TemplaterBase, TemplaterRunOptions } from '.'; */ import fs from 'fs-extra'; import Docker from 'dockerode'; +import { JsonValue } from '@backstage/config'; export class CookieCutter implements TemplaterBase { - private docker:Docker; + private docker: Docker; constructor() { this.docker = new Docker(); } + private async fetchTemplateCookieCutter( + directory: string, + ): Promise> { + try { + return await fs.readJSON(`${directory}/cookiecutter.json`); + } catch (ex) { + return {}; + } + } + public async run(options: TemplaterRunOptions): Promise { - - - - // first we need to make cookiecutter.json in the directory provided with the input values. + // First lets grab the default cookiecutter.json file + const cookieCutterJson = await this.fetchTemplateCookieCutter( + options.directory, + ); + const cookieInfo = { - _copy_without_render: ['.github/workflows/*'], + ...cookieCutterJson, ...options.values, }; await fs.writeJSON(`${options.directory}/cookiecutter.json`, cookieInfo); + const realTemplatePath = await fs.promises.realpath(options.directory); - const outDir = realTemplatePath + '/result'; + const outDir = `${realTemplatePath}/result`; + await this.docker.run( + 'backstage/cookiecutter', + ['cookiecutter', '--no-input', '-o', '/result', '/template', '--verbose'], + process.stdout, + { + Volumes: { '/result': {}, '/template': {} }, + HostConfig: { + Binds: [`${outDir}:/result`, `${realTemplatePath}:/template`], + }, + }, + ); - await this.docker.run('backstage/cookiecutter', ['cookiecutter', '--no-input', '-o', '/result', '/template', '--verbose'], process.stdout, {Volumes: { '/result': {}, '/template': {}}, HostConfig: { - Binds: [`${outDir}:/result`, `${realTemplatePath}:/template`], - }}); - return outDir; } }