From cd310988c950e33f59842a72242f790df1a47643 Mon Sep 17 00:00:00 2001 From: Thomas Boop Date: Mon, 6 Jun 2022 22:39:28 -0400 Subject: [PATCH 1/2] slight refactor, bring pod phase to k8s lib, better types --- packages/hooklib/src/interfaces.ts | 8 ----- packages/k8s/src/hooks/prepare-job.ts | 26 +++++++-------- packages/k8s/src/hooks/run-container-step.ts | 34 +++++++++++++------- packages/k8s/src/index.ts | 4 +-- packages/k8s/src/k8s/index.ts | 5 +-- packages/k8s/src/k8s/utils.ts | 9 ++++++ 6 files changed, 49 insertions(+), 37 deletions(-) diff --git a/packages/hooklib/src/interfaces.ts b/packages/hooklib/src/interfaces.ts index 53e20c1..66f6feb 100644 --- a/packages/hooklib/src/interfaces.ts +++ b/packages/hooklib/src/interfaces.ts @@ -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 diff --git a/packages/k8s/src/hooks/prepare-job.ts b/packages/k8s/src/hooks/prepare-job.ts index 0549563..7bff8d6 100644 --- a/packages/k8s/src/hooks/prepare-job.ts +++ b/packages/k8s/src/hooks/prepare-job.ts @@ -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 } diff --git a/packages/k8s/src/hooks/run-container-step.ts b/packages/k8s/src/hooks/run-container-step.ts index 7de4dfa..73594e0 100644 --- a/packages/k8s/src/hooks/run-container-step.ts +++ b/packages/k8s/src/hooks/run-container-step.ts @@ -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 { +export async function runContainerStep( + stepContainer: RunContainerStepArgs +): Promise { 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 { )} 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 diff --git a/packages/k8s/src/index.ts b/packages/k8s/src/index.ts index 8ce9d39..047bfe2 100644 --- a/packages/k8s/src/index.ts +++ b/packages/k8s/src/index.ts @@ -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 { 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 diff --git a/packages/k8s/src/k8s/index.ts b/packages/k8s/src/k8s/index.ts index c60ec41..e210e94 100644 --- a/packages/k8s/src/k8s/index.ts +++ b/packages/k8s/src/k8s/index.ts @@ -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 { 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 { diff --git a/packages/k8s/src/k8s/utils.ts b/packages/k8s/src/k8s/utils.ts index 7fc235a..a8901c4 100644 --- a/packages/k8s/src/k8s/utils.ts +++ b/packages/k8s/src/k8s/utils.ts @@ -70,3 +70,12 @@ export function containerVolumes( return mounts } + +export enum PodPhase { + PENDING = 'Pending', + RUNNING = 'Running', + SUCCEEDED = 'Succeeded', + FAILED = 'Failed', + UNKNOWN = 'Unknown', + COMPLETED = 'Completed' +} From 7c4e0f8d51e371e1f46c6c004ec25ad6e47bca90 Mon Sep 17 00:00:00 2001 From: Thomas Boop Date: Mon, 6 Jun 2022 22:42:46 -0400 Subject: [PATCH 2/2] update limitations --- packages/k8s/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/k8s/README.md b/packages/k8s/README.md index 981e9b3..ddf8815 100644 --- a/packages/k8s/README.md +++ b/packages/k8s/README.md @@ -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