Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37747d02a9 |
@@ -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:
|
|
||||||
- "*"
|
|
||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Generated
+5
-13
@@ -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
@@ -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": {
|
||||||
|
|||||||
Generated
+646
-552
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
|||||||
Generated
+430
-239
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
|
||||||
|
|||||||
Generated
+460
-453
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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' {} \\;`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user