Techdocs without docker-in-docker (#2438)

We're deploying backstage to GKE and have had issues getting techdocs
volume mount logic to work because of mixing paths between the host and
the container running the backend. So we added a condition where if
mkdocs is found in the backend container it will use that to generate
the docs rather than spinning it up in a docker container.
This commit is contained in:
Perry Manuk
2020-09-17 15:26:39 +02:00
committed by GitHub
parent 2331e79897
commit 21a366077a
3 changed files with 73 additions and 12 deletions
@@ -18,6 +18,7 @@ import { Entity } from '@backstage/catalog-model';
import { Writable, PassThrough } from 'stream';
import Docker from 'dockerode';
import { SupportedGeneratorKey } from './types';
import { spawn } from 'child_process';
// TODO: Implement proper support for more generators.
export function getGeneratorKey(entity: Entity): SupportedGeneratorKey {
@@ -38,6 +39,13 @@ type RunDockerContainerOptions = {
createOptions?: Docker.ContainerCreateOptions;
};
export type RunCommandOptions = {
command: string;
args: string[];
options: object;
logStream?: Writable;
};
export async function runDockerContainer({
imageName,
args,
@@ -96,3 +104,41 @@ export async function runDockerContainer({
return { error, statusCode };
}
/**
*
* @param options the options object
* @param options.command the command to run
* @param options.args the arguments to pass the command
* @param options.options options used in spawn
* @param options.logStream the log streamer to capture log messages
*/
export const runCommand = async ({
command,
args,
options,
logStream = new PassThrough(),
}: RunCommandOptions) => {
await new Promise((resolve, reject) => {
const process = spawn(command, args, options);
process.stdout.on('data', stream => {
logStream.write(stream);
});
process.stderr.on('data', stream => {
logStream.write(stream);
});
process.on('error', error => {
return reject(error);
});
process.on('close', code => {
if (code !== 0) {
return reject(`Command ${command} failed, exit code: ${code}`);
}
return resolve();
});
});
};
@@ -24,7 +24,9 @@ import {
GeneratorRunOptions,
GeneratorRunResult,
} from './types';
import { runDockerContainer } from './helpers';
import { runDockerContainer, runCommand } from './helpers';
const commandExists = require('command-exists-promise');
export class TechdocsGenerator implements GeneratorBase {
private readonly logger: Logger;
@@ -46,17 +48,29 @@ export class TechdocsGenerator implements GeneratorBase {
);
try {
await runDockerContainer({
imageName: 'spotify/techdocs',
args: ['build', '-d', '/result'],
logStream,
docsDir: directory,
resultDir,
dockerClient,
});
this.logger.info(
`[TechDocs]: Successfully generated docs from ${directory} into ${resultDir}`,
);
const mkdocsInstalled = await commandExists('mkdocs');
if (mkdocsInstalled) {
await runCommand({
command: 'mkdocs',
args: ['build', '-d', resultDir, '-v'],
options: {
cwd: directory,
},
logStream,
});
} else {
await runDockerContainer({
imageName: 'spotify/techdocs',
args: ['build', '-d', '/result'],
logStream,
docsDir: directory,
resultDir,
dockerClient,
});
this.logger.info(
`[TechDocs]: Successfully generated docs from ${directory} into ${resultDir}`,
);
}
} catch (error) {
this.logger.debug(
`[TechDocs]: Failed to generate docs from ${directory} into ${resultDir}`,