diff --git a/examples/prepare-job.json b/examples/prepare-job.json index abc9474..cf6f838 100644 --- a/examples/prepare-job.json +++ b/examples/prepare-job.json @@ -5,7 +5,7 @@ "args": { "container": { "image": "node:14.16", - "workingDirectory": "/__w/thboop-test2/thboop-test2", + "workingDirectory": "/__w/repo/repo", "createOptions": "--cpus 1", "environmentVariables": { "NODE_ENV": "development" diff --git a/examples/run-container-step.json b/examples/run-container-step.json index cf3cf97..7a7cfac 100644 --- a/examples/run-container-step.json +++ b/examples/run-container-step.json @@ -16,7 +16,7 @@ "echo \"hello world2\"" ], "entryPoint": "bash", - "workingDirectory": "/__w/thboop-test2/thboop-test2", + "workingDirectory": "/__w/repo/repo", "createOptions": "--cpus 1", "environmentVariables": { "NODE_ENV": "development" diff --git a/examples/run-script-step.json b/examples/run-script-step.json index 2b7aebf..1b57dc1 100644 --- a/examples/run-script-step.json +++ b/examples/run-script-step.json @@ -21,6 +21,6 @@ "/foo/bar", "bar/foo" ], - "workingDirectory": "/__w/thboop-test2/thboop-test2" + "workingDirectory": "/__w/repo/repo" } } \ No newline at end of file diff --git a/packages/docker/src/dockerCommands/container.ts b/packages/docker/src/dockerCommands/container.ts index eee67a5..9af91c8 100644 --- a/packages/docker/src/dockerCommands/container.ts +++ b/packages/docker/src/dockerCommands/container.ts @@ -2,11 +2,9 @@ import * as core from '@actions/core' import * as fs from 'fs' import { ContainerInfo, - JobContainerInfo, Registry, RunContainerStepArgs, - ServiceContainerInfo, - StepContainerInfo + ServiceContainerInfo } from 'hooklib/lib' import * as path from 'path' import { env } from 'process' @@ -52,7 +50,7 @@ export async function createContainer( const mountVolumes = [ ...(args.userMountVolumes || []), - ...((args as JobContainerInfo | StepContainerInfo).systemMountVolumes || []) + ...(args.systemMountVolumes || []) ] for (const mountVolume of mountVolumes) { dockerArgs.push( @@ -345,8 +343,9 @@ export async function containerExecStep( dockerArgs.push(`"${key}"`) } - // Todo figure out prepend path and update it here - // (we need to pass path in as -e Path={fullpath}) where {fullpath is the prepend path added to the current containers path} + if (args.prependPath?.length) { + dockerArgs.push('-e', `"PATH=${args.prependPath.join(':')}:$PATH"`) + } dockerArgs.push(containerId) dockerArgs.push(args.entryPoint) diff --git a/packages/docker/tests/cleanup-job-test.ts b/packages/docker/tests/cleanup-job-test.ts index 648718c..6847ebe 100644 --- a/packages/docker/tests/cleanup-job-test.ts +++ b/packages/docker/tests/cleanup-job-test.ts @@ -1,16 +1,7 @@ import * as fs from 'fs' -import * as path from 'path' -import { v4 as uuidv4 } from 'uuid' import { cleanupJob, prepareJob } from '../src/hooks' import TestSetup from './test-setup' -const prepareJobInputPath = path.resolve( - `${__dirname}/../../../examples/prepare-job.json` -) - -let prepareJobOutputPath: string -let prepareJobDefinition: any - let testSetup: TestSetup jest.useRealTimers() @@ -20,28 +11,25 @@ describe('cleanup job', () => { testSetup = new TestSetup() testSetup.initialize() - const prepareJobRawData = fs.readFileSync(prepareJobInputPath, 'utf8') - prepareJobDefinition = JSON.parse(prepareJobRawData.toString()) + const prepareJobDefinition = JSON.parse( + fs.readFileSync( + `${__dirname}/../../../examples/prepare-job.json`, + 'utf-8' + ) + ) - prepareJobOutputPath = `${ - testSetup.testDir - }/prepare-job-output-${uuidv4()}.json` - fs.writeFileSync(prepareJobOutputPath, '') + const prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output.json' + ) - prepareJobDefinition.args.container.userMountVolumes = - testSetup.userMountVolumes - prepareJobDefinition.args.container.systemMountVolumes = - testSetup.systemMountVolumes - prepareJobDefinition.args.container.workingDirectory = - testSetup.containerWorkingDirectory prepareJobDefinition.args.container.registry = null - prepareJobDefinition.args.services.forEach(s => (s.registry = null)) - - await prepareJob(prepareJobDefinition.args, prepareJobOutputPath) + prepareJobDefinition.args.services.forEach(s => { + s.registry = null + }) + await prepareJob(prepareJobDefinition.args, prepareJobOutput) }) afterEach(() => { - fs.rmSync(prepareJobOutputPath, { force: true }) testSetup.teardown() }) diff --git a/packages/docker/tests/e2e-test.ts b/packages/docker/tests/e2e-test.ts index ce4b721..67f63d9 100644 --- a/packages/docker/tests/e2e-test.ts +++ b/packages/docker/tests/e2e-test.ts @@ -1,6 +1,5 @@ import * as fs from 'fs' import * as path from 'path' -import { v4 as uuidv4 } from 'uuid' import { cleanupJob, prepareJob, @@ -9,61 +8,41 @@ import { } from '../src/hooks' import TestSetup from './test-setup' -const prepareJobJson = fs.readFileSync( - path.resolve(__dirname + '/../../../examples/prepare-job.json'), - 'utf8' -) +const definitions = { + prepareJob: JSON.parse( + fs.readFileSync( + path.resolve(__dirname + '/../../../examples/prepare-job.json'), + 'utf8' + ) + ), -const containerStepJson = fs.readFileSync( - path.resolve(__dirname + '/../../../examples/run-container-step.json'), - 'utf8' -) + runContainerStep: JSON.parse( + fs.readFileSync( + path.resolve(__dirname + '/../../../examples/run-container-step.json'), + 'utf8' + ) + ), -let prepareJobDefinition: any -let scriptStepDefinition: any -let runContainerStepDefinition: any - -let prepareJobOutputFilePath: string + runScriptStep: JSON.parse( + fs.readFileSync( + path.resolve(__dirname + '/../../../examples/run-script-step.json'), + 'utf-8' + ) + ) +} let testSetup: TestSetup describe('e2e', () => { beforeEach(() => { - // init dirs testSetup = new TestSetup() testSetup.initialize() - - prepareJobDefinition = JSON.parse(prepareJobJson) - prepareJobDefinition.args.container.userMountVolumes = - testSetup.userMountVolumes - prepareJobDefinition.args.container.systemMountVolumes = + definitions.prepareJob.args.container.systemMountVolumes = testSetup.systemMountVolumes - prepareJobDefinition.args.container.workingDirectory = - testSetup.containerWorkingDirectory - prepareJobDefinition.args.container.registry = null - prepareJobDefinition.args.services.forEach(s => (s.registry = null)) - - const scriptStepJson = fs.readFileSync( - path.resolve(__dirname + '/../../../examples/run-script-step.json'), - 'utf8' - ) - scriptStepDefinition = JSON.parse(scriptStepJson) - scriptStepDefinition.args.workingDirectory = - testSetup.containerWorkingDirectory - scriptStepDefinition.args.registry = null - - runContainerStepDefinition = JSON.parse(containerStepJson) - runContainerStepDefinition.args.workingDirectory = - testSetup.containerWorkingDirectory - runContainerStepDefinition.args.userMountVolumes = - testSetup.userMountVolumes - runContainerStepDefinition.args.systemMountVolumes = - runContainerStepDefinition.args.registry = null - - prepareJobOutputFilePath = `${ - testSetup.testDir - }/prepare-job-output-${uuidv4()}.json` - fs.writeFileSync(prepareJobOutputFilePath, '') + definitions.prepareJob.args.container.registry = null + definitions.prepareJob.args.services.forEach(s => { + s.registry = null + }) }) afterEach(() => { @@ -71,31 +50,45 @@ describe('e2e', () => { }) it('should prepare job, then run script step, then run container step then cleanup', async () => { + const prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output.json' + ) + await expect( - prepareJob(prepareJobDefinition.args, prepareJobOutputFilePath) + prepareJob(definitions.prepareJob.args, prepareJobOutput) ).resolves.not.toThrow() - let rawState = fs.readFileSync(prepareJobOutputFilePath, 'utf-8') + + let rawState = fs.readFileSync(prepareJobOutput, 'utf-8') let resp = JSON.parse(rawState) + await expect( - runScriptStep(scriptStepDefinition.args, resp.state) + runScriptStep(definitions.runScriptStep.args, resp.state) ).resolves.not.toThrow() + await expect( - runContainerStep(runContainerStepDefinition.args, resp.state) + runContainerStep(definitions.runContainerStep.args, resp.state) ).resolves.not.toThrow() + await expect(cleanupJob()).resolves.not.toThrow() }) it('should prepare job, then run script step, then run container step with Dockerfile then cleanup', async () => { + const prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output.json' + ) + await expect( - prepareJob(prepareJobDefinition.args, prepareJobOutputFilePath) - ).resolves.not.toThrow() - let rawState = fs.readFileSync(prepareJobOutputFilePath, 'utf-8') - let resp = JSON.parse(rawState) - await expect( - runScriptStep(scriptStepDefinition.args, resp.state) + prepareJob(definitions.prepareJob.args, prepareJobOutput) ).resolves.not.toThrow() - const dockerfilePath = `${testSetup.testDir}/Dockerfile` + let rawState = fs.readFileSync(prepareJobOutput, 'utf-8') + let resp = JSON.parse(rawState) + + await expect( + runScriptStep(definitions.runScriptStep.args, resp.state) + ).resolves.not.toThrow() + + const dockerfilePath = `${testSetup.workingDirectory}/Dockerfile` fs.writeFileSync( dockerfilePath, `FROM ubuntu:latest @@ -103,14 +96,17 @@ ENV TEST=test ENTRYPOINT [ "tail", "-f", "/dev/null" ] ` ) + const containerStepDataCopy = JSON.parse( - JSON.stringify(runContainerStepDefinition) + JSON.stringify(definitions.runContainerStep) ) + containerStepDataCopy.args.dockerfile = 'Dockerfile' - containerStepDataCopy.args.context = '.' + await expect( runContainerStep(containerStepDataCopy.args, resp.state) ).resolves.not.toThrow() + await expect(cleanupJob()).resolves.not.toThrow() }) }) diff --git a/packages/docker/tests/prepare-job-test.ts b/packages/docker/tests/prepare-job-test.ts index 12c5e6f..d761b7d 100644 --- a/packages/docker/tests/prepare-job-test.ts +++ b/packages/docker/tests/prepare-job-test.ts @@ -1,36 +1,28 @@ import * as fs from 'fs' -import { v4 as uuidv4 } from 'uuid' import { prepareJob } from '../src/hooks' import TestSetup from './test-setup' jest.useRealTimers() -let prepareJobOutputPath: string -let prepareJobData: any -const prepareJobInputPath = `${__dirname}/../../../examples/prepare-job.json` +const prepareJobDefinition = JSON.parse( + fs.readFileSync(`${__dirname}/../../../examples/prepare-job.json`, 'utf-8') +) let testSetup: TestSetup describe('prepare job', () => { - beforeEach(async () => { + beforeEach(() => { testSetup = new TestSetup() testSetup.initialize() - let prepareJobRawData = fs.readFileSync(prepareJobInputPath, 'utf8') - prepareJobData = JSON.parse(prepareJobRawData.toString()) - - prepareJobData.args.container.userMountVolumes = testSetup.userMountVolumes - prepareJobData.args.container.systemMountVolumes = + prepareJobDefinition.args.container.systemMountVolumes = testSetup.systemMountVolumes - prepareJobData.args.container.workingDirectory = - testSetup.containerWorkingDirectory - prepareJobData.args.container.registry = null - prepareJobData.args.services.forEach(s => (s.registry = null)) - - prepareJobOutputPath = `${ - testSetup.testDir - }/prepare-job-output-${uuidv4()}.json` - fs.writeFileSync(prepareJobOutputPath, '') + prepareJobDefinition.args.container.workingDirectory = + testSetup.workingDirectory + prepareJobDefinition.args.container.registry = null + prepareJobDefinition.args.services.forEach(s => { + s.registry = null + }) }) afterEach(() => { @@ -38,38 +30,68 @@ describe('prepare job', () => { }) it('should not throw', async () => { + const prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output.json' + ) await expect( - prepareJob(prepareJobData.args, prepareJobOutputPath) + prepareJob(prepareJobDefinition.args, prepareJobOutput) ).resolves.not.toThrow() - expect(() => fs.readFileSync(prepareJobOutputPath, 'utf-8')).not.toThrow() + expect(() => fs.readFileSync(prepareJobOutput, 'utf-8')).not.toThrow() }) it('should have JSON output written to a file', async () => { - await prepareJob(prepareJobData.args, prepareJobOutputPath) - const prepareJobOutputContent = fs.readFileSync( - prepareJobOutputPath, - 'utf-8' + const prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output.json' ) + await prepareJob(prepareJobDefinition.args, prepareJobOutput) + const prepareJobOutputContent = fs.readFileSync(prepareJobOutput, 'utf-8') expect(() => JSON.parse(prepareJobOutputContent)).not.toThrow() }) it('should have context written to a file', async () => { - await prepareJob(prepareJobData.args, prepareJobOutputPath) - const prepareJobOutputContent = fs.readFileSync( - prepareJobOutputPath, - 'utf-8' + const prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output.json' + ) + await prepareJob(prepareJobDefinition.args, prepareJobOutput) + const parsedPrepareJobOutput = JSON.parse( + fs.readFileSync(prepareJobOutput, 'utf-8') ) - const parsedPrepareJobOutput = JSON.parse(prepareJobOutputContent) expect(parsedPrepareJobOutput.context).toBeDefined() }) - it('should have container ids written to file', async () => { - await prepareJob(prepareJobData.args, prepareJobOutputPath) - const prepareJobOutputContent = fs.readFileSync( - prepareJobOutputPath, - 'utf-8' + it('should have isAlpine field set correctly', async () => { + let prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output-alpine.json' ) + const prepareJobArgsClone = JSON.parse( + JSON.stringify(prepareJobDefinition.args) + ) + prepareJobArgsClone.container.image = 'alpine:latest' + await prepareJob(prepareJobArgsClone, prepareJobOutput) + + let parsedPrepareJobOutput = JSON.parse( + fs.readFileSync(prepareJobOutput, 'utf-8') + ) + expect(parsedPrepareJobOutput.isAlpine).toBe(true) + + prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output-ubuntu.json' + ) + prepareJobArgsClone.container.image = 'ubuntu:latest' + await prepareJob(prepareJobArgsClone, prepareJobOutput) + parsedPrepareJobOutput = JSON.parse( + fs.readFileSync(prepareJobOutput, 'utf-8') + ) + expect(parsedPrepareJobOutput.isAlpine).toBe(false) + }) + + it('should have container ids written to file', async () => { + const prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output.json' + ) + await prepareJob(prepareJobDefinition.args, prepareJobOutput) + const prepareJobOutputContent = fs.readFileSync(prepareJobOutput, 'utf-8') const parsedPrepareJobOutput = JSON.parse(prepareJobOutputContent) expect(parsedPrepareJobOutput.context.container.id).toBeDefined() @@ -78,11 +100,11 @@ describe('prepare job', () => { }) it('should have ports for context written in form [containerPort]:[hostPort]', async () => { - await prepareJob(prepareJobData.args, prepareJobOutputPath) - const prepareJobOutputContent = fs.readFileSync( - prepareJobOutputPath, - 'utf-8' + const prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output.json' ) + await prepareJob(prepareJobDefinition.args, prepareJobOutput) + const prepareJobOutputContent = fs.readFileSync(prepareJobOutput, 'utf-8') const parsedPrepareJobOutput = JSON.parse(prepareJobOutputContent) const mainContainerPorts = parsedPrepareJobOutput.context.container.ports diff --git a/packages/docker/tests/run-script-step.ts b/packages/docker/tests/run-script-step.ts new file mode 100644 index 0000000..1dc7cca --- /dev/null +++ b/packages/docker/tests/run-script-step.ts @@ -0,0 +1,51 @@ +import * as fs from 'fs' +import { PrepareJobResponse } from 'hooklib/lib' +import * as path from 'path' +import { prepareJob, runScriptStep } from '../src/hooks' +import TestSetup from './test-setup' + +jest.useRealTimers() + +let testSetup: TestSetup + +const definitions = { + prepareJob: JSON.parse( + fs.readFileSync( + path.resolve(__dirname + '/../../../examples/prepare-job.json'), + 'utf8' + ) + ), + + runScriptStep: JSON.parse( + fs.readFileSync( + path.resolve(__dirname + '/../../../examples/run-script-step.json'), + 'utf-8' + ) + ) +} + +let prepareJobResponse: PrepareJobResponse + +describe('run-script-step', () => { + beforeEach(async () => { + testSetup = new TestSetup() + testSetup.initialize() + + const prepareJobOutput = testSetup.createOutputFile( + 'prepare-job-output.json' + ) + definitions.prepareJob.args.container.registry = null + definitions.prepareJob.args.services.forEach(s => { + s.registry = null + }) + await prepareJob(definitions.prepareJob.args, prepareJobOutput) + + prepareJobResponse = JSON.parse(fs.readFileSync(prepareJobOutput, 'utf-8')) + }) + + it('Should run script step without exceptions', async () => { + await expect( + runScriptStep(definitions.runScriptStep.args, prepareJobResponse.state) + ).resolves.not.toThrow() + }) +}) diff --git a/packages/docker/tests/test-setup.ts b/packages/docker/tests/test-setup.ts index efb9c9d..011631f 100644 --- a/packages/docker/tests/test-setup.ts +++ b/packages/docker/tests/test-setup.ts @@ -1,11 +1,13 @@ import * as fs from 'fs' import { Mount } from 'hooklib' +import * as path from 'path' import { env } from 'process' import { v4 as uuidv4 } from 'uuid' export default class TestSetup { private testdir: string private runnerMockDir: string + readonly runnerOutputDir: string private runnerMockSubdirs = { work: '_work', @@ -16,54 +18,45 @@ export default class TestSetup { githubHome: '_work/_temp/_github_home', githubWorkflow: '_work/_temp/_github_workflow' } - private readonly projectName = 'test' + + private readonly projectName = 'repo' constructor() { this.testdir = `${__dirname}/_temp/${uuidv4()}` this.runnerMockDir = `${this.testdir}/runner/_layout` - } - - public initialize(): void { - for (const dir of this.allTestDirectories) { - fs.mkdirSync(dir, { recursive: true }) - } - env.RUNNER_NAME = 'test' - env.RUNNER_TEMP = `${this.runnerMockDir}/${this.runnerMockSubdirs.workTemp}` - env.GITHUB_WORKSPACE = this.runnerProjectWorkDir - } - - public teardown(): void { - fs.rmdirSync(this.testdir, { recursive: true }) - } - - public get userMountVolumes(): Mount[] { - return [ - { - sourceVolumePath: 'my_docker_volume', - targetVolumePath: '/volume_mount', - readOnly: false - } - ] - } - - public get runnerProjectWorkDir() { - return `${this.runnerMockDir}/_work/${this.projectName}/${this.projectName}` - } - - public get testDir() { - return this.testdir + this.runnerOutputDir = `${this.testdir}/outputs` } private get allTestDirectories() { - const resp = [this.testdir, this.runnerMockDir, this.runnerProjectWorkDir] + const resp = [this.testdir, this.runnerMockDir, this.runnerOutputDir] for (const [key, value] of Object.entries(this.runnerMockSubdirs)) { resp.push(`${this.runnerMockDir}/${value}`) } + resp.push( + `${this.runnerMockDir}/_work/${this.projectName}/${this.projectName}` + ) + return resp } + public initialize(): void { + env['GITHUB_WORKSPACE'] = this.workingDirectory + env['RUNNER_NAME'] = 'test' + env[ + 'RUNNER_TEMP' + ] = `${this.runnerMockDir}/${this.runnerMockSubdirs.workTemp}` + + for (const dir of this.allTestDirectories) { + fs.mkdirSync(dir, { recursive: true }) + } + } + + public teardown(): void { + fs.rmdirSync(this.testdir, { recursive: true }) + } + public get systemMountVolumes(): Mount[] { return [ { @@ -109,6 +102,16 @@ export default class TestSetup { ] } + public createOutputFile(name: string): string { + let filePath = path.join(this.runnerOutputDir, name || `${uuidv4()}.json`) + fs.writeFileSync(filePath, '') + return filePath + } + + public get workingDirectory(): string { + return `${this.runnerMockDir}/_work/${this.projectName}/${this.projectName}` + } + public get containerWorkingDirectory(): string { return `/__w/${this.projectName}/${this.projectName}` } diff --git a/packages/hooklib/src/interfaces.ts b/packages/hooklib/src/interfaces.ts index f29dcbd..53e20c1 100644 --- a/packages/hooklib/src/interfaces.ts +++ b/packages/hooklib/src/interfaces.ts @@ -34,6 +34,7 @@ export interface ContainerInfo { createOptions?: string environmentVariables?: { [key: string]: string } userMountVolumes?: Mount[] + systemMountVolumes?: Mount[] registry?: Registry portMappings?: string[] } diff --git a/packages/k8s/tests/run-script-step-test.ts b/packages/k8s/tests/run-script-step-test.ts index 64c05b7..dfa6edb 100644 --- a/packages/k8s/tests/run-script-step-test.ts +++ b/packages/k8s/tests/run-script-step-test.ts @@ -1,7 +1,7 @@ -import { prepareJob, cleanupJob, runScriptStep } from '../src/hooks' -import { TestHelper } from './test-setup' -import * as path from 'path' import * as fs from 'fs' +import * as path from 'path' +import { cleanupJob, prepareJob, runScriptStep } from '../src/hooks' +import { TestHelper } from './test-setup' jest.useRealTimers() @@ -45,7 +45,7 @@ describe('Run script step', () => { NODE_ENV: 'development' }, prependPath: ['/foo/bar', 'bar/foo'], - workingDirectory: '/__w/thboop-test2/thboop-test2' + workingDirectory: '/__w/repo/repo' } const state = { jobPod: prepareJobOutputData.state.jobPod