Compare commits

4 Commits

Author SHA1 Message Date
Nikola Jokic
638bd19c9d Release 0.5.1 (#131)
* Release 0.5.1

* Add one more PR that is part of the release
2024-02-05 15:00:35 +01:00
Nikola Jokic
50e14cf868 Switch exec pod promise to reject on websocket error (#127)
* Switch exec pod promise to reject on websocket error

* Fix incorrectly resolved merge conflict

* Apply suggestions from code review

Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>

---------

Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>
2024-02-05 14:40:15 +01:00
Nikola Jokic
921be5b85f Fix is alpine check using shlex (#130) 2024-02-05 09:50:51 +01:00
Nikola Jokic
0cce49705b Try to get response body message and log entire error response in debug mode (#123) 2023-12-15 13:01:04 +01:00
8 changed files with 79 additions and 36 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "hooks",
"version": "0.5.0",
"version": "0.5.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "hooks",
"version": "0.5.0",
"version": "0.5.1",
"license": "MIT",
"devDependencies": {
"@types/jest": "^27.5.1",

View File

@@ -1,6 +1,6 @@
{
"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",
"main": "",
"directories": {

View File

@@ -79,7 +79,9 @@ export async function prepareJob(
)
} catch (err) {
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) {
@@ -98,7 +100,7 @@ export async function prepareJob(
)
} catch (err) {
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')
@@ -110,7 +112,11 @@ export async function prepareJob(
JOB_CONTAINER_NAME
)
} 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}`)
generateResponseFile(responseFile, createdPod, isAlpine)

View File

@@ -36,7 +36,15 @@ export async function runContainerStep(
core.debug(`Created secret ${secretName} for container job envs`)
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) {
throw new Error(
`Expected job ${JSON.stringify(
@@ -46,7 +54,15 @@ export async function runContainerStep(
}
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(
podName,
new Set([PodPhase.COMPLETED, PodPhase.RUNNING, PodPhase.SUCCEEDED]),
@@ -58,6 +74,7 @@ export async function runContainerStep(
core.debug('Waiting for container job to complete')
await waitForJobToComplete(job.metadata.name)
// pod has failed so pull the status code from the container
const status = await getPodStatus(podName)
if (status?.phase === 'Succeeded') {

View File

@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as fs from 'fs'
import * as core from '@actions/core'
import { RunScriptStepArgs } from 'hooklib'
import { execPodStep } from '../k8s'
import { writeEntryPointScript } from '../k8s/utils'
@@ -28,7 +29,9 @@ export async function runScriptStep(
JOB_CONTAINER_NAME
)
} 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 {
fs.rmSync(runnerPath)
}

View File

@@ -14,7 +14,8 @@ import {
PodPhase,
mergePodSpecWithOptions,
mergeObjectMeta,
useKubeScheduler
useKubeScheduler,
fixArgs
} from './utils'
const kc = new k8s.KubeConfig()
@@ -226,31 +227,37 @@ export async function execPodStep(
stdin?: stream.Readable
): Promise<void> {
const exec = new k8s.Exec(kc)
await new Promise(async function (resolve, reject) {
await exec.exec(
namespace(),
podName,
containerName,
command,
process.stdout,
process.stderr,
stdin ?? null,
false /* tty */,
resp => {
// kube.exec returns an error if exit code is not 0, but we can't actually get the exit code
if (resp.status === 'Success') {
resolve(resp.code)
} else {
core.debug(
JSON.stringify({
message: resp?.message,
details: resp?.details
})
)
reject(resp?.message)
command = fixArgs(command)
// 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.
await new Promise(function (resolve, reject) {
exec
.exec(
namespace(),
podName,
containerName,
command,
process.stdout,
process.stderr,
stdin ?? null,
false /* tty */,
resp => {
// kube.exec returns an error if exit code is not 0, but we can't actually get the exit code
if (resp.status === 'Success') {
resolve(resp.code)
} else {
core.debug(
JSON.stringify({
message: 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))
})
}

View File

@@ -91,6 +91,15 @@ describe('Prepare job', () => {
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 () => {
process.env[ENV_HOOK_TEMPLATE_PATH] = path.join(
__dirname,

View File

@@ -1,8 +1,9 @@
<!-- ## Features -->
## Bugs
- Add option to use the kubernetes scheduler for workflow pods [#111]
- Docker and K8s: Fix shell arguments when split by the runner [#115]
- K8s: Try to get response body message and log entire error response in debug log [#123]
- Switch exec pod promise to reject on websocket error [#127]
- Fix is alpine check using shlex [#130]
<!-- ## Misc -->