From c17cf4fd23556921f54afe9738162e5750bbfa86 Mon Sep 17 00:00:00 2001 From: John Philip Date: Sat, 22 Jun 2024 13:00:35 -0400 Subject: [PATCH] add publisher extension point Signed-off-by: John Philip --- plugins/techdocs-backend/src/plugin.ts | 14 ++++ plugins/techdocs-node/src/extensions.ts | 27 +++++- plugins/techdocs-node/src/index.ts | 2 + .../src/stages/publish/publish.test.ts | 17 ++++ .../src/stages/publish/publish.ts | 83 +++++++++++++++++-- .../techdocs-node/src/stages/publish/types.ts | 11 +++ 6 files changed, 144 insertions(+), 10 deletions(-) diff --git a/plugins/techdocs-backend/src/plugin.ts b/plugins/techdocs-backend/src/plugin.ts index 094335a0f0..1b495bafee 100644 --- a/plugins/techdocs-backend/src/plugin.ts +++ b/plugins/techdocs-backend/src/plugin.ts @@ -30,11 +30,14 @@ import { PreparerBase, Preparers, Publisher, + PublisherBase, + PublisherType, RemoteProtocol, techdocsBuildsExtensionPoint, TechdocsGenerator, techdocsGeneratorExtensionPoint, techdocsPreparerExtensionPoint, + techdocsPublisherExtensionPoint, } from '@backstage/plugin-techdocs-node'; import Docker from 'dockerode'; import { createRouter } from '@backstage/plugin-techdocs-backend'; @@ -87,6 +90,16 @@ export const techdocsPlugin = createBackendPlugin({ }, }); + let customTechdocsPublisher: PublisherBase | undefined; + env.registerExtensionPoint(techdocsPublisherExtensionPoint, { + registerPublisher(type: PublisherType, publisher: PublisherBase) { + if (customTechdocsPublisher) { + throw new Error(`Publisher for type ${type} is already registered`); + } + customTechdocsPublisher = publisher; + }, + }); + env.registerInit({ deps: { config: coreServices.rootConfig, @@ -135,6 +148,7 @@ export const techdocsPlugin = createBackendPlugin({ const publisher = await Publisher.fromConfig(config, { logger: winstonLogger, discovery: discovery, + customPublisher: customTechdocsPublisher, }); // checks if the publisher is working and logs the result diff --git a/plugins/techdocs-node/src/extensions.ts b/plugins/techdocs-node/src/extensions.ts index 8e7c9ef244..c32a0d2db5 100644 --- a/plugins/techdocs-node/src/extensions.ts +++ b/plugins/techdocs-node/src/extensions.ts @@ -15,7 +15,13 @@ */ import { createExtensionPoint } from '@backstage/backend-plugin-api'; import { DocsBuildStrategy } from './techdocsTypes'; -import { PreparerBase, RemoteProtocol, TechdocsGenerator } from './stages'; +import { + PreparerBase, + PublisherBase, + PublisherType, + RemoteProtocol, + TechdocsGenerator, +} from './stages'; import * as winston from 'winston'; /** @@ -75,3 +81,22 @@ export const techdocsPreparerExtensionPoint = createExtensionPoint({ id: 'techdocs.preparer', }); + +/** + * Extension point type for configuring a custom Techdocs publisher + * + * @public + */ +export interface TechdocsPublisherExtensionPoint { + registerPublisher(type: PublisherType, publisher: PublisherBase): void; +} + +/** + * Extension point for configuring a custom Techdocs publisher + * + * @public + */ +export const techdocsPublisherExtensionPoint = + createExtensionPoint({ + id: 'techdocs.publisher', + }); diff --git a/plugins/techdocs-node/src/index.ts b/plugins/techdocs-node/src/index.ts index b2efadf423..18f15f39ce 100644 --- a/plugins/techdocs-node/src/index.ts +++ b/plugins/techdocs-node/src/index.ts @@ -27,7 +27,9 @@ export { techdocsBuildsExtensionPoint, techdocsGeneratorExtensionPoint, techdocsPreparerExtensionPoint, + techdocsPublisherExtensionPoint, type TechdocsBuildsExtensionPoint, type TechdocsGeneratorExtensionPoint, type TechdocsPreparerExtensionPoint, + type TechdocsPublisherExtensionPoint, } from './extensions'; diff --git a/plugins/techdocs-node/src/stages/publish/publish.test.ts b/plugins/techdocs-node/src/stages/publish/publish.test.ts index 4059eccfeb..0063839e2f 100644 --- a/plugins/techdocs-node/src/stages/publish/publish.test.ts +++ b/plugins/techdocs-node/src/stages/publish/publish.test.ts @@ -25,6 +25,7 @@ import { AwsS3Publish } from './awsS3'; import { AzureBlobStoragePublish } from './azureBlobStorage'; import { OpenStackSwiftPublish } from './openStackSwift'; import { mockServices } from '@backstage/backend-test-utils'; +import { PublisherBase } from './types'; const logger = loggerToWinstonLogger(mockServices.logger.mock()); const discovery: jest.Mocked = { @@ -184,4 +185,20 @@ describe('Publisher', () => { }); expect(publisher).toBeInstanceOf(OpenStackSwiftPublish); }); + + it('registers a custom publisher if provided', async () => { + const mockConfig = new ConfigReader({}); + + const customPublisher = { + publish: jest.fn(), + } as unknown as PublisherBase; + + const publisher = await Publisher.fromConfig(mockConfig, { + logger, + discovery, + customPublisher, + }); + + expect(publisher).toBe(customPublisher); + }); }); diff --git a/plugins/techdocs-node/src/stages/publish/publish.ts b/plugins/techdocs-node/src/stages/publish/publish.ts index 732b161b42..4fb731a0ba 100644 --- a/plugins/techdocs-node/src/stages/publish/publish.ts +++ b/plugins/techdocs-node/src/stages/publish/publish.ts @@ -20,14 +20,47 @@ import { AzureBlobStoragePublish } from './azureBlobStorage'; import { GoogleGCSPublish } from './googleStorage'; import { LocalPublish } from './local'; import { OpenStackSwiftPublish } from './openStackSwift'; -import { PublisherFactory, PublisherBase, PublisherType } from './types'; +import { + PublisherFactory, + PublisherBase, + PublisherType, + PublisherBuilder, +} from './types'; /** * Factory class to create a TechDocs publisher based on defined publisher type in app config. * Uses `techdocs.publisher.type`. * @public */ -export class Publisher { + +type CustomPublisherType = PublisherType | 'techdocs'; + +export class Publisher implements PublisherBuilder { + private publishers: Map = new Map(); + + register(type: CustomPublisherType, publisher: PublisherBase): void { + this.publishers.set(type, publisher); + } + + get(config: Config): PublisherBase { + const publisherType = (config.getOptionalString( + 'techdocs.publisher.type', + ) ?? 'local') as PublisherType; + + if (!publisherType) { + throw new Error('TechDocs publisher type not specified for the entity'); + } + + const publisher = this.publishers.get(publisherType); + if (!publisher) { + throw new Error( + `TechDocs publisher '${publisherType}' is not registered`, + ); + } + + return publisher; + } + /** * Returns a instance of TechDocs publisher * @param config - A Backstage configuration @@ -37,7 +70,14 @@ export class Publisher { config: Config, options: PublisherFactory, ): Promise { - const { logger, discovery } = options; + const { logger, discovery, customPublisher } = options; + + const publishers = new Publisher(); + + if (customPublisher) { + publishers.register('techdocs', customPublisher); + return customPublisher; + } const publisherType = (config.getOptionalString( 'techdocs.publisher.type', @@ -46,26 +86,51 @@ export class Publisher { switch (publisherType) { case 'googleGcs': logger.info('Creating Google Storage Bucket publisher for TechDocs'); - return GoogleGCSPublish.fromConfig(config, logger); + publishers.register( + publisherType, + GoogleGCSPublish.fromConfig(config, logger), + ); + break; case 'awsS3': logger.info('Creating AWS S3 Bucket publisher for TechDocs'); - return await AwsS3Publish.fromConfig(config, logger); + publishers.register( + publisherType, + await AwsS3Publish.fromConfig(config, logger), + ); + break; case 'azureBlobStorage': logger.info( 'Creating Azure Blob Storage Container publisher for TechDocs', ); - return AzureBlobStoragePublish.fromConfig(config, logger); + publishers.register( + publisherType, + AzureBlobStoragePublish.fromConfig(config, logger), + ); + break; case 'openStackSwift': logger.info( 'Creating OpenStack Swift Container publisher for TechDocs', ); - return OpenStackSwiftPublish.fromConfig(config, logger); + publishers.register( + publisherType, + OpenStackSwiftPublish.fromConfig(config, logger), + ); + break; case 'local': logger.info('Creating Local publisher for TechDocs'); - return LocalPublish.fromConfig(config, logger, discovery); + publishers.register( + publisherType, + LocalPublish.fromConfig(config, logger, discovery), + ); + break; default: logger.info('Creating Local publisher for TechDocs'); - return LocalPublish.fromConfig(config, logger, discovery); + publishers.register( + publisherType, + LocalPublish.fromConfig(config, logger, discovery), + ); } + + return publishers.get(config); } } diff --git a/plugins/techdocs-node/src/stages/publish/types.ts b/plugins/techdocs-node/src/stages/publish/types.ts index ff5b9acade..3acd48a468 100644 --- a/plugins/techdocs-node/src/stages/publish/types.ts +++ b/plugins/techdocs-node/src/stages/publish/types.ts @@ -17,6 +17,7 @@ import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Logger } from 'winston'; import express from 'express'; +import { Config } from '@backstage/config'; /** * Options for building publishers @@ -25,6 +26,7 @@ import express from 'express'; export type PublisherFactory = { logger: Logger; discovery: PluginEndpointDiscovery; + customPublisher?: PublisherBase | undefined; }; /** @@ -157,3 +159,12 @@ export interface PublisherBase { */ migrateDocsCase?(migrateRequest: MigrateRequest): Promise; } + +/** + * Definition for a TechDocs publisher builder + * @public + */ +export type PublisherBuilder = { + register(type: PublisherType, publisher: PublisherBase): void; + get(config: Config): PublisherBase; +};