refactor(techdocs-common): clean up generator api

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2022-02-22 08:55:50 +01:00
parent e007c4fd7c
commit ced3d7cef8
7 changed files with 284 additions and 19 deletions
+9 -11
View File
@@ -17,21 +17,19 @@ import { ScmIntegrationRegistry } from '@backstage/integration';
import { UrlReader } from '@backstage/backend-common';
import { Writable } from 'stream';
// Warning: (ae-missing-release-tag) "DirectoryPreparer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public
export class DirectoryPreparer implements PreparerBase {
constructor(config: Config, _logger: Logger_2, reader: UrlReader);
// @deprecated
constructor(config: Config, _logger: Logger_2 | null, reader: UrlReader);
// Warning: (ae-forgotten-export) The symbol "DirectoryPreparerOptions" needs to be exported by the entry point index.d.ts
//
// (undocumented)
static fromConfig(options: DirectoryPreparerOptions): DirectoryPreparer;
// Warning: (ae-forgotten-export) The symbol "PreparerOptions" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "PreparerResponse" needs to be exported by the entry point index.d.ts
//
// (undocumented)
prepare(
entity: Entity,
options?: {
logger?: Logger_2;
etag?: string;
},
): Promise<PreparerResponse>;
prepare(entity: Entity, options?: PreparerOptions): Promise<PreparerResponse>;
}
// Warning: (ae-missing-release-tag) "GeneratorBase" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+195
View File
@@ -0,0 +1,195 @@
/*
* Copyright 2022 The Backstage Authors
*
* 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.
*/
export interface Config {
app: {
baseUrl: string; // defined in core, but repeated here without doc
};
backend: {
/** Backend configuration for when request authentication is enabled */
auth?: {
/** Keys shared by all backends for signing and validating backend tokens. */
keys: {
/**
* Secret for generating tokens. Should be a base64 string, recommended
* length is 24 bytes.
*
* @visibility secret
*/
secret: string;
}[];
};
baseUrl: string; // defined in core, but repeated here without doc
/** Address that the backend should listen to. */
listen:
| string
| {
/** Address of the interface that the backend should bind to. */
host?: string;
/** Port that the backend should listen to. */
port?: string | number;
};
/**
* HTTPS configuration for the backend. If omitted the backend will serve HTTP.
*
* Setting this to `true` will cause self-signed certificates to be generated, which
* can be useful for local development or other non-production scenarios.
*/
https?:
| true
| {
/** Certificate configuration */
certificate?: {
/** PEM encoded certificate. Use $file to load in a file */
cert: string;
/**
* PEM encoded certificate key. Use $file to load in a file.
* @visibility secret
*/
key: string;
};
};
/** Database connection configuration, select base database type using the `client` field */
database: {
/** Default database client to use */
client: 'sqlite3' | 'pg';
/**
* Base database connection string or Knex object
* @secret
*/
connection: string | object;
/** Database name prefix override */
prefix?: string;
/**
* Whether to ensure the given database exists by creating it if it does not.
* Defaults to true if unspecified.
*/
ensureExists?: boolean;
/**
* How plugins databases are managed/divided in the provided database instance.
*
* `database` -> Plugins are each given their own database to manage their schemas/tables.
*
* `schema` -> Plugins will be given their own schema (in the specified/default database)
* to manage their tables.
*
* NOTE: Currently only supported by the `pg` client.
*
* @default database
*/
pluginDivisionMode?: 'database' | 'schema';
/**
* Arbitrary config object to pass to knex when initializing
* (https://knexjs.org/#Installation-client). Most notable is the debug
* and asyncStackTraces booleans
*/
knexConfig?: object;
/** Plugin specific database configuration and client override */
plugin?: {
[pluginId: string]: {
/** Database client override */
client?: 'sqlite3' | 'pg';
/**
* Database connection string or Knex object override
* @secret
*/
connection?: string | object;
/**
* Whether to ensure the given database exists by creating it if it does not.
* Defaults to base config if unspecified.
*/
ensureExists?: boolean;
/**
* Arbitrary config object to pass to knex when initializing
* (https://knexjs.org/#Installation-client). Most notable is the
* debug and asyncStackTraces booleans.
*
* This is merged recursively into the base knexConfig
*/
knexConfig?: object;
};
};
};
/** Cache connection configuration, select cache type using the `store` field */
cache?:
| {
store: 'memory';
}
| {
store: 'memcache';
/**
* A memcache connection string in the form `user:pass@host:port`.
* @secret
*/
connection: string;
};
cors?: {
origin?: string | string[];
methods?: string | string[];
allowedHeaders?: string | string[];
exposedHeaders?: string | string[];
credentials?: boolean;
maxAge?: number;
preflightContinue?: boolean;
optionsSuccessStatus?: number;
};
/**
* Configuration related to URL reading, used for example for reading catalog info
* files, scaffolder templates, and techdocs content.
*/
reading?: {
/**
* A list of targets to allow outgoing requests to. Users will be able to make
* requests on behalf of the backend to the targets that are allowed by this list.
*/
allow?: Array<{
/**
* A host to allow outgoing requests to, being either a full host or
* a subdomain wildcard pattern with a leading `*`. For example `example.com`
* and `*.example.com` are valid values, `prod.*.example.com` is not.
* The host may also contain a port, for example `example.com:8080`.
*/
host: string;
/**
* An optional list of paths. In case they are present only targets matching
* any of them will are allowed. You can use trailing slashes to make sure only
* subdirectories are allowed, for example `/mydir/` will allow targets with
* paths like `/mydir/a` but will block paths like `/mydir2`.
*/
paths?: string[];
}>;
};
/**
* Content Security Policy options.
*
* The keys are the plain policy ID, e.g. "upgrade-insecure-requests". The
* values are on the format that the helmet library expects them, as an
* array of strings. There is also the special value false, which means to
* remove the default value that Backstage puts in place for that policy.
*/
csp?: { [policyId: string]: string[] | false };
};
}
@@ -26,9 +26,18 @@ import {
SupportedGeneratorKey,
} from './types';
/**
* Collection of docs generators
* @public
*/
export class Generators implements GeneratorBuilder {
private generatorMap = new Map<SupportedGeneratorKey, GeneratorBase>();
/**
* Returns a generators instance containing a generator for Tech Docs
* @param config - A Backstage configuration
* @param options - Options to configure the Tech Docs generator
*/
static async fromConfig(
config: Config,
options: { logger: Logger; containerRunner: ContainerRunner },
@@ -41,10 +50,19 @@ export class Generators implements GeneratorBuilder {
return generators;
}
/**
* Register a generator in the generators collection
* @param generatorKey - Unique identifier for the generator
* @param generator - The generator instance to register
*/
register(generatorKey: SupportedGeneratorKey, generator: GeneratorBase) {
this.generatorMap.set(generatorKey, generator);
}
/**
* Returns the generator for a given Tech Docs entity
* @param entity - A Tech Docs entity instance
*/
get(entity: Entity): GeneratorBase {
const generatorKey = getGeneratorKey(entity);
const generator = this.generatorMap.get(generatorKey);
@@ -16,7 +16,9 @@
export { TechdocsGenerator } from './techdocs';
export { Generators } from './generators';
export type {
GeneratorBuilder,
GeneratorBase,
GeneratorBuilder,
GeneratorFactory,
GeneratorRunOptions,
SupportedGeneratorKey,
} from './types';
@@ -38,7 +38,12 @@ import {
GeneratorRunOptions,
} from './types';
import { ForwardedError } from '@backstage/errors';
import { GeneratorFactory } from './types';
/**
* Generates documentation files
* @public
*/
export class TechdocsGenerator implements GeneratorBase {
/**
* The default docker image (and version) used to generate content. Public
@@ -50,10 +55,12 @@ export class TechdocsGenerator implements GeneratorBase {
private readonly options: GeneratorConfig;
private readonly scmIntegrations: ScmIntegrationRegistry;
static fromConfig(
config: Config,
options: { containerRunner: ContainerRunner; logger: Logger },
) {
/**
* Returns a instance of Tech Docs generator
* @param config - A Backstage configuration
* @param options - Options to configure the generator
*/
static fromConfig(config: Config, options: GeneratorFactory) {
const { containerRunner, logger } = options;
const scmIntegrations = ScmIntegrations.fromConfig(config);
return new TechdocsGenerator({
@@ -76,6 +83,7 @@ export class TechdocsGenerator implements GeneratorBase {
this.scmIntegrations = options.scmIntegrations;
}
/** {@inheritDoc GeneratorBase.run} */
public async run(options: GeneratorRunOptions): Promise<void> {
const {
inputDir,
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { ContainerRunner } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
import { Writable } from 'stream';
import { Logger } from 'winston';
@@ -22,6 +23,15 @@ import { ParsedLocationAnnotation } from '../../helpers';
// Determines where the generator will be run
export type GeneratorRunInType = 'docker' | 'local';
/**
* Options for building generators
* @public
*/
export type GeneratorFactory = {
containerRunner: ContainerRunner;
logger: Logger;
};
/**
* The techdocs generator configurations options.
*/
@@ -51,18 +61,27 @@ export type GeneratorRunOptions = {
logStream?: Writable;
};
/**
* Generates documentation files
* @public
*/
export type GeneratorBase = {
// Runs the generator with the values
/**
* Runs the generator with the values
* @public
*/
run(opts: GeneratorRunOptions): Promise<void>;
};
/**
* List of supported generator options
* @public
*/
export type SupportedGeneratorKey = 'techdocs' | string;
/**
* The generator builder holds the generator ready for run time
* @public
*/
export type GeneratorBuilder = {
register(protocol: SupportedGeneratorKey, generator: GeneratorBase): void;
@@ -26,18 +26,43 @@ import { Logger } from 'winston';
import { parseReferenceAnnotation, transformDirLocation } from '../../helpers';
import { PreparerBase, PreparerResponse } from './types';
export type DirectoryPreparerOptions = {
config: Config;
reader: UrlReader;
};
export type PreparerOptions = { logger?: Logger; etag?: string };
/**
* Prepares files before building documentation
*
* @public
*/
export class DirectoryPreparer implements PreparerBase {
private readonly scmIntegrations: ScmIntegrationRegistry;
private readonly reader: UrlReader;
constructor(config: Config, _logger: Logger, reader: UrlReader) {
/**
* @deprecated use static fromConfig method instead.
*/
constructor(config: Config, _logger: Logger | null, reader: UrlReader) {
this.reader = reader;
this.scmIntegrations = ScmIntegrations.fromConfig(config);
}
static fromConfig(options: DirectoryPreparerOptions): DirectoryPreparer {
return new DirectoryPreparer(options.config, null, options.reader);
}
/**
*
* @param entity - The parts of the format that's common to all versions/kinds of entity
* @param options - Optional logger and etag
* @returns
*/
async prepare(
entity: Entity,
options?: { logger?: Logger; etag?: string },
options?: PreparerOptions,
): Promise<PreparerResponse> {
const annotation = parseReferenceAnnotation(
'backstage.io/techdocs-ref',