From 2ac77aeec216ed02611a8ac2247d547ead10921a Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 17 Dec 2020 10:57:39 +0100 Subject: [PATCH] techdocs-backend: Remove individual preparers and generators from app backend, to use factory methods When a new Backstage app is setup, the techdocs backend file contains all the preparers (dir, github, gitlab, etc.) and same for generators. While this providers added customizibility, it comes with a cost of complexity in setting up the app backend. New preparers' updates would need users to update their app's techdocs backend file. Scaffolder backend has already moved to this proposed pattern. --- packages/backend/src/plugins/techdocs.ts | 35 +++++++------------ .../src/stages/generate/generators.ts | 18 ++++++++-- .../src/stages/prepare/preparers.ts | 35 +++++++++++++++++-- .../src/stages/publish/publish.test.ts | 23 ++++++++---- .../src/stages/publish/publish.ts | 12 ++++--- .../src/service/standaloneServer.ts | 2 +- 6 files changed, 87 insertions(+), 38 deletions(-) diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts index 4822de9c5c..afe92cc347 100644 --- a/packages/backend/src/plugins/techdocs.ts +++ b/packages/backend/src/plugins/techdocs.ts @@ -15,12 +15,8 @@ */ import { createRouter, - DirectoryPreparer, Preparers, Generators, - TechdocsGenerator, - CommonGitPreparer, - UrlPreparer, Publisher, } from '@backstage/plugin-techdocs-backend'; import { PluginEnvironment } from '../types'; @@ -33,30 +29,25 @@ export default async function createPlugin({ reader, }: PluginEnvironment) { // Preparers are responsible for fetching source files for documentation. - const preparers = new Preparers(); - - const directoryPreparer = new DirectoryPreparer(logger); - preparers.register('dir', directoryPreparer); - - const commonGitPreparer = new CommonGitPreparer(logger); - preparers.register('github', commonGitPreparer); - preparers.register('gitlab', commonGitPreparer); - preparers.register('azure/api', commonGitPreparer); - - const urlPreparer = new UrlPreparer(reader, logger); - preparers.register('url', urlPreparer); + const preparers = await Preparers.fromConfig(config, { + logger, + reader, + }); // Generators are used for generating documentation sites. - const generators = new Generators(); - const techdocsGenerator = new TechdocsGenerator(logger, config); - generators.register('techdocs', techdocsGenerator); + const generators = await Generators.fromConfig(config, { + logger, + }); - // Publishers are used for + // Publisher is used for // 1. Publishing generated files to storage // 2. Fetching files from storage and passing them to TechDocs frontend. - const publisher = Publisher.fromConfig(config, logger, discovery); + const publisher = await Publisher.fromConfig(config, { + logger, + discovery, + }); - // Docker client used by the generators. + // Docker client (conditionally) used by the generators, based on techdocs.generators config. const dockerClient = new Docker(); return await createRouter({ diff --git a/packages/techdocs-common/src/stages/generate/generators.ts b/packages/techdocs-common/src/stages/generate/generators.ts index f28a169942..cbfa5e7553 100644 --- a/packages/techdocs-common/src/stages/generate/generators.ts +++ b/packages/techdocs-common/src/stages/generate/generators.ts @@ -14,18 +14,32 @@ * limitations under the License. */ +import { Logger } from 'winston'; +import { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { TechdocsGenerator } from '.'; import { GeneratorBase, SupportedGeneratorKey, GeneratorBuilder, } from './types'; - -import { Entity } from '@backstage/catalog-model'; import { getGeneratorKey } from './helpers'; export class Generators implements GeneratorBuilder { private generatorMap = new Map(); + static async fromConfig( + config: Config, + { logger }: { logger: Logger }, + ): Promise { + const generators = new Generators(); + + const techdocsGenerator = new TechdocsGenerator(logger, config); + generators.register('techdocs', techdocsGenerator); + + return generators; + } + register(generatorKey: SupportedGeneratorKey, generator: GeneratorBase) { this.generatorMap.set(generatorKey, generator); } diff --git a/packages/techdocs-common/src/stages/prepare/preparers.ts b/packages/techdocs-common/src/stages/prepare/preparers.ts index 52a47957e8..4a2d60eb31 100644 --- a/packages/techdocs-common/src/stages/prepare/preparers.ts +++ b/packages/techdocs-common/src/stages/prepare/preparers.ts @@ -13,14 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { PreparerBase, RemoteProtocol, PreparerBuilder } from './types'; +import { Logger } from 'winston'; +import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { DirectoryPreparer, CommonGitPreparer, UrlPreparer } from '.'; +import { PreparerBase, RemoteProtocol, PreparerBuilder } from './types'; import { parseReferenceAnnotation } from '../../helpers'; +type factoryOptions = { + logger: Logger; + reader: UrlReader; +}; + export class Preparers implements PreparerBuilder { private preparerMap = new Map(); + static async fromConfig( + // @ts-ignore + // Config not used now, but will be used in urlPreparer when it starts using + // @backstage/integration to get the tokens for providers. + config: Config, + { logger, reader }: factoryOptions, + ): Promise { + const preparers = new Preparers(); + + const directoryPreparer = new DirectoryPreparer(logger); + preparers.register('dir', directoryPreparer); + + const commonGitPreparer = new CommonGitPreparer(logger); + preparers.register('github', commonGitPreparer); + preparers.register('gitlab', commonGitPreparer); + preparers.register('azure/api', commonGitPreparer); + + const urlPreparer = new UrlPreparer(reader, logger); + preparers.register('url', urlPreparer); + + return preparers; + } + register(protocol: RemoteProtocol, preparer: PreparerBase) { this.preparerMap.set(protocol, preparer); } diff --git a/packages/techdocs-common/src/stages/publish/publish.test.ts b/packages/techdocs-common/src/stages/publish/publish.test.ts index dbced6db29..c61b9aa3ec 100644 --- a/packages/techdocs-common/src/stages/publish/publish.test.ts +++ b/packages/techdocs-common/src/stages/publish/publish.test.ts @@ -23,24 +23,27 @@ import { LocalPublish } from './local'; import { GoogleGCSPublish } from './googleStorage'; const logger = getVoidLogger(); -const testDiscovery: jest.Mocked = { +const discovery: jest.Mocked = { getBaseUrl: jest.fn().mockResolvedValueOnce('http://localhost:7000'), getExternalBaseUrl: jest.fn(), }; describe('Publisher', () => { - it('should create local publisher by default', () => { + it('should create local publisher by default', async () => { const mockConfig = new ConfigReader({ techdocs: { requestUrl: 'http://localhost:7000', }, }); - const publisher = Publisher.fromConfig(mockConfig, logger, testDiscovery); + const publisher = await Publisher.fromConfig(mockConfig, { + logger, + discovery, + }); expect(publisher).toBeInstanceOf(LocalPublish); }); - it('should create local publisher from config', () => { + it('should create local publisher from config', async () => { const mockConfig = new ConfigReader({ techdocs: { requestUrl: 'http://localhost:7000', @@ -50,11 +53,14 @@ describe('Publisher', () => { }, }); - const publisher = Publisher.fromConfig(mockConfig, logger, testDiscovery); + const publisher = await Publisher.fromConfig(mockConfig, { + logger, + discovery, + }); expect(publisher).toBeInstanceOf(LocalPublish); }); - it('should create google gcs publisher from config', () => { + it('should create google gcs publisher from config', async () => { const mockConfig = new ConfigReader({ techdocs: { requestUrl: 'http://localhost:7000', @@ -69,7 +75,10 @@ describe('Publisher', () => { }, }); - const publisher = Publisher.fromConfig(mockConfig, logger, testDiscovery); + const publisher = await Publisher.fromConfig(mockConfig, { + logger, + discovery, + }); expect(publisher).toBeInstanceOf(GoogleGCSPublish); }); }); diff --git a/packages/techdocs-common/src/stages/publish/publish.ts b/packages/techdocs-common/src/stages/publish/publish.ts index 04a9d89996..95b5cf83e2 100644 --- a/packages/techdocs-common/src/stages/publish/publish.ts +++ b/packages/techdocs-common/src/stages/publish/publish.ts @@ -21,16 +21,20 @@ import { PublisherType, PublisherBase } from './types'; import { LocalPublish } from './local'; import { GoogleGCSPublish } from './googleStorage'; +type factoryOptions = { + logger: Logger; + discovery: PluginEndpointDiscovery; +}; + /** * Factory class to create a TechDocs publisher based on defined publisher type in app config. * Uses `techdocs.publisher.type`. */ export class Publisher { - static fromConfig( + static async fromConfig( config: Config, - logger: Logger, - discovery: PluginEndpointDiscovery, - ): PublisherBase { + { logger, discovery }: factoryOptions, + ): Promise { const publisherType = (config.getOptionalString( 'techdocs.publisher.type', ) ?? 'local') as PublisherType; diff --git a/plugins/techdocs-backend/src/service/standaloneServer.ts b/plugins/techdocs-backend/src/service/standaloneServer.ts index 3faf736ca0..65a286feef 100644 --- a/plugins/techdocs-backend/src/service/standaloneServer.ts +++ b/plugins/techdocs-backend/src/service/standaloneServer.ts @@ -59,7 +59,7 @@ export async function startStandaloneServer( const techdocsGenerator = new TechdocsGenerator(logger, config); generators.register('techdocs', techdocsGenerator); - const publisher = Publisher.fromConfig(config, logger, discovery); + const publisher = await Publisher.fromConfig(config, { logger, discovery }); const dockerClient = new Docker();