Compare commits

..
18 changed files with 1598 additions and 1480 deletions
-28
View File
@@ -1,28 +0,0 @@
version: 2
updates:
# Group updates into a single PR per workspace package
- package-ecosystem: npm
directory: "/packages/docker"
schedule:
interval: weekly
groups:
all-dependencies:
patterns:
- "*"
- package-ecosystem: npm
directory: "/packages/hooklib"
schedule:
interval: weekly
groups:
all-dependencies:
patterns:
- "*"
- package-ecosystem: npm
directory: "/packages/k8s"
schedule:
interval: weekly
groups:
all-dependencies:
patterns:
- "*"
+2 -14
View File
@@ -13,11 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: actions/setup-node@v6 - run: npm install
with:
node-version: 24
cache: npm
- run: npm ci
name: Install dependencies name: Install dependencies
- run: npm run bootstrap - run: npm run bootstrap
name: Bootstrap the packages name: Bootstrap the packages
@@ -36,11 +32,7 @@ jobs:
needs: format-and-lint needs: format-and-lint
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: actions/setup-node@v6 - run: npm install
with:
node-version: 24
cache: npm
- run: npm ci
name: Install dependencies name: Install dependencies
- run: npm run bootstrap - run: npm run bootstrap
name: Bootstrap the packages name: Bootstrap the packages
@@ -55,10 +47,6 @@ jobs:
needs: format-and-lint needs: format-and-lint
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
- run: sed -i "s|{{PATHTOREPO}}|$(pwd)|" packages/k8s/tests/test-kind.yaml - run: sed -i "s|{{PATHTOREPO}}|$(pwd)|" packages/k8s/tests/test-kind.yaml
name: Setup kind cluster yaml config name: Setup kind cluster yaml config
- uses: helm/[email protected] - uses: helm/[email protected]
-4
View File
@@ -12,10 +12,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
- name: Install dependencies - name: Install dependencies
run: npm install run: npm install
+5 -13
View File
@@ -1,12 +1,12 @@
{ {
"name": "hooks", "name": "hooks",
"version": "0.8.1", "version": "0.8.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hooks", "name": "hooks",
"version": "0.8.1", "version": "0.8.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"eslint-plugin-jest": "^29.0.1" "eslint-plugin-jest": "^29.0.1"
@@ -560,7 +560,6 @@
"integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/scope-manager": "8.45.0",
@@ -591,7 +590,6 @@
"integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/types": "8.45.0", "@typescript-eslint/types": "8.45.0",
@@ -788,7 +786,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -1084,7 +1081,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.8.9", "baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746", "caniuse-lite": "^1.0.30001746",
@@ -1587,7 +1583,6 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz",
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -1649,7 +1644,6 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"eslint-config-prettier": "bin/cli.js" "eslint-config-prettier": "bin/cli.js"
}, },
@@ -3171,9 +3165,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@@ -3652,7 +3646,6 @@
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -4387,7 +4380,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
+2 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "hooks", "name": "hooks",
"version": "0.8.1", "version": "0.8.0",
"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", "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": "", "main": "",
"directories": { "directories": {
@@ -8,11 +8,10 @@
}, },
"scripts": { "scripts": {
"test": "npm run test --prefix packages/docker && npm run test --prefix packages/k8s", "test": "npm run test --prefix packages/docker && npm run test --prefix packages/k8s",
"bootstrap": "npm install --prefix packages/hooklib && npm ci --prefix packages/k8s && npm ci --prefix packages/docker", "bootstrap": "npm install --prefix packages/hooklib && npm install --prefix packages/k8s && npm install --prefix packages/docker",
"format": "prettier --write '**/*.ts'", "format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'", "format-check": "prettier --check '**/*.ts'",
"lint": "eslint packages/**/*.ts", "lint": "eslint packages/**/*.ts",
"lint:fix": "eslint packages/**/*.ts --fix",
"build-all": "npm run build --prefix packages/hooklib && npm run build --prefix packages/k8s && npm run build --prefix packages/docker" "build-all": "npm run build --prefix packages/hooklib && npm run build --prefix packages/k8s && npm run build --prefix packages/docker"
}, },
"repository": { "repository": {
+646 -552
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -13,8 +13,8 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^2.0.2", "@actions/core": "^1.11.1",
"@actions/exec": "^2.0.0", "@actions/exec": "^1.1.1",
"hooklib": "file:../hooklib", "hooklib": "file:../hooklib",
"shlex": "^3.0.0", "shlex": "^3.0.0",
"uuid": "^11.1.0" "uuid": "^11.1.0"
+430 -239
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -22,6 +22,6 @@
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"dependencies": { "dependencies": {
"@actions/core": "^2.0.2" "@actions/core": "^1.11.1"
} }
} }
+10 -2
View File
@@ -1,11 +1,15 @@
# K8s Hooks # K8s Hooks
## Description ## Description
This implementation provides a way to dynamically spin up jobs to run container workflows, rather then relying on the default docker implementation. It is meant to be used when the runner itself is running in k8s, for example when using the [Actions Runner Controller](https://github.com/actions-runner-controller/actions-runner-controller) This implementation provides a way to dynamically spin up jobs to run container workflows, rather then relying on the default docker implementation. It is meant to be used when the runner itself is running in k8s, for example when using the [Actions Runner Controller](https://github.com/actions-runner-controller/actions-runner-controller)
## Pre-requisites ## Pre-requisites
Some things are expected to be set when using these hooks Some things are expected to be set when using these hooks
- The runner itself should be running in a pod, with a service account with the following permissions - The runner itself should be running in a pod, with a service account with the following permissions
``` ```
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: Role
@@ -26,6 +30,7 @@ rules:
resources: ["secrets"] resources: ["secrets"]
verbs: ["get", "list", "create", "delete"] verbs: ["get", "list", "create", "delete"]
``` ```
- The `ACTIONS_RUNNER_POD_NAME` env should be set to the name of the pod - The `ACTIONS_RUNNER_POD_NAME` env should be set to the name of the pod
- The `ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER` env should be set to true to prevent the runner from running any jobs outside of a container - The `ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER` env should be set to true to prevent the runner from running any jobs outside of a container
- The runner pod should map a persistent volume claim into the `_work` directory - The runner pod should map a persistent volume claim into the `_work` directory
@@ -34,11 +39,14 @@ rules:
- `RUNNER_WORKSPACE` is expected to be set to the workspace of 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 - `GITHUB_WORKSPACE` is expected to be set to the workspace of the job
## Limitations ## Limitations
- A [job containers](https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container) will be required for all jobs - A [job containers](https://docs.github.com/en/actions/using-jobs/running-jobs-in-a-container) will be required for all jobs
- Building container actions from a dockerfile is not supported at this time - 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 - 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 - Docker [create options](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idcontaineroptions) are not supported
- Container actions will have to specify the entrypoint, since the default entrypoint will be overridden to run the commands from the workflow. - Container actions will have to specify the entrypoint, since the default entrypoint will be overridden to run the commands from the workflow.
- Container actions need to have the following binaries in their container image: `sh`, `env`, `tail`. - The job container is expected to have the following tools:
- `sh`
- `tar`
- `tail` - to download/upload the workspace
+460 -453
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -13,9 +13,9 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^2.0.2", "@actions/core": "^1.11.1",
"@actions/exec": "^2.0.0", "@actions/exec": "^1.1.1",
"@actions/io": "^2.0.0", "@actions/io": "^1.1.3",
"@kubernetes/client-node": "^1.3.0", "@kubernetes/client-node": "^1.3.0",
"hooklib": "file:../hooklib", "hooklib": "file:../hooklib",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
+3 -52
View File
@@ -6,7 +6,6 @@ import { execCpFromPod, execCpToPod, execPodStep } from '../k8s'
import { writeRunScript, sleep, listDirAllCommand } from '../k8s/utils' import { writeRunScript, sleep, listDirAllCommand } from '../k8s/utils'
import { JOB_CONTAINER_NAME } from './constants' import { JOB_CONTAINER_NAME } from './constants'
import { dirname } from 'path' import { dirname } from 'path'
import * as shlex from 'shlex'
export async function runScriptStep( export async function runScriptStep(
args: RunScriptStepArgs, args: RunScriptStepArgs,
@@ -23,53 +22,9 @@ export async function runScriptStep(
) )
const workdir = dirname(process.env.RUNNER_WORKSPACE as string) const workdir = dirname(process.env.RUNNER_WORKSPACE as string)
const runnerTemp = `${workdir}/_temp`
const containerTemp = '/__w/_temp' const containerTemp = '/__w/_temp'
const containerTempSrc = '/__w/_temp_pre' const runnerTemp = `${workdir}/_temp`
// Ensure base and staging dirs exist before copying await execCpToPod(state.jobPod, runnerTemp, containerTemp)
await execPodStep(
[
'sh',
'-c',
'mkdir -p /__w && mkdir -p /__w/_temp && mkdir -p /__w/_temp_pre'
],
state.jobPod,
JOB_CONTAINER_NAME
)
await execCpToPod(state.jobPod, runnerTemp, containerTempSrc)
// Copy GitHub directories from temp to /github
// Merge strategy:
// - Overwrite files in _runner_file_commands
// - Append files not already present elsewhere
const mergeCommands = [
'set -e',
'mkdir -p /__w/_temp /__w/_temp_pre',
'SRC=/__w/_temp_pre',
'DST=/__w/_temp',
// Overwrite _runner_file_commands
'cp -a "$SRC/_runner_file_commands/." "$DST/_runner_file_commands"',
`find "$SRC" -type f ! -path "*/_runner_file_commands/*" -exec sh -c '
rel="\${1#$2/}"
target="$3/$rel"
mkdir -p "$(dirname "$target")"
cp -a "$1" "$target"
' _ {} "$SRC" "$DST" \\;`,
// Remove _temp_pre after merging
'rm -rf /__w/_temp_pre'
]
try {
await execPodStep(
['sh', '-c', mergeCommands.join(' && ')],
state.jobPod,
JOB_CONTAINER_NAME
)
} catch (err) {
core.debug(`Failed to merge temp directories: ${JSON.stringify(err)}`)
const message = (err as any)?.response?.body?.message || err
throw new Error(`failed to merge temp dirs: ${message}`)
}
// Execute the entrypoint script // Execute the entrypoint script
args.entryPoint = 'sh' args.entryPoint = 'sh'
@@ -96,11 +51,7 @@ export async function runScriptStep(
core.debug( core.debug(
`Copying from job pod '${state.jobPod}' ${containerTemp} to ${runnerTemp}` `Copying from job pod '${state.jobPod}' ${containerTemp} to ${runnerTemp}`
) )
await execCpFromPod( await execCpFromPod(state.jobPod, containerTemp, workdir)
state.jobPod,
`${containerTemp}/_runner_file_commands`,
`${workdir}/_temp`
)
} catch (error) { } catch (error) {
core.warning('Failed to copy _temp from pod') core.warning('Failed to copy _temp from pod')
} }
+14 -65
View File
@@ -20,10 +20,8 @@ import {
listDirAllCommand, listDirAllCommand,
sleep, sleep,
EXTERNALS_VOLUME_NAME, EXTERNALS_VOLUME_NAME,
GITHUB_VOLUME_NAME, GITHUB_VOLUME_NAME
WORK_VOLUME
} from './utils' } from './utils'
import * as shlex from 'shlex'
const kc = new k8s.KubeConfig() const kc = new k8s.KubeConfig()
@@ -93,33 +91,13 @@ export async function createJobPod(
appPod.spec = new k8s.V1PodSpec() appPod.spec = new k8s.V1PodSpec()
appPod.spec.containers = containers appPod.spec.containers = containers
appPod.spec.securityContext = {
fsGroup: 1001
}
// Extract working directory from GITHUB_WORKSPACE
// GITHUB_WORKSPACE is like /__w/repo-name/repo-name
const githubWorkspace = process.env.GITHUB_WORKSPACE
const workingDirPath = githubWorkspace?.split('/').slice(-2).join('/') ?? ''
const initCommands = [
'mkdir -p /mnt/externals',
'mkdir -p /mnt/work',
'mkdir -p /mnt/github',
'mv /home/runner/externals/* /mnt/externals/'
]
if (workingDirPath) {
initCommands.push(`mkdir -p /mnt/work/${workingDirPath}`)
}
appPod.spec.initContainers = [ appPod.spec.initContainers = [
{ {
name: 'fs-init', name: 'fs-init',
image: image:
process.env.ACTIONS_RUNNER_IMAGE || process.env.ACTIONS_RUNNER_IMAGE ||
'ghcr.io/actions/actions-runner:latest', 'ghcr.io/actions/actions-runner:latest',
command: ['sh', '-c', initCommands.join(' && ')], command: ['sh', '-c', 'mv /home/runner/externals/* /mnt/externals'],
securityContext: { securityContext: {
runAsGroup: 1001, runAsGroup: 1001,
runAsUser: 1001 runAsUser: 1001
@@ -128,14 +106,6 @@ export async function createJobPod(
{ {
name: EXTERNALS_VOLUME_NAME, name: EXTERNALS_VOLUME_NAME,
mountPath: '/mnt/externals' mountPath: '/mnt/externals'
},
{
name: WORK_VOLUME,
mountPath: '/mnt/work'
},
{
name: GITHUB_VOLUME_NAME,
mountPath: '/mnt/github'
} }
] ]
} }
@@ -151,10 +121,6 @@ export async function createJobPod(
{ {
name: GITHUB_VOLUME_NAME, name: GITHUB_VOLUME_NAME,
emptyDir: {} emptyDir: {}
},
{
name: WORK_VOLUME,
emptyDir: {}
} }
] ]
@@ -207,17 +173,9 @@ export async function createContainerStepPod(
appPod.spec.restartPolicy = 'Never' appPod.spec.restartPolicy = 'Never'
appPod.spec.volumes = [ appPod.spec.volumes = [
{
name: EXTERNALS_VOLUME_NAME,
emptyDir: {}
},
{ {
name: GITHUB_VOLUME_NAME, name: GITHUB_VOLUME_NAME,
emptyDir: {} emptyDir: {}
},
{
name: WORK_VOLUME,
emptyDir: {}
} }
] ]
@@ -389,15 +347,7 @@ export async function execCpToPod(
while (true) { while (true) {
try { try {
const exec = new k8s.Exec(kc) const exec = new k8s.Exec(kc)
// Use tar to extract with --no-same-owner to avoid ownership issues. const command = ['tar', 'xf', '-', '-C', containerPath]
// Then use find to fix permissions. The -m flag helps but we also need to fix permissions after.
const command = [
'sh',
'-c',
`tar xf - --no-same-owner -C ${shlex.quote(containerPath)} 2>/dev/null; ` +
`find ${shlex.quote(containerPath)} -type f -exec chmod u+rw {} \\; 2>/dev/null; ` +
`find ${shlex.quote(containerPath)} -type d -exec chmod u+rwx {} \\; 2>/dev/null`
]
const readStream = tar.pack(runnerPath) const readStream = tar.pack(runnerPath)
const errStream = new WritableStreamBuffer() const errStream = new WritableStreamBuffer()
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
@@ -415,7 +365,7 @@ export async function execCpToPod(
if (errStream.size()) { if (errStream.size()) {
reject( reject(
new Error( new Error(
`Error from execCpToPod - status: ${status.status}, details: \n ${errStream.getContentsAsString()}` `Error from cpFromPod - details: \n ${errStream.getContentsAsString()}`
) )
) )
} }
@@ -437,16 +387,16 @@ export async function execCpToPod(
} }
} }
let attempts = 15
const delay = 1000
for (let i = 0; i < attempts; i++) {
try {
const want = await localCalculateOutputHashSorted([ const want = await localCalculateOutputHashSorted([
'sh', 'sh',
'-c', '-c',
listDirAllCommand(runnerPath) listDirAllCommand(runnerPath)
]) ])
let attempts = 15
const delay = 1000
for (let i = 0; i < attempts; i++) {
try {
const got = await execCalculateOutputHashSorted( const got = await execCalculateOutputHashSorted(
podName, podName,
JOB_CONTAINER_NAME, JOB_CONTAINER_NAME,
@@ -478,6 +428,11 @@ export async function execCpFromPod(
core.debug( core.debug(
`Copying from pod ${podName} ${containerPath} to ${targetRunnerPath}` `Copying from pod ${podName} ${containerPath} to ${targetRunnerPath}`
) )
const want = await execCalculateOutputHashSorted(
podName,
JOB_CONTAINER_NAME,
['sh', '-c', listDirAllCommand(containerPath)]
)
let attempt = 0 let attempt = 0
while (true) { while (true) {
@@ -538,12 +493,6 @@ export async function execCpFromPod(
const delay = 1000 const delay = 1000
for (let i = 0; i < attempts; i++) { for (let i = 0; i < attempts; i++) {
try { try {
const want = await execCalculateOutputHashSorted(
podName,
JOB_CONTAINER_NAME,
['sh', '-c', listDirAllCommand(containerPath)]
)
const got = await localCalculateOutputHashSorted([ const got = await localCalculateOutputHashSorted([
'sh', 'sh',
'-c', '-c',
@@ -831,7 +780,7 @@ export async function isPodContainerAlpine(
[ [
'sh', 'sh',
'-c', '-c',
`[ $(cat /etc/*release* | grep -i -e "^ID=*alpine*" -c) != 0 ] || exit 1` `'[ $(cat /etc/*release* | grep -i -e "^ID=*alpine*" -c) != 0 ] || exit 1'`
], ],
podName, podName,
containerName containerName
+2 -12
View File
@@ -15,17 +15,12 @@ export const ENV_USE_KUBE_SCHEDULER = 'ACTIONS_RUNNER_USE_KUBE_SCHEDULER'
export const EXTERNALS_VOLUME_NAME = 'externals' export const EXTERNALS_VOLUME_NAME = 'externals'
export const GITHUB_VOLUME_NAME = 'github' export const GITHUB_VOLUME_NAME = 'github'
export const WORK_VOLUME = 'work'
export const CONTAINER_VOLUMES: k8s.V1VolumeMount[] = [ export const CONTAINER_VOLUMES: k8s.V1VolumeMount[] = [
{ {
name: EXTERNALS_VOLUME_NAME, name: EXTERNALS_VOLUME_NAME,
mountPath: '/__e' mountPath: '/__e'
}, },
{
name: WORK_VOLUME,
mountPath: '/__w'
},
{ {
name: GITHUB_VOLUME_NAME, name: GITHUB_VOLUME_NAME,
mountPath: '/github' mountPath: '/github'
@@ -107,7 +102,7 @@ export function writeContainerStepScript(
rm "$0" # remove script after running rm "$0" # remove script after running
mv /__w/_temp/_github_home /github/home && \ mv /__w/_temp/_github_home /github/home && \
mv /__w/_temp/_github_workflow /github/workflow && \ mv /__w/_temp/_github_workflow /github/workflow && \
mv /__w/_temp/_runner_file_commands /github/file_commands || true && \ mv /__w/_temp/_runner_file_commands /github/file_commands && \
mv /__w/${parts.join('/')}/ /github/workspace && \ mv /__w/${parts.join('/')}/ /github/workspace && \
cd /github/workspace && \ cd /github/workspace && \
exec ${environmentPrefix} ${entryPoint} ${ exec ${environmentPrefix} ${entryPoint} ${
@@ -288,11 +283,6 @@ function mergeLists<T>(base?: T[], from?: T[]): T[] {
} }
export function fixArgs(args: string[]): string[] { export function fixArgs(args: string[]): string[] {
// Preserve shell command strings passed via `sh -c` without re-tokenizing.
// Retokenizing would split the script into multiple args, breaking `sh -c`.
if (args.length >= 2 && args[0] === 'sh' && args[1] === '-c') {
return args
}
return shlex.split(args.join(' ')) return shlex.split(args.join(' '))
} }
@@ -301,5 +291,5 @@ export async function sleep(ms: number): Promise<void> {
} }
export function listDirAllCommand(dir: string): string { export function listDirAllCommand(dir: string): string {
return `cd ${shlex.quote(dir)} && find . -type f -not -path '*/_runner_hook_responses*' -exec stat -c '%s %n' {} \\;` return `cd ${shlex.quote(dir)} && find . -not -path '*/_runner_hook_responses*' -exec stat -c '%b %n' {} \\;`
} }
-1
View File
@@ -26,7 +26,6 @@ describe('e2e', () => {
afterEach(async () => { afterEach(async () => {
await testHelper.cleanup() await testHelper.cleanup()
}) })
it('should prepare job, run script step, run container step then cleanup without errors', async () => { it('should prepare job, run script step, run container step then cleanup without errors', async () => {
await expect( await expect(
prepareJob(prepareJobData.args, prepareJobOutputFilePath) prepareJob(prepareJobData.args, prepareJobOutputFilePath)
+6 -18
View File
@@ -45,7 +45,7 @@ describe('Prepare job', () => {
process.env.GITHUB_WORKSPACE as string, process.env.GITHUB_WORKSPACE as string,
'myvolume' 'myvolume'
) )
fs.mkdirSync(userVolumeMount, { recursive: true }) fs.mkdirSync(userVolumeMount)
fs.writeFileSync(path.join(userVolumeMount, 'file.txt'), 'hello') fs.writeFileSync(path.join(userVolumeMount, 'file.txt'), 'hello')
prepareJobData.args.container.userMountVolumes = [ prepareJobData.args.container.userMountVolumes = [
{ {
@@ -63,7 +63,11 @@ describe('Prepare job', () => {
) )
await execPodStep( await execPodStep(
['sh', '-c', '[ "$(cat /__w/myvolume/file.txt)" = "hello" ] || exit 5'], [
'sh',
'-c',
'\'[ "$(cat /__w/myvolume/file.txt)" = "hello" ] || exit 5\''
],
content!.state!.jobPod, content!.state!.jobPod,
JOB_CONTAINER_NAME JOB_CONTAINER_NAME
).then(output => { ).then(output => {
@@ -227,20 +231,4 @@ describe('Prepare job', () => {
expect(() => content.context.services[0].image).not.toThrow() expect(() => content.context.services[0].image).not.toThrow()
} }
) )
it('should prepare job with container with non-root user', async () => {
prepareJobData.args!.container!.image =
'ghcr.io/actions/actions-runner:latest' // known to use user 1001
await expect(
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
).resolves.not.toThrow()
const content = JSON.parse(
fs.readFileSync(prepareJobOutputFilePath).toString()
)
expect(content.state.jobPod).toBeTruthy()
expect(content.context.container.image).toBe(
'ghcr.io/actions/actions-runner:latest'
)
})
}) })
+6 -12
View File
@@ -1,21 +1,15 @@
<!-- ## Features --> ## Features
- k8s: remove dependency on the runner's volume [#244]
## Bugs ## Bugs
- Change command to remove sudo to fix fs-init initial container [#263] - docker: fix readOnly volumes in createContainer [#236]
- Sort 'find' output before hashing for consistency [#267]
- feat: check if required binaries are present [#272]
- Allow non-root container [#264]
- Improve validation checks after copying [#285]
- Fix workingDir permissions issue by creating it within init container [#283]
- Fix event.json not being copied to /github/workflow in kubernetes-novolume mode [#287]
- Reduce the amount of data copied to the workflow pod [#293]
- Overwrite runner file commands [#298]
## Misc ## Misc
- Dependency updates [#276] [#277] [#278] [#279] [#304] - bump all dependencies [#234] [#240] [#239] [#238]
- Group dependabot updates [#289] - bump actions [#254]
## SHA-256 Checksums ## SHA-256 Checksums