mirror of
https://github.com/actions/runner-container-hooks.git
synced 2025-12-18 10:46:43 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
638bd19c9d | ||
|
|
50e14cf868 | ||
|
|
921be5b85f | ||
|
|
0cce49705b |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "hooks",
|
"name": "hooks",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "hooks",
|
"name": "hooks",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^27.5.1",
|
"@types/jest": "^27.5.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hooks",
|
"name": "hooks",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"description": "Three projects are included - k8s: a kubernetes hook implementation that spins up pods dynamically to run a job - docker: A hook implementation of the runner's docker implementation - A hook lib, which contains shared typescript definitions and utilities that the other packages consume",
|
"description": "Three projects are included - k8s: a kubernetes hook implementation that spins up pods dynamically to run a job - docker: A hook implementation of the runner's docker implementation - A hook lib, which contains shared typescript definitions and utilities that the other packages consume",
|
||||||
"main": "",
|
"main": "",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ export async function prepareJob(
|
|||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await prunePods()
|
await prunePods()
|
||||||
throw new Error(`failed to create job pod: ${err}`)
|
core.debug(`createPod failed: ${JSON.stringify(err)}`)
|
||||||
|
const message = (err as any)?.response?.body?.message || err
|
||||||
|
throw new Error(`failed to create job pod: ${message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!createdPod?.metadata?.name) {
|
if (!createdPod?.metadata?.name) {
|
||||||
@@ -98,7 +100,7 @@ export async function prepareJob(
|
|||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await prunePods()
|
await prunePods()
|
||||||
throw new Error(`Pod failed to come online with error: ${err}`)
|
throw new Error(`pod failed to come online with error: ${err}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
core.debug('Job pod is ready for traffic')
|
core.debug('Job pod is ready for traffic')
|
||||||
@@ -110,7 +112,11 @@ export async function prepareJob(
|
|||||||
JOB_CONTAINER_NAME
|
JOB_CONTAINER_NAME
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Failed to determine if the pod is alpine: ${err}`)
|
core.debug(
|
||||||
|
`Failed to determine if the pod is alpine: ${JSON.stringify(err)}`
|
||||||
|
)
|
||||||
|
const message = (err as any)?.response?.body?.message || err
|
||||||
|
throw new Error(`failed to determine if the pod is alpine: ${message}`)
|
||||||
}
|
}
|
||||||
core.debug(`Setting isAlpine to ${isAlpine}`)
|
core.debug(`Setting isAlpine to ${isAlpine}`)
|
||||||
generateResponseFile(responseFile, createdPod, isAlpine)
|
generateResponseFile(responseFile, createdPod, isAlpine)
|
||||||
|
|||||||
@@ -36,7 +36,15 @@ export async function runContainerStep(
|
|||||||
core.debug(`Created secret ${secretName} for container job envs`)
|
core.debug(`Created secret ${secretName} for container job envs`)
|
||||||
const container = createContainerSpec(stepContainer, secretName, extension)
|
const container = createContainerSpec(stepContainer, secretName, extension)
|
||||||
|
|
||||||
const job = await createJob(container, extension)
|
let job: k8s.V1Job
|
||||||
|
try {
|
||||||
|
job = await createJob(container, extension)
|
||||||
|
} catch (err) {
|
||||||
|
core.debug(`createJob failed: ${JSON.stringify(err)}`)
|
||||||
|
const message = (err as any)?.response?.body?.message || err
|
||||||
|
throw new Error(`failed to run script step: ${message}`)
|
||||||
|
}
|
||||||
|
|
||||||
if (!job.metadata?.name) {
|
if (!job.metadata?.name) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected job ${JSON.stringify(
|
`Expected job ${JSON.stringify(
|
||||||
@@ -46,7 +54,15 @@ 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)
|
let podName: string
|
||||||
|
try {
|
||||||
|
podName = await getContainerJobPodName(job.metadata.name)
|
||||||
|
} catch (err) {
|
||||||
|
core.debug(`getContainerJobPodName failed: ${JSON.stringify(err)}`)
|
||||||
|
const message = (err as any)?.response?.body?.message || err
|
||||||
|
throw new Error(`failed to get container job pod name: ${message}`)
|
||||||
|
}
|
||||||
|
|
||||||
await waitForPodPhases(
|
await waitForPodPhases(
|
||||||
podName,
|
podName,
|
||||||
new Set([PodPhase.COMPLETED, PodPhase.RUNNING, PodPhase.SUCCEEDED]),
|
new Set([PodPhase.COMPLETED, PodPhase.RUNNING, PodPhase.SUCCEEDED]),
|
||||||
@@ -58,6 +74,7 @@ export async function runContainerStep(
|
|||||||
|
|
||||||
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') {
|
if (status?.phase === 'Succeeded') {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
import * as core from '@actions/core'
|
||||||
import { RunScriptStepArgs } from 'hooklib'
|
import { RunScriptStepArgs } from 'hooklib'
|
||||||
import { execPodStep } from '../k8s'
|
import { execPodStep } from '../k8s'
|
||||||
import { writeEntryPointScript } from '../k8s/utils'
|
import { writeEntryPointScript } from '../k8s/utils'
|
||||||
@@ -28,7 +29,9 @@ export async function runScriptStep(
|
|||||||
JOB_CONTAINER_NAME
|
JOB_CONTAINER_NAME
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`failed to run script step: ${err}`)
|
core.debug(`execPodStep failed: ${JSON.stringify(err)}`)
|
||||||
|
const message = (err as any)?.response?.body?.message || err
|
||||||
|
throw new Error(`failed to run script step: ${message}`)
|
||||||
} finally {
|
} finally {
|
||||||
fs.rmSync(runnerPath)
|
fs.rmSync(runnerPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import {
|
|||||||
PodPhase,
|
PodPhase,
|
||||||
mergePodSpecWithOptions,
|
mergePodSpecWithOptions,
|
||||||
mergeObjectMeta,
|
mergeObjectMeta,
|
||||||
useKubeScheduler
|
useKubeScheduler,
|
||||||
|
fixArgs
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
const kc = new k8s.KubeConfig()
|
const kc = new k8s.KubeConfig()
|
||||||
@@ -226,31 +227,37 @@ export async function execPodStep(
|
|||||||
stdin?: stream.Readable
|
stdin?: stream.Readable
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const exec = new k8s.Exec(kc)
|
const exec = new k8s.Exec(kc)
|
||||||
await new Promise(async function (resolve, reject) {
|
command = fixArgs(command)
|
||||||
await exec.exec(
|
// Exec returns a websocket. If websocket fails, we should reject the promise. Otherwise, websocket will call a callback. Since at that point, websocket is not failing, we can safely resolve or reject the promise.
|
||||||
namespace(),
|
await new Promise(function (resolve, reject) {
|
||||||
podName,
|
exec
|
||||||
containerName,
|
.exec(
|
||||||
command,
|
namespace(),
|
||||||
process.stdout,
|
podName,
|
||||||
process.stderr,
|
containerName,
|
||||||
stdin ?? null,
|
command,
|
||||||
false /* tty */,
|
process.stdout,
|
||||||
resp => {
|
process.stderr,
|
||||||
// kube.exec returns an error if exit code is not 0, but we can't actually get the exit code
|
stdin ?? null,
|
||||||
if (resp.status === 'Success') {
|
false /* tty */,
|
||||||
resolve(resp.code)
|
resp => {
|
||||||
} else {
|
// kube.exec returns an error if exit code is not 0, but we can't actually get the exit code
|
||||||
core.debug(
|
if (resp.status === 'Success') {
|
||||||
JSON.stringify({
|
resolve(resp.code)
|
||||||
message: resp?.message,
|
} else {
|
||||||
details: resp?.details
|
core.debug(
|
||||||
})
|
JSON.stringify({
|
||||||
)
|
message: resp?.message,
|
||||||
reject(resp?.message)
|
details: resp?.details
|
||||||
|
})
|
||||||
|
)
|
||||||
|
reject(resp?.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
// If exec.exec fails, explicitly reject the outer promise
|
||||||
|
// eslint-disable-next-line github/no-then
|
||||||
|
.catch(e => reject(e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,15 @@ describe('Prepare job', () => {
|
|||||||
expect(services[0].args).toBe(undefined)
|
expect(services[0].args).toBe(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should determine alpine correctly', async () => {
|
||||||
|
prepareJobData.args.container.image = 'alpine:latest'
|
||||||
|
await prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
||||||
|
const content = JSON.parse(
|
||||||
|
fs.readFileSync(prepareJobOutputFilePath).toString()
|
||||||
|
)
|
||||||
|
expect(content.isAlpine).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
it('should run pod with extensions applied', async () => {
|
it('should run pod with extensions applied', async () => {
|
||||||
process.env[ENV_HOOK_TEMPLATE_PATH] = path.join(
|
process.env[ENV_HOOK_TEMPLATE_PATH] = path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<!-- ## Features -->
|
<!-- ## Features -->
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
- Add option to use the kubernetes scheduler for workflow pods [#111]
|
- K8s: Try to get response body message and log entire error response in debug log [#123]
|
||||||
- Docker and K8s: Fix shell arguments when split by the runner [#115]
|
- Switch exec pod promise to reject on websocket error [#127]
|
||||||
|
- Fix is alpine check using shlex [#130]
|
||||||
|
|
||||||
<!-- ## Misc -->
|
<!-- ## Misc -->
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user