From c62c43d12f5e28411c78d7e09da4b270137d2706 Mon Sep 17 00:00:00 2001 From: blam Date: Sun, 26 Apr 2020 03:10:11 +0200 Subject: [PATCH] feat(scaffolder): working more on the scaffolder to make it nice --- plugins/scaffolder-backend/package.json | 10 +++- .../scaffolder-backend/src/lib/repo/disk.ts | 48 +++++++++++++------ .../scaffolder-backend/src/lib/repo/index.ts | 8 ++-- .../src/lib/templater/cookiecutter.ts | 34 +++++++++++++ .../src/lib/templater/index.ts | 48 +++++++++++++++++++ 5 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 plugins/scaffolder-backend/src/lib/templater/cookiecutter.ts create mode 100644 plugins/scaffolder-backend/src/lib/templater/index.ts diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index b531f78e9e..5bfde524c7 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -11,9 +11,15 @@ "clean": "backstage-cli clean" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4" + "@backstage/cli": "^0.1.1-alpha.4", + "@types/fs-extra": "^8.1.0", + "@types/supertest": "^2.0.8", + "supertest": "^4.0.2" }, "dependencies": { - "express": "^4.17.1" + "express": "^4.17.1", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "winston": "^3.2.1" } } diff --git a/plugins/scaffolder-backend/src/lib/repo/disk.ts b/plugins/scaffolder-backend/src/lib/repo/disk.ts index c53277a69a..60eb46b9c6 100644 --- a/plugins/scaffolder-backend/src/lib/repo/disk.ts +++ b/plugins/scaffolder-backend/src/lib/repo/disk.ts @@ -15,12 +15,18 @@ */ import glob from 'glob'; -import { promises as fs } from 'fs'; +import * as fs from 'fs-extra'; import { logger } from 'lib/logger'; import { Template, RepositoryBase as Base } from '.'; +interface DiskIndexEntry { + contents: Template; + location: string; +} + export class DiskRepository implements Base { private repository: Template[] = []; + private localIndex: DiskIndexEntry[] = []; constructor(private repoDir = `${__dirname}/templates`) { this.reindex(); @@ -31,14 +37,25 @@ export class DiskRepository implements Base { } public async reindex(): Promise { - this.repository = await this.index(); + this.localIndex = await this.index(); + this.repository = this.localIndex.map(({ contents }) => contents); } - public async prepare(template: string): Promise { - return template; + public async prepare(templateId: string): Promise { + const template = this.localIndex.find( + ({ contents }) => contents.id === templateId, + ); + + if (!template) { + throw new Error('Template no found'); + } + + const tempDir = await fs.promises.mkdtemp(templateId); + await fs.copy(template.location, tempDir); + return tempDir; } - private async index(): Promise { + private async index(): Promise { return new Promise((resolve, reject) => { glob(`${this.repoDir}/**/template-info.json`, async (err, matches) => { if (err) { @@ -46,32 +63,35 @@ export class DiskRepository implements Base { } const fileContents: Array<{ - path: string; + location: string; contents: string; }> = await Promise.all( - matches.map(async (path: string) => ({ - path, - contents: await fs.readFile(path, 'utf-8'), + matches.map(async (location: string) => ({ + location, + contents: await fs.readFile(location, 'utf-8'), })), ); const validFiles = fileContents.reduce( - (templates: Template[], currentFile) => { + (diskIndexEntries: DiskIndexEntry[], currentFile) => { try { const parsed: Template = JSON.parse(currentFile.contents); - return [...templates, parsed]; + return [ + ...diskIndexEntries, + { location: currentFile.location, contents: parsed }, + ]; } catch (ex) { logger.error('Failure parsing JSON for template', { - path: currentFile.path, + path: currentFile.location, }); } - return templates; + return diskIndexEntries; }, [], ); - resolve(validFiles as Template[]); + resolve(validFiles); }); }); } diff --git a/plugins/scaffolder-backend/src/lib/repo/index.ts b/plugins/scaffolder-backend/src/lib/repo/index.ts index ade9f0397e..358e8efd8c 100644 --- a/plugins/scaffolder-backend/src/lib/repo/index.ts +++ b/plugins/scaffolder-backend/src/lib/repo/index.ts @@ -23,13 +23,15 @@ export interface Template { } export abstract class RepositoryBase { + // lists all templates available abstract async list(): Promise; + // can be used to build an index of the available templates; abstract async reindex(): Promise; - // returns a directory to run cookiecutter in + // returns a directory to run the templaterin abstract async prepare(id: string): Promise; } -class Interface implements RepositoryBase { +class RepositoryImplementation implements RepositoryBase { repo?: RepositoryBase; constructor() { @@ -45,4 +47,4 @@ class Interface implements RepositoryBase { reindex = () => this.repo!.reindex(); } -export const Repository = new Interface(); +export const Repository = new RepositoryImplementation(); diff --git a/plugins/scaffolder-backend/src/lib/templater/cookiecutter.ts b/plugins/scaffolder-backend/src/lib/templater/cookiecutter.ts new file mode 100644 index 0000000000..219d511ef1 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templater/cookiecutter.ts @@ -0,0 +1,34 @@ +import { TemplaterBase, TemplaterRunOptions } from '.'; + +/* + * 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 * as fs from 'fs-extra'; + +class CookieCutter implements TemplaterBase { + public async run(options: TemplaterRunOptions): Promise { + // first we need to make cookiecutter.json in the directory provided with the input values. + const cookieInfo = { + _copy_without_render: ['.github/workflows/*'], + ...options.values, + }; + + await fs.writeJSON(options.directory, cookieInfo); + + // run cookie cutter with new json + } +} + +export const CookieCutterTemplater = new CookieCutter(); diff --git a/plugins/scaffolder-backend/src/lib/templater/index.ts b/plugins/scaffolder-backend/src/lib/templater/index.ts new file mode 100644 index 0000000000..fe07030cad --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/templater/index.ts @@ -0,0 +1,48 @@ +/* + * 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 { CookieCutterTemplater } from './cookiecutter'; + +export interface RequiredTemplateValues { + componentId: string; +} +export interface TemplaterRunOptions { + directory: string; + values: RequiredTemplateValues & object; +} + +export abstract class TemplaterBase { + // runs the templating with the values and returns the directory to push the VCS + abstract async run(opts: TemplaterRunOptions): Promise; +} + +class TemplaterImplementation implements TemplaterBase { + templater?: TemplaterBase; + + constructor() { + this.templater = new CookieCutterTemplaterutterTemplater(); + } + + public setTemplater(templater: TemplaterBase) { + this.templater = templater; + } + + public async run(opts: TemplaterRunOptions) { + return this.templater!.run(opts); + } +} + +export const Templater = new TemplaterImplementation();