mirror of
https://github.com/actions/runner-container-hooks.git
synced 2025-12-14 16:46:43 +00:00
slight refactor, bring pod phase to k8s lib, better types
This commit is contained in:
@@ -74,14 +74,6 @@ export enum Protocol {
|
|||||||
UDP = 'udp'
|
UDP = 'udp'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PodPhase {
|
|
||||||
PENDING = 'Pending',
|
|
||||||
RUNNING = 'Running',
|
|
||||||
SUCCEEDED = 'Succeeded',
|
|
||||||
FAILED = 'Failed',
|
|
||||||
UNKNOWN = 'Unknown'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PrepareJobResponse {
|
export interface PrepareJobResponse {
|
||||||
state?: object
|
state?: object
|
||||||
context?: ContainerContext
|
context?: ContainerContext
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import * as core from '@actions/core'
|
|||||||
import * as io from '@actions/io'
|
import * as io from '@actions/io'
|
||||||
import * as k8s from '@kubernetes/client-node'
|
import * as k8s from '@kubernetes/client-node'
|
||||||
import {
|
import {
|
||||||
ContextPorts,
|
|
||||||
PodPhase,
|
PodPhase,
|
||||||
prepareJobArgs,
|
containerVolumes,
|
||||||
writeToResponseFile
|
DEFAULT_CONTAINER_ENTRY_POINT,
|
||||||
} from 'hooklib'
|
DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
||||||
|
} from '../k8s/utils'
|
||||||
|
import { ContextPorts, prepareJobArgs, writeToResponseFile } from 'hooklib'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {
|
import {
|
||||||
containerPorts,
|
containerPorts,
|
||||||
@@ -18,11 +19,6 @@ import {
|
|||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
waitForPodPhases
|
waitForPodPhases
|
||||||
} from '../k8s'
|
} from '../k8s'
|
||||||
import {
|
|
||||||
containerVolumes,
|
|
||||||
DEFAULT_CONTAINER_ENTRY_POINT,
|
|
||||||
DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
|
||||||
} from '../k8s/utils'
|
|
||||||
import { JOB_CONTAINER_NAME } from './constants'
|
import { JOB_CONTAINER_NAME } from './constants'
|
||||||
|
|
||||||
export async function prepareJob(
|
export async function prepareJob(
|
||||||
@@ -40,14 +36,14 @@ export async function prepareJob(
|
|||||||
await copyExternalsToRoot()
|
await copyExternalsToRoot()
|
||||||
let container: k8s.V1Container | undefined = undefined
|
let container: k8s.V1Container | undefined = undefined
|
||||||
if (args.container?.image) {
|
if (args.container?.image) {
|
||||||
core.info(`Using image '${args.container.image}' for job image`)
|
core.debug(`Using image '${args.container.image}' for job image`)
|
||||||
container = createPodSpec(args.container, JOB_CONTAINER_NAME, true)
|
container = createPodSpec(args.container, JOB_CONTAINER_NAME, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
let services: k8s.V1Container[] = []
|
let services: k8s.V1Container[] = []
|
||||||
if (args.services?.length) {
|
if (args.services?.length) {
|
||||||
services = args.services.map(service => {
|
services = args.services.map(service => {
|
||||||
core.info(`Adding service '${service.image}' to pod definition`)
|
core.debug(`Adding service '${service.image}' to pod definition`)
|
||||||
return createPodSpec(service, service.image.split(':')[0])
|
return createPodSpec(service, service.image.split(':')[0])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -65,6 +61,9 @@ export async function prepareJob(
|
|||||||
if (!createdPod?.metadata?.name) {
|
if (!createdPod?.metadata?.name) {
|
||||||
throw new Error('created pod should have metadata.name')
|
throw new Error('created pod should have metadata.name')
|
||||||
}
|
}
|
||||||
|
core.debug(
|
||||||
|
`Job pod created, waiting for it to come online ${createdPod?.metadata?.name}`
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await waitForPodPhases(
|
await waitForPodPhases(
|
||||||
@@ -77,7 +76,7 @@ export async function prepareJob(
|
|||||||
throw new Error(`Pod failed to come online with error: ${err}`)
|
throw new Error(`Pod failed to come online with error: ${err}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
core.info('Pod is ready for traffic')
|
core.debug('Job pod is ready for traffic')
|
||||||
|
|
||||||
let isAlpine = false
|
let isAlpine = false
|
||||||
try {
|
try {
|
||||||
@@ -88,7 +87,7 @@ export async function prepareJob(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Failed to determine if the pod is alpine: ${err}`)
|
throw new Error(`Failed to determine if the pod is alpine: ${err}`)
|
||||||
}
|
}
|
||||||
|
core.debug(`Setting isAlpine to ${isAlpine}`)
|
||||||
generateResponseFile(responseFile, createdPod, isAlpine)
|
generateResponseFile(responseFile, createdPod, isAlpine)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +159,6 @@ function createPodSpec(
|
|||||||
name: string,
|
name: string,
|
||||||
jobContainer = false
|
jobContainer = false
|
||||||
): k8s.V1Container {
|
): k8s.V1Container {
|
||||||
core.info(JSON.stringify(container))
|
|
||||||
if (!container.entryPointArgs) {
|
if (!container.entryPointArgs) {
|
||||||
container.entryPointArgs = DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
container.entryPointArgs = DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as k8s from '@kubernetes/client-node'
|
import * as k8s from '@kubernetes/client-node'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import { PodPhase } from 'hooklib'
|
import { RunContainerStepArgs } from 'hooklib'
|
||||||
import {
|
import {
|
||||||
createJob,
|
createJob,
|
||||||
createSecretForEnvs,
|
createSecretForEnvs,
|
||||||
@@ -11,18 +11,20 @@ import {
|
|||||||
waitForPodPhases
|
waitForPodPhases
|
||||||
} from '../k8s'
|
} from '../k8s'
|
||||||
import { JOB_CONTAINER_NAME } from './constants'
|
import { JOB_CONTAINER_NAME } from './constants'
|
||||||
import { containerVolumes } from '../k8s/utils'
|
import { containerVolumes, PodPhase } from '../k8s/utils'
|
||||||
|
|
||||||
export async function runContainerStep(stepContainer): Promise<number> {
|
export async function runContainerStep(
|
||||||
|
stepContainer: RunContainerStepArgs
|
||||||
|
): Promise<number> {
|
||||||
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
|
||||||
if (stepContainer['environmentVariables']) {
|
core.debug('')
|
||||||
secretName = await createSecretForEnvs(
|
if (stepContainer.environmentVariables) {
|
||||||
stepContainer['environmentVariables']
|
secretName = await createSecretForEnvs(stepContainer.environmentVariables)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
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) {
|
||||||
@@ -32,27 +34,37 @@ export async function runContainerStep(stepContainer): Promise<number> {
|
|||||||
)} to have correctly set the metadata.name`
|
)} to have correctly set the 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,
|
||||||
new Set([PodPhase.COMPLETED, PodPhase.RUNNING, PodPhase.SUCCEEDED]),
|
new Set([PodPhase.COMPLETED, PodPhase.RUNNING, PodPhase.SUCCEEDED]),
|
||||||
new Set([PodPhase.PENDING, PodPhase.UNKNOWN])
|
new Set([PodPhase.PENDING, PodPhase.UNKNOWN])
|
||||||
)
|
)
|
||||||
|
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')
|
||||||
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?.containerStatuses?.length) {
|
if (!status?.containerStatuses?.length) {
|
||||||
core.warning(`Can't determine container status`)
|
core.error(
|
||||||
return 0
|
`Can't determine container status from response: ${JSON.stringify(
|
||||||
|
status
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
const exitCode =
|
const exitCode =
|
||||||
status.containerStatuses[status.containerStatuses.length - 1].state
|
status.containerStatuses[status.containerStatuses.length - 1].state
|
||||||
?.terminated?.exitCode
|
?.terminated?.exitCode
|
||||||
return Number(exitCode) || 0
|
return Number(exitCode) || 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPodSpec(container, secretName?: string): k8s.V1Container {
|
function createPodSpec(
|
||||||
|
container: RunContainerStepArgs,
|
||||||
|
secretName?: string
|
||||||
|
): k8s.V1Container {
|
||||||
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
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Command, getInputFromStdin, prepareJobArgs } from 'hooklib'
|
import { Command, getInputFromStdin, prepareJobArgs } from 'hooklib'
|
||||||
|
import * as core from '@actions/core'
|
||||||
import {
|
import {
|
||||||
cleanupJob,
|
cleanupJob,
|
||||||
prepareJob,
|
prepareJob,
|
||||||
@@ -34,8 +35,7 @@ async function run(): Promise<void> {
|
|||||||
throw new Error(`Command not recognized: ${command}`)
|
throw new Error(`Command not recognized: ${command}`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
core.error(JSON.stringify(error))
|
||||||
console.log(error)
|
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
}
|
}
|
||||||
process.exitCode = exitCode
|
process.exitCode = exitCode
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as k8s from '@kubernetes/client-node'
|
import * as k8s from '@kubernetes/client-node'
|
||||||
import { ContainerInfo, PodPhase, Registry } from 'hooklib'
|
import { ContainerInfo, Registry } from 'hooklib'
|
||||||
import * as stream from 'stream'
|
import * as stream from 'stream'
|
||||||
import {
|
import {
|
||||||
getJobPodName,
|
getJobPodName,
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
getVolumeClaimName,
|
getVolumeClaimName,
|
||||||
RunnerInstanceLabel
|
RunnerInstanceLabel
|
||||||
} from '../hooks/constants'
|
} from '../hooks/constants'
|
||||||
|
import { PodPhase } from './utils'
|
||||||
|
|
||||||
const kc = new k8s.KubeConfig()
|
const kc = new k8s.KubeConfig()
|
||||||
|
|
||||||
@@ -355,7 +356,7 @@ async function getPodPhase(podName: string): Promise<PodPhase> {
|
|||||||
if (!pod.status?.phase || !podPhaseLookup.has(pod.status.phase)) {
|
if (!pod.status?.phase || !podPhaseLookup.has(pod.status.phase)) {
|
||||||
return PodPhase.UNKNOWN
|
return PodPhase.UNKNOWN
|
||||||
}
|
}
|
||||||
return pod.status?.phase
|
return pod.status?.phase as PodPhase
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isJobSucceeded(jobName: string): Promise<boolean> {
|
async function isJobSucceeded(jobName: string): Promise<boolean> {
|
||||||
|
|||||||
@@ -70,3 +70,12 @@ export function containerVolumes(
|
|||||||
|
|
||||||
return mounts
|
return mounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PodPhase {
|
||||||
|
PENDING = 'Pending',
|
||||||
|
RUNNING = 'Running',
|
||||||
|
SUCCEEDED = 'Succeeded',
|
||||||
|
FAILED = 'Failed',
|
||||||
|
UNKNOWN = 'Unknown',
|
||||||
|
COMPLETED = 'Completed'
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user