mirror of
https://github.com/actions/runner-container-hooks.git
synced 2025-12-14 00:26:44 +00:00
169 lines
4.3 KiB
TypeScript
169 lines
4.3 KiB
TypeScript
import * as k8s from '@kubernetes/client-node'
|
|
import * as fs from 'fs'
|
|
import { Mount } from 'hooklib'
|
|
import * as path from 'path'
|
|
import { v1 as uuidv4 } from 'uuid'
|
|
import { POD_VOLUME_NAME } from './index'
|
|
|
|
export const DEFAULT_CONTAINER_ENTRY_POINT_ARGS = [`-f`, `/dev/null`]
|
|
export const DEFAULT_CONTAINER_ENTRY_POINT = 'tail'
|
|
|
|
export function containerVolumes(
|
|
userMountVolumes: Mount[] = [],
|
|
jobContainer = true,
|
|
containerAction = false
|
|
): k8s.V1VolumeMount[] {
|
|
const mounts: k8s.V1VolumeMount[] = [
|
|
{
|
|
name: POD_VOLUME_NAME,
|
|
mountPath: '/__w'
|
|
}
|
|
]
|
|
|
|
const workspacePath = process.env.GITHUB_WORKSPACE as string
|
|
if (containerAction) {
|
|
const i = workspacePath.lastIndexOf('_work/')
|
|
const workspaceRelativePath = workspacePath.slice(i + '_work/'.length)
|
|
mounts.push(
|
|
{
|
|
name: POD_VOLUME_NAME,
|
|
mountPath: '/github/workspace',
|
|
subPath: workspaceRelativePath
|
|
},
|
|
{
|
|
name: POD_VOLUME_NAME,
|
|
mountPath: '/github/file_commands',
|
|
subPath: '_temp/_runner_file_commands'
|
|
}
|
|
)
|
|
return mounts
|
|
}
|
|
|
|
if (!jobContainer) {
|
|
return mounts
|
|
}
|
|
|
|
mounts.push(
|
|
{
|
|
name: POD_VOLUME_NAME,
|
|
mountPath: '/__e',
|
|
subPath: 'externals'
|
|
},
|
|
{
|
|
name: POD_VOLUME_NAME,
|
|
mountPath: '/github/home',
|
|
subPath: '_temp/_github_home'
|
|
},
|
|
{
|
|
name: POD_VOLUME_NAME,
|
|
mountPath: '/github/workflow',
|
|
subPath: '_temp/_github_workflow'
|
|
}
|
|
)
|
|
|
|
if (!userMountVolumes?.length) {
|
|
return mounts
|
|
}
|
|
|
|
for (const userVolume of userMountVolumes) {
|
|
let sourceVolumePath = ''
|
|
if (path.isAbsolute(userVolume.sourceVolumePath)) {
|
|
if (!userVolume.sourceVolumePath.startsWith(workspacePath)) {
|
|
throw new Error(
|
|
'Volume mounts outside of the work folder are not supported'
|
|
)
|
|
}
|
|
// source volume path should be relative path
|
|
sourceVolumePath = userVolume.sourceVolumePath.slice(
|
|
workspacePath.length + 1
|
|
)
|
|
} else {
|
|
sourceVolumePath = userVolume.sourceVolumePath
|
|
}
|
|
|
|
mounts.push({
|
|
name: POD_VOLUME_NAME,
|
|
mountPath: userVolume.targetVolumePath,
|
|
subPath: sourceVolumePath,
|
|
readOnly: userVolume.readOnly
|
|
})
|
|
}
|
|
|
|
return mounts
|
|
}
|
|
|
|
export function writeEntryPointScript(
|
|
workingDirectory: string,
|
|
entryPoint: string,
|
|
entryPointArgs?: string[],
|
|
prependPath?: string[],
|
|
environmentVariables?: { [key: string]: string }
|
|
): { containerPath: string; runnerPath: string } {
|
|
let exportPath = ''
|
|
if (prependPath?.length) {
|
|
// TODO: remove compatibility with typeof prependPath === 'string' as we bump to next major version, the hooks will lose PrependPath compat with runners 2.293.0 and older
|
|
const prepend =
|
|
typeof prependPath === 'string' ? prependPath : prependPath.join(':')
|
|
exportPath = `export PATH=${prepend}:$PATH`
|
|
}
|
|
let environmentPrefix = ''
|
|
|
|
if (environmentVariables && Object.entries(environmentVariables).length) {
|
|
const envBuffer: string[] = []
|
|
for (const [key, value] of Object.entries(environmentVariables)) {
|
|
if (
|
|
key.includes(`=`) ||
|
|
key.includes(`'`) ||
|
|
key.includes(`"`) ||
|
|
key.includes(`$`)
|
|
) {
|
|
throw new Error(
|
|
`environment key ${key} is invalid - the key must not contain =, $, ', or "`
|
|
)
|
|
}
|
|
envBuffer.push(
|
|
`"${key}=${value
|
|
.replace(/\\/g, '\\\\')
|
|
.replace(/"/g, '\\"')
|
|
.replace(/\$/g, '\\$')}"`
|
|
)
|
|
}
|
|
environmentPrefix = `env ${envBuffer.join(' ')} `
|
|
}
|
|
|
|
const content = `#!/bin/sh -l
|
|
${exportPath}
|
|
cd ${workingDirectory} && \
|
|
exec ${environmentPrefix} ${entryPoint} ${
|
|
entryPointArgs?.length ? entryPointArgs.join(' ') : ''
|
|
}
|
|
`
|
|
const filename = `${uuidv4()}.sh`
|
|
const entryPointPath = `${process.env.RUNNER_TEMP}/${filename}`
|
|
fs.writeFileSync(entryPointPath, content)
|
|
return {
|
|
containerPath: `/__w/_temp/${filename}`,
|
|
runnerPath: entryPointPath
|
|
}
|
|
}
|
|
|
|
export function generateContainerName(image: string): string {
|
|
const nameWithTag = image.split('/').pop()
|
|
const name = nameWithTag?.split(':').at(0)
|
|
|
|
if (!name) {
|
|
throw new Error(`Image definition '${image}' is invalid`)
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
export enum PodPhase {
|
|
PENDING = 'Pending',
|
|
RUNNING = 'Running',
|
|
SUCCEEDED = 'Succeeded',
|
|
FAILED = 'Failed',
|
|
UNKNOWN = 'Unknown',
|
|
COMPLETED = 'Completed'
|
|
}
|