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.
This commit is contained in:
Himanshu Mishra
2020-12-17 10:57:39 +01:00
parent 9d7b50f2f3
commit 2ac77aeec2
6 changed files with 87 additions and 38 deletions
@@ -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<SupportedGeneratorKey, GeneratorBase>();
static async fromConfig(
config: Config,
{ logger }: { logger: Logger },
): Promise<GeneratorBuilder> {
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);
}
@@ -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<RemoteProtocol, PreparerBase>();
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<PreparerBuilder> {
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);
}
@@ -23,24 +23,27 @@ import { LocalPublish } from './local';
import { GoogleGCSPublish } from './googleStorage';
const logger = getVoidLogger();
const testDiscovery: jest.Mocked<PluginEndpointDiscovery> = {
const discovery: jest.Mocked<PluginEndpointDiscovery> = {
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);
});
});
@@ -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<PublisherBase> {
const publisherType = (config.getOptionalString(
'techdocs.publisher.type',
) ?? 'local') as PublisherType;