mirror of
https://github.com/actions/runner-container-hooks.git
synced 2025-12-12 23:56:44 +00:00
Fix working directory and write state for appPod to be used in run-script-step (#8)
* added initial entrypoint script * change workingg directory working with addition to fix prepare-job state output * added prepend path * added run-script-step file generation, removed prepend path from container-step and prepare job * latest changes with testing run script step * fix the mounts real fast * cleanup * fix tests * add kind test * add kind yaml to ignore and run it during ci * fix kind option * remove gitignore * lowercase pwd * checkout first! * ignore test file in build.yaml * fixed wrong working directory and added test to run script step testing for the env * handle env's/escaping better * added single quote escape to env escapes * surounded env value with single quote * added spacing around run-container-step, changed examples to actually echo hello world * refactored tests * make sure to escape properly * set addition mounts for container steps * fixup container action mounts Co-authored-by: Thomas Boop <thboop@github.com> Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
This commit is contained in:
8
.github/workflows/build.yaml
vendored
8
.github/workflows/build.yaml
vendored
@@ -10,8 +10,12 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: helm/kind-action@v1.2.0
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- run: sed -i "s|{{PATHTOREPO}}|$(pwd)|" packages/k8s/tests/test-kind.yaml
|
||||||
|
name: Setup kind cluster yaml config
|
||||||
|
- uses: helm/kind-action@v1.2.0
|
||||||
|
with:
|
||||||
|
config: packages/k8s/tests/test-kind.yaml
|
||||||
- run: npm install
|
- run: npm install
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
- run: npm run bootstrap
|
- run: npm run bootstrap
|
||||||
@@ -22,6 +26,6 @@ jobs:
|
|||||||
- name: Check linter
|
- name: Check linter
|
||||||
run: |
|
run: |
|
||||||
npm run lint
|
npm run lint
|
||||||
git diff --exit-code
|
git diff --exit-code -- ':!packages/k8s/tests/test-kind.yaml'
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: npm run test
|
run: npm run test
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
lib/
|
lib/
|
||||||
dist/
|
dist/
|
||||||
**/tests/_temp/**
|
**/tests/_temp/**
|
||||||
|
packages/k8s/tests/test-kind.yaml
|
||||||
3
examples/example-script.sh
Normal file
3
examples/example-script.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Hello World"
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
"image": "node:14.16",
|
"image": "node:14.16",
|
||||||
"dockerfile": null,
|
"dockerfile": null,
|
||||||
"entryPointArgs": [
|
"entryPointArgs": [
|
||||||
"-c",
|
"-e",
|
||||||
"echo \"hello world2\""
|
"example-script.sh"
|
||||||
],
|
],
|
||||||
"entryPoint": "bash",
|
"entryPoint": "bash",
|
||||||
"workingDirectory": "/__w/repo/repo",
|
"workingDirectory": "/__w/repo/repo",
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
},
|
},
|
||||||
"args": {
|
"args": {
|
||||||
"entryPointArgs": [
|
"entryPointArgs": [
|
||||||
"-c",
|
"-e",
|
||||||
"echo \"hello world\""
|
"example-script.sh"
|
||||||
],
|
],
|
||||||
"entryPoint": "bash",
|
"entryPoint": "bash",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as fs from 'fs'
|
import { PrepareJobArgs } from 'hooklib/lib'
|
||||||
import { cleanupJob, prepareJob } from '../src/hooks'
|
import { cleanupJob, prepareJob } from '../src/hooks'
|
||||||
import TestSetup from './test-setup'
|
import TestSetup from './test-setup'
|
||||||
|
|
||||||
@@ -11,22 +11,16 @@ describe('cleanup job', () => {
|
|||||||
testSetup = new TestSetup()
|
testSetup = new TestSetup()
|
||||||
testSetup.initialize()
|
testSetup.initialize()
|
||||||
|
|
||||||
const prepareJobDefinition = JSON.parse(
|
const prepareJobDefinition = testSetup.getPrepareJobDefinition()
|
||||||
fs.readFileSync(
|
|
||||||
`${__dirname}/../../../examples/prepare-job.json`,
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
const prepareJobOutput = testSetup.createOutputFile(
|
const prepareJobOutput = testSetup.createOutputFile(
|
||||||
'prepare-job-output.json'
|
'prepare-job-output.json'
|
||||||
)
|
)
|
||||||
|
|
||||||
prepareJobDefinition.args.container.registry = null
|
await prepareJob(
|
||||||
prepareJobDefinition.args.services.forEach(s => {
|
prepareJobDefinition.args as PrepareJobArgs,
|
||||||
s.registry = null
|
prepareJobOutput
|
||||||
})
|
)
|
||||||
await prepareJob(prepareJobDefinition.args, prepareJobOutput)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
import * as fs from 'fs'
|
|
||||||
import { containerBuild } from '../src/dockerCommands'
|
import { containerBuild } from '../src/dockerCommands'
|
||||||
import TestSetup from './test-setup'
|
import TestSetup from './test-setup'
|
||||||
|
|
||||||
let testSetup
|
let testSetup
|
||||||
let runContainerStepDefinition
|
let runContainerStepDefinition
|
||||||
const runContainerStepInputPath = `${__dirname}/../../../examples/run-container-step.json`
|
|
||||||
|
|
||||||
describe('container build', () => {
|
describe('container build', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
testSetup = new TestSetup()
|
testSetup = new TestSetup()
|
||||||
testSetup.initialize()
|
testSetup.initialize()
|
||||||
|
|
||||||
let runContainerStepJson = fs.readFileSync(
|
runContainerStepDefinition = testSetup.getRunContainerStepDefinition()
|
||||||
runContainerStepInputPath,
|
|
||||||
'utf8'
|
|
||||||
)
|
|
||||||
runContainerStepDefinition = JSON.parse(runContainerStepJson.toString())
|
|
||||||
runContainerStepDefinition.image = ''
|
|
||||||
const actionPath = testSetup.initializeDockerAction()
|
|
||||||
runContainerStepDefinition.dockerfile = `${actionPath}/Dockerfile`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -26,6 +17,9 @@ describe('container build', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should build container', async () => {
|
it('should build container', async () => {
|
||||||
|
runContainerStepDefinition.image = ''
|
||||||
|
const actionPath = testSetup.initializeDockerAction()
|
||||||
|
runContainerStepDefinition.dockerfile = `${actionPath}/Dockerfile`
|
||||||
await expect(
|
await expect(
|
||||||
containerBuild(runContainerStepDefinition, 'example-test-tag')
|
containerBuild(runContainerStepDefinition, 'example-test-tag')
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
|
||||||
import {
|
import {
|
||||||
cleanupJob,
|
cleanupJob,
|
||||||
prepareJob,
|
prepareJob,
|
||||||
@@ -8,28 +7,7 @@ import {
|
|||||||
} from '../src/hooks'
|
} from '../src/hooks'
|
||||||
import TestSetup from './test-setup'
|
import TestSetup from './test-setup'
|
||||||
|
|
||||||
const definitions = {
|
let definitions
|
||||||
prepareJob: JSON.parse(
|
|
||||||
fs.readFileSync(
|
|
||||||
path.resolve(__dirname + '/../../../examples/prepare-job.json'),
|
|
||||||
'utf8'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
runContainerStep: JSON.parse(
|
|
||||||
fs.readFileSync(
|
|
||||||
path.resolve(__dirname + '/../../../examples/run-container-step.json'),
|
|
||||||
'utf8'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
runScriptStep: JSON.parse(
|
|
||||||
fs.readFileSync(
|
|
||||||
path.resolve(__dirname + '/../../../examples/run-script-step.json'),
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let testSetup: TestSetup
|
let testSetup: TestSetup
|
||||||
|
|
||||||
@@ -37,12 +15,12 @@ describe('e2e', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
testSetup = new TestSetup()
|
testSetup = new TestSetup()
|
||||||
testSetup.initialize()
|
testSetup.initialize()
|
||||||
definitions.prepareJob.args.container.systemMountVolumes =
|
|
||||||
testSetup.systemMountVolumes
|
definitions = {
|
||||||
definitions.prepareJob.args.container.registry = null
|
prepareJob: testSetup.getPrepareJobDefinition(),
|
||||||
definitions.prepareJob.args.services.forEach(s => {
|
runScriptStep: testSetup.getRunScriptStepDefinition(),
|
||||||
s.registry = null
|
runContainerStep: testSetup.getRunContainerStepDefinition()
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import TestSetup from './test-setup'
|
|||||||
|
|
||||||
jest.useRealTimers()
|
jest.useRealTimers()
|
||||||
|
|
||||||
const prepareJobDefinition = JSON.parse(
|
let prepareJobDefinition
|
||||||
fs.readFileSync(`${__dirname}/../../../examples/prepare-job.json`, 'utf-8')
|
|
||||||
)
|
|
||||||
|
|
||||||
let testSetup: TestSetup
|
let testSetup: TestSetup
|
||||||
|
|
||||||
@@ -14,15 +12,7 @@ describe('prepare job', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
testSetup = new TestSetup()
|
testSetup = new TestSetup()
|
||||||
testSetup.initialize()
|
testSetup.initialize()
|
||||||
|
prepareJobDefinition = testSetup.getPrepareJobDefinition()
|
||||||
prepareJobDefinition.args.container.systemMountVolumes =
|
|
||||||
testSetup.systemMountVolumes
|
|
||||||
prepareJobDefinition.args.container.workingDirectory =
|
|
||||||
testSetup.workingDirectory
|
|
||||||
prepareJobDefinition.args.container.registry = null
|
|
||||||
prepareJobDefinition.args.services.forEach(s => {
|
|
||||||
s.registry = null
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { PrepareJobResponse } from 'hooklib/lib'
|
import { PrepareJobResponse } from 'hooklib/lib'
|
||||||
import * as path from 'path'
|
|
||||||
import { prepareJob, runScriptStep } from '../src/hooks'
|
import { prepareJob, runScriptStep } from '../src/hooks'
|
||||||
import TestSetup from './test-setup'
|
import TestSetup from './test-setup'
|
||||||
|
|
||||||
@@ -8,36 +7,23 @@ jest.useRealTimers()
|
|||||||
|
|
||||||
let testSetup: TestSetup
|
let testSetup: TestSetup
|
||||||
|
|
||||||
const definitions = {
|
let 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
|
let prepareJobResponse: PrepareJobResponse
|
||||||
|
|
||||||
describe('run-script-step', () => {
|
describe('run script step', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
testSetup = new TestSetup()
|
testSetup = new TestSetup()
|
||||||
testSetup.initialize()
|
testSetup.initialize()
|
||||||
|
|
||||||
|
definitions = {
|
||||||
|
prepareJob: testSetup.getPrepareJobDefinition(),
|
||||||
|
runScriptStep: testSetup.getRunScriptStepDefinition()
|
||||||
|
}
|
||||||
|
|
||||||
const prepareJobOutput = testSetup.createOutputFile(
|
const prepareJobOutput = testSetup.createOutputFile(
|
||||||
'prepare-job-output.json'
|
'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)
|
await prepareJob(definitions.prepareJob.args, prepareJobOutput)
|
||||||
|
|
||||||
prepareJobResponse = JSON.parse(fs.readFileSync(prepareJobOutput, 'utf-8'))
|
prepareJobResponse = JSON.parse(fs.readFileSync(prepareJobOutput, 'utf-8'))
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { Mount } from 'hooklib'
|
import { Mount } from 'hooklib'
|
||||||
|
import { HookData } from 'hooklib/lib'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { env } from 'process'
|
import { env } from 'process'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
@@ -51,13 +52,18 @@ export default class TestSetup {
|
|||||||
for (const dir of this.allTestDirectories) {
|
for (const dir of this.allTestDirectories) {
|
||||||
fs.mkdirSync(dir, { recursive: true })
|
fs.mkdirSync(dir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fs.copyFileSync(
|
||||||
|
path.resolve(`${__dirname}/../../../examples/example-script.sh`),
|
||||||
|
`${env.RUNNER_TEMP}/example-script.sh`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public teardown(): void {
|
public teardown(): void {
|
||||||
fs.rmdirSync(this.testdir, { recursive: true })
|
fs.rmdirSync(this.testdir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
public get systemMountVolumes(): Mount[] {
|
private get systemMountVolumes(): Mount[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
sourceVolumePath: '/var/run/docker.sock',
|
sourceVolumePath: '/var/run/docker.sock',
|
||||||
@@ -140,4 +146,51 @@ echo "::set-output name=time::$time"`
|
|||||||
fs.writeFileSync(entryPointPath, content)
|
fs.writeFileSync(entryPointPath, content)
|
||||||
fs.chmodSync(entryPointPath, 0o755)
|
fs.chmodSync(entryPointPath, 0o755)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPrepareJobDefinition(): HookData {
|
||||||
|
const prepareJob = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.resolve(__dirname + '/../../../examples/prepare-job.json'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
prepareJob.args.container.systemMountVolumes = this.systemMountVolumes
|
||||||
|
prepareJob.args.container.workingDirectory = this.workingDirectory
|
||||||
|
prepareJob.args.container.userMountVolumes = undefined
|
||||||
|
prepareJob.args.container.registry = null
|
||||||
|
prepareJob.args.services.forEach(s => {
|
||||||
|
s.registry = null
|
||||||
|
})
|
||||||
|
|
||||||
|
return prepareJob
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRunScriptStepDefinition(): HookData {
|
||||||
|
const runScriptStep = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.resolve(__dirname + '/../../../examples/run-script-step.json'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
runScriptStep.args.entryPointArgs[1] = `/__w/_temp/example-script.sh`
|
||||||
|
return runScriptStep
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRunContainerStepDefinition(): HookData {
|
||||||
|
const runContainerStep = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.resolve(__dirname + '/../../../examples/run-container-step.json'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
runContainerStep.args.entryPointArgs[1] = `/__w/_temp/example-script.sh`
|
||||||
|
runContainerStep.args.systemMountVolumes = this.systemMountVolumes
|
||||||
|
runContainerStep.args.workingDirectory = this.workingDirectory
|
||||||
|
runContainerStep.args.userMountVolumes = undefined
|
||||||
|
runContainerStep.args.registry = null
|
||||||
|
return runContainerStep
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,13 @@ function generateResponseFile(
|
|||||||
appPod: k8s.V1Pod,
|
appPod: k8s.V1Pod,
|
||||||
isAlpine
|
isAlpine
|
||||||
): void {
|
): void {
|
||||||
|
if (!appPod.metadata?.name) {
|
||||||
|
throw new Error('app pod must have metadata.name specified')
|
||||||
|
}
|
||||||
const response = {
|
const response = {
|
||||||
state: {},
|
state: {
|
||||||
|
jobPod: appPod.metadata.name
|
||||||
|
},
|
||||||
context: {},
|
context: {},
|
||||||
isAlpine
|
isAlpine
|
||||||
}
|
}
|
||||||
@@ -163,13 +168,11 @@ function createPodSpec(
|
|||||||
name: string,
|
name: string,
|
||||||
jobContainer = false
|
jobContainer = false
|
||||||
): k8s.V1Container {
|
): k8s.V1Container {
|
||||||
if (!container.entryPointArgs) {
|
|
||||||
container.entryPointArgs = DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
|
||||||
}
|
|
||||||
container.entryPointArgs = DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
|
||||||
if (!container.entryPoint) {
|
if (!container.entryPoint) {
|
||||||
container.entryPoint = DEFAULT_CONTAINER_ENTRY_POINT
|
container.entryPoint = DEFAULT_CONTAINER_ENTRY_POINT
|
||||||
|
container.entryPointArgs = DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
||||||
}
|
}
|
||||||
|
|
||||||
const podContainer = {
|
const podContainer = {
|
||||||
name,
|
name,
|
||||||
image: container.image,
|
image: container.image,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as k8s from '@kubernetes/client-node'
|
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
|
import * as k8s from '@kubernetes/client-node'
|
||||||
import { RunContainerStepArgs } from 'hooklib'
|
import { RunContainerStepArgs } from 'hooklib'
|
||||||
import {
|
import {
|
||||||
createJob,
|
createJob,
|
||||||
@@ -10,8 +10,14 @@ import {
|
|||||||
waitForJobToComplete,
|
waitForJobToComplete,
|
||||||
waitForPodPhases
|
waitForPodPhases
|
||||||
} from '../k8s'
|
} from '../k8s'
|
||||||
|
import {
|
||||||
|
containerVolumes,
|
||||||
|
DEFAULT_CONTAINER_ENTRY_POINT,
|
||||||
|
DEFAULT_CONTAINER_ENTRY_POINT_ARGS,
|
||||||
|
PodPhase,
|
||||||
|
writeEntryPointScript
|
||||||
|
} from '../k8s/utils'
|
||||||
import { JOB_CONTAINER_NAME } from './constants'
|
import { JOB_CONTAINER_NAME } from './constants'
|
||||||
import { containerVolumes, PodPhase } from '../k8s/utils'
|
|
||||||
|
|
||||||
export async function runContainerStep(
|
export async function runContainerStep(
|
||||||
stepContainer: RunContainerStepArgs
|
stepContainer: RunContainerStepArgs
|
||||||
@@ -19,13 +25,16 @@ export async function runContainerStep(
|
|||||||
if (stepContainer.dockerfile) {
|
if (stepContainer.dockerfile) {
|
||||||
throw new Error('Building container actions is not currently supported')
|
throw new Error('Building container actions is not currently supported')
|
||||||
}
|
}
|
||||||
|
|
||||||
let secretName: string | undefined = undefined
|
let secretName: string | undefined = undefined
|
||||||
core.debug('')
|
core.debug('')
|
||||||
if (stepContainer.environmentVariables) {
|
if (stepContainer.environmentVariables) {
|
||||||
secretName = await createSecretForEnvs(stepContainer.environmentVariables)
|
secretName = await createSecretForEnvs(stepContainer.environmentVariables)
|
||||||
}
|
}
|
||||||
|
|
||||||
core.debug(`Created secret ${secretName} for container job envs`)
|
core.debug(`Created secret ${secretName} for container job envs`)
|
||||||
const container = createPodSpec(stepContainer, secretName)
|
const container = createPodSpec(stepContainer, secretName)
|
||||||
|
|
||||||
const job = await createJob(container)
|
const job = await createJob(container)
|
||||||
if (!job.metadata?.name) {
|
if (!job.metadata?.name) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -35,6 +44,7 @@ export async function runContainerStep(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
core.debug(`Job created, waiting for pod to start: ${job.metadata?.name}`)
|
core.debug(`Job created, waiting for pod to start: ${job.metadata?.name}`)
|
||||||
|
|
||||||
const podName = await getContainerJobPodName(job.metadata.name)
|
const podName = await getContainerJobPodName(job.metadata.name)
|
||||||
await waitForPodPhases(
|
await waitForPodPhases(
|
||||||
podName,
|
podName,
|
||||||
@@ -42,11 +52,16 @@ export async function runContainerStep(
|
|||||||
new Set([PodPhase.PENDING, PodPhase.UNKNOWN])
|
new Set([PodPhase.PENDING, PodPhase.UNKNOWN])
|
||||||
)
|
)
|
||||||
core.debug('Container step is running or complete, pulling logs')
|
core.debug('Container step is running or complete, pulling logs')
|
||||||
|
|
||||||
await getPodLogs(podName, JOB_CONTAINER_NAME)
|
await getPodLogs(podName, JOB_CONTAINER_NAME)
|
||||||
|
|
||||||
core.debug('Waiting for container job to complete')
|
core.debug('Waiting for container job to complete')
|
||||||
await waitForJobToComplete(job.metadata.name)
|
await waitForJobToComplete(job.metadata.name)
|
||||||
// pod has failed so pull the status code from the container
|
// pod has failed so pull the status code from the container
|
||||||
const status = await getPodStatus(podName)
|
const status = await getPodStatus(podName)
|
||||||
|
if (status?.phase === 'Succeeded') {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
if (!status?.containerStatuses?.length) {
|
if (!status?.containerStatuses?.length) {
|
||||||
core.error(
|
core.error(
|
||||||
`Can't determine container status from response: ${JSON.stringify(
|
`Can't determine container status from response: ${JSON.stringify(
|
||||||
@@ -68,9 +83,18 @@ function createPodSpec(
|
|||||||
const podContainer = new k8s.V1Container()
|
const podContainer = new k8s.V1Container()
|
||||||
podContainer.name = JOB_CONTAINER_NAME
|
podContainer.name = JOB_CONTAINER_NAME
|
||||||
podContainer.image = container.image
|
podContainer.image = container.image
|
||||||
if (container.entryPoint) {
|
|
||||||
podContainer.command = [container.entryPoint, ...container.entryPointArgs]
|
const { entryPoint, entryPointArgs } = container
|
||||||
}
|
container.entryPoint = 'sh'
|
||||||
|
|
||||||
|
const { containerPath } = writeEntryPointScript(
|
||||||
|
container.workingDirectory,
|
||||||
|
entryPoint || DEFAULT_CONTAINER_ENTRY_POINT,
|
||||||
|
entryPoint ? entryPointArgs || [] : DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
||||||
|
)
|
||||||
|
container.entryPointArgs = ['-e', containerPath]
|
||||||
|
podContainer.command = [container.entryPoint, ...container.entryPointArgs]
|
||||||
|
|
||||||
if (secretName) {
|
if (secretName) {
|
||||||
podContainer.envFrom = [
|
podContainer.envFrom = [
|
||||||
{
|
{
|
||||||
@@ -81,7 +105,7 @@ function createPodSpec(
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
podContainer.volumeMounts = containerVolumes()
|
podContainer.volumeMounts = containerVolumes(undefined, false, true)
|
||||||
|
|
||||||
return podContainer
|
return podContainer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,35 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import * as fs from 'fs'
|
||||||
import { RunScriptStepArgs } from 'hooklib'
|
import { RunScriptStepArgs } from 'hooklib'
|
||||||
import { execPodStep } from '../k8s'
|
import { execPodStep } from '../k8s'
|
||||||
import { getJobPodName, JOB_CONTAINER_NAME } from './constants'
|
import { writeEntryPointScript } from '../k8s/utils'
|
||||||
|
import { JOB_CONTAINER_NAME } from './constants'
|
||||||
|
|
||||||
export async function runScriptStep(
|
export async function runScriptStep(
|
||||||
args: RunScriptStepArgs,
|
args: RunScriptStepArgs,
|
||||||
state,
|
state,
|
||||||
responseFile
|
responseFile
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cb = new CommandsBuilder(
|
const { entryPoint, entryPointArgs, environmentVariables } = args
|
||||||
args.entryPoint,
|
const { containerPath, runnerPath } = writeEntryPointScript(
|
||||||
args.entryPointArgs,
|
args.workingDirectory,
|
||||||
args.environmentVariables
|
entryPoint,
|
||||||
|
entryPointArgs,
|
||||||
|
args.prependPath,
|
||||||
|
environmentVariables
|
||||||
)
|
)
|
||||||
await execPodStep(cb.command, getJobPodName(), JOB_CONTAINER_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
class CommandsBuilder {
|
args.entryPoint = 'sh'
|
||||||
constructor(
|
args.entryPointArgs = ['-e', containerPath]
|
||||||
private entryPoint: string,
|
try {
|
||||||
private entryPointArgs: string[],
|
await execPodStep(
|
||||||
private environmentVariables: { [key: string]: string }
|
[args.entryPoint, ...args.entryPointArgs],
|
||||||
) {}
|
state.jobPod,
|
||||||
|
JOB_CONTAINER_NAME
|
||||||
get command(): string[] {
|
)
|
||||||
const envCommands: string[] = []
|
} catch (err) {
|
||||||
if (
|
throw new Error(`failed to run script step: ${JSON.stringify(err)}`)
|
||||||
this.environmentVariables &&
|
} finally {
|
||||||
Object.entries(this.environmentVariables).length
|
fs.rmSync(runnerPath)
|
||||||
) {
|
|
||||||
for (const [key, value] of Object.entries(this.environmentVariables)) {
|
|
||||||
envCommands.push(`${key}=${value}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ['env', ...envCommands, this.entryPoint, ...this.entryPointArgs]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,12 +190,8 @@ export async function execPodStep(
|
|||||||
containerName: string,
|
containerName: string,
|
||||||
stdin?: stream.Readable
|
stdin?: stream.Readable
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// TODO, we need to add the path from `prependPath` to the PATH variable. How can we do that? Maybe another exec before running this one?
|
|
||||||
// Maybe something like, get the current path, if these entries aren't in it, add them, then set the current path to that?
|
|
||||||
|
|
||||||
// TODO: how do we set working directory? There doesn't seem to be an easy way to do it. Should we cd then execute our bash script?
|
|
||||||
const exec = new k8s.Exec(kc)
|
const exec = new k8s.Exec(kc)
|
||||||
return new Promise(async function (resolve, reject) {
|
await new Promise(async function (resolve, reject) {
|
||||||
try {
|
try {
|
||||||
await exec.exec(
|
await exec.exec(
|
||||||
namespace(),
|
namespace(),
|
||||||
@@ -209,16 +205,19 @@ export async function execPodStep(
|
|||||||
resp => {
|
resp => {
|
||||||
// kube.exec returns an error if exit code is not 0, but we can't actually get the exit code
|
// kube.exec returns an error if exit code is not 0, but we can't actually get the exit code
|
||||||
if (resp.status === 'Success') {
|
if (resp.status === 'Success') {
|
||||||
resolve()
|
resolve(resp.code)
|
||||||
} else {
|
} else {
|
||||||
reject(
|
reject(
|
||||||
JSON.stringify({ message: resp?.message, details: resp?.details })
|
JSON.stringify({
|
||||||
|
message: resp?.message,
|
||||||
|
details: resp?.details
|
||||||
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error)
|
reject(JSON.stringify(error))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import * as k8s from '@kubernetes/client-node'
|
import * as k8s from '@kubernetes/client-node'
|
||||||
|
import * as fs from 'fs'
|
||||||
import { Mount } from 'hooklib'
|
import { Mount } from 'hooklib'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import { v1 as uuidv4 } from 'uuid'
|
||||||
import { POD_VOLUME_NAME } from './index'
|
import { POD_VOLUME_NAME } from './index'
|
||||||
|
|
||||||
export const DEFAULT_CONTAINER_ENTRY_POINT_ARGS = [`-f`, `/dev/null`]
|
export const DEFAULT_CONTAINER_ENTRY_POINT_ARGS = [`-f`, `/dev/null`]
|
||||||
@@ -8,7 +10,8 @@ export const DEFAULT_CONTAINER_ENTRY_POINT = 'tail'
|
|||||||
|
|
||||||
export function containerVolumes(
|
export function containerVolumes(
|
||||||
userMountVolumes: Mount[] = [],
|
userMountVolumes: Mount[] = [],
|
||||||
jobContainer = true
|
jobContainer = true,
|
||||||
|
containerAction = false
|
||||||
): k8s.V1VolumeMount[] {
|
): k8s.V1VolumeMount[] {
|
||||||
const mounts: k8s.V1VolumeMount[] = [
|
const mounts: k8s.V1VolumeMount[] = [
|
||||||
{
|
{
|
||||||
@@ -17,6 +20,23 @@ export function containerVolumes(
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (containerAction) {
|
||||||
|
const workspace = process.env.GITHUB_WORKSPACE as string
|
||||||
|
mounts.push(
|
||||||
|
{
|
||||||
|
name: POD_VOLUME_NAME,
|
||||||
|
mountPath: '/github/workspace',
|
||||||
|
subPath: workspace.substring(workspace.indexOf('work/') + 1)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: POD_VOLUME_NAME,
|
||||||
|
mountPath: '/github/file_commands',
|
||||||
|
subPath: workspace.substring(workspace.indexOf('work/') + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return mounts
|
||||||
|
}
|
||||||
|
|
||||||
if (!jobContainer) {
|
if (!jobContainer) {
|
||||||
return mounts
|
return mounts
|
||||||
}
|
}
|
||||||
@@ -71,6 +91,48 @@ export function containerVolumes(
|
|||||||
return mounts
|
return mounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function writeEntryPointScript(
|
||||||
|
workingDirectory: string,
|
||||||
|
entryPoint: string,
|
||||||
|
entryPointArgs?: string[],
|
||||||
|
prependPath?: string[],
|
||||||
|
environmentVariables?: { [key: string]: string }
|
||||||
|
): { containerPath: string; runnerPath: string } {
|
||||||
|
let exportPath = ''
|
||||||
|
if (prependPath?.length) {
|
||||||
|
exportPath = `export PATH=${prependPath.join(':')}:$PATH`
|
||||||
|
}
|
||||||
|
let environmentPrefix = ''
|
||||||
|
|
||||||
|
if (environmentVariables && Object.entries(environmentVariables).length) {
|
||||||
|
const envBuffer: string[] = []
|
||||||
|
for (const [key, value] of Object.entries(environmentVariables)) {
|
||||||
|
envBuffer.push(
|
||||||
|
`"${key}=${value
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/"/g, '\\"')
|
||||||
|
.replace(/=/g, '\\=')}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
environmentPrefix = `env ${envBuffer.join(' ')} `
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = `#!/bin/sh -l
|
||||||
|
${exportPath}
|
||||||
|
cd ${workingDirectory} && \
|
||||||
|
exec ${environmentPrefix} ${entryPoint} ${
|
||||||
|
entryPointArgs?.length ? entryPointArgs.join(' ') : ''
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const filename = `${uuidv4()}.sh`
|
||||||
|
const entryPointPath = `${process.env.RUNNER_TEMP}/${filename}`
|
||||||
|
fs.writeFileSync(entryPointPath, content)
|
||||||
|
return {
|
||||||
|
containerPath: `/__w/_temp/${filename}`,
|
||||||
|
runnerPath: entryPointPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export enum PodPhase {
|
export enum PodPhase {
|
||||||
PENDING = 'Pending',
|
PENDING = 'Pending',
|
||||||
RUNNING = 'Running',
|
RUNNING = 'Running',
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
import * as path from 'path'
|
import { cleanupJob, prepareJob } from '../src/hooks'
|
||||||
import * as fs from 'fs'
|
|
||||||
import { prepareJob, cleanupJob } from '../src/hooks'
|
|
||||||
import { TestHelper } from './test-setup'
|
import { TestHelper } from './test-setup'
|
||||||
|
|
||||||
let testHelper: TestHelper
|
let testHelper: TestHelper
|
||||||
|
|
||||||
const prepareJobJsonPath = path.resolve(
|
|
||||||
`${__dirname}/../../../examples/prepare-job.json`
|
|
||||||
)
|
|
||||||
|
|
||||||
let prepareJobOutputFilePath: string
|
|
||||||
|
|
||||||
describe('Cleanup Job', () => {
|
describe('Cleanup Job', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const prepareJobJson = fs.readFileSync(prepareJobJsonPath)
|
|
||||||
let prepareJobData = JSON.parse(prepareJobJson.toString())
|
|
||||||
|
|
||||||
testHelper = new TestHelper()
|
testHelper = new TestHelper()
|
||||||
await testHelper.initialize()
|
await testHelper.initialize()
|
||||||
prepareJobOutputFilePath = testHelper.createFile('prepare-job-output.json')
|
let prepareJobData = testHelper.getPrepareJobDefinition()
|
||||||
|
const prepareJobOutputFilePath = testHelper.createFile(
|
||||||
|
'prepare-job-output.json'
|
||||||
|
)
|
||||||
await prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
await prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
||||||
})
|
})
|
||||||
it('should not throw', async () => {
|
it('should not throw', async () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
|
||||||
import {
|
import {
|
||||||
cleanupJob,
|
cleanupJob,
|
||||||
prepareJob,
|
prepareJob,
|
||||||
@@ -12,26 +11,15 @@ jest.useRealTimers()
|
|||||||
|
|
||||||
let testHelper: TestHelper
|
let testHelper: TestHelper
|
||||||
|
|
||||||
const prepareJobJsonPath = path.resolve(
|
|
||||||
`${__dirname}/../../../examples/prepare-job.json`
|
|
||||||
)
|
|
||||||
const runScriptStepJsonPath = path.resolve(
|
|
||||||
`${__dirname}/../../../examples/run-script-step.json`
|
|
||||||
)
|
|
||||||
let runContainerStepJsonPath = path.resolve(
|
|
||||||
`${__dirname}/../../../examples/run-container-step.json`
|
|
||||||
)
|
|
||||||
|
|
||||||
let prepareJobData: any
|
let prepareJobData: any
|
||||||
|
|
||||||
let prepareJobOutputFilePath: string
|
let prepareJobOutputFilePath: string
|
||||||
describe('e2e', () => {
|
describe('e2e', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const prepareJobJson = fs.readFileSync(prepareJobJsonPath)
|
|
||||||
prepareJobData = JSON.parse(prepareJobJson.toString())
|
|
||||||
|
|
||||||
testHelper = new TestHelper()
|
testHelper = new TestHelper()
|
||||||
await testHelper.initialize()
|
await testHelper.initialize()
|
||||||
|
|
||||||
|
prepareJobData = testHelper.getPrepareJobDefinition()
|
||||||
prepareJobOutputFilePath = testHelper.createFile('prepare-job-output.json')
|
prepareJobOutputFilePath = testHelper.createFile('prepare-job-output.json')
|
||||||
})
|
})
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -42,8 +30,7 @@ describe('e2e', () => {
|
|||||||
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
|
|
||||||
const scriptStepContent = fs.readFileSync(runScriptStepJsonPath)
|
const scriptStepData = testHelper.getRunScriptStepDefinition()
|
||||||
const scriptStepData = JSON.parse(scriptStepContent.toString())
|
|
||||||
|
|
||||||
const prepareJobOutputJson = fs.readFileSync(prepareJobOutputFilePath)
|
const prepareJobOutputJson = fs.readFileSync(prepareJobOutputFilePath)
|
||||||
const prepareJobOutputData = JSON.parse(prepareJobOutputJson.toString())
|
const prepareJobOutputData = JSON.parse(prepareJobOutputJson.toString())
|
||||||
@@ -52,8 +39,7 @@ describe('e2e', () => {
|
|||||||
runScriptStep(scriptStepData.args, prepareJobOutputData.state, null)
|
runScriptStep(scriptStepData.args, prepareJobOutputData.state, null)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
|
|
||||||
const runContainerStepContent = fs.readFileSync(runContainerStepJsonPath)
|
const runContainerStepData = testHelper.getRunContainerStepDefinition()
|
||||||
const runContainerStepData = JSON.parse(runContainerStepContent.toString())
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
runContainerStep(runContainerStepData.args)
|
runContainerStep(runContainerStepData.args)
|
||||||
|
|||||||
@@ -8,20 +8,15 @@ jest.useRealTimers()
|
|||||||
|
|
||||||
let testHelper: TestHelper
|
let testHelper: TestHelper
|
||||||
|
|
||||||
const prepareJobJsonPath = path.resolve(
|
|
||||||
`${__dirname}/../../../examples/prepare-job.json`
|
|
||||||
)
|
|
||||||
let prepareJobData: any
|
let prepareJobData: any
|
||||||
|
|
||||||
let prepareJobOutputFilePath: string
|
let prepareJobOutputFilePath: string
|
||||||
|
|
||||||
describe('Prepare job', () => {
|
describe('Prepare job', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const prepareJobJson = fs.readFileSync(prepareJobJsonPath)
|
|
||||||
prepareJobData = JSON.parse(prepareJobJson.toString())
|
|
||||||
|
|
||||||
testHelper = new TestHelper()
|
testHelper = new TestHelper()
|
||||||
await testHelper.initialize()
|
await testHelper.initialize()
|
||||||
|
prepareJobData = testHelper.getPrepareJobDefinition()
|
||||||
prepareJobOutputFilePath = testHelper.createFile('prepare-job-output.json')
|
prepareJobOutputFilePath = testHelper.createFile('prepare-job-output.json')
|
||||||
})
|
})
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -42,28 +37,29 @@ describe('Prepare job', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should prepare job with absolute path for userVolumeMount', async () => {
|
it('should prepare job with absolute path for userVolumeMount', async () => {
|
||||||
prepareJobData.args.container.userMountVolumes.forEach(v => {
|
prepareJobData.args.container.userMountVolumes = [
|
||||||
if (!path.isAbsolute(v.sourceVolumePath)) {
|
{
|
||||||
v.sourceVolumePath = path.join(
|
sourceVolumePath: path.join(
|
||||||
process.env.GITHUB_WORKSPACE as string,
|
process.env.GITHUB_WORKSPACE as string,
|
||||||
v.sourceVolumePath
|
'/myvolume'
|
||||||
)
|
),
|
||||||
|
targetVolumePath: '/volume_mount',
|
||||||
|
readOnly: false
|
||||||
}
|
}
|
||||||
})
|
]
|
||||||
await expect(
|
await expect(
|
||||||
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an exception if the user volume mount is absolute path outside of GITHUB_WORKSPACE', async () => {
|
it('should throw an exception if the user volume mount is absolute path outside of GITHUB_WORKSPACE', async () => {
|
||||||
prepareJobData.args.container.userMountVolumes.forEach(v => {
|
prepareJobData.args.container.userMountVolumes = [
|
||||||
if (!path.isAbsolute(v.sourceVolumePath)) {
|
{
|
||||||
v.sourceVolumePath = path.join(
|
sourceVolumePath: '/somewhere/not/in/gh-workspace',
|
||||||
'/path/outside/of/github-workspace',
|
targetVolumePath: '/containermount',
|
||||||
v.sourceVolumePath
|
readOnly: false
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
]
|
||||||
await expect(
|
await expect(
|
||||||
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
||||||
).rejects.toThrow()
|
).rejects.toThrow()
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import * as fs from 'fs'
|
|
||||||
import * as path from 'path'
|
|
||||||
import { runContainerStep } from '../src/hooks'
|
import { runContainerStep } from '../src/hooks'
|
||||||
import { TestHelper } from './test-setup'
|
import { TestHelper } from './test-setup'
|
||||||
|
|
||||||
@@ -7,25 +5,37 @@ jest.useRealTimers()
|
|||||||
|
|
||||||
let testHelper: TestHelper
|
let testHelper: TestHelper
|
||||||
|
|
||||||
let runContainerStepJsonPath = path.resolve(
|
|
||||||
`${__dirname}/../../../examples/run-container-step.json`
|
|
||||||
)
|
|
||||||
|
|
||||||
let runContainerStepData: any
|
let runContainerStepData: any
|
||||||
|
|
||||||
describe('Run container step', () => {
|
describe('Run container step', () => {
|
||||||
beforeAll(async () => {
|
beforeEach(async () => {
|
||||||
const content = fs.readFileSync(runContainerStepJsonPath)
|
|
||||||
runContainerStepData = JSON.parse(content.toString())
|
|
||||||
testHelper = new TestHelper()
|
testHelper = new TestHelper()
|
||||||
await testHelper.initialize()
|
await testHelper.initialize()
|
||||||
|
runContainerStepData = testHelper.getRunContainerStepDefinition()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await testHelper.cleanup()
|
||||||
|
})
|
||||||
|
|
||||||
it('should not throw', async () => {
|
it('should not throw', async () => {
|
||||||
|
const exitCode = await runContainerStep(runContainerStepData.args)
|
||||||
|
expect(exitCode).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if the working directory does not exist', async () => {
|
||||||
|
runContainerStepData.args.workingDirectory = '/foo/bar'
|
||||||
|
await expect(runContainerStep(runContainerStepData.args)).rejects.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should shold have env variables available', async () => {
|
||||||
|
runContainerStepData.args.entryPoint = 'bash'
|
||||||
|
runContainerStepData.args.entryPointArgs = [
|
||||||
|
'-c',
|
||||||
|
"'if [[ -z $NODE_ENV ]]; then exit 1; fi'"
|
||||||
|
]
|
||||||
await expect(
|
await expect(
|
||||||
runContainerStep(runContainerStepData.args)
|
runContainerStep(runContainerStepData.args)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
})
|
})
|
||||||
afterEach(async () => {
|
|
||||||
await testHelper.cleanup()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
|
||||||
import { cleanupJob, prepareJob, runScriptStep } from '../src/hooks'
|
import { cleanupJob, prepareJob, runScriptStep } from '../src/hooks'
|
||||||
import { TestHelper } from './test-setup'
|
import { TestHelper } from './test-setup'
|
||||||
|
|
||||||
@@ -7,22 +6,21 @@ jest.useRealTimers()
|
|||||||
|
|
||||||
let testHelper: TestHelper
|
let testHelper: TestHelper
|
||||||
|
|
||||||
const prepareJobJsonPath = path.resolve(
|
|
||||||
`${__dirname}/../../../examples/prepare-job.json`
|
|
||||||
)
|
|
||||||
let prepareJobData: any
|
|
||||||
|
|
||||||
let prepareJobOutputFilePath: string
|
|
||||||
let prepareJobOutputData: any
|
let prepareJobOutputData: any
|
||||||
|
|
||||||
|
let runScriptStepDefinition
|
||||||
|
|
||||||
describe('Run script step', () => {
|
describe('Run script step', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const prepareJobJson = fs.readFileSync(prepareJobJsonPath)
|
|
||||||
prepareJobData = JSON.parse(prepareJobJson.toString())
|
|
||||||
|
|
||||||
testHelper = new TestHelper()
|
testHelper = new TestHelper()
|
||||||
await testHelper.initialize()
|
await testHelper.initialize()
|
||||||
prepareJobOutputFilePath = testHelper.createFile('prepare-job-output.json')
|
const prepareJobOutputFilePath = testHelper.createFile(
|
||||||
|
'prepare-job-output.json'
|
||||||
|
)
|
||||||
|
|
||||||
|
const prepareJobData = testHelper.getPrepareJobDefinition()
|
||||||
|
runScriptStepDefinition = testHelper.getRunScriptStepDefinition()
|
||||||
|
|
||||||
await prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
await prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
||||||
const outputContent = fs.readFileSync(prepareJobOutputFilePath)
|
const outputContent = fs.readFileSync(prepareJobOutputFilePath)
|
||||||
prepareJobOutputData = JSON.parse(outputContent.toString())
|
prepareJobOutputData = JSON.parse(outputContent.toString())
|
||||||
@@ -38,21 +36,39 @@ describe('Run script step', () => {
|
|||||||
// npm run test run-script-step
|
// npm run test run-script-step
|
||||||
|
|
||||||
it('should not throw an exception', async () => {
|
it('should not throw an exception', async () => {
|
||||||
const args = {
|
|
||||||
entryPointArgs: ['-c', 'echo "test"'],
|
|
||||||
entryPoint: 'bash',
|
|
||||||
environmentVariables: {
|
|
||||||
NODE_ENV: 'development'
|
|
||||||
},
|
|
||||||
prependPath: ['/foo/bar', 'bar/foo'],
|
|
||||||
workingDirectory: '/__w/repo/repo'
|
|
||||||
}
|
|
||||||
const state = {
|
|
||||||
jobPod: prepareJobOutputData.state.jobPod
|
|
||||||
}
|
|
||||||
const responseFile = null
|
|
||||||
await expect(
|
await expect(
|
||||||
runScriptStep(args, state, responseFile)
|
runScriptStep(
|
||||||
|
runScriptStepDefinition.args,
|
||||||
|
prepareJobOutputData.state,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
).resolves.not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if the working directory does not exist', async () => {
|
||||||
|
runScriptStepDefinition.args.workingDirectory = '/foo/bar'
|
||||||
|
await expect(
|
||||||
|
runScriptStep(
|
||||||
|
runScriptStepDefinition.args,
|
||||||
|
prepareJobOutputData.state,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
).rejects.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should shold have env variables available', async () => {
|
||||||
|
runScriptStepDefinition.args.entryPoint = 'bash'
|
||||||
|
|
||||||
|
runScriptStepDefinition.args.entryPointArgs = [
|
||||||
|
'-c',
|
||||||
|
"'if [[ -z $NODE_ENV ]]; then exit 1; fi'"
|
||||||
|
]
|
||||||
|
await expect(
|
||||||
|
runScriptStep(
|
||||||
|
runScriptStepDefinition.args,
|
||||||
|
prepareJobOutputData.state,
|
||||||
|
null
|
||||||
|
)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
18
packages/k8s/tests/test-kind.yaml
Normal file
18
packages/k8s/tests/test-kind.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
kind: Cluster
|
||||||
|
apiVersion: kind.x-k8s.io/v1alpha4
|
||||||
|
nodes:
|
||||||
|
- role: control-plane
|
||||||
|
# add a mount from /path/to/my/files on the host to /files on the node
|
||||||
|
extraMounts:
|
||||||
|
- hostPath: {{PATHTOREPO}}
|
||||||
|
containerPath: {{PATHTOREPO}}
|
||||||
|
# optional: if set, the mount is read-only.
|
||||||
|
# default false
|
||||||
|
readOnly: false
|
||||||
|
# optional: if set, the mount needs SELinux relabeling.
|
||||||
|
# default false
|
||||||
|
selinuxRelabel: false
|
||||||
|
# optional: set propagation mode (None, HostToContainer or Bidirectional)
|
||||||
|
# see https://kubernetes.io/docs/concepts/storage/volumes/#mount-propagation
|
||||||
|
# default None
|
||||||
|
propagation: None
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as k8s from '@kubernetes/client-node'
|
import * as k8s from '@kubernetes/client-node'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
import { HookData } from 'hooklib/lib'
|
||||||
|
import * as path from 'path'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
const kc = new k8s.KubeConfig()
|
const kc = new k8s.KubeConfig()
|
||||||
@@ -20,17 +22,27 @@ export class TestHelper {
|
|||||||
public async initialize(): Promise<void> {
|
public async initialize(): Promise<void> {
|
||||||
process.env['ACTIONS_RUNNER_POD_NAME'] = `${this.podName}`
|
process.env['ACTIONS_RUNNER_POD_NAME'] = `${this.podName}`
|
||||||
process.env['ACTIONS_RUNNER_CLAIM_NAME'] = `${this.podName}-work`
|
process.env['ACTIONS_RUNNER_CLAIM_NAME'] = `${this.podName}-work`
|
||||||
process.env['RUNNER_WORKSPACE'] = `${this.tempDirPath}/work/repo`
|
process.env['RUNNER_WORKSPACE'] = `${this.tempDirPath}/_work/repo`
|
||||||
process.env['GITHUB_WORKSPACE'] = `${this.tempDirPath}/work/repo/repo`
|
process.env['RUNNER_TEMP'] = `${this.tempDirPath}/_work/_temp`
|
||||||
|
process.env['GITHUB_WORKSPACE'] = `${this.tempDirPath}/_work/repo/repo`
|
||||||
process.env['ACTIONS_RUNNER_KUBERNETES_NAMESPACE'] = 'default'
|
process.env['ACTIONS_RUNNER_KUBERNETES_NAMESPACE'] = 'default'
|
||||||
|
|
||||||
|
fs.mkdirSync(`${this.tempDirPath}/_work/repo/repo`, { recursive: true })
|
||||||
|
fs.mkdirSync(`${this.tempDirPath}/externals`, { recursive: true })
|
||||||
|
fs.mkdirSync(process.env.RUNNER_TEMP, { recursive: true })
|
||||||
|
|
||||||
|
fs.copyFileSync(
|
||||||
|
path.resolve(`${__dirname}/../../../examples/example-script.sh`),
|
||||||
|
`${process.env.RUNNER_TEMP}/example-script.sh`
|
||||||
|
)
|
||||||
|
|
||||||
await this.cleanupK8sResources()
|
await this.cleanupK8sResources()
|
||||||
try {
|
try {
|
||||||
await this.createTestVolume()
|
await this.createTestVolume()
|
||||||
await this.createTestJobPod()
|
await this.createTestJobPod()
|
||||||
} catch {}
|
} catch (e) {
|
||||||
fs.mkdirSync(`${this.tempDirPath}/work/repo/repo`, { recursive: true })
|
console.log(JSON.stringify(e))
|
||||||
fs.mkdirSync(`${this.tempDirPath}/externals`, { recursive: true })
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async cleanup(): Promise<void> {
|
public async cleanup(): Promise<void> {
|
||||||
@@ -116,7 +128,7 @@ export class TestHelper {
|
|||||||
volumeMode: 'Filesystem',
|
volumeMode: 'Filesystem',
|
||||||
accessModes: ['ReadWriteOnce'],
|
accessModes: ['ReadWriteOnce'],
|
||||||
hostPath: {
|
hostPath: {
|
||||||
path: this.tempDirPath
|
path: `${this.tempDirPath}/_work`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,4 +151,47 @@ export class TestHelper {
|
|||||||
}
|
}
|
||||||
await k8sApi.createNamespacedPersistentVolumeClaim('default', volumeClaim)
|
await k8sApi.createNamespacedPersistentVolumeClaim('default', volumeClaim)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPrepareJobDefinition(): HookData {
|
||||||
|
const prepareJob = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.resolve(__dirname + '/../../../examples/prepare-job.json'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
prepareJob.args.container.userMountVolumes = undefined
|
||||||
|
prepareJob.args.container.registry = null
|
||||||
|
prepareJob.args.services.forEach(s => {
|
||||||
|
s.registry = null
|
||||||
|
})
|
||||||
|
|
||||||
|
return prepareJob
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRunScriptStepDefinition(): HookData {
|
||||||
|
const runScriptStep = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.resolve(__dirname + '/../../../examples/run-script-step.json'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
runScriptStep.args.entryPointArgs[1] = `/__w/_temp/example-script.sh`
|
||||||
|
return runScriptStep
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRunContainerStepDefinition(): HookData {
|
||||||
|
const runContainerStep = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.resolve(__dirname + '/../../../examples/run-container-step.json'),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
runContainerStep.args.entryPointArgs[1] = `/__w/_temp/example-script.sh`
|
||||||
|
runContainerStep.args.userMountVolumes = undefined
|
||||||
|
runContainerStep.args.registry = null
|
||||||
|
return runContainerStep
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user