diff --git a/packages/catalog-model/src/entity/Entity.ts b/packages/catalog-model/src/entity/Entity.ts index 70d2ba4d2b..d4f10ab883 100644 --- a/packages/catalog-model/src/entity/Entity.ts +++ b/packages/catalog-model/src/entity/Entity.ts @@ -48,7 +48,7 @@ export type Entity = { * @see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta * @see https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/ */ -export type EntityMeta = { +export type EntityMeta = Record & { /** * A globally unique ID for the entity. * diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts index 090c892294..0bef220a63 100644 --- a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts @@ -26,6 +26,7 @@ export interface TemplateEntityV1alpha1 extends Entity { kind: typeof KIND; spec: { type: string; + path?: string; }; } @@ -39,6 +40,7 @@ export class TemplateEntityV1alpha1Policy implements EntityPolicy { spec: yup .object({ type: yup.string().required().min(1), + path: yup.string(), }) .required(), }); diff --git a/plugins/scaffolder-backend/src/scaffolder/index.ts b/plugins/scaffolder-backend/src/scaffolder/index.ts index 29a3d99ef0..4cc3f21a9d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ export * from './templater'; - +export * from './prepare'; export * from './templater/cookiecutter'; diff --git a/plugins/scaffolder-backend/src/scaffolder/storage/index.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/file.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/storage/index.ts rename to plugins/scaffolder-backend/src/scaffolder/prepare/file.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/storage/types.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts similarity index 75% rename from plugins/scaffolder-backend/src/scaffolder/storage/types.ts rename to plugins/scaffolder-backend/src/scaffolder/prepare/index.ts index 80c860a911..748e5cebc7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/storage/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/index.ts @@ -13,12 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; - -export type StorageBase = { - /** - * - * @param id - */ - prepare(template: TemplateEntityV1alpha1): Promise; -}; +export * from './preparers'; +export * from './types'; diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts new file mode 100644 index 0000000000..dc3e318926 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/preparers.ts @@ -0,0 +1,51 @@ +/* + * 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 { PreparerBase, RemoteLocation, PreparerBuilder } from './types'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; + +export class Preparers implements PreparerBuilder { + private preparerMap = new Map(); + + register(key: RemoteLocation, processor: PreparerBase) { + this.preparerMap.set(key, processor); + } + + get(template: TemplateEntityV1alpha1): PreparerBase { + const preparerKey = this.getPreparerKeyFromEntity(template); + const preparer = this.preparerMap.get(preparerKey); + + if (!preparer) { + throw new Error(`No preparer registered for type ${preparerKey}`); + } + + return preparer; + } + + private getPreparerKeyFromEntity( + entity: TemplateEntityV1alpha1, + ): RemoteLocation { + const annotation = + entity.metadata.annotations?.['backstage.io/managed-by-location'] ?? ''; + const [key] = annotation?.split(':'); + + if (!key) { + throw new Error('Failed to parse the location data'); + } + + return key as RemoteLocation; + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts b/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts new file mode 100644 index 0000000000..1c703d10fd --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/prepare/types.ts @@ -0,0 +1,32 @@ +/* + * 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 type { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; + +export type PreparerBase = { + /** + * Given an Entity definition from the Service Catalog, go and prepare a directory + * with contents from the remote location in temporary storage and return the path + * @param template The template entity from the Service Catalog + */ + prepare(template: TemplateEntityV1alpha1): Promise; +}; + +export type PreparerBuilder = { + register(key: RemoteLocation, preparer: PreparerBase): void; + get(key: TemplateEntityV1alpha1): PreparerBase; +}; + +export type RemoteLocation = 'file'; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 97c177b915..57ac1413fc 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -17,10 +17,11 @@ import { Logger } from 'winston'; import Router from 'express-promise-router'; import express from 'express'; -import { StorageBase, TemplaterBase } from '../scaffolder'; +import { PreparerBuilder, TemplaterBase } from '../scaffolder'; +import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; export interface RouterOptions { - storage: StorageBase; + preparers: PreparerBuilder; templater: TemplaterBase; logger: Logger; } @@ -29,22 +30,43 @@ export async function createRouter( options: RouterOptions, ): Promise { const router = Router(); - const { storage, templater, logger: parentLogger } = options; + const { preparers, templater, logger: parentLogger } = options; const logger = parentLogger.child({ plugin: 'scaffolder' }); - router - .get('/v1/templates', async (_, res) => { - const templates = await storage.list(); - res.status(200).json(templates); - }) - .post('/v1/jobs', async (_, res) => { - // TODO(blam): Actually make this function work - const mock = 'templateid'; - res.status(201).json({ accepted: true }); + router.post('/v1/jobs', async (_, res) => { + // TODO(blam): Actually make this function work + // res.status(201).json({ accepted: true }); - const path = await storage.prepare(mock); - await templater.run({ directory: path, values: { componentId: 'test' } }); - }); + const mockEntity: 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: { + type: 'cookiecutter', + path: '.', + }, + }; + + // fetch the entity from service catalog here. + const preparer = preparers.get(mockEntity); + + // + const path = await preparer.prepare(mockEntity); + + await templater.run({ directory: path, values: { componentId: 'test' } }); + }); const app = express(); app.set('logger', logger);