Merge pull request #3966 from backstage/orkohunter/techdocs-cli-uses-techdocs-common

This commit is contained in:
Himanshu Mishra
2021-01-11 23:23:26 +01:00
committed by GitHub
8 changed files with 63 additions and 56 deletions
+9
View File
@@ -0,0 +1,9 @@
---
'@backstage/techdocs-common': patch
'@backstage/plugin-techdocs-backend': patch
---
Improve techdocs-common Generator API for it to be used by techdocs-cli. TechDocs generator.run function now takes
an input AND an output directory. Most probably you use techdocs-common via plugin-techdocs-backend, and so there
is no breaking change for you.
But if you use techdocs-common separately, you need to create an output directory and pass into the generator.
@@ -80,14 +80,14 @@ describe('helpers', () => {
const imageName = 'spotify/techdocs';
const args = ['build', '-d', '/result'];
const docsDir = os.tmpdir();
const resultDir = os.tmpdir();
const outputDir = os.tmpdir();
it('should pull the techdocs docker container', async () => {
await runDockerContainer({
imageName,
args,
docsDir,
resultDir,
outputDir,
dockerClient: mockDocker,
});
@@ -103,7 +103,7 @@ describe('helpers', () => {
imageName,
args,
docsDir,
resultDir,
outputDir,
dockerClient: mockDocker,
});
@@ -118,7 +118,7 @@ describe('helpers', () => {
},
WorkingDir: '/content',
HostConfig: {
Binds: [`${docsDir}:/content`, `${resultDir}:/result`],
Binds: [`${docsDir}:/content`, `${outputDir}:/result`],
},
},
);
@@ -129,7 +129,7 @@ describe('helpers', () => {
imageName,
args,
docsDir,
resultDir,
outputDir,
dockerClient: mockDocker,
});
@@ -151,7 +151,7 @@ describe('helpers', () => {
imageName,
args,
docsDir,
resultDir,
outputDir,
dockerClient: mockDocker,
}),
).rejects.toThrow(new RegExp(`.+: ${dockerError}`));
@@ -39,7 +39,7 @@ type RunDockerContainerOptions = {
args: string[];
logStream?: Writable;
docsDir: string;
resultDir: string;
outputDir: string;
dockerClient: Docker;
createOptions?: Docker.ContainerCreateOptions;
};
@@ -56,7 +56,7 @@ export async function runDockerContainer({
args,
logStream = new PassThrough(),
docsDir,
resultDir,
outputDir,
dockerClient,
createOptions,
}: RunDockerContainerOptions) {
@@ -89,7 +89,7 @@ export async function runDockerContainer({
},
WorkingDir: '/content',
HostConfig: {
Binds: [`${docsDir}:/content`, `${resultDir}:/result`],
Binds: [`${docsDir}:/content`, `${outputDir}:/result`],
},
...createOptions,
},
@@ -14,18 +14,12 @@
* limitations under the License.
*/
import fs from 'fs-extra';
import path from 'path';
import os from 'os';
import { Logger } from 'winston';
import { PassThrough } from 'stream';
import { Config } from '@backstage/config';
import {
GeneratorBase,
GeneratorRunOptions,
GeneratorRunResult,
} from './types';
import { GeneratorBase, GeneratorRunOptions } from './types';
import {
runDockerContainer,
runCommand,
@@ -64,40 +58,37 @@ export class TechdocsGenerator implements GeneratorBase {
}
public async run({
directory,
inputDir,
outputDir,
dockerClient,
parsedLocationAnnotation,
}: GeneratorRunOptions): Promise<GeneratorRunResult> {
const tmpdirPath = os.tmpdir();
// Fixes a problem with macOS returning a path that is a symlink
const tmpdirResolvedPath = fs.realpathSync(tmpdirPath);
const resultDir = fs.mkdtempSync(
path.join(tmpdirResolvedPath, 'techdocs-tmp-'),
);
}: GeneratorRunOptions): Promise<void> {
const [log, logStream] = createStream();
// TODO: In future mkdocs.yml can be mkdocs.yaml. So, use a config variable here to find out
// the correct file name.
// Do some updates to mkdocs.yml before generating docs e.g. adding repo_url
await patchMkdocsYmlPreBuild(
path.join(directory, 'mkdocs.yml'),
this.logger,
parsedLocationAnnotation,
);
if (parsedLocationAnnotation) {
await patchMkdocsYmlPreBuild(
path.join(inputDir, 'mkdocs.yml'),
this.logger,
parsedLocationAnnotation,
);
}
try {
switch (this.options.runGeneratorIn) {
case 'local':
await runCommand({
command: 'mkdocs',
args: ['build', '-d', resultDir, '-v'],
args: ['build', '-d', outputDir, '-v'],
options: {
cwd: directory,
cwd: inputDir,
},
logStream,
});
this.logger.info(
`Successfully generated docs from ${directory} into ${resultDir} using local mkdocs`,
`Successfully generated docs from ${inputDir} into ${outputDir} using local mkdocs`,
);
break;
case 'docker':
@@ -105,12 +96,12 @@ export class TechdocsGenerator implements GeneratorBase {
imageName: 'spotify/techdocs',
args: ['build', '-d', '/result'],
logStream,
docsDir: directory,
resultDir,
docsDir: inputDir,
outputDir,
dockerClient,
});
this.logger.info(
`Successfully generated docs from ${directory} into ${resultDir} using techdocs-container`,
`Successfully generated docs from ${inputDir} into ${outputDir} using techdocs-container`,
);
break;
default:
@@ -120,14 +111,12 @@ export class TechdocsGenerator implements GeneratorBase {
}
} catch (error) {
this.logger.debug(
`Failed to generate docs from ${directory} into ${resultDir}`,
`Failed to generate docs from ${inputDir} into ${outputDir}`,
);
this.logger.debug(`Build failed with error: ${log}`);
throw new Error(
`Failed to generate docs from ${directory} into ${resultDir} with error ${error.message}`,
`Failed to generate docs from ${inputDir} into ${outputDir} with error ${error.message}`,
);
}
return { resultDir };
}
}
@@ -18,32 +18,26 @@ import Docker from 'dockerode';
import { Entity } from '@backstage/catalog-model';
import { ParsedLocationAnnotation } from '../../helpers';
/**
* 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 receive.
*
* @param {string} directory The directory of the uncompiled documentation, with the values from the frontend
* @param {string} inputDir The directory of the uncompiled documentation, with the values from the frontend
* @param {string} outputDir Directory to store generated docs in. Usually - a newly created temporary directory.
* @param {Docker} dockerClient A docker client to run any generator on top of your directory
* @param {ParsedLocationAnnotation} parsedLocationAnnotation backstage.io/techdocs-ref annotation of an entity
* @param {Writable} [logStream] A dedicated log stream
*/
export type GeneratorRunOptions = {
directory: string;
inputDir: string;
outputDir: string;
dockerClient: Docker;
parsedLocationAnnotation: ParsedLocationAnnotation;
parsedLocationAnnotation?: ParsedLocationAnnotation;
logStream?: Writable;
};
export type GeneratorBase = {
// runs the generator with the values and returns the directory to be published
run(opts: GeneratorRunOptions): Promise<GeneratorRunResult>;
// Runs the generator with the values
run(opts: GeneratorRunOptions): Promise<void>;
};
/**
@@ -17,4 +17,4 @@ export { DirectoryPreparer } from './dir';
export { CommonGitPreparer } from './commonGit';
export { UrlPreparer } from './url';
export { Preparers } from './preparers';
export type { PreparerBuilder, PreparerBase } from './types';
export type { PreparerBuilder, PreparerBase, RemoteProtocol } from './types';
+1
View File
@@ -40,6 +40,7 @@
"dockerode": "^3.2.1",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"fs-extra": "^9.0.1",
"knex": "^0.21.6",
"winston": "^3.2.1"
},
@@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs-extra';
import os from 'os';
import path from 'path';
import Docker from 'dockerode';
import { Logger } from 'winston';
import { Entity } from '@backstage/catalog-model';
@@ -78,8 +81,17 @@ export class DocsBuilder {
const parsedLocationAnnotation = getLocationForEntity(this.entity);
this.logger.info(`Running generator on entity ${getEntityId(this.entity)}`);
const { resultDir } = await this.generator.run({
directory: preparedDir,
// Create a temporary directory to store the generated files in.
const tmpdirPath = os.tmpdir();
// Fixes a problem with macOS returning a path that is a symlink
const tmpdirResolvedPath = fs.realpathSync(tmpdirPath);
const outputDir = await fs.mkdtemp(
path.join(tmpdirResolvedPath, 'techdocs-tmp-'),
);
await this.generator.run({
inputDir: preparedDir,
outputDir,
dockerClient: this.dockerClient,
parsedLocationAnnotation,
});
@@ -87,9 +99,11 @@ export class DocsBuilder {
this.logger.info(`Running publisher on entity ${getEntityId(this.entity)}`);
await this.publisher.publish({
entity: this.entity,
directory: resultDir,
directory: outputDir,
});
// TODO: Remove the generated directory once published.
if (!this.entity.metadata.uid) {
throw new Error(
'Trying to build documentation for entity not in service catalog',