diff --git a/.changeset/fair-fans-flow.md b/.changeset/fair-fans-flow.md new file mode 100644 index 0000000000..abdc22440b --- /dev/null +++ b/.changeset/fair-fans-flow.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-techdocs-node': patch +--- + +Adds extension point for publishers to the techdocs backend diff --git a/plugins/techdocs-backend/src/plugin.ts b/plugins/techdocs-backend/src/plugin.ts index e3a0c60a15..fd0574fedc 100644 --- a/plugins/techdocs-backend/src/plugin.ts +++ b/plugins/techdocs-backend/src/plugin.ts @@ -29,11 +29,14 @@ import { PreparerBase, Preparers, Publisher, + PublisherBase, + PublisherType, RemoteProtocol, techdocsBuildsExtensionPoint, TechdocsGenerator, techdocsGeneratorExtensionPoint, techdocsPreparerExtensionPoint, + techdocsPublisherExtensionPoint, } from '@backstage/plugin-techdocs-node'; import { createRouter } from '@backstage/plugin-techdocs-backend'; import * as winston from 'winston'; @@ -85,6 +88,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, @@ -128,6 +141,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/api-report.md b/plugins/techdocs-node/api-report.md index 0b30170006..f03f9e1b0e 100644 --- a/plugins/techdocs-node/api-report.md +++ b/plugins/techdocs-node/api-report.md @@ -183,11 +183,15 @@ export class Preparers implements PreparerBuilder { } // @public -export class Publisher { +export class Publisher implements PublisherBuilder { static fromConfig( config: Config, options: PublisherFactory, ): Promise; + // (undocumented) + get(config: Config): PublisherBase; + // (undocumented) + register(type: PublisherType | 'techdocs', publisher: PublisherBase): void; } // @public @@ -202,10 +206,17 @@ export interface PublisherBase { publish(request: PublishRequest): Promise; } +// @public +export type PublisherBuilder = { + register(type: PublisherType, publisher: PublisherBase): void; + get(config: Config): PublisherBase; +}; + // @public export type PublisherFactory = { logger: Logger; discovery: PluginEndpointDiscovery; + customPublisher?: PublisherBase | undefined; }; // @public @@ -303,6 +314,15 @@ export interface TechdocsPreparerExtensionPoint { // @public export const techdocsPreparerExtensionPoint: ExtensionPoint; +// @public +export interface TechdocsPublisherExtensionPoint { + // (undocumented) + registerPublisher(type: PublisherType, publisher: PublisherBase): void; +} + +// @public +export const techdocsPublisherExtensionPoint: ExtensionPoint; + // @public export const transformDirLocation: ( entity: Entity, 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/index.ts b/plugins/techdocs-node/src/stages/publish/index.ts index c6f86f6bc1..f342ccf0ae 100644 --- a/plugins/techdocs-node/src/stages/publish/index.ts +++ b/plugins/techdocs-node/src/stages/publish/index.ts @@ -23,4 +23,5 @@ export type { MigrateRequest, ReadinessResponse, TechDocsMetadata, + PublisherBuilder, } from './types'; 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..09a6d9b375 100644 --- a/plugins/techdocs-node/src/stages/publish/publish.ts +++ b/plugins/techdocs-node/src/stages/publish/publish.ts @@ -20,14 +20,45 @@ 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 { +export class Publisher implements PublisherBuilder { + private publishers: Map = + new Map(); + + register(type: PublisherType | 'techdocs', 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 +68,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 +84,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; +};