Compare commits

..

16 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
5f503f27d3 Update package-lock.json after dependency updates
Co-authored-by: nikola-jokic <97525037+nikola-jokic@users.noreply.github.com>
2025-12-11 14:27:55 +00:00
copilot-swe-agent[bot]
287a0458a1 Initial plan 2025-12-11 14:19:39 +00:00
dependabot[bot]
b8af7ebe0e Bump the all-dependencies group in /packages/docker with 6 updates
Bumps the all-dependencies group in /packages/docker with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@actions/exec](https://github.com/actions/toolkit/tree/HEAD/packages/exec) | `1.1.1` | `2.0.0` |
| [uuid](https://github.com/uuidjs/uuid) | `11.1.0` | `13.0.0` |
| [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) | `7.28.4` | `7.28.5` |
| [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) | `7.28.3` | `7.28.5` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.45.0` | `8.49.0` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.4` | `29.4.6` |


Updates `@actions/exec` from 1.1.1 to 2.0.0
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/exec/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/exec)

Updates `uuid` from 11.1.0 to 13.0.0
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v11.1.0...v13.0.0)

Updates `@babel/core` from 7.28.4 to 7.28.5
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.5/packages/babel-core)

Updates `@babel/preset-env` from 7.28.3 to 7.28.5
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.5/packages/babel-preset-env)

Updates `@typescript-eslint/parser` from 8.45.0 to 8.49.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.49.0/packages/parser)

Updates `ts-jest` from 29.4.4 to 29.4.6
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.4...v29.4.6)

---
updated-dependencies:
- dependency-name: "@actions/exec"
  dependency-version: 2.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-dependencies
- dependency-name: uuid
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-dependencies
- dependency-name: "@babel/core"
  dependency-version: 7.28.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-dependencies
- dependency-name: "@babel/preset-env"
  dependency-version: 7.28.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.49.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: all-dependencies
- dependency-name: ts-jest
  dependency-version: 29.4.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-10 20:51:03 +00:00
Nikola Jokic
f8e1cae677 Reduce the amount of data copied to the workflow pod (#293)
* run script copies back only runner file commands

* wip

* fix

* fmt

* user volume mount

* try doing only file commands

* typo

* remove _temp_pre

* Update packages/k8s/src/hooks/run-script-step.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/k8s/src/hooks/run-script-step.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* better escape

* no useless escapes

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-10 21:49:35 +01:00
Nikola Jokic
996cc75daf Group dependabot updates (#289) 2025-12-10 21:49:27 +01:00
dependabot[bot]
adf5e34937 Bump js-yaml from 4.1.0 to 4.1.1 (#276)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-27 16:34:47 +01:00
dependabot[bot]
4041f8648c Bump js-yaml from 4.1.0 to 4.1.1 in /packages/hooklib (#277)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-27 16:34:17 +01:00
dependabot[bot]
1f60eaf940 Bump glob from 10.4.5 to 10.5.0 in /packages/k8s (#278)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.4.5 to 10.5.0.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-27 16:34:01 +01:00
dependabot[bot]
c3d8e2ab20 Bump glob from 10.4.5 to 10.5.0 in /packages/docker (#279)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.4.5 to 10.5.0.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-27 16:33:39 +01:00
zarko-a
3f829eef9e Fix event.json not being copied to /github/workflow in kubernetes-novolume mode (#287)
In run-script-step, the _temp directory was being copied to the workflow pod,
but the _github_home and _github_workflow directories were not being moved
from their temporary location to the /github directory structure where they
are expected by GitHub Actions.

This caused event.json to be missing at /github/workflow/event.json, breaking
actions that depend on GITHUB_EVENT_PATH.

The fix adds a setup step that copies _github_home and _github_workflow from
/__w/_temp/ to /github/ after copying the temp directory to the pod, matching
the behavior of run-container-step and prepareJobScript.

Uses cp -r instead of symlinks to avoid symlink validation errors when copying
files back from the pod to the runner.
2025-11-26 11:47:19 +01:00
zarko-a
011ffb284e Fix workingDir permissions issue by creating it within init container (#283)
* Fix workingDir permissions issue by creating it within init container

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* rework init commands

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-26 11:46:47 +01:00
Vincent Van Ouytsel
0951cc73e4 Improve validation checks after copying (#285)
* fix: calculate hash again after failure

The hash from the source is calculated only once. The source hash is
checked with the destination hash, but if the destination hash does not
match, the destination match is calculated again.

The problem is that if the source hash is incorrect, the check will keep
failing because the source hash is never re-calculated.

Now, in the event that the hashes do not match, the hash of the source
and the destination are calculated again.

* fix: use size instead of block size

Previously the %b parameter was used with stat. This displays the block
size of the file. We noticed that in some cases the block size of the
source and the destination file could be slightly different. Since the
source and target run in different containers, they can have different
block sizes defined. If the block size did not match, the hash would also not match, even if
the file content would be exactly the same.

With this change, the block size is no longer used. Instead the actual
size in bytes of the file is listed.
2025-11-24 16:14:02 +01:00
Nikola Jokic
15e808935c Allow non-root container (#264)
* Allow non-root container

* format

* add lint:fix and fix lint errors

* fix tests and volume mounts
2025-11-21 14:44:29 +01:00
vvanouytsel-trendminer
ad9cb43c31 feat: check if required binaries are present (#272)
* feat: check if required binaries are present

Previously the necessary binaries were copied over using the runner
container. This lead to issues in case your main container was using the
musl libc implementation.

Instead of copying over any binaries, the initContainer now checks if
the required binaries are present in the main container.

* feat: get rid of the init container

* fix: add _runner_file_commands

* fix: do not fail if _runner_file_commands does not exist

It seems that for container actions this directory does not exist.
2025-11-10 15:01:40 +01:00
zarko-a
2934de33f8 Sort 'find' output before hashing for consistency (#267)
* Sort 'find' output before hashing for consistency across different platforms

* fix style issues
2025-11-04 12:06:36 +01:00
Jiang Long
ea25fd1b3e Change command to remove sudo to fix fs-init inital container (#263)
* Change command to copy externals instead of move

* fix: using only mv, remove sudo
2025-10-21 15:47:08 +02:00
14 changed files with 553 additions and 492 deletions

28
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
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:
- "*"

6
package-lock.json generated
View File

@@ -3165,9 +3165,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"

View File

@@ -12,6 +12,7 @@
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.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"
},
"repository": {

File diff suppressed because it is too large Load Diff

View File

@@ -14,20 +14,20 @@
"license": "MIT",
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1",
"@actions/exec": "^2.0.0",
"hooklib": "file:../hooklib",
"shlex": "^3.0.0",
"uuid": "^11.1.0"
"uuid": "^13.0.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.4",
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.14",
"@typescript-eslint/parser": "^8.37.0",
"@typescript-eslint/parser": "^8.49.0",
"@vercel/ncc": "^0.38.3",
"jest": "^30.0.4",
"ts-jest": "^29.4.0",
"ts-jest": "^29.4.6",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3"

View File

@@ -2803,9 +2803,9 @@
"license": "ISC"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -41,3 +41,4 @@ rules:
- 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
- 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`.

View File

@@ -4071,9 +4071,9 @@
}
},
"node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dev": true,
"license": "ISC",
"dependencies": {

View File

@@ -104,7 +104,7 @@ export async function runContainerStep(
try {
core.debug(`Executing container step script in pod ${podName}`)
return await execPodStep(
['/__e/sh', '-e', containerPath],
['sh', '-e', containerPath],
pod.metadata.name,
JOB_CONTAINER_NAME
)
@@ -133,7 +133,7 @@ function createContainerSpec(
podContainer.name = JOB_CONTAINER_NAME
podContainer.image = container.image
podContainer.workingDir = '/__w'
podContainer.command = ['/__e/tail']
podContainer.command = ['tail']
podContainer.args = DEFAULT_CONTAINER_ENTRY_POINT_ARGS
podContainer.volumeMounts = CONTAINER_VOLUMES

View File

@@ -6,6 +6,7 @@ import { execCpFromPod, execCpToPod, execPodStep } from '../k8s'
import { writeRunScript, sleep, listDirAllCommand } from '../k8s/utils'
import { JOB_CONTAINER_NAME } from './constants'
import { dirname } from 'path'
import * as shlex from 'shlex'
export async function runScriptStep(
args: RunScriptStepArgs,
@@ -22,9 +23,52 @@ export async function runScriptStep(
)
const workdir = dirname(process.env.RUNNER_WORKSPACE as string)
const containerTemp = '/__w/_temp'
const runnerTemp = `${workdir}/_temp`
await execCpToPod(state.jobPod, runnerTemp, containerTemp)
const containerTemp = '/__w/_temp'
const containerTempSrc = '/__w/_temp_pre'
// Ensure base and staging dirs exist before copying
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
`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
args.entryPoint = 'sh'
@@ -51,7 +95,11 @@ export async function runScriptStep(
core.debug(
`Copying from job pod '${state.jobPod}' ${containerTemp} to ${runnerTemp}`
)
await execCpFromPod(state.jobPod, containerTemp, workdir)
await execCpFromPod(
state.jobPod,
`${containerTemp}/_runner_file_commands`,
`${workdir}/_temp`
)
} catch (error) {
core.warning('Failed to copy _temp from pod')
}

View File

@@ -20,8 +20,10 @@ import {
listDirAllCommand,
sleep,
EXTERNALS_VOLUME_NAME,
GITHUB_VOLUME_NAME
GITHUB_VOLUME_NAME,
WORK_VOLUME
} from './utils'
import * as shlex from 'shlex'
const kc = new k8s.KubeConfig()
@@ -91,13 +93,33 @@ export async function createJobPod(
appPod.spec = new k8s.V1PodSpec()
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 = [
{
name: 'fs-init',
image:
process.env.ACTIONS_RUNNER_IMAGE ||
'ghcr.io/actions/actions-runner:latest',
command: ['sh', '-c', 'sudo mv /home/runner/externals/* /mnt/externals'],
command: ['sh', '-c', initCommands.join(' && ')],
securityContext: {
runAsGroup: 1001,
runAsUser: 1001
@@ -106,6 +128,14 @@ export async function createJobPod(
{
name: EXTERNALS_VOLUME_NAME,
mountPath: '/mnt/externals'
},
{
name: WORK_VOLUME,
mountPath: '/mnt/work'
},
{
name: GITHUB_VOLUME_NAME,
mountPath: '/mnt/github'
}
]
}
@@ -121,6 +151,10 @@ export async function createJobPod(
{
name: GITHUB_VOLUME_NAME,
emptyDir: {}
},
{
name: WORK_VOLUME,
emptyDir: {}
}
]
@@ -169,33 +203,6 @@ export async function createContainerStepPod(
appPod.spec = new k8s.V1PodSpec()
appPod.spec.containers = [container]
appPod.spec.initContainers = [
{
name: 'fs-init',
image:
process.env.ACTIONS_RUNNER_IMAGE ||
'ghcr.io/actions/actions-runner:latest',
command: [
'bash',
'-c',
`sudo cp $(which sh) /mnt/externals/sh \
&& sudo cp $(which tail) /mnt/externals/tail \
&& sudo cp $(which env) /mnt/externals/env \
&& sudo chmod -R 777 /mnt/externals`
],
securityContext: {
runAsGroup: 1001,
runAsUser: 1001,
privileged: true
},
volumeMounts: [
{
name: EXTERNALS_VOLUME_NAME,
mountPath: '/mnt/externals'
}
]
}
]
appPod.spec.restartPolicy = 'Never'
@@ -207,6 +214,10 @@ export async function createContainerStepPod(
{
name: GITHUB_VOLUME_NAME,
emptyDir: {}
},
{
name: WORK_VOLUME,
emptyDir: {}
}
]
@@ -271,19 +282,18 @@ export async function execPodStep(
})
}
export async function execCalculateOutputHash(
export async function execCalculateOutputHashSorted(
podName: string,
containerName: string,
command: string[]
): Promise<string> {
const exec = new k8s.Exec(kc)
// Create a writable stream that updates a SHA-256 hash with stdout data
const hash = createHash('sha256')
const hashWriter = new stream.Writable({
let output = ''
const outputWriter = new stream.Writable({
write(chunk, _enc, cb) {
try {
hash.update(chunk.toString('utf8') as Buffer)
output += chunk.toString('utf8')
cb()
} catch (e) {
cb(e as Error)
@@ -298,7 +308,7 @@ export async function execCalculateOutputHash(
podName,
containerName,
command,
hashWriter, // capture stdout for hashing
outputWriter, // capture stdout
process.stderr,
null,
false /* tty */,
@@ -320,27 +330,46 @@ export async function execCalculateOutputHash(
.catch(e => reject(e))
})
// finalize hash and return digest
hashWriter.end()
outputWriter.end()
// Sort lines for consistent ordering across platforms
const sortedOutput =
output
.split('\n')
.filter(line => line.length > 0)
.sort()
.join('\n') + '\n'
const hash = createHash('sha256')
hash.update(sortedOutput)
return hash.digest('hex')
}
export async function localCalculateOutputHash(
export async function localCalculateOutputHashSorted(
commands: string[]
): Promise<string> {
return await new Promise<string>((resolve, reject) => {
const hash = createHash('sha256')
const child = spawn(commands[0], commands.slice(1), {
stdio: ['ignore', 'pipe', 'ignore']
})
let output = ''
child.stdout.on('data', chunk => {
hash.update(chunk)
output += chunk.toString('utf8')
})
child.on('error', reject)
child.on('close', (code: number) => {
if (code === 0) {
// Sort lines for consistent ordering across distributions/platforms
const sortedOutput =
output
.split('\n')
.filter(line => line.length > 0)
.sort()
.join('\n') + '\n'
const hash = createHash('sha256')
hash.update(sortedOutput)
resolve(hash.digest('hex'))
} else {
reject(new Error(`child process exited with code ${code}`))
@@ -360,7 +389,15 @@ export async function execCpToPod(
while (true) {
try {
const exec = new k8s.Exec(kc)
const command = ['tar', 'xf', '-', '-C', containerPath]
// Use tar to extract with --no-same-owner to avoid ownership issues.
// 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 errStream = new WritableStreamBuffer()
await new Promise((resolve, reject) => {
@@ -378,7 +415,7 @@ export async function execCpToPod(
if (errStream.size()) {
reject(
new Error(
`Error from cpFromPod - details: \n ${errStream.getContentsAsString()}`
`Error from execCpToPod - status: ${status.status}, details: \n ${errStream.getContentsAsString()}`
)
)
}
@@ -400,22 +437,22 @@ export async function execCpToPod(
}
}
const want = await localCalculateOutputHash([
'sh',
'-c',
listDirAllCommand(runnerPath)
])
let attempts = 15
const delay = 1000
for (let i = 0; i < attempts; i++) {
try {
const got = await execCalculateOutputHash(podName, JOB_CONTAINER_NAME, [
const want = await localCalculateOutputHashSorted([
'sh',
'-c',
listDirAllCommand(containerPath)
listDirAllCommand(runnerPath)
])
const got = await execCalculateOutputHashSorted(
podName,
JOB_CONTAINER_NAME,
['sh', '-c', listDirAllCommand(containerPath)]
)
if (got !== want) {
core.debug(
`The hash of the directory does not match the expected value; want='${want}' got='${got}'`
@@ -441,11 +478,6 @@ export async function execCpFromPod(
core.debug(
`Copying from pod ${podName} ${containerPath} to ${targetRunnerPath}`
)
const want = await execCalculateOutputHash(podName, JOB_CONTAINER_NAME, [
'sh',
'-c',
listDirAllCommand(containerPath)
])
let attempt = 0
while (true) {
@@ -506,7 +538,13 @@ export async function execCpFromPod(
const delay = 1000
for (let i = 0; i < attempts; i++) {
try {
const got = await localCalculateOutputHash([
const want = await execCalculateOutputHashSorted(
podName,
JOB_CONTAINER_NAME,
['sh', '-c', listDirAllCommand(containerPath)]
)
const got = await localCalculateOutputHashSorted([
'sh',
'-c',
listDirAllCommand(targetRunnerPath)
@@ -793,7 +831,7 @@ export async function isPodContainerAlpine(
[
'sh',
'-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,
containerName

View File

@@ -15,12 +15,17 @@ export const ENV_USE_KUBE_SCHEDULER = 'ACTIONS_RUNNER_USE_KUBE_SCHEDULER'
export const EXTERNALS_VOLUME_NAME = 'externals'
export const GITHUB_VOLUME_NAME = 'github'
export const WORK_VOLUME = 'work'
export const CONTAINER_VOLUMES: k8s.V1VolumeMount[] = [
{
name: EXTERNALS_VOLUME_NAME,
mountPath: '/__e'
},
{
name: WORK_VOLUME,
mountPath: '/__w'
},
{
name: GITHUB_VOLUME_NAME,
mountPath: '/github'
@@ -102,7 +107,7 @@ export function writeContainerStepScript(
rm "$0" # remove script after running
mv /__w/_temp/_github_home /github/home && \
mv /__w/_temp/_github_workflow /github/workflow && \
mv /__w/_temp/_runner_file_commands /github/file_commands && \
mv /__w/_temp/_runner_file_commands /github/file_commands || true && \
mv /__w/${parts.join('/')}/ /github/workspace && \
cd /github/workspace && \
exec ${environmentPrefix} ${entryPoint} ${
@@ -283,6 +288,11 @@ function mergeLists<T>(base?: T[], from?: T[]): T[] {
}
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(' '))
}
@@ -291,5 +301,5 @@ export async function sleep(ms: number): Promise<void> {
}
export function listDirAllCommand(dir: string): string {
return `cd ${shlex.quote(dir)} && find . -not -path '*/_runner_hook_responses*' -exec stat -c '%b %n' {} \\;`
return `cd ${shlex.quote(dir)} && find . -not -path '*/_runner_hook_responses*' -exec stat -c '%s %n' {} \\;`
}

View File

@@ -26,6 +26,7 @@ describe('e2e', () => {
afterEach(async () => {
await testHelper.cleanup()
})
it('should prepare job, run script step, run container step then cleanup without errors', async () => {
await expect(
prepareJob(prepareJobData.args, prepareJobOutputFilePath)

View File

@@ -45,7 +45,7 @@ describe('Prepare job', () => {
process.env.GITHUB_WORKSPACE as string,
'myvolume'
)
fs.mkdirSync(userVolumeMount)
fs.mkdirSync(userVolumeMount, { recursive: true })
fs.writeFileSync(path.join(userVolumeMount, 'file.txt'), 'hello')
prepareJobData.args.container.userMountVolumes = [
{
@@ -63,11 +63,7 @@ describe('Prepare job', () => {
)
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,
JOB_CONTAINER_NAME
).then(output => {
@@ -231,4 +227,20 @@ describe('Prepare job', () => {
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'
)
})
})