Merge branch 'master' into new-release-31-aug-20

This commit is contained in:
Patrik Oldsberg
2020-09-02 16:17:40 +02:00
committed by GitHub
101 changed files with 2379 additions and 1145 deletions
@@ -19,7 +19,6 @@ 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,
@@ -27,6 +26,7 @@ import {
PublisherBase,
LocalPublish,
} from '../techdocs';
import { resolvePackagePath } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
type RouterOptions = {
@@ -39,6 +39,11 @@ type RouterOptions = {
dockerClient: Docker;
};
const staticDocsDir = resolvePackagePath(
'@backstage/plugin-techdocs-backend',
'static/docs',
);
export async function createRouter({
preparers,
generators,
@@ -102,10 +107,7 @@ export async function createRouter({
});
if (publisher instanceof LocalPublish) {
router.use(
'/static/docs/',
express.static(path.resolve(__dirname, `../../static/docs`)),
);
router.use('/static/docs/', express.static(staticDocsDir));
router.use(
'/static/docs/:kind/:namespace/:name',
async (req, res, next) => {
@@ -0,0 +1,47 @@
/*
* 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 { Generators, TechdocsGenerator } from './';
import { getVoidLogger } from '@backstage/backend-common';
const logger = getVoidLogger();
const mockEntity = {
apiVersion: 'version',
kind: 'TestKind',
metadata: {
name: 'testName',
},
};
describe('generators', () => {
it('should return error if no generator is registered', async () => {
const generators = new Generators();
expect(() => generators.get(mockEntity)).toThrowError(
'No generator registered for entity: "techdocs"',
);
});
it('should return correct registered generator', async () => {
const generators = new Generators();
const techdocs = new TechdocsGenerator(logger);
generators.register('techdocs', techdocs);
expect(generators.get(mockEntity)).toBe(techdocs);
});
});
@@ -15,32 +15,29 @@
*/
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;
}
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(generatorKey: SupportedGeneratorKey, generator: GeneratorBase) {
this.generatorMap.set(generatorKey, generator);
}
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,103 @@
/*
* 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 Stream, { PassThrough } from 'stream';
import os from 'os';
import Docker from 'dockerode';
import { runDockerContainer, getGeneratorKey } from './helpers';
const mockEntity = {
apiVersion: 'version',
kind: 'TestKind',
metadata: {
name: 'testName',
},
};
const mockDocker = new Docker() as jest.Mocked<Docker>;
describe('helpers', () => {
describe('getGeneratorKey', () => {
it('should return techdocs as the only generator key', () => {
const key = getGeneratorKey(mockEntity);
expect(key).toBe('techdocs');
});
});
describe('runDockerContainer', () => {
beforeEach(() => {
jest.spyOn(mockDocker, 'pull').mockImplementation((async (
_image: string,
_something: any,
handler: (err: Error | undefined, stream: PassThrough) => void,
) => {
const mockStream = new PassThrough();
handler(undefined, mockStream);
mockStream.end();
}) as any);
jest
.spyOn(mockDocker, 'run')
.mockResolvedValue([{ Error: null, StatusCode: 0 }]);
});
const imageName = 'spotify/techdocs';
const args = ['build', '-d', '/result'];
const docsDir = os.tmpdir();
const resultDir = os.tmpdir();
it('should pull the techdocs docker container', async () => {
await runDockerContainer({
imageName,
args,
docsDir,
resultDir,
dockerClient: mockDocker,
});
expect(mockDocker.pull).toHaveBeenCalledWith(
imageName,
{},
expect.any(Function),
);
});
it('should run the techdocs docker container', async () => {
await runDockerContainer({
imageName,
args,
docsDir,
resultDir,
dockerClient: mockDocker,
});
expect(mockDocker.run).toHaveBeenCalledWith(
imageName,
args,
expect.any(Stream),
{
Volumes: {
'/content': {},
'/result': {},
},
WorkingDir: '/content',
HostConfig: {
Binds: [`${docsDir}:/content`, `${resultDir}:/result`],
},
},
);
});
});
});
@@ -79,9 +79,10 @@ export const checkoutGitRepository = async (
if (fs.existsSync(repositoryTmpPath)) {
const repository = await Repository.open(repositoryTmpPath);
const currentBranchName = (await repository.getCurrentBranch()).shorthand();
await repository.mergeBranches(
parsedGitLocation.ref,
`origin/${parsedGitLocation.ref}`,
currentBranchName,
`origin/${currentBranchName}`,
);
return repositoryTmpPath;
}
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import path from 'path';
import { Logger } from 'winston';
import { Entity } from '@backstage/catalog-model';
import { PublisherBase } from './types';
import { resolvePackagePath } from '@backstage/backend-common';
export class LocalPublish implements PublisherBase {
private readonly logger: Logger;
@@ -39,9 +39,9 @@ export class LocalPublish implements PublisherBase {
| { remoteUrl: string } {
const entityNamespace = entity.metadata.namespace ?? 'default';
const publishDir = path.resolve(
__dirname,
'../../../../static/docs/',
const publishDir = resolvePackagePath(
'@backstage/plugin-techdocs-backend',
'static/docs',
entity.kind,
entityNamespace,
entity.metadata.name,