From 3ed9753d658c18c7dbf70b368ef3101b5affb2e6 Mon Sep 17 00:00:00 2001 From: Philip Harrison Date: Wed, 28 Jul 2021 16:33:54 +0100 Subject: [PATCH] Extract container service and helpers from updater Moving some shared bits out of the updater class to reduce bloat. --- __tests__/container-service.test.ts | 24 ++++++++ __tests__/utils.test.ts | 20 +++++++ src/container-service.ts | 35 +++++++++++ src/file-types.ts | 27 +++++++++ src/updater.ts | 92 +++++++---------------------- src/utils.ts | 12 ++++ 6 files changed, 139 insertions(+), 71 deletions(-) create mode 100644 __tests__/container-service.test.ts create mode 100644 __tests__/utils.test.ts create mode 100644 src/container-service.ts create mode 100644 src/file-types.ts create mode 100644 src/utils.ts diff --git a/__tests__/container-service.test.ts b/__tests__/container-service.test.ts new file mode 100644 index 0000000..64bdc38 --- /dev/null +++ b/__tests__/container-service.test.ts @@ -0,0 +1,24 @@ +import Docker from 'dockerode' + +import {ContainerService} from '../src/container-service' +import {ImageService} from '../src/image-service' + +describe('ContainerService', () => { + const docker = new Docker() + let container: any + + beforeAll(async () => { + await ImageService.pull('alpine') + container = await docker.createContainer({ + Image: 'alpine', + AttachStdout: true, + AttachStderr: true, + Cmd: ['/bin/sh', '-c', 'echo $VAR'], + Env: ['VAR=env-var'] + }) + }) + + test('runs containers', async () => { + await ContainerService.run(container) + }) +}) diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts new file mode 100644 index 0000000..0f1c35c --- /dev/null +++ b/__tests__/utils.test.ts @@ -0,0 +1,20 @@ +import {base64DecodeDependencyFile} from '../src/utils' + +describe('base64DecodeDependencyFile', () => { + test('clones the dependency file', () => { + const dependencyFile = { + name: 'package.json', + content: 'dGVzdCBzdHJpbmc=', + directory: '/', + type: 'file', + support_file: false, + content_encoding: 'utf-8', + deleted: false, + operation: 'add' + } + + const decoded = base64DecodeDependencyFile(dependencyFile) + expect(decoded.content).toEqual('test string') + expect(dependencyFile.content).toEqual('dGVzdCBzdHJpbmc=') + }) +}) diff --git a/src/container-service.ts b/src/container-service.ts new file mode 100644 index 0000000..c0e04b5 --- /dev/null +++ b/src/container-service.ts @@ -0,0 +1,35 @@ +import * as core from '@actions/core' +import {Container} from 'dockerode' +import {pack} from 'tar-stream' +import {FileFetcherInput, FileUpdaterInput} from './file-types' + +export const ContainerService = { + async storeInput( + name: string, + path: string, + container: Container, + input: FileFetcherInput | FileUpdaterInput + ): Promise { + const tar = pack() + tar.entry({name}, JSON.stringify(input)) + tar.finalize() + await container.putArchive(tar, {path}) + }, + + async run(container: Container): Promise { + try { + const stream = await container.attach({ + stream: true, + stdout: true, + stderr: true + }) + container.modem.demuxStream(stream, process.stdout, process.stderr) + + await container.start() + await container.wait() + } finally { + await container.remove() + core.info(`Cleaned up container ${container.id}`) + } + } +} diff --git a/src/file-types.ts b/src/file-types.ts new file mode 100644 index 0000000..d40d15b --- /dev/null +++ b/src/file-types.ts @@ -0,0 +1,27 @@ +import {Credential, JobDetails} from './api-client' + +export type FetchedFiles = { + base_commit_sha: string + dependency_files: any[] + base64_dependency_files: any[] +} + +export type FileFetcherInput = { + job: JobDetails + credentials: Credential[] +} + +export type DependencyFile = { + name: string + content: any + directory: string + type: string + support_file: boolean + content_encoding: string + deleted: boolean + operation: string +} + +export type FileUpdaterInput = FetchedFiles & { + job: JobDetails +} diff --git a/src/updater.ts b/src/updater.ts index 06a8b31..5af72f6 100644 --- a/src/updater.ts +++ b/src/updater.ts @@ -3,7 +3,9 @@ import Docker, {Container} from 'dockerode' import path from 'path' import fs from 'fs' import {Credential, JobDetails, APIClient} from './api-client' -import {pack} from 'tar-stream' +import {ContainerService} from './container-service' +import {base64DecodeDependencyFile} from './utils' +import {DependencyFile, FetchedFiles, FileUpdaterInput} from './file-types' const JOB_INPUT_FILENAME = 'job.json' const JOB_INPUT_PATH = `/home/dependabot/dependabot-updater` @@ -11,9 +13,6 @@ const JOB_OUTPUT_FILENAME = 'output.json' const JOB_OUTPUT_PATH = '/home/dependabot/dependabot-updater/output' const REPO_CONTENTS_PATH = '/home/dependabot/dependabot-updater/repo' -const decode = (str: string): string => - Buffer.from(str, 'base64').toString('binary') - export class Updater { docker: Docker constructor( @@ -47,22 +46,21 @@ export class Updater { } } - private decodeBase64Content(file: DependencyFile): string { - const fileCopy = JSON.parse(JSON.stringify(file)) - fileCopy.content = decode(fileCopy.content) - return fileCopy - } - private async runFileFetcher( details: JobDetails, credentials: Credential[] ): Promise { const container = await this.createContainer('fetch_files') - await this.storeContainerInput(container, { - job: details, - credentials - }) - await this.runContainer(container) + await ContainerService.storeInput( + JOB_INPUT_FILENAME, + JOB_INPUT_PATH, + container, + { + job: details, + credentials + } + ) + await ContainerService.run(container) const outputPath = path.join(__dirname, '../output/output.json') if (!fs.existsSync(outputPath)) { @@ -76,7 +74,7 @@ export class Updater { base_commit_sha: fileFetcherOutput.base_commit_sha, base64_dependency_files: fileFetcherOutput.base64_dependency_files, dependency_files: fileFetcherOutput.base64_dependency_files.map( - (file: DependencyFile) => this.decodeBase64Content(file) + (file: DependencyFile) => base64DecodeDependencyFile(file) ) } @@ -95,8 +93,13 @@ export class Updater { dependency_files: files.dependency_files, job: details } - await this.storeContainerInput(container, containerInput) - await this.runContainer(container) + await ContainerService.storeInput( + JOB_INPUT_FILENAME, + JOB_INPUT_PATH, + container, + containerInput + ) + await ContainerService.run(container) } private async createContainer(updaterCommand: string): Promise { @@ -125,57 +128,4 @@ export class Updater { core.info(`Created ${updaterCommand} container: ${container.id}`) return container } - - private async storeContainerInput( - container: Container, - input: FileFetcherInput | FileUpdaterInput - ): Promise { - const tar = pack() - tar.entry({name: JOB_INPUT_FILENAME}, JSON.stringify(input)) - tar.finalize() - await container.putArchive(tar, {path: JOB_INPUT_PATH}) - } - - private async runContainer(container: Container): Promise { - try { - await container.start() - const stream = await container.attach({ - stream: true, - stdout: true, - stderr: true - }) - container.modem.demuxStream(stream, process.stdout, process.stderr) - - await container.wait() - } finally { - await container.remove() - core.info(`Cleaned up container ${container.id}`) - } - } -} - -type FileFetcherInput = { - job: JobDetails - credentials: Credential[] -} - -type FetchedFiles = { - base_commit_sha: string - dependency_files: any[] - base64_dependency_files: any[] -} - -type DependencyFile = { - name: string - content: any - directory: string - type: string - support_file: boolean - content_encoding: string - deleted: boolean - operation: string -} - -type FileUpdaterInput = FetchedFiles & { - job: JobDetails } diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..3d5ef4f --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,12 @@ +import {DependencyFile} from './file-types' + +const base64Decode = (str: string): string => + Buffer.from(str, 'base64').toString('binary') + +export const base64DecodeDependencyFile = ( + file: DependencyFile +): DependencyFile => { + const fileCopy = JSON.parse(JSON.stringify(file)) + fileCopy.content = base64Decode(fileCopy.content) + return fileCopy +}