mirror of
https://github.com/actions/runner-container-hooks.git
synced 2025-12-14 08:36:45 +00:00
@@ -74,14 +74,6 @@ export enum Protocol {
|
||||
UDP = 'udp'
|
||||
}
|
||||
|
||||
export enum PodPhase {
|
||||
PENDING = 'Pending',
|
||||
RUNNING = 'Running',
|
||||
SUCCEEDED = 'Succeeded',
|
||||
FAILED = 'Failed',
|
||||
UNKNOWN = 'Unknown'
|
||||
}
|
||||
|
||||
export interface PrepareJobResponse {
|
||||
state?: object
|
||||
context?: ContainerContext
|
||||
|
||||
@@ -27,3 +27,10 @@ Some things are expected to be set when using these hooks
|
||||
- Some actions runner env's are expected to be set. These are set automatically by the runner.
|
||||
- `RUNNER_WORKSPACE` is expected to be set to the workspace of the runner
|
||||
- `GITHUB_WORKSPACE` is expected to be set to the workspace of the job
|
||||
|
||||
|
||||
## Limitations
|
||||
- Container actions
|
||||
- Building container actions from a dockerfile is not supported at this time
|
||||
- Container actions will not have access to the services network or job container network
|
||||
- Docker [create options](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idcontaineroptions) are not supported
|
||||
|
||||
@@ -2,11 +2,12 @@ import * as core from '@actions/core'
|
||||
import * as io from '@actions/io'
|
||||
import * as k8s from '@kubernetes/client-node'
|
||||
import {
|
||||
ContextPorts,
|
||||
PodPhase,
|
||||
prepareJobArgs,
|
||||
writeToResponseFile
|
||||
} from 'hooklib'
|
||||
containerVolumes,
|
||||
DEFAULT_CONTAINER_ENTRY_POINT,
|
||||
DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
||||
} from '../k8s/utils'
|
||||
import { ContextPorts, prepareJobArgs, writeToResponseFile } from 'hooklib'
|
||||
import path from 'path'
|
||||
import {
|
||||
containerPorts,
|
||||
@@ -18,11 +19,6 @@ import {
|
||||
requiredPermissions,
|
||||
waitForPodPhases
|
||||
} from '../k8s'
|
||||
import {
|
||||
containerVolumes,
|
||||
DEFAULT_CONTAINER_ENTRY_POINT,
|
||||
DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
||||
} from '../k8s/utils'
|
||||
import { JOB_CONTAINER_NAME } from './constants'
|
||||
|
||||
export async function prepareJob(
|
||||
@@ -40,14 +36,14 @@ export async function prepareJob(
|
||||
await copyExternalsToRoot()
|
||||
let container: k8s.V1Container | undefined = undefined
|
||||
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)
|
||||
}
|
||||
|
||||
let services: k8s.V1Container[] = []
|
||||
if (args.services?.length) {
|
||||
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])
|
||||
})
|
||||
}
|
||||
@@ -65,6 +61,9 @@ export async function prepareJob(
|
||||
if (!createdPod?.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 {
|
||||
await waitForPodPhases(
|
||||
@@ -77,7 +76,7 @@ export async function prepareJob(
|
||||
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
|
||||
try {
|
||||
@@ -88,7 +87,7 @@ export async function prepareJob(
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to determine if the pod is alpine: ${err}`)
|
||||
}
|
||||
|
||||
core.debug(`Setting isAlpine to ${isAlpine}`)
|
||||
generateResponseFile(responseFile, createdPod, isAlpine)
|
||||
}
|
||||
|
||||
@@ -160,7 +159,6 @@ function createPodSpec(
|
||||
name: string,
|
||||
jobContainer = false
|
||||
): k8s.V1Container {
|
||||
core.info(JSON.stringify(container))
|
||||
if (!container.entryPointArgs) {
|
||||
container.entryPointArgs = DEFAULT_CONTAINER_ENTRY_POINT_ARGS
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as k8s from '@kubernetes/client-node'
|
||||
import * as core from '@actions/core'
|
||||
import { PodPhase } from 'hooklib'
|
||||
import { RunContainerStepArgs } from 'hooklib'
|
||||
import {
|
||||
createJob,
|
||||
createSecretForEnvs,
|
||||
@@ -11,18 +11,20 @@ import {
|
||||
waitForPodPhases
|
||||
} from '../k8s'
|
||||
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) {
|
||||
throw new Error('Building container actions is not currently supported')
|
||||
}
|
||||
let secretName: string | undefined = undefined
|
||||
if (stepContainer['environmentVariables']) {
|
||||
secretName = await createSecretForEnvs(
|
||||
stepContainer['environmentVariables']
|
||||
)
|
||||
core.debug('')
|
||||
if (stepContainer.environmentVariables) {
|
||||
secretName = await createSecretForEnvs(stepContainer.environmentVariables)
|
||||
}
|
||||
core.debug(`Created secret ${secretName} for container job envs`)
|
||||
const container = createPodSpec(stepContainer, secretName)
|
||||
const job = await createJob(container)
|
||||
if (!job.metadata?.name) {
|
||||
@@ -32,27 +34,37 @@ export async function runContainerStep(stepContainer): Promise<number> {
|
||||
)} 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)
|
||||
await waitForPodPhases(
|
||||
podName,
|
||||
new Set([PodPhase.COMPLETED, PodPhase.RUNNING, PodPhase.SUCCEEDED]),
|
||||
new Set([PodPhase.PENDING, PodPhase.UNKNOWN])
|
||||
)
|
||||
core.debug('Container step is running or complete, pulling logs')
|
||||
await getPodLogs(podName, JOB_CONTAINER_NAME)
|
||||
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?.containerStatuses?.length) {
|
||||
core.warning(`Can't determine container status`)
|
||||
return 0
|
||||
core.error(
|
||||
`Can't determine container status from response: ${JSON.stringify(
|
||||
status
|
||||
)}`
|
||||
)
|
||||
return 1
|
||||
}
|
||||
const exitCode =
|
||||
status.containerStatuses[status.containerStatuses.length - 1].state
|
||||
?.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()
|
||||
podContainer.name = JOB_CONTAINER_NAME
|
||||
podContainer.image = container.image
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Command, getInputFromStdin, prepareJobArgs } from 'hooklib'
|
||||
import * as core from '@actions/core'
|
||||
import {
|
||||
cleanupJob,
|
||||
prepareJob,
|
||||
@@ -34,8 +35,7 @@ async function run(): Promise<void> {
|
||||
throw new Error(`Command not recognized: ${command}`)
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
core.error(JSON.stringify(error))
|
||||
exitCode = 1
|
||||
}
|
||||
process.exitCode = exitCode
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as k8s from '@kubernetes/client-node'
|
||||
import { ContainerInfo, PodPhase, Registry } from 'hooklib'
|
||||
import { ContainerInfo, Registry } from 'hooklib'
|
||||
import * as stream from 'stream'
|
||||
import {
|
||||
getJobPodName,
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
getVolumeClaimName,
|
||||
RunnerInstanceLabel
|
||||
} from '../hooks/constants'
|
||||
import { PodPhase } from './utils'
|
||||
|
||||
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)) {
|
||||
return PodPhase.UNKNOWN
|
||||
}
|
||||
return pod.status?.phase
|
||||
return pod.status?.phase as PodPhase
|
||||
}
|
||||
|
||||
async function isJobSucceeded(jobName: string): Promise<boolean> {
|
||||
|
||||
@@ -70,3 +70,12 @@ export function containerVolumes(
|
||||
|
||||
return mounts
|
||||
}
|
||||
|
||||
export enum PodPhase {
|
||||
PENDING = 'Pending',
|
||||
RUNNING = 'Running',
|
||||
SUCCEEDED = 'Succeeded',
|
||||
FAILED = 'Failed',
|
||||
UNKNOWN = 'Unknown',
|
||||
COMPLETED = 'Completed'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user