Mob/techdocs end to end (#1736)

* Initial techdocs end to end heavily inspired by scaffolder

* added some tests and fixed linting

* fix(techdocs-backend): fix eslint

* fix(techdocs-backend): cleanup commented test

* Removed unused dependency

* fix(techdocs-backend): updated standalone server

* moved type dependency to devDependencies

* fix: updated dependencies and devDependencies.

* Update plugins/techdocs-backend/src/techdocs/stages/generate/types.ts

Co-authored-by: Emma Indal <emma.indahl@gmail.com>

Co-authored-by: Emma Indal <emmai@spotify.com>
Co-authored-by: ellinors <ellinors@spotify.com>
Co-authored-by: Emma Indal <emma.indahl@gmail.com>
This commit is contained in:
Sebastian Qvarfordt
2020-08-04 13:04:48 +02:00
committed by GitHub
parent 9d6a1c350a
commit 97f3f58844
26 changed files with 835 additions and 7 deletions
+2
View File
@@ -0,0 +1,2 @@
static
!src/techdocs/stages/build
+7
View File
@@ -22,14 +22,21 @@
},
"dependencies": {
"@backstage/backend-common": "^0.1.1-alpha.16",
"@backstage/catalog-model": "^0.1.1-alpha.16",
"@backstage/config": "^0.1.1-alpha.16",
"@types/dockerode": "^2.5.34",
"@types/express": "^4.17.6",
"dockerode": "^3.2.1",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"fs-extra": "^9.0.1",
"knex": "^0.21.1",
"node-fetch": "^2.6.0",
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.16",
"@types/node-fetch": "^2.5.7",
"supertest": "^4.0.2"
},
"files": [
+1
View File
@@ -15,3 +15,4 @@
*/
export * from './service/router';
export * from './techdocs';
@@ -15,6 +15,9 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
import { Preparers, LocalPublish, Generators } from '../techdocs';
import { ConfigReader } from '@backstage/config';
import Docker from 'dockerode';
import express from 'express';
import request from 'supertest';
import { createRouter } from './router';
@@ -24,7 +27,12 @@ describe('createRouter', () => {
beforeAll(async () => {
const router = await createRouter({
preparers: new Preparers(),
generators: new Generators(),
publisher: new LocalPublish(),
logger: getVoidLogger(),
dockerClient: new Docker(),
config: ConfigReader.fromConfigs([]),
});
app = express().use(router);
});
+60 -2
View File
@@ -17,18 +17,76 @@ import { Logger } from 'winston';
import Router from 'express-promise-router';
import express from 'express';
import Knex from 'knex';
import fetch from 'node-fetch';
import { Config } from '@backstage/config';
import path from 'path';
import Docker from 'dockerode';
import {
GeneratorBuilder,
PreparerBuilder,
PublisherBase,
LocalPublish,
} from '../techdocs';
import { Entity } from '@backstage/catalog-model';
type RouterOptions = {
preparers: PreparerBuilder;
generators: GeneratorBuilder;
publisher: PublisherBase;
logger: Logger;
database?: Knex; // TODO: Make database required when we're implementing database stuff.
config: Config;
dockerClient: Docker;
};
export async function createRouter({}: RouterOptions): Promise<express.Router> {
export async function createRouter({
preparers,
generators,
publisher,
config,
dockerClient,
}: RouterOptions): Promise<express.Router> {
const router = Router();
router.get('/', (_, res) => {
router.get('/', async (_, res) => {
res.status(200).send('Hello TechDocs Backend');
});
// TODO: This route should not exist in the future
router.get('/buildall', async (_, res) => {
const baseUrl = config.getString('backend.baseUrl');
const entitiesResponse = (await (
await fetch(`${baseUrl}/catalog/entities`)
).json()) as Entity[];
const entitiesWithDocs = entitiesResponse.filter(
entity => entity.metadata.annotations?.['backstage.io/techdocs-ref'],
);
entitiesWithDocs.forEach(async entity => {
const preparer = preparers.get(entity);
const generator = generators.get(entity);
const { resultDir } = await generator.run({
directory: await preparer.prepare(entity),
dockerClient,
});
publisher.publish({
entity,
directory: resultDir,
});
});
res.send('Successfully generated documentation');
});
if (publisher instanceof LocalPublish) {
router.use(
'/static/docs/',
express.static(path.resolve(__dirname, `../../static/docs`)),
);
}
return router;
}
@@ -18,6 +18,15 @@ import { createServiceBuilder } from '@backstage/backend-common';
import { Server } from 'http';
import { Logger } from 'winston';
import { createRouter } from './router';
import Docker from 'dockerode';
import {
Preparers,
DirectoryPreparer,
Generators,
TechdocsGenerator,
LocalPublish,
} from '../techdocs';
import { ConfigReader } from '@backstage/config';
export interface ServerOptions {
port: number;
@@ -31,9 +40,27 @@ export async function startStandaloneServer(
const logger = options.logger.child({ service: 'techdocs-backend' });
logger.debug('Creating application...');
const preparers = new Preparers();
const directoryPreparer = new DirectoryPreparer();
preparers.register('dir', directoryPreparer);
const generators = new Generators();
const techdocsGenerator = new TechdocsGenerator();
generators.register('techdocs', techdocsGenerator);
const publisher = new LocalPublish();
const dockerClient = new Docker();
logger.debug('Starting application server...');
const router = await createRouter({ logger });
const router = await createRouter({
preparers,
generators,
logger,
publisher,
dockerClient,
config: ConfigReader.fromConfigs([]),
});
const service = createServiceBuilder(module)
.enableCors({ origin: 'http://localhost:3000' })
.addRouter('/techdocs', router);
@@ -0,0 +1,16 @@
/*
* Copyright 2020 Spotify AB
*
* 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 * from './stages';
@@ -0,0 +1,46 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
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>();
register(templaterKey: SupportedGeneratorKey, templater: GeneratorBase) {
this.generatorMap.set(templaterKey, templater);
}
get(entity: Entity): GeneratorBase {
const generatorKey = getGeneratorKey(entity);
const generator = this.generatorMap.get(generatorKey);
if (!generator) {
throw new Error(
`No generator registered for entity: "${generatorKey}"`,
);
}
return generator;
}
}
@@ -0,0 +1,80 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import { Entity } from '@backstage/catalog-model';
import { Writable, PassThrough } from 'stream';
import Docker from 'dockerode';
import { SupportedGeneratorKey } from './types';
// TODO: Implement proper support for more generators.
export function getGeneratorKey(entity: Entity): SupportedGeneratorKey {
if (!entity) {
throw new Error('No entity provided');
}
return 'techdocs';
}
type RunDockerContainerOptions = {
imageName: string;
args: string[];
logStream?: Writable;
docsDir: string;
resultDir: string;
dockerClient: Docker;
createOptions?: Docker.ContainerCreateOptions;
};
export async function runDockerContainer({
imageName,
args,
logStream = new PassThrough(),
docsDir,
resultDir,
dockerClient,
createOptions,
}: RunDockerContainerOptions) {
const [{ Error: error, StatusCode: statusCode }] = await dockerClient.run(
imageName,
args,
logStream,
{
Volumes: {
'/content': {},
'/result': {},
},
WorkingDir: '/content',
HostConfig: {
Binds: [`${docsDir}:/content`, `${resultDir}:/result`],
},
...createOptions,
},
);
if (error) {
throw new Error(
`Docker failed to run with the following error message: ${error}`,
);
}
if (statusCode !== 0) {
throw new Error(
`Docker container returned a non-zero exit code (${statusCode})`,
);
}
return { error, statusCode };
}
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { TechdocsGenerator } from './techdocs';
export { Generators } from './generators';
export type { GeneratorBuilder } from './types';
@@ -0,0 +1,48 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import {
GeneratorBase,
GeneratorRunOptions,
GeneratorRunResult,
} from './types';
import { runDockerContainer } from './helpers';
import fs from 'fs';
import path from 'path';
import os from 'os';
export class TechdocsGenerator implements GeneratorBase {
public async run({
directory,
logStream,
dockerClient,
}: GeneratorRunOptions): Promise<GeneratorRunResult> {
const resultDir = fs.mkdtempSync(path.join(os.tmpdir(), `techdocs-tmp-`));
await runDockerContainer({
imageName: 'spotify/techdocs',
args: ['build', '-d', '/result'],
logStream,
docsDir: directory,
resultDir,
dockerClient,
});
console.log(
`[TechDocs]: Successfully generated docs from ${directory} into ${resultDir}`,
);
return { resultDir };
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import type { Writable } from 'stream';
import Docker from 'dockerode';
import { Entity } from '@backstage/catalog-model';
/**
* The returned directory from the generator which is ready
* to pass to the next stage of the TechDocs which is publishing
*/
export type GeneratorRunResult = {
resultDir: string;
};
/**
* The values that the generator will recieve. The directory of the
* uncompiled documentation, with the values from the frontend. A dedicated log stream and a docker
* client to run any generator on top of your directory.
*/
export type GeneratorRunOptions = {
directory: string;
logStream?: Writable;
dockerClient: Docker;
};
export type GeneratorBase = {
// runs the generator with the values and returns the directory to be published
run(opts: GeneratorRunOptions): Promise<GeneratorRunResult>;
};
/**
* List of supported generator options
*/
export type SupportedGeneratorKey = 'techdocs' | string;
/**
* The generator builder holds the generator ready for run time
*/
export type GeneratorBuilder = {
register(protocol: SupportedGeneratorKey, generator: GeneratorBase): void;
get(entity: Entity): GeneratorBase;
};
@@ -0,0 +1,19 @@
/*
* Copyright 2020 Spotify AB
*
* 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 * from './generate';
export * from './prepare';
export * from './publish';
@@ -0,0 +1,59 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import { DirectoryPreparer } from './dir';
const createMockEntity = (annotations: {}) => {
return {
apiVersion: 'version',
kind: 'TestKind',
metadata: {
name: 'testName',
annotations: {
...annotations,
},
},
};
};
describe('directory preparer', () => {
it('should merge managed-by-location and techdocs-ref when techdocs-ref is relative', async () => {
const directoryPreparer = new DirectoryPreparer();
const mockEntity = createMockEntity({
'backstage.io/managed-by-location':
'file:/directory/documented-component.yaml',
'backstage.io/techdocs-ref': 'dir:./our-documentation',
});
expect(await directoryPreparer.prepare(mockEntity)).toEqual(
'/directory/our-documentation',
);
});
it('should merge managed-by-location and techdocs-ref when techdocs-ref is absolute', async () => {
const directoryPreparer = new DirectoryPreparer();
const mockEntity = createMockEntity({
'backstage.io/managed-by-location':
'file:/directory/documented-component.yaml',
'backstage.io/techdocs-ref': 'dir:/our-documentation/techdocs',
});
expect(await directoryPreparer.prepare(mockEntity)).toEqual(
'/our-documentation/techdocs',
);
});
});
@@ -0,0 +1,38 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import { PreparerBase } from './types';
import { Entity } from '@backstage/catalog-model';
import path from 'path';
import { parseReferenceAnnotation } from './helpers';
export class DirectoryPreparer implements PreparerBase {
prepare(entity: Entity): Promise<string> {
const { location: managedByLocation } = parseReferenceAnnotation(
'backstage.io/managed-by-location',
entity,
);
const { location: techdocsLocation } = parseReferenceAnnotation(
'backstage.io/techdocs-ref',
entity,
);
const managedByLocationDirectory = path.dirname(managedByLocation);
return new Promise(resolve => {
resolve(path.resolve(managedByLocationDirectory, techdocsLocation));
});
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import { Entity } from '@backstage/catalog-model';
import { InputError } from '@backstage/backend-common';
import { RemoteProtocol } from './types';
export type ParsedLocationAnnotation = {
protocol: RemoteProtocol;
location: string;
};
export const parseReferenceAnnotation = (
annotationName: string,
entity: Entity,
): ParsedLocationAnnotation => {
const annotation = entity.metadata.annotations?.[annotationName];
if (!annotation) {
throw new InputError(
`No location annotation provided in entity: ${entity.metadata.name}`,
);
}
// split on the first colon for the protocol and the rest after the first split
// is the location.
const [protocol, location] = annotation.split(/:(.+)/) as [
RemoteProtocol?,
string?,
];
if (!protocol || !location) {
throw new InputError(
`Failure to parse either protocol or location for entity: ${entity.metadata.name}`,
);
}
return {
protocol,
location,
};
};
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { DirectoryPreparer } from './dir';
export { Preparers } from './preparers';
export type { PreparerBuilder } from './types';
@@ -0,0 +1,38 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import { PreparerBase, RemoteProtocol, PreparerBuilder } from './types';
import { Entity } from '@backstage/catalog-model';
import { parseReferenceAnnotation } from './helpers';
export class Preparers implements PreparerBuilder {
private preparerMap = new Map<RemoteProtocol, PreparerBase>();
register(protocol: RemoteProtocol, preparer: PreparerBase) {
this.preparerMap.set(protocol, preparer);
}
get(entity: Entity): PreparerBase {
const { protocol } = parseReferenceAnnotation('backstage.io/techdocs-ref', entity);
const preparer = this.preparerMap.get(protocol);
if (!preparer) {
throw new Error(`No preparer registered for type: "${protocol}"`);
}
return preparer;
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import type { Entity } from '@backstage/catalog-model';
import { Logger } from 'winston';
export type PreparerBase = {
/**
* Given an Entity definition from the Service Catalog, go and prepare a directory
* with contents from the location in temporary storage and return the path
* @param entity The entity from the Service Catalog
*/
prepare(entity: Entity, opts?: { logger: Logger }): Promise<string>;
};
export type PreparerBuilder = {
register(protocol: RemoteProtocol, preparer: PreparerBase): void;
get(entity: Entity): PreparerBase;
};
export type RemoteProtocol = 'dir' | string;
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { LocalPublish } from './local';
export type { PublisherBase } from './types';
@@ -0,0 +1,57 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import { LocalPublish } from './local';
import fs from 'fs-extra';
import path from 'path';
const createMockEntity = (annotations = {}) => {
return {
apiVersion: 'version',
kind: 'TestKind',
metadata: {
name: 'test-component-name',
annotations: {
...annotations,
},
},
};
};
describe('local publisher', () => {
it('should publish generated documentation dir', async () => {
const publisher = new LocalPublish();
const mockEntity = createMockEntity();
const tempDir = fs.mkdtempSync(`${__dirname}/test-component-folder-`);
expect(tempDir).toBeTruthy();
fs.closeSync(fs.openSync(path.join(tempDir, '/mock-file'), 'w'));
await publisher.publish({ entity: mockEntity, directory: tempDir });
const publishDir = path.resolve(
__dirname,
`../../../../static/docs/${mockEntity.metadata.name}`,
);
expect(fs.existsSync(publishDir)).toBeTruthy();
expect(fs.existsSync(path.join(publishDir, '/mock-file'))).toBeTruthy();
fs.removeSync(publishDir);
fs.removeSync(tempDir);
});
});
@@ -0,0 +1,54 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import { PublisherBase } from './types';
import { Entity } from '@backstage/catalog-model';
import path from 'path';
import fs from 'fs-extra';
export class LocalPublish implements PublisherBase {
publish({
entity,
directory,
}: {
entity: Entity;
directory: string;
}):
| Promise<{
remoteUrl: string;
}>
| { remoteUrl: string } {
const publishDir = path.resolve(
__dirname,
`../../../../static/docs/${entity.metadata.name}`,
);
if (!fs.existsSync(publishDir)) {
fs.mkdirSync(publishDir, { recursive: true });
}
return new Promise((resolve, reject) => {
fs.copy(directory, publishDir, err => {
if (err) {
reject(err);
}
resolve({
remoteUrl: `http://localhost:7000/techdocs/static/docs/${entity.metadata.name}`,
});
});
});
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
import { Entity } from '@backstage/catalog-model';
/**
* Publisher is in charge of taking a folder created by
* the builder, and pushing it to storage
*/
export type PublisherBase = {
/**
*
* @param opts object containing the entity from the service
* catalog, and the directory that has been generated
*/
publish(opts: {
entity: Entity;
directory: string;
}): Promise<{ remoteUrl: string }> | { remoteUrl: string };
};