From 130d0842a1d4f62a5aba2358eed5cbf2f5d8a953 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 2 May 2024 14:22:20 +0200 Subject: [PATCH 01/13] techdocs-backend: instantiate containerRunner internally Signed-off-by: Vincenzo Scamporlino --- .../backend-legacy/src/plugins/techdocs.ts | 7 - .../src/commands/generate/generate.ts | 14 -- plugins/techdocs-backend/src/plugin.ts | 7 - plugins/techdocs-node/package.json | 1 + .../stages/generate/DockerContainerRunner.ts | 137 ++++++++++++++++++ .../src/stages/generate/generators.ts | 2 - .../src/stages/generate/index.ts | 2 + .../src/stages/generate/techdocs.ts | 10 +- .../src/stages/generate/types.ts | 30 +++- yarn.lock | 1 + 10 files changed, 174 insertions(+), 37 deletions(-) create mode 100644 plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts diff --git a/packages/backend-legacy/src/plugins/techdocs.ts b/packages/backend-legacy/src/plugins/techdocs.ts index 6e369f8f0f..21048d4bb9 100644 --- a/packages/backend-legacy/src/plugins/techdocs.ts +++ b/packages/backend-legacy/src/plugins/techdocs.ts @@ -14,14 +14,12 @@ * limitations under the License. */ -import { DockerContainerRunner } from '@backstage/backend-common'; import { createRouter, Generators, Preparers, Publisher, } from '@backstage/plugin-techdocs-backend'; -import Docker from 'dockerode'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; @@ -34,14 +32,9 @@ export default async function createPlugin( reader: env.reader, }); - // Docker client (conditionally) used by the generators, based on techdocs.generators config. - const dockerClient = new Docker(); - const containerRunner = new DockerContainerRunner({ dockerClient }); - // Generators are used for generating documentation sites. const generators = await Generators.fromConfig(env.config, { logger: env.logger, - containerRunner, }); // Publisher is used for diff --git a/packages/techdocs-cli/src/commands/generate/generate.ts b/packages/techdocs-cli/src/commands/generate/generate.ts index bf6910ff4a..4553c18810 100644 --- a/packages/techdocs-cli/src/commands/generate/generate.ts +++ b/packages/techdocs-cli/src/commands/generate/generate.ts @@ -17,16 +17,11 @@ import { resolve } from 'path'; import { OptionValues } from 'commander'; import fs from 'fs-extra'; -import Docker from 'dockerode'; import { TechdocsGenerator, ParsedLocationAnnotation, getMkdocsYml, } from '@backstage/plugin-techdocs-node'; -import { - ContainerRunner, - DockerContainerRunner, -} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { convertTechDocsRefToLocationAnnotation, @@ -75,14 +70,6 @@ export default async function generate(opts: OptionValues) { }, }); - // Docker client (conditionally) used by the generators, based on techdocs.generators config. - let containerRunner: ContainerRunner | undefined; - - if (opts.docker) { - const dockerClient = new Docker(); - containerRunner = new DockerContainerRunner({ dockerClient }); - } - let parsedLocationAnnotation = {} as ParsedLocationAnnotation; if (opts.techdocsRef) { try { @@ -97,7 +84,6 @@ export default async function generate(opts: OptionValues) { // Generate docs using @backstage/plugin-techdocs-node const techdocsGenerator = await TechdocsGenerator.fromConfig(config, { logger, - containerRunner, }); logger.info('Generating documentation...'); diff --git a/plugins/techdocs-backend/src/plugin.ts b/plugins/techdocs-backend/src/plugin.ts index 094335a0f0..e3a0c60a15 100644 --- a/plugins/techdocs-backend/src/plugin.ts +++ b/plugins/techdocs-backend/src/plugin.ts @@ -16,7 +16,6 @@ import { cacheToPluginCacheManager, - DockerContainerRunner, loggerToWinstonLogger, } from '@backstage/backend-common'; import { @@ -36,7 +35,6 @@ import { techdocsGeneratorExtensionPoint, techdocsPreparerExtensionPoint, } from '@backstage/plugin-techdocs-node'; -import Docker from 'dockerode'; import { createRouter } from '@backstage/plugin-techdocs-backend'; import * as winston from 'winston'; @@ -118,14 +116,9 @@ export const techdocsPlugin = createBackendPlugin({ preparers.register(protocol, preparer); } - // Docker client (conditionally) used by the generators, based on techdocs.generators config. - const dockerClient = new Docker(); - const containerRunner = new DockerContainerRunner({ dockerClient }); - // Generators are used for generating documentation sites. const generators = await Generators.fromConfig(config, { logger: winstonLogger, - containerRunner, customGenerator: customTechdocsGenerator, }); diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 7ef38c56ff..8b7f87b230 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -64,6 +64,7 @@ "@smithy/node-http-handler": "^2.1.7", "@trendyol-js/openstack-swift-sdk": "^0.0.7", "@types/express": "^4.17.6", + "dockerode": "^4.0.0", "express": "^4.17.1", "fs-extra": "^11.2.0", "git-url-parse": "^14.0.0", diff --git a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts new file mode 100644 index 0000000000..31cb6bcaa8 --- /dev/null +++ b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts @@ -0,0 +1,137 @@ +/* + * Copyright 2020 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. + */ + +import Docker from 'dockerode'; +import fs from 'fs-extra'; +import { ForwardedError } from '@backstage/errors'; +import { PassThrough } from 'stream'; +import { pipeline as pipelineStream } from 'stream'; +import { promisify } from 'util'; +import { ContainerRunner, RunContainerOptions } from './types'; + +const pipeline = promisify(pipelineStream); + +export type UserOptions = { + User?: string; +}; + +/** + * A {@link ContainerRunner} for Docker containers. + * + * @public + */ +export class DockerContainerRunner implements ContainerRunner { + private readonly dockerClient: Docker; + + constructor() { + this.dockerClient = new Docker(); + } + + async runContainer(options: RunContainerOptions) { + const { + imageName, + command, + args, + logStream = new PassThrough(), + mountDirs = {}, + workingDir, + envVars = {}, + pullImage = true, + defaultUser = false, + } = options; + + // Show a better error message when Docker is unavailable. + try { + await this.dockerClient.ping(); + } catch (e) { + throw new ForwardedError( + 'This operation requires Docker. Docker does not appear to be available. Docker.ping() failed with', + e, + ); + } + + if (pullImage) { + await new Promise((resolve, reject) => { + this.dockerClient.pull(imageName, {}, (err, stream) => { + if (err) { + reject(err); + return; + } + + pipeline(stream, logStream, { end: false }) + .then(resolve) + .catch(reject); + }); + }); + } + + const userOptions: UserOptions = {}; + if (!defaultUser && process.getuid && process.getgid) { + // Files that are created inside the Docker container will be owned by + // root on the host system on non Mac systems, because of reasons. Mainly the fact that + // volume sharing is done using NFS on Mac and actual mounts in Linux world. + // So we set the user in the container as the same user and group id as the host. + // On Windows we don't have process.getuid nor process.getgid + userOptions.User = `${process.getuid()}:${process.getgid()}`; + } + + // Initialize volumes to mount based on mountDirs map + const Volumes: { [T: string]: object } = {}; + for (const containerDir of Object.values(mountDirs)) { + Volumes[containerDir] = {}; + } + + // Create bind volumes + const Binds: string[] = []; + for (const [hostDir, containerDir] of Object.entries(mountDirs)) { + // Need to use realpath here as Docker mounting does not like + // symlinks for binding volumes + const realHostDir = await fs.realpath(hostDir); + Binds.push(`${realHostDir}:${containerDir}`); + } + + // Create docker environment variables array + const Env = []; + for (const [key, value] of Object.entries(envVars)) { + Env.push(`${key}=${value}`); + } + + const [{ Error: error, StatusCode: statusCode }] = + await this.dockerClient.run(imageName, args, logStream, { + Volumes, + HostConfig: { + AutoRemove: true, + Binds, + }, + ...(workingDir ? { WorkingDir: workingDir } : {}), + Entrypoint: command, + Env, + ...userOptions, + } as Docker.ContainerCreateOptions); + + 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})`, + ); + } + } +} diff --git a/plugins/techdocs-node/src/stages/generate/generators.ts b/plugins/techdocs-node/src/stages/generate/generators.ts index 603c714370..60798cf9c0 100644 --- a/plugins/techdocs-node/src/stages/generate/generators.ts +++ b/plugins/techdocs-node/src/stages/generate/generators.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { ContainerRunner } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { Logger } from 'winston'; @@ -42,7 +41,6 @@ export class Generators implements GeneratorBuilder { config: Config, options: { logger: Logger; - containerRunner: ContainerRunner; customGenerator?: TechdocsGenerator; }, ): Promise { diff --git a/plugins/techdocs-node/src/stages/generate/index.ts b/plugins/techdocs-node/src/stages/generate/index.ts index aaf27026c2..eb0aacff88 100644 --- a/plugins/techdocs-node/src/stages/generate/index.ts +++ b/plugins/techdocs-node/src/stages/generate/index.ts @@ -17,6 +17,8 @@ export { TechdocsGenerator } from './techdocs'; export { Generators } from './generators'; export { getMkdocsYml } from './helpers'; export type { + RunContainerOptions, + ContainerRunner, GeneratorBase, GeneratorOptions, GeneratorBuilder, diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index c5130a3f8f..6d7e3030a3 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { ContainerRunner } from '@backstage/backend-common'; import { Config } from '@backstage/config'; import path from 'path'; import { Logger } from 'winston'; @@ -36,6 +35,7 @@ import { patchMkdocsYmlWithPlugins, } from './mkdocsPatchers'; import { + ContainerRunner, GeneratorBase, GeneratorConfig, GeneratorOptions, @@ -43,6 +43,7 @@ import { GeneratorRunOptions, } from './types'; import { ForwardedError } from '@backstage/errors'; +import { DockerContainerRunner } from './DockerContainerRunner'; /** * Generates documentation files @@ -55,10 +56,9 @@ export class TechdocsGenerator implements GeneratorBase { */ public static readonly defaultDockerImage = 'spotify/techdocs:v1.2.3'; private readonly logger: Logger; - private readonly containerRunner?: ContainerRunner; private readonly options: GeneratorConfig; private readonly scmIntegrations: ScmIntegrationRegistry; - + private containerRunner?: ContainerRunner; /** * Returns a instance of TechDocs generator * @param config - A Backstage configuration @@ -157,9 +157,7 @@ export class TechdocsGenerator implements GeneratorBase { break; case 'docker': if (this.containerRunner === undefined) { - throw new Error( - "Invalid state: containerRunner cannot be undefined when runIn is 'docker'", - ); + this.containerRunner = new DockerContainerRunner(); } await this.containerRunner.runContainer({ imageName: diff --git a/plugins/techdocs-node/src/stages/generate/types.ts b/plugins/techdocs-node/src/stages/generate/types.ts index 149c3c2195..bde764c713 100644 --- a/plugins/techdocs-node/src/stages/generate/types.ts +++ b/plugins/techdocs-node/src/stages/generate/types.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { ContainerRunner } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Writable } from 'stream'; import { Logger } from 'winston'; @@ -99,3 +98,32 @@ export type DefaultMkdocsContent = { docs_dir: string; plugins: String[]; }; + +/** + * Options passed to the {@link ContainerRunner.runContainer} method. + * + * @public + */ +export type RunContainerOptions = { + imageName: string; + command?: string | string[]; + args: string[]; + logStream?: Writable; + mountDirs?: Record; + workingDir?: string; + envVars?: Record; + pullImage?: boolean; + defaultUser?: boolean; +}; + +/** + * Handles the running of containers, on behalf of others. + * + * @public + */ +export interface ContainerRunner { + /** + * Runs a container image to completion. + */ + runContainer(opts: RunContainerOptions): Promise; +} diff --git a/yarn.lock b/yarn.lock index 7351a9dd46..8da874b506 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7802,6 +7802,7 @@ __metadata: "@types/recursive-readdir": ^2.2.0 "@types/supertest": ^2.0.8 aws-sdk-client-mock: ^4.0.0 + dockerode: ^4.0.0 express: ^4.17.1 fs-extra: ^11.2.0 git-url-parse: ^14.0.0 From f62d673ae8a8c3ea13e40d3b7c4e7562ff0f1456 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 2 May 2024 14:22:47 +0200 Subject: [PATCH 02/13] techdocs-node: move DockerContainerRunner tests Signed-off-by: Vincenzo Scamporlino --- .../generate/DockerContainerRunner.test.ts | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts diff --git a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts new file mode 100644 index 0000000000..23536f8c50 --- /dev/null +++ b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts @@ -0,0 +1,214 @@ +/* + * Copyright 2020 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. + */ + +import fs from 'fs-extra'; +import Docker from 'dockerode'; +import Stream, { PassThrough } from 'stream'; +import { DockerContainerRunner, UserOptions } from './DockerContainerRunner'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { ContainerRunner } from './types'; + +const mockDocker = new Docker() as jest.Mocked; + +describe('DockerContainerRunner', () => { + let containerTaskApi: ContainerRunner; + + const inputDir = createMockDirectory(); + const outputDir = createMockDirectory(); + + beforeEach(() => { + inputDir.clear(); + outputDir.clear(); + + 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 }]); + + jest + .spyOn(mockDocker, 'ping') + .mockResolvedValue(Buffer.from('OK', 'utf-8')); + + containerTaskApi = new DockerContainerRunner(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + const imageName = 'dockerOrg/image'; + const args = ['bash', '-c', 'echo test']; + const mountDirs = { + [inputDir.path]: '/input', + [outputDir.path]: '/output', + }; + const workingDir = inputDir.path; + const envVars = { HOME: '/tmp', LOG_LEVEL: 'debug' }; + const envVarsArray = ['HOME=/tmp', 'LOG_LEVEL=debug']; + + it('should pull the docker container', async () => { + await containerTaskApi.runContainer({ + imageName, + args, + }); + + expect(mockDocker.pull).toHaveBeenCalledWith( + imageName, + {}, + expect.any(Function), + ); + + expect(mockDocker.run).toHaveBeenCalled(); + }); + + it('should not pull the docker container when pullImage is false', async () => { + await containerTaskApi.runContainer({ + imageName, + args, + pullImage: false, + }); + + expect(mockDocker.pull).not.toHaveBeenCalled(); + expect(mockDocker.run).toHaveBeenCalled(); + }); + + it('should call the dockerClient run command with the correct arguments passed through', async () => { + await containerTaskApi.runContainer({ + imageName, + args, + mountDirs, + envVars, + workingDir, + }); + + expect(mockDocker.run).toHaveBeenCalledWith( + imageName, + args, + expect.any(Stream), + expect.objectContaining({ + Env: envVarsArray, + WorkingDir: workingDir, + HostConfig: { + AutoRemove: true, + Binds: expect.arrayContaining([ + `${await fs.realpath(inputDir.path)}:/input`, + `${await fs.realpath(outputDir.path)}:/output`, + ]), + }, + Volumes: { + '/input': {}, + '/output': {}, + }, + }), + ); + }); + + it('should ping docker to test availability', async () => { + await containerTaskApi.runContainer({ + imageName, + args, + }); + + expect(mockDocker.ping).toHaveBeenCalled(); + }); + + it('should pass through the user and group id from the host machine and set the home dir', async () => { + await containerTaskApi.runContainer({ + imageName, + args, + }); + + const userOptions: UserOptions = {}; + if (process.getuid && process.getgid) { + userOptions.User = `${process.getuid()}:${process.getgid()}`; + } + + expect(mockDocker.run).toHaveBeenCalledWith( + imageName, + args, + expect.any(Stream), + expect.objectContaining({ + ...userOptions, + }), + ); + }); + + it('throws a correct error if the command fails in docker', async () => { + mockDocker.run.mockResolvedValueOnce([ + { + Error: new Error('Something went wrong with docker'), + StatusCode: 0, + }, + ]); + + await expect( + containerTaskApi.runContainer({ + imageName, + args, + }), + ).rejects.toThrow(/Something went wrong with docker/); + }); + + describe('where docker is unavailable', () => { + const dockerError = 'a docker error'; + + beforeEach(() => { + jest.spyOn(mockDocker, 'ping').mockImplementationOnce(() => { + throw new Error(dockerError); + }); + }); + + it('should throw with a descriptive error message including the docker error message', async () => { + await expect( + containerTaskApi.runContainer({ + imageName, + args, + }), + ).rejects.toThrow(new RegExp(`.+: ${dockerError}`)); + }); + }); + + it('should pass through the log stream to the docker client', async () => { + const logStream = new PassThrough(); + await containerTaskApi.runContainer({ + imageName, + args, + logStream, + }); + + expect(mockDocker.run).toHaveBeenCalledWith( + imageName, + args, + logStream, + expect.objectContaining({ + HostConfig: { + AutoRemove: true, + Binds: [], + }, + Volumes: {}, + }), + ); + }); +}); From 21c750bd6f5f4631185311fcbe52a02afaa1be84 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 2 May 2024 14:23:07 +0200 Subject: [PATCH 03/13] techdocs-node: containerRunner api report Signed-off-by: Vincenzo Scamporlino --- plugins/techdocs-node/api-report.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs-node/api-report.md b/plugins/techdocs-node/api-report.md index 2d3ca90983..b74772a376 100644 --- a/plugins/techdocs-node/api-report.md +++ b/plugins/techdocs-node/api-report.md @@ -7,7 +7,6 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { ContainerRunner } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import express from 'express'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; @@ -19,6 +18,11 @@ import { UrlReader } from '@backstage/backend-common'; import * as winston from 'winston'; import { Writable } from 'stream'; +// @public +export interface ContainerRunner { + runContainer(opts: RunContainerOptions): Promise; +} + // @public export class DirectoryPreparer implements PreparerBase { static fromConfig(config: Config, options: PreparerConfig): DirectoryPreparer; @@ -72,7 +76,6 @@ export class Generators implements GeneratorBuilder { config: Config, options: { logger: Logger; - containerRunner: ContainerRunner; customGenerator?: TechdocsGenerator; }, ): Promise; @@ -236,6 +239,19 @@ export type ReadinessResponse = { // @public export type RemoteProtocol = 'url' | 'dir'; +// @public +export type RunContainerOptions = { + imageName: string; + command?: string | string[]; + args: string[]; + logStream?: Writable; + mountDirs?: Record; + workingDir?: string; + envVars?: Record; + pullImage?: boolean; + defaultUser?: boolean; +}; + // @public export type SupportedGeneratorKey = 'techdocs' | string; From 48c38f0213e6efa583372b3152f4e830dc048a55 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 2 May 2024 14:37:19 +0200 Subject: [PATCH 04/13] changeset Signed-off-by: Vincenzo Scamporlino --- .changeset/famous-roses-smell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-roses-smell.md diff --git a/.changeset/famous-roses-smell.md b/.changeset/famous-roses-smell.md new file mode 100644 index 0000000000..9ea661cfac --- /dev/null +++ b/.changeset/famous-roses-smell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +`TechdocsGenerator` won't require a `containerRunner` option anymore for generating TechDocs in docker From 117d694e0a7bfe9701a303e89d98ead178f3f7b5 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 2 May 2024 14:37:37 +0200 Subject: [PATCH 05/13] backend: remove dockerode dep Signed-off-by: Vincenzo Scamporlino --- yarn.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 8da874b506..a5ffde3e54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26491,7 +26491,6 @@ __metadata: "@types/luxon": ^3.0.0 azure-devops-node-api: ^12.0.0 better-sqlite3: ^9.0.0 - dockerode: ^4.0.0 example-app: "link:../app" express: ^4.17.1 express-prom-bundle: ^7.0.0 From 6e2e5bcce6772bd00fec38d7c4b9ea35e1134b17 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 2 May 2024 21:08:21 +0200 Subject: [PATCH 06/13] techdocs-node: refactor DockerContainerRunner tests Signed-off-by: Vincenzo Scamporlino --- .../generate/DockerContainerRunner.test.ts | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts index 23536f8c50..066394ba86 100644 --- a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts +++ b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts @@ -15,13 +15,26 @@ */ import fs from 'fs-extra'; -import Docker from 'dockerode'; import Stream, { PassThrough } from 'stream'; import { DockerContainerRunner, UserOptions } from './DockerContainerRunner'; import { createMockDirectory } from '@backstage/backend-test-utils'; import { ContainerRunner } from './types'; -const mockDocker = new Docker() as jest.Mocked; +const mockPull = jest.fn(); +const mockRun = jest.fn(); +const mockPing = jest.fn(); + +jest.mock( + 'dockerode', + () => + function MockDocker() { + return { + pull: mockPull, + run: mockRun, + ping: mockPing, + }; + }, +); describe('DockerContainerRunner', () => { let containerTaskApi: ContainerRunner; @@ -33,23 +46,19 @@ describe('DockerContainerRunner', () => { inputDir.clear(); outputDir.clear(); - 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 }]); - - jest - .spyOn(mockDocker, 'ping') - .mockResolvedValue(Buffer.from('OK', 'utf-8')); + mockPull.mockImplementation( + ( + _repoTag: string, + _options: {}, + callback: (error?: any, result?: any) => void, + ) => { + const mockStream = new PassThrough(); + callback(undefined, mockStream); + mockStream.end(); + }, + ); + mockRun.mockResolvedValue([{ Error: null, StatusCode: 0 }]); + mockPing.mockResolvedValue(Buffer.from('OK', 'utf-8')); containerTaskApi = new DockerContainerRunner(); }); @@ -58,7 +67,7 @@ describe('DockerContainerRunner', () => { jest.clearAllMocks(); }); - const imageName = 'dockerOrg/image'; + const imageName = 'dockerorg/image'; const args = ['bash', '-c', 'echo test']; const mountDirs = { [inputDir.path]: '/input', @@ -74,13 +83,9 @@ describe('DockerContainerRunner', () => { args, }); - expect(mockDocker.pull).toHaveBeenCalledWith( - imageName, - {}, - expect.any(Function), - ); + expect(mockPull).toHaveBeenCalledWith(imageName, {}, expect.any(Function)); - expect(mockDocker.run).toHaveBeenCalled(); + expect(mockRun).toHaveBeenCalled(); }); it('should not pull the docker container when pullImage is false', async () => { @@ -90,8 +95,8 @@ describe('DockerContainerRunner', () => { pullImage: false, }); - expect(mockDocker.pull).not.toHaveBeenCalled(); - expect(mockDocker.run).toHaveBeenCalled(); + expect(mockPull).not.toHaveBeenCalled(); + expect(mockRun).toHaveBeenCalled(); }); it('should call the dockerClient run command with the correct arguments passed through', async () => { @@ -103,7 +108,7 @@ describe('DockerContainerRunner', () => { workingDir, }); - expect(mockDocker.run).toHaveBeenCalledWith( + expect(mockRun).toHaveBeenCalledWith( imageName, args, expect.any(Stream), @@ -131,7 +136,7 @@ describe('DockerContainerRunner', () => { args, }); - expect(mockDocker.ping).toHaveBeenCalled(); + expect(mockPing).toHaveBeenCalled(); }); it('should pass through the user and group id from the host machine and set the home dir', async () => { @@ -145,7 +150,7 @@ describe('DockerContainerRunner', () => { userOptions.User = `${process.getuid()}:${process.getgid()}`; } - expect(mockDocker.run).toHaveBeenCalledWith( + expect(mockRun).toHaveBeenCalledWith( imageName, args, expect.any(Stream), @@ -156,7 +161,7 @@ describe('DockerContainerRunner', () => { }); it('throws a correct error if the command fails in docker', async () => { - mockDocker.run.mockResolvedValueOnce([ + mockRun.mockResolvedValueOnce([ { Error: new Error('Something went wrong with docker'), StatusCode: 0, @@ -175,7 +180,7 @@ describe('DockerContainerRunner', () => { const dockerError = 'a docker error'; beforeEach(() => { - jest.spyOn(mockDocker, 'ping').mockImplementationOnce(() => { + mockPing.mockImplementationOnce(() => { throw new Error(dockerError); }); }); @@ -198,7 +203,7 @@ describe('DockerContainerRunner', () => { logStream, }); - expect(mockDocker.run).toHaveBeenCalledWith( + expect(mockRun).toHaveBeenCalledWith( imageName, args, logStream, From 2110d76586ee7ae8842c3847b839435d8637ffcf Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 2 May 2024 21:25:09 +0200 Subject: [PATCH 07/13] techdocs: remove dockerode dependency Signed-off-by: Vincenzo Scamporlino --- .changeset/late-falcons-sort.md | 7 +++++++ .../default-app/packages/backend/package.json.hbs | 1 - packages/techdocs-cli/package.json | 2 -- plugins/techdocs-backend/package.json | 2 -- yarn.lock | 5 +---- 5 files changed, 8 insertions(+), 9 deletions(-) create mode 100644 .changeset/late-falcons-sort.md diff --git a/.changeset/late-falcons-sort.md b/.changeset/late-falcons-sort.md new file mode 100644 index 0000000000..3f6468e652 --- /dev/null +++ b/.changeset/late-falcons-sort.md @@ -0,0 +1,7 @@ +--- +'@backstage/create-app': patch +'@backstage/plugin-techdocs-backend': patch +'@techdocs/cli': patch +--- + +Removed `dockerode` dependency. diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 049ea1c30c..b2c9b239d6 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -47,7 +47,6 @@ }, "devDependencies": { "@backstage/cli": "^{{version '@backstage/cli'}}", - "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", "@types/luxon": "^2.0.4" diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 74c90b4854..7a20eee17b 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -61,9 +61,7 @@ "@backstage/cli-common": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-techdocs-node": "workspace:^", - "@types/dockerode": "^3.3.0", "commander": "^12.0.0", - "dockerode": "^4.0.0", "fs-extra": "^11.0.0", "global-agent": "^3.0.0", "http-proxy": "^1.18.1", diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 1abf653784..3346214371 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -69,7 +69,6 @@ "@backstage/plugin-search-backend-module-techdocs": "workspace:^", "@backstage/plugin-techdocs-node": "workspace:^", "@types/express": "^4.17.6", - "dockerode": "^4.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", @@ -83,7 +82,6 @@ "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/dockerode": "^3.3.0", "msw": "^1.0.0", "supertest": "^6.1.3" }, diff --git a/yarn.lock b/yarn.lock index a5ffde3e54..86104b48a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7728,9 +7728,7 @@ __metadata: "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-search-backend-module-techdocs": "workspace:^" "@backstage/plugin-techdocs-node": "workspace:^" - "@types/dockerode": ^3.3.0 "@types/express": ^4.17.6 - dockerode: ^4.0.0 express: ^4.17.1 express-promise-router: ^4.1.0 fs-extra: ^11.2.0 @@ -16607,14 +16605,12 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/plugin-techdocs-node": "workspace:^" "@types/commander": ^2.12.2 - "@types/dockerode": ^3.3.0 "@types/fs-extra": ^11.0.0 "@types/http-proxy": ^1.17.4 "@types/node": ^18.17.8 "@types/serve-handler": ^6.1.0 "@types/webpack-env": ^1.15.3 commander: ^12.0.0 - dockerode: ^4.0.0 find-process: ^1.4.5 fs-extra: ^11.0.0 global-agent: ^3.0.0 @@ -26491,6 +26487,7 @@ __metadata: "@types/luxon": ^3.0.0 azure-devops-node-api: ^12.0.0 better-sqlite3: ^9.0.0 + dockerode: ^4.0.0 example-app: "link:../app" express: ^4.17.1 express-prom-bundle: ^7.0.0 From c674ffc1641f248666c1924d4b2a9df43f09d0fe Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 13 Jun 2024 15:04:48 +0200 Subject: [PATCH 08/13] techdocs-node: do not export ContainerRunner definitions Signed-off-by: Vincenzo Scamporlino --- .changeset/famous-roses-smell.md | 4 +-- plugins/techdocs-node/api-report.md | 20 ------------- .../generate/DockerContainerRunner.test.ts | 3 +- .../stages/generate/DockerContainerRunner.ts | 20 +++++++++---- .../src/stages/generate/generators.test.ts | 10 +------ .../src/stages/generate/index.ts | 2 -- .../src/stages/generate/techdocs.ts | 17 ++++------- .../src/stages/generate/types.ts | 30 ------------------- 8 files changed, 24 insertions(+), 82 deletions(-) diff --git a/.changeset/famous-roses-smell.md b/.changeset/famous-roses-smell.md index 9ea661cfac..26cbc4b9d3 100644 --- a/.changeset/famous-roses-smell.md +++ b/.changeset/famous-roses-smell.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-techdocs-node': patch +'@backstage/plugin-techdocs-node': minor --- -`TechdocsGenerator` won't require a `containerRunner` option anymore for generating TechDocs in docker +**BREAKING**: `TechdocsGenerator` won't require a `containerRunner` option anymore for generating TechDocs in docker. diff --git a/plugins/techdocs-node/api-report.md b/plugins/techdocs-node/api-report.md index b74772a376..81bba6990f 100644 --- a/plugins/techdocs-node/api-report.md +++ b/plugins/techdocs-node/api-report.md @@ -18,11 +18,6 @@ import { UrlReader } from '@backstage/backend-common'; import * as winston from 'winston'; import { Writable } from 'stream'; -// @public -export interface ContainerRunner { - runContainer(opts: RunContainerOptions): Promise; -} - // @public export class DirectoryPreparer implements PreparerBase { static fromConfig(config: Config, options: PreparerConfig): DirectoryPreparer; @@ -52,7 +47,6 @@ export type GeneratorBuilder = { // @public export type GeneratorOptions = { - containerRunner?: ContainerRunner; logger: Logger; }; @@ -239,19 +233,6 @@ export type ReadinessResponse = { // @public export type RemoteProtocol = 'url' | 'dir'; -// @public -export type RunContainerOptions = { - imageName: string; - command?: string | string[]; - args: string[]; - logStream?: Writable; - mountDirs?: Record; - workingDir?: string; - envVars?: Record; - pullImage?: boolean; - defaultUser?: boolean; -}; - // @public export type SupportedGeneratorKey = 'techdocs' | string; @@ -280,7 +261,6 @@ export interface TechDocsDocument extends IndexableDocument { export class TechdocsGenerator implements GeneratorBase { constructor(options: { logger: Logger; - containerRunner?: ContainerRunner; config: Config; scmIntegrations: ScmIntegrationRegistry; }); diff --git a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts index 066394ba86..4a9e364af3 100644 --- a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts +++ b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.test.ts @@ -18,7 +18,6 @@ import fs from 'fs-extra'; import Stream, { PassThrough } from 'stream'; import { DockerContainerRunner, UserOptions } from './DockerContainerRunner'; import { createMockDirectory } from '@backstage/backend-test-utils'; -import { ContainerRunner } from './types'; const mockPull = jest.fn(); const mockRun = jest.fn(); @@ -37,7 +36,7 @@ jest.mock( ); describe('DockerContainerRunner', () => { - let containerTaskApi: ContainerRunner; + let containerTaskApi: DockerContainerRunner; const inputDir = createMockDirectory(); const outputDir = createMockDirectory(); diff --git a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts index 31cb6bcaa8..b7cb7f1c86 100644 --- a/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts +++ b/plugins/techdocs-node/src/stages/generate/DockerContainerRunner.ts @@ -20,7 +20,7 @@ import { ForwardedError } from '@backstage/errors'; import { PassThrough } from 'stream'; import { pipeline as pipelineStream } from 'stream'; import { promisify } from 'util'; -import { ContainerRunner, RunContainerOptions } from './types'; +import { Writable } from 'stream'; const pipeline = promisify(pipelineStream); @@ -29,18 +29,26 @@ export type UserOptions = { }; /** - * A {@link ContainerRunner} for Docker containers. - * - * @public + * @internal */ -export class DockerContainerRunner implements ContainerRunner { +export class DockerContainerRunner { private readonly dockerClient: Docker; constructor() { this.dockerClient = new Docker(); } - async runContainer(options: RunContainerOptions) { + async runContainer(options: { + imageName: string; + command?: string | string[]; + args: string[]; + logStream?: Writable; + mountDirs?: Record; + workingDir?: string; + envVars?: Record; + pullImage?: boolean; + defaultUser?: boolean; + }) { const { imageName, command, diff --git a/plugins/techdocs-node/src/stages/generate/generators.test.ts b/plugins/techdocs-node/src/stages/generate/generators.test.ts index 3e0571af54..f80f070796 100644 --- a/plugins/techdocs-node/src/stages/generate/generators.test.ts +++ b/plugins/techdocs-node/src/stages/generate/generators.test.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - ContainerRunner, - loggerToWinstonLogger, -} from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { Generators } from './generators'; import { TechdocsGenerator } from './techdocs'; @@ -34,10 +31,6 @@ const mockEntity = { }; describe('generators', () => { - const containerRunner: jest.Mocked = { - runContainer: jest.fn(), - }; - it('should return error if no generator is registered', async () => { const generators = new Generators(); @@ -50,7 +43,6 @@ describe('generators', () => { const generators = new Generators(); const techdocs = TechdocsGenerator.fromConfig(new ConfigReader({}), { logger, - containerRunner, }); generators.register('techdocs', techdocs); diff --git a/plugins/techdocs-node/src/stages/generate/index.ts b/plugins/techdocs-node/src/stages/generate/index.ts index eb0aacff88..aaf27026c2 100644 --- a/plugins/techdocs-node/src/stages/generate/index.ts +++ b/plugins/techdocs-node/src/stages/generate/index.ts @@ -17,8 +17,6 @@ export { TechdocsGenerator } from './techdocs'; export { Generators } from './generators'; export { getMkdocsYml } from './helpers'; export type { - RunContainerOptions, - ContainerRunner, GeneratorBase, GeneratorOptions, GeneratorBuilder, diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index 6d7e3030a3..b53e1c27d0 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -35,7 +35,6 @@ import { patchMkdocsYmlWithPlugins, } from './mkdocsPatchers'; import { - ContainerRunner, GeneratorBase, GeneratorConfig, GeneratorOptions, @@ -58,18 +57,17 @@ export class TechdocsGenerator implements GeneratorBase { private readonly logger: Logger; private readonly options: GeneratorConfig; private readonly scmIntegrations: ScmIntegrationRegistry; - private containerRunner?: ContainerRunner; + /** * Returns a instance of TechDocs generator * @param config - A Backstage configuration * @param options - Options to configure the generator */ static fromConfig(config: Config, options: GeneratorOptions) { - const { containerRunner, logger } = options; + const { logger } = options; const scmIntegrations = ScmIntegrations.fromConfig(config); return new TechdocsGenerator({ logger, - containerRunner, config, scmIntegrations, }); @@ -77,13 +75,11 @@ export class TechdocsGenerator implements GeneratorBase { constructor(options: { logger: Logger; - containerRunner?: ContainerRunner; config: Config; scmIntegrations: ScmIntegrationRegistry; }) { this.logger = options.logger; this.options = readGeneratorConfig(options.config, options.logger); - this.containerRunner = options.containerRunner; this.scmIntegrations = options.scmIntegrations; } @@ -155,11 +151,9 @@ export class TechdocsGenerator implements GeneratorBase { `Successfully generated docs from ${inputDir} into ${outputDir} using local mkdocs`, ); break; - case 'docker': - if (this.containerRunner === undefined) { - this.containerRunner = new DockerContainerRunner(); - } - await this.containerRunner.runContainer({ + case 'docker': { + const containerRunner = new DockerContainerRunner(); + await containerRunner.runContainer({ imageName: this.options.dockerImage ?? TechdocsGenerator.defaultDockerImage, args: ['build', '-d', '/output'], @@ -176,6 +170,7 @@ export class TechdocsGenerator implements GeneratorBase { `Successfully generated docs from ${inputDir} into ${outputDir} using techdocs-container`, ); break; + } default: throw new Error( `Invalid config value "${this.options.runIn}" provided in 'techdocs.generators.techdocs'.`, diff --git a/plugins/techdocs-node/src/stages/generate/types.ts b/plugins/techdocs-node/src/stages/generate/types.ts index bde764c713..8a54ec5188 100644 --- a/plugins/techdocs-node/src/stages/generate/types.ts +++ b/plugins/techdocs-node/src/stages/generate/types.ts @@ -27,7 +27,6 @@ export type GeneratorRunInType = 'docker' | 'local'; * @public */ export type GeneratorOptions = { - containerRunner?: ContainerRunner; logger: Logger; }; @@ -98,32 +97,3 @@ export type DefaultMkdocsContent = { docs_dir: string; plugins: String[]; }; - -/** - * Options passed to the {@link ContainerRunner.runContainer} method. - * - * @public - */ -export type RunContainerOptions = { - imageName: string; - command?: string | string[]; - args: string[]; - logStream?: Writable; - mountDirs?: Record; - workingDir?: string; - envVars?: Record; - pullImage?: boolean; - defaultUser?: boolean; -}; - -/** - * Handles the running of containers, on behalf of others. - * - * @public - */ -export interface ContainerRunner { - /** - * Runs a container image to completion. - */ - runContainer(opts: RunContainerOptions): Promise; -} From e8c01ddc0a7d210bd6d7490445398348164510fc Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 17 Jun 2024 10:00:52 +0200 Subject: [PATCH 09/13] techdocs-node: still export ContainerRunner from backend-common Signed-off-by: Vincenzo Scamporlino --- .changeset/famous-roses-smell.md | 4 ++-- plugins/techdocs-node/api-report.md | 3 +++ plugins/techdocs-node/src/stages/generate/techdocs.ts | 11 ++++++++--- plugins/techdocs-node/src/stages/generate/types.ts | 2 ++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.changeset/famous-roses-smell.md b/.changeset/famous-roses-smell.md index 26cbc4b9d3..a091a6f32e 100644 --- a/.changeset/famous-roses-smell.md +++ b/.changeset/famous-roses-smell.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-techdocs-node': minor +'@backstage/plugin-techdocs-node': patch --- -**BREAKING**: `TechdocsGenerator` won't require a `containerRunner` option anymore for generating TechDocs in docker. +`TechdocsGenerator` won't require a `containerRunner` option anymore for generating TechDocs in docker. diff --git a/plugins/techdocs-node/api-report.md b/plugins/techdocs-node/api-report.md index 81bba6990f..7275586ef8 100644 --- a/plugins/techdocs-node/api-report.md +++ b/plugins/techdocs-node/api-report.md @@ -7,6 +7,7 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; +import { ContainerRunner } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import express from 'express'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; @@ -48,6 +49,7 @@ export type GeneratorBuilder = { // @public export type GeneratorOptions = { logger: Logger; + containerRunner?: ContainerRunner; }; // @public @@ -261,6 +263,7 @@ export interface TechDocsDocument extends IndexableDocument { export class TechdocsGenerator implements GeneratorBase { constructor(options: { logger: Logger; + containerRunner?: ContainerRunner; config: Config; scmIntegrations: ScmIntegrationRegistry; }); diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index b53e1c27d0..d66fd1e6f4 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -43,6 +43,7 @@ import { } from './types'; import { ForwardedError } from '@backstage/errors'; import { DockerContainerRunner } from './DockerContainerRunner'; +import { ContainerRunner } from '@backstage/backend-common'; /** * Generates documentation files @@ -55,6 +56,7 @@ export class TechdocsGenerator implements GeneratorBase { */ public static readonly defaultDockerImage = 'spotify/techdocs:v1.2.3'; private readonly logger: Logger; + private readonly containerRunner: ContainerRunner; private readonly options: GeneratorConfig; private readonly scmIntegrations: ScmIntegrationRegistry; @@ -64,10 +66,11 @@ export class TechdocsGenerator implements GeneratorBase { * @param options - Options to configure the generator */ static fromConfig(config: Config, options: GeneratorOptions) { - const { logger } = options; + const { containerRunner, logger } = options; const scmIntegrations = ScmIntegrations.fromConfig(config); return new TechdocsGenerator({ logger, + containerRunner, config, scmIntegrations, }); @@ -75,11 +78,14 @@ export class TechdocsGenerator implements GeneratorBase { constructor(options: { logger: Logger; + containerRunner?: ContainerRunner; config: Config; scmIntegrations: ScmIntegrationRegistry; }) { this.logger = options.logger; this.options = readGeneratorConfig(options.config, options.logger); + this.containerRunner = + options.containerRunner || new DockerContainerRunner(); this.scmIntegrations = options.scmIntegrations; } @@ -152,8 +158,7 @@ export class TechdocsGenerator implements GeneratorBase { ); break; case 'docker': { - const containerRunner = new DockerContainerRunner(); - await containerRunner.runContainer({ + await this.containerRunner.runContainer({ imageName: this.options.dockerImage ?? TechdocsGenerator.defaultDockerImage, args: ['build', '-d', '/output'], diff --git a/plugins/techdocs-node/src/stages/generate/types.ts b/plugins/techdocs-node/src/stages/generate/types.ts index 8a54ec5188..80468905fd 100644 --- a/plugins/techdocs-node/src/stages/generate/types.ts +++ b/plugins/techdocs-node/src/stages/generate/types.ts @@ -18,6 +18,7 @@ import { Entity } from '@backstage/catalog-model'; import { Writable } from 'stream'; import { Logger } from 'winston'; import { ParsedLocationAnnotation } from '../../helpers'; +import { ContainerRunner } from '@backstage/backend-common'; // Determines where the generator will be run export type GeneratorRunInType = 'docker' | 'local'; @@ -28,6 +29,7 @@ export type GeneratorRunInType = 'docker' | 'local'; */ export type GeneratorOptions = { logger: Logger; + containerRunner?: ContainerRunner; }; /** From 3bcfbf39d129541f25f8169b0993c1f87671f245 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 17 Jun 2024 10:28:29 +0200 Subject: [PATCH 10/13] techdocs-node: mark as deprecated Signed-off-by: Vincenzo Scamporlino --- plugins/techdocs-node/src/stages/generate/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/techdocs-node/src/stages/generate/types.ts b/plugins/techdocs-node/src/stages/generate/types.ts index 80468905fd..ca816d560f 100644 --- a/plugins/techdocs-node/src/stages/generate/types.ts +++ b/plugins/techdocs-node/src/stages/generate/types.ts @@ -29,6 +29,10 @@ export type GeneratorRunInType = 'docker' | 'local'; */ export type GeneratorOptions = { logger: Logger; + /** + * @deprecated containerRunner is now instantiated in + * the generator and this option will be removed in the future + */ containerRunner?: ContainerRunner; }; From d7f9894950079d98c01c1b87062b66d3b7cc4ead Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 17 Jun 2024 13:46:43 +0200 Subject: [PATCH 11/13] techdocs-node: move default containerRunner init Signed-off-by: Vincenzo Scamporlino --- plugins/techdocs-node/src/stages/generate/techdocs.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index d66fd1e6f4..e5e2b7191e 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -56,7 +56,7 @@ export class TechdocsGenerator implements GeneratorBase { */ public static readonly defaultDockerImage = 'spotify/techdocs:v1.2.3'; private readonly logger: Logger; - private readonly containerRunner: ContainerRunner; + private readonly containerRunner?: ContainerRunner; private readonly options: GeneratorConfig; private readonly scmIntegrations: ScmIntegrationRegistry; @@ -84,8 +84,7 @@ export class TechdocsGenerator implements GeneratorBase { }) { this.logger = options.logger; this.options = readGeneratorConfig(options.config, options.logger); - this.containerRunner = - options.containerRunner || new DockerContainerRunner(); + this.containerRunner = options.containerRunner; this.scmIntegrations = options.scmIntegrations; } @@ -158,7 +157,9 @@ export class TechdocsGenerator implements GeneratorBase { ); break; case 'docker': { - await this.containerRunner.runContainer({ + const containerRunner = + this.containerRunner || new DockerContainerRunner(); + await containerRunner.runContainer({ imageName: this.options.dockerImage ?? TechdocsGenerator.defaultDockerImage, args: ['build', '-d', '/output'], From f59e1c67d072b7748609bd52c893d83c4fa269c9 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 17 Jun 2024 15:51:06 +0200 Subject: [PATCH 12/13] techdocs-node: add missing option Signed-off-by: Vincenzo Scamporlino --- plugins/techdocs-node/api-report.md | 1 + plugins/techdocs-node/src/stages/generate/generators.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/plugins/techdocs-node/api-report.md b/plugins/techdocs-node/api-report.md index 7275586ef8..3966b7d2d8 100644 --- a/plugins/techdocs-node/api-report.md +++ b/plugins/techdocs-node/api-report.md @@ -72,6 +72,7 @@ export class Generators implements GeneratorBuilder { config: Config, options: { logger: Logger; + containerRunner?: ContainerRunner; customGenerator?: TechdocsGenerator; }, ): Promise; diff --git a/plugins/techdocs-node/src/stages/generate/generators.ts b/plugins/techdocs-node/src/stages/generate/generators.ts index 60798cf9c0..0c20ac1588 100644 --- a/plugins/techdocs-node/src/stages/generate/generators.ts +++ b/plugins/techdocs-node/src/stages/generate/generators.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ContainerRunner } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { Logger } from 'winston'; @@ -41,6 +42,7 @@ export class Generators implements GeneratorBuilder { config: Config, options: { logger: Logger; + containerRunner?: ContainerRunner; customGenerator?: TechdocsGenerator; }, ): Promise { From a311472366315d1761c52c8150f7fcebeb593c38 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 17 Jun 2024 16:24:55 +0200 Subject: [PATCH 13/13] create-app: remove dockerode Signed-off-by: Vincenzo Scamporlino --- .../templates/default-app/packages/backend/package.json.hbs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index b2c9b239d6..8d95ee60a5 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -40,7 +40,6 @@ "@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}", "app": "link:../app", "better-sqlite3": "^9.0.0", - "dockerode": "^3.3.1", "node-gyp": "^10.0.0", "pg": "^8.11.3", "winston": "^3.2.1"