Compare commits

..

33 Commits

Author SHA1 Message Date
Nikola Jokic
70841b6972 bump docker dep as well 2025-04-16 14:43:46 +02:00
Nikola Jokic
1c2ae5d20a rework 2025-04-16 14:25:04 +02:00
Nikola Jokic
928f63d88a exclude eslint.config.js 2025-04-16 13:42:50 +02:00
Nikola Jokic
3bda7ef21e wip 2025-04-16 10:26:22 +02:00
Nikola Jokic
3b0e87c9a7 fmt 2025-04-15 14:53:53 +02:00
Nikola Jokic
a7349e7d70 fix errors and bump client node to stable version 2025-04-15 14:53:21 +02:00
Nikola Jokic
ff583c8917 bump all dependencies 2025-04-15 14:29:29 +02:00
dependabot[bot]
e47f9b8af4 Bump jsonpath-plus from 10.1.0 to 10.3.0 in /packages/k8s (#213)
Bumps [jsonpath-plus](https://github.com/s3u/JSONPath) from 10.1.0 to 10.3.0.
- [Release notes](https://github.com/s3u/JSONPath/releases)
- [Changelog](https://github.com/JSONPath-Plus/JSONPath/blob/main/CHANGES.md)
- [Commits](https://github.com/s3u/JSONPath/compare/v10.1.0...v10.3.0)

---
updated-dependencies:
- dependency-name: jsonpath-plus
  dependency-version: 10.3.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-04-15 14:25:32 +02:00
dependabot[bot]
54e14cb7f3 Bump braces from 3.0.2 to 3.0.3 in /packages/hooklib (#194)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 14:37:19 +02:00
Grant Buskey
ef2229fc0b feat(k8s): add /github/home to containerAction mounts and surface createSecretForEnvs errors #181 (#198)
* feat: add /github/home to containerAction mounts #181

* fix: add debug logging for failed secret creations #181
2025-04-14 14:12:51 +02:00
Andre Klärner
88dc98f8ef k8s: start logging from the beginning (#184) 2025-04-14 14:03:05 +02:00
Joan Miquel Luque
b388518d40 feat(k8s): Use pod affinity when KubeScheduler is enabled #201 (#212)
Signed-off-by: Joan Miquel Luque Oliver <joan.luque@dynatrace.com>
2025-04-14 13:36:21 +02:00
dependabot[bot]
7afb8f9323 Bump cross-spawn from 7.0.3 to 7.0.6 in /packages/k8s (#196)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 16:51:12 +01:00
Robin Bobbitt
d4c5425b22 support alternative network modes (#209) 2025-03-24 16:33:43 +01:00
dependabot[bot]
120636d3d7 Bump ws from 7.5.8 to 7.5.10 in /packages/k8s (#192)
Bumps [ws](https://github.com/websockets/ws) from 7.5.8 to 7.5.10.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.8...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 11:41:28 -05:00
Josh Gross
5e805a0546 Remove dependency on deprecated release actions (#193)
* Update to the latest available actions

* Remove dependency on deprecated release actions

* Add release workflow fixes from testing
2024-11-06 11:41:09 -05:00
Josh Gross
27bae0b2b7 Update to the latest available actions (#191) 2024-11-06 11:18:49 -05:00
dependabot[bot]
8eed1ad1b6 Bump jsonpath-plus and @kubernetes/client-node in /packages/k8s (#187)
Bumps [jsonpath-plus](https://github.com/s3u/JSONPath) to 10.1.0 and updates ancestor dependency [@kubernetes/client-node](https://github.com/kubernetes-client/javascript). These dependencies need to be updated together.


Updates `jsonpath-plus` from 9.0.0 to 10.1.0
- [Release notes](https://github.com/s3u/JSONPath/releases)
- [Changelog](https://github.com/JSONPath-Plus/JSONPath/blob/main/CHANGES.md)
- [Commits](https://github.com/s3u/JSONPath/compare/v9.0.0...v10.1.0)

Updates `@kubernetes/client-node` from 0.22.0 to 0.22.2
- [Release notes](https://github.com/kubernetes-client/javascript/releases)
- [Changelog](https://github.com/kubernetes-client/javascript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kubernetes-client/javascript/compare/0.22.0...0.22.2)

---
updated-dependencies:
- dependency-name: jsonpath-plus
  dependency-type: indirect
- dependency-name: "@kubernetes/client-node"
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 10:34:45 -05:00
dependabot[bot]
7b404841b2 Bump braces from 3.0.2 to 3.0.3 in /packages/k8s (#188)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 10:34:16 -05:00
Josh Gross
977d53963d Remove @actions/runner-akvelon from CODEOWNERS (#190) 2024-11-05 18:13:42 -05:00
Josh Gross
77b40ac6df Prepare 0.6.2 Release (#189) 2024-11-05 14:36:03 -05:00
Oliver Radwell
ee10d95fd4 Bump kubernetes/client-node from 0.18.1 to 0.22.0 (#182) 2024-11-05 13:22:04 -05:00
Nikola Jokic
73655d4639 Release 0.6.1 (#172) 2024-06-19 13:42:23 +02:00
Nikola Jokic
ca4ea17d58 Skip writing extension containers in output context file (#154) 2024-06-19 11:49:43 +02:00
dependabot[bot]
ed70e2f8e0 Bump tar from 6.1.11 to 6.2.1 in /packages/k8s (#156)
Bumps [tar](https://github.com/isaacs/node-tar) from 6.1.11 to 6.2.1.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.1.11...v6.2.1)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-18 17:35:16 +02:00
dependabot[bot]
aeabaf144a Bump braces from 3.0.2 to 3.0.3 in /packages/docker (#171)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-18 17:34:53 +02:00
dependabot[bot]
8388a36f44 Bump ws from 7.5.7 to 7.5.10 in /packages/docker (#170)
Bumps [ws](https://github.com/websockets/ws) from 7.5.7 to 7.5.10.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.7...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-18 16:19:49 +02:00
Nikola Jokic
9705deeb08 Release 0.6.0 (#148) 2024-03-14 14:36:02 +01:00
Katarzyna
99efdeca99 Mount /github/workflow to docker action pods (#137)
* Mount /github/workflow to docker action pods, the same way as for container job pods

* Adjust tests
2024-03-14 12:36:27 +01:00
dependabot[bot]
bb09a79b22 Bump jose from 4.11.4 to 4.15.5 in /packages/k8s (#142)
Bumps [jose](https://github.com/panva/jose) from 4.11.4 to 4.15.5.
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/v4.15.5/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v4.11.4...v4.15.5)

---
updated-dependencies:
- dependency-name: jose
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-14 12:33:36 +01:00
Katarzyna
746e644039 ADR-0134 superseding ADR-0096 (#136)
Related to https://github.com/actions/runner-container-hooks/issues/132
2024-03-14 12:33:06 +01:00
Katarzyna
7223e1dbb2 Use ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE to extend service containers (#134)
https://github.com/actions/runner-container-hooks/issues/132

Co-authored-by: Katarzyna Radkowska <katarzyna.radkowska@sabre.com>
2024-02-20 16:19:29 +01:00
Katarzyna
af27abe1f7 Read logs also from failed child (container job/container action) pod (#135)
Co-authored-by: Katarzyna Radkowska <katarzyna.radkowska@sabre.com>
2024-02-20 12:01:11 +01:00
35 changed files with 9981 additions and 18371 deletions

View File

@@ -1,4 +0,0 @@
dist/
lib/
node_modules/
**/tests/**

View File

@@ -1,56 +0,0 @@
{
"plugins": ["@typescript-eslint"],
"extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"eslint-comments/no-use": "off",
"import/no-namespace": "off",
"no-constant-condition": "off",
"no-unused-vars": "off",
"i18n-text/no-en": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"camelcase": "off",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error",
"no-shadow": "off",
"@typescript-eslint/no-shadow": ["error"]
},
"env": {
"node": true,
"es6": true
}
}

View File

@@ -10,7 +10,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: sed -i "s|{{PATHTOREPO}}|$(pwd)|" packages/k8s/tests/test-kind.yaml
name: Setup kind cluster yaml config
- uses: helm/kind-action@v1.2.0

View File

@@ -42,7 +42,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -69,4 +69,4 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@@ -1,76 +1,70 @@
name: CD - Release new version
on:
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm install
name: Install dependencies
- run: npm run bootstrap
name: Bootstrap the packages
- run: npm run build-all
name: Build packages
- uses: actions/github-script@v6
- uses: actions/checkout@v4
- name: Install dependencies
run: npm install
- name: Bootstrap the packages
run: npm run bootstrap
- name: Build packages
run: npm run build-all
- uses: actions/github-script@v7
id: releaseVersion
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
const fs = require('fs');
const hookVersion = require('./package.json').version
core.setOutput('version', hookVersion);
return require('./package.json').version
- name: Zip up releases
run: |
zip -r -j actions-runner-hooks-docker-${{ steps.releaseVersion.outputs.version }}.zip packages/docker/dist
zip -r -j actions-runner-hooks-k8s-${{ steps.releaseVersion.outputs.version }}.zip packages/k8s/dist
zip -r -j actions-runner-hooks-docker-${{ steps.releaseVersion.outputs.result }}.zip packages/docker/dist
zip -r -j actions-runner-hooks-k8s-${{ steps.releaseVersion.outputs.result }}.zip packages/k8s/dist
- name: Calculate SHA
id: sha
shell: bash
run: |
sha_docker=$(sha256sum actions-runner-hooks-docker-${{ steps.releaseVersion.outputs.version }}.zip | awk '{print $1}')
sha_docker=$(sha256sum actions-runner-hooks-docker-${{ steps.releaseVersion.outputs.result }}.zip | awk '{print $1}')
echo "Docker SHA: $sha_docker"
echo "docker-sha=$sha_docker" >> $GITHUB_OUTPUT
sha_k8s=$(sha256sum actions-runner-hooks-k8s-${{ steps.releaseVersion.outputs.version }}.zip | awk '{print $1}')
sha_k8s=$(sha256sum actions-runner-hooks-k8s-${{ steps.releaseVersion.outputs.result }}.zip | awk '{print $1}')
echo "K8s SHA: $sha_k8s"
echo "k8s-sha=$sha_k8s" >> $GITHUB_OUTPUT
- name: replace SHA
- name: Create release notes
id: releaseNotes
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
var releaseNotes = fs.readFileSync('${{ github.workspace }}/releaseNotes.md', 'utf8').replace(/<HOOK_VERSION>/g, '${{ steps.releaseVersion.outputs.version }}')
var releaseNotes = fs.readFileSync('${{ github.workspace }}/releaseNotes.md', 'utf8').replace(/<HOOK_VERSION>/g, '${{ steps.releaseVersion.outputs.result }}')
releaseNotes = releaseNotes.replace(/<DOCKER_SHA>/g, '${{ steps.sha.outputs.docker-sha }}')
releaseNotes = releaseNotes.replace(/<K8S_SHA>/g, '${{ steps.sha.outputs.k8s-sha }}')
console.log(releaseNotes)
core.setOutput('note', releaseNotes);
- uses: actions/create-release@v1
id: createRelease
name: Create ${{ steps.releaseVersion.outputs.version }} Hook Release
fs.writeFileSync('${{ github.workspace }}/finalReleaseNotes.md', releaseNotes);
- name: Create ${{ steps.releaseVersion.outputs.result }} Hook Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: "v${{ steps.releaseVersion.outputs.version }}"
release_name: "v${{ steps.releaseVersion.outputs.version }}"
body: |
${{ steps.releaseNotes.outputs.note }}
- name: Upload K8s hooks
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/actions-runner-hooks-k8s-${{ steps.releaseVersion.outputs.version }}.zip
asset_name: actions-runner-hooks-k8s-${{ steps.releaseVersion.outputs.version }}.zip
asset_content_type: application/octet-stream
- name: Upload docker hooks
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/actions-runner-hooks-docker-${{ steps.releaseVersion.outputs.version }}.zip
asset_name: actions-runner-hooks-docker-${{ steps.releaseVersion.outputs.version }}.zip
asset_content_type: application/octet-stream
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create v${{ steps.releaseVersion.outputs.result }} \
--title "v${{ steps.releaseVersion.outputs.result }}" \
--repo ${{ github.repository }} \
--notes-file ${{ github.workspace }}/finalReleaseNotes.md \
--latest \
${{ github.workspace }}/actions-runner-hooks-k8s-${{ steps.releaseVersion.outputs.result }}.zip \
${{ github.workspace }}/actions-runner-hooks-docker-${{ steps.releaseVersion.outputs.result }}.zip

View File

@@ -1 +1 @@
* @actions/actions-launch @actions/runner-akvelon
* @actions/actions-launch

View File

@@ -2,7 +2,7 @@
**Date:** 3 August 2023
**Status**: Accepted <!--Accepted|Rejected|Superceded|Deprecated-->
**Status**: Superceded [^1]
## Context
@@ -30,3 +30,5 @@ In case the hook is able to read the extended spec, it will first create a defau
## Consequences
The addition of hook extensions will provide a better user experience for users who need to customize the pods created by the container hook. However, it will require additional effort to provide the template to the runner pod, and configure it properly.
[^1]: Superseded by [ADR 0134](0134-hook-extensions.md)

View File

@@ -0,0 +1,41 @@
# ADR 0134: Hook extensions
**Date:** 20 February 2024
**Status**: Accepted [^1]
## Context
The current implementation of container hooks does not allow users to customize the pods created by the hook.
While the implementation is designed to be used as is or as a starting point, building and maintaining a custom hook implementation just to specify additional fields is not a good user experience.
## Decision
We have decided to add hook extensions to the container hook implementation.
This will allow users to customize the pods created by the hook by specifying additional fields.
The hook extensions will be implemented in a way that is backwards-compatible with the existing hook implementation.
To allow customization, the runner executing the hook should have `ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE` environment variable pointing to a yaml file on the runner system.
The extension specified in that file will be applied both for job pods, and container steps.
If environment variable is set, but the file can't be read, the hook will fail, signaling incorrect configuration.
If the environment variable does not exist, the hook will apply the default spec.
In case the hook is able to read the extended spec, it will first create a default configuration, and then merged modified fields in the following way:
1. The `.metadata` fields that will be appended if they are not reserved are `labels` and `annotations`.
2. The pod spec fields except for `containers` and `volumes` are applied from the template, possibly overwriting the field.
3. The volumes are applied in form of appending additional volumes to the default volumes.
4. The containers are merged based on the name assigned to them:
1. If the name of the container *is* "$job", the `name` and the `image` fields are going to be ignored and the spec will be applied so that `env`, `volumeMounts`, `ports` are appended to the default container spec created by the hook, while the rest of the fields are going to be applied to the newly created container spec.
2. If the name of the container *starts with* "$", and matches the name of the [container service](https://docs.github.com/en/actions/using-containerized-services/about-service-containers), the `name` and the `image` fields are going to be ignored and the spec will be applied to that service container, so that `env`, `volumeMounts`, `ports` are appended to the default container spec for service created by the hook, while the rest of the fields are going to be applied to the created container spec.
If there is no container service with such name defined in the workflow, such spec extension will be ignored.
3. If the name of the container *does not start with* "$", the entire spec of the container will be added to the pod definition.
## Consequences
The addition of hook extensions will provide a better user experience for users who need to customize the pods created by the container hook.
However, it will require additional effort to provide the template to the runner pod, and configure it properly.
[^1]: Supersedes [ADR 0096](0096-hook-extensions.md)

82
eslint.config.js Normal file
View File

@@ -0,0 +1,82 @@
import { defineConfig, globalIgnores } from 'eslint/config'
import typescriptEslint from '@typescript-eslint/eslint-plugin'
import globals from 'globals'
import tsParser from '@typescript-eslint/parser'
import github from 'eslint-plugin-github'
export default defineConfig([
globalIgnores(['**/dist/', '**/lib/', '**/node_modules/', '**/tests/**/*', 'eslint.config.js']),
github.getFlatConfigs().recommended,
{
plugins: {
'@typescript-eslint': typescriptEslint
},
files: ['packages/**/*.ts'],
languageOptions: {
globals: globals.node,
parser: tsParser,
ecmaVersion: 9,
sourceType: 'module',
parserOptions: {
project: './tsconfig.json'
}
},
rules: {
'eslint-comments/no-use': 'off',
'import/no-namespace': 'off',
'no-constant-condition': 'off',
'no-unused-vars': 'off',
'i18n-text/no-en': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-member-accessibility': [
'error',
{
accessibility: 'no-public'
}
],
'@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/array-type': 'error',
'@typescript-eslint/await-thenable': 'error',
camelcase: 'off',
'@typescript-eslint/explicit-function-return-type': [
'error',
{
allowExpressions: true
}
],
'@typescript-eslint/no-array-constructor': 'error',
'@typescript-eslint/no-empty-interface': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-for-in-array': 'error',
'@typescript-eslint/no-inferrable-types': 'error',
'@typescript-eslint/no-misused-new': 'error',
'@typescript-eslint/no-namespace': 'error',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/no-unnecessary-qualifier': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/no-useless-constructor': 'error',
'@typescript-eslint/no-var-requires': 'error',
'@typescript-eslint/prefer-for-of': 'warn',
'@typescript-eslint/prefer-function-type': 'warn',
'@typescript-eslint/prefer-includes': 'error',
'@typescript-eslint/prefer-string-starts-ends-with': 'error',
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/require-array-sort-compare': 'error',
'@typescript-eslint/restrict-plus-operands': 'error',
'@typescript-eslint/unbound-method': 'error',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error']
}
}
])

View File

@@ -9,7 +9,7 @@ spec:
runAsGroup: 3000
restartPolicy: Never
containers:
- name: $job # overwirtes job container
- name: $job # overwrites job container
env:
- name: ENV1
value: "value1"
@@ -20,11 +20,22 @@ spec:
args:
- -c
- sleep 50
- name: $redis # overwrites redis service
env:
- name: ENV2
value: "value2"
image: "busybox:1.28" # Ignored
resources:
requests:
memory: "1Mi"
cpu: "1"
limits:
memory: "1Gi"
cpu: "2"
- name: side-car
image: "ubuntu:latest" # required
command:
- sh
- sh
args:
- -c
- sleep 60
- -c
- sleep 60

5699
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "hooks",
"version": "0.5.1",
"version": "0.6.2",
"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": "",
"directories": {
@@ -11,7 +11,7 @@
"bootstrap": "npm install --prefix packages/hooklib && npm install --prefix packages/k8s && npm install --prefix packages/docker",
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"lint": "eslint packages/**/*.ts",
"lint": "eslint",
"build-all": "npm run build --prefix packages/hooklib && npm run build --prefix packages/k8s && npm run build --prefix packages/docker"
},
"repository": {
@@ -25,12 +25,12 @@
},
"homepage": "https://github.com/actions/runner-container-hooks#readme",
"devDependencies": {
"@types/jest": "^27.5.1",
"@types/node": "^17.0.23",
"@typescript-eslint/parser": "^5.18.0",
"eslint": "^8.12.0",
"eslint-plugin-github": "^4.3.6",
"prettier": "^2.6.2",
"typescript": "^4.6.3"
"@types/jest": "^29.5.14",
"@types/node": "^22.14.1",
"@typescript-eslint/parser": "^8.30.1",
"eslint": "^9.24.0",
"eslint-plugin-github": "^6.0.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
}
}

View File

@@ -1,13 +0,0 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*-test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
setupFilesAfterEnv: ['./jest.setup.js'],
verbose: true
}

View File

@@ -0,0 +1,12 @@
{
"clearMocks": true,
"moduleFileExtensions": ["js", "ts"],
"testEnvironment": "node",
"testMatch": ["**/*-test.ts"],
"testRunner": "jest-circus/runner",
"transform": {
"^.+\\.ts$": "ts-jest"
},
"verbose": true,
"testTimeout": 500000
}

View File

@@ -1 +0,0 @@
jest.setTimeout(500000)

File diff suppressed because it is too large Load Diff

View File

@@ -10,21 +10,21 @@
"author": "",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.9.1",
"@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1",
"hooklib": "file:../hooklib",
"shlex": "^2.1.2",
"uuid": "^8.3.2"
"uuid": "^11.1.0"
},
"devDependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^17.0.23",
"@typescript-eslint/parser": "^5.18.0",
"@vercel/ncc": "^0.33.4",
"jest": "^27.5.1",
"ts-jest": "^27.1.4",
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.14.1",
"typescript": "^4.6.3"
"@types/jest": "^29.5.14",
"@types/node": "^22.14.1",
"@typescript-eslint/parser": "^8.30.1",
"@vercel/ncc": "^0.38.3",
"jest": "^29.7.0",
"ts-jest": "^29.3.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3"
}
}

View File

@@ -31,9 +31,13 @@ export async function prepareJob(
core.info('No containers exist, skipping hook invocation')
exit(0)
}
const networkName = generateNetworkName()
// Create network
await networkCreate(networkName)
let networkName = process.env.ACTIONS_RUNNER_NETWORK_DRIVER
if (!networkName) {
networkName = generateNetworkName()
// Create network
await networkCreate(networkName)
}
// Create Job Container
let containerMetadata: ContainerMetadata | undefined = undefined

View File

@@ -45,9 +45,8 @@ export default class TestSetup {
public initialize(): void {
env['GITHUB_WORKSPACE'] = this.workingDirectory
env['RUNNER_NAME'] = 'test'
env[
'RUNNER_TEMP'
] = `${this.runnerMockDir}/${this.runnerMockSubdirs.workTemp}`
env['RUNNER_TEMP'] =
`${this.runnerMockDir}/${this.runnerMockSubdirs.workTemp}`
for (const dir of this.allTestDirectories) {
fs.mkdirSync(dir, { recursive: true })

File diff suppressed because it is too large Load Diff

View File

@@ -14,15 +14,15 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^17.0.23",
"@typescript-eslint/parser": "^5.18.0",
"@types/node": "^22.14.1",
"@typescript-eslint/parser": "^8.30.1",
"@zeit/ncc": "^0.22.3",
"eslint": "^8.12.0",
"eslint-plugin-github": "^4.3.6",
"prettier": "^2.6.2",
"typescript": "^4.6.3"
"eslint": "^9.24.0",
"eslint-plugin-github": "^6.0.0",
"prettier": "^3.5.3",
"typescript": "^5.8.3"
},
"dependencies": {
"@actions/core": "^1.9.1"
"@actions/core": "^1.11.1"
}
}

View File

@@ -1,13 +0,0 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*-test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
setupFilesAfterEnv: ['./jest.setup.js'],
verbose: true
}

View File

@@ -0,0 +1,12 @@
{
"clearMocks": true,
"moduleFileExtensions": ["js", "ts"],
"testEnvironment": "node",
"testMatch": ["**/*-test.ts"],
"testRunner": "jest-circus/runner",
"transform": {
"^.+\\.ts$": "ts-jest"
},
"verbose": true,
"testTimeout": 500000
}

View File

@@ -1 +0,0 @@
jest.setTimeout(500000)

File diff suppressed because it is too large Load Diff

View File

@@ -8,25 +8,27 @@
"build": "tsc && npx ncc build",
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"lint": "eslint src/**/*.ts"
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix"
},
"author": "",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.9.1",
"@actions/core": "^1.11.1",
"@actions/exec": "^1.1.1",
"@actions/io": "^1.1.2",
"@kubernetes/client-node": "^0.18.1",
"@actions/io": "^1.1.3",
"@kubernetes/client-node": "^1.1.2",
"hooklib": "file:../hooklib",
"js-yaml": "^4.1.0",
"shlex": "^2.1.2"
"shlex": "^2.1.2",
"uuid": "^11.1.0"
},
"devDependencies": {
"@types/jest": "^27.4.1",
"@types/node": "^17.0.23",
"@vercel/ncc": "^0.33.4",
"jest": "^27.5.1",
"ts-jest": "^27.1.4",
"typescript": "^4.6.3"
"@types/jest": "^29.5.14",
"@types/node": "^22.14.1",
"@vercel/ncc": "^0.38.3",
"jest": "^29.7.0",
"ts-jest": "^29.3.2",
"typescript": "^5.8.3"
}
}

View File

@@ -41,6 +41,7 @@ export function getSecretName(): string {
export const MAX_POD_NAME_LENGTH = 63
export const STEP_POD_NAME_SUFFIX_LENGTH = 8
export const CONTAINER_EXTENSION_PREFIX = '$'
export const JOB_CONTAINER_NAME = 'job'
export const JOB_CONTAINER_EXTENSION_NAME = '$job'

View File

@@ -26,7 +26,7 @@ import {
PodPhase,
fixArgs
} from '../k8s/utils'
import { JOB_CONTAINER_EXTENSION_NAME, JOB_CONTAINER_NAME } from './constants'
import { CONTAINER_EXTENSION_PREFIX, JOB_CONTAINER_NAME } from './constants'
export async function prepareJob(
args: PrepareJobArgs,
@@ -60,7 +60,7 @@ export async function prepareJob(
service,
generateContainerName(service.image),
false,
undefined
extension
)
})
}
@@ -119,11 +119,12 @@ export async function prepareJob(
throw new Error(`failed to determine if the pod is alpine: ${message}`)
}
core.debug(`Setting isAlpine to ${isAlpine}`)
generateResponseFile(responseFile, createdPod, isAlpine)
generateResponseFile(responseFile, args, createdPod, isAlpine)
}
function generateResponseFile(
responseFile: string,
args: PrepareJobArgs,
appPod: k8s.V1Pod,
isAlpine
): void {
@@ -156,24 +157,27 @@ function generateResponseFile(
}
}
const serviceContainers = appPod.spec?.containers.filter(
c => c.name !== JOB_CONTAINER_NAME
)
if (serviceContainers?.length) {
response.context['services'] = serviceContainers.map(c => {
const ctxPorts: ContextPorts = {}
if (c.ports?.length) {
for (const port of c.ports) {
ctxPorts[port.containerPort] = port.hostPort
}
}
if (args.services?.length) {
const serviceContainerNames =
args.services?.map(s => generateContainerName(s.image)) || []
return {
image: c.image,
ports: ctxPorts
}
})
response.context['services'] = appPod?.spec?.containers
?.filter(c => serviceContainerNames.includes(c.name))
.map(c => {
const ctxPorts: ContextPorts = {}
if (c.ports?.length) {
for (const port of c.ports) {
ctxPorts[port.containerPort] = port.hostPort
}
}
return {
image: c.image,
ports: ctxPorts
}
})
}
writeToResponseFile(responseFile, JSON.stringify(response))
}
@@ -235,7 +239,7 @@ export function createContainerSpec(
}
const from = extension.spec?.containers?.find(
c => c.name === JOB_CONTAINER_EXTENSION_NAME
c => c.name === CONTAINER_EXTENSION_PREFIX + name
)
if (from) {

View File

@@ -12,10 +12,10 @@ import {
} from '../k8s'
import {
containerVolumes,
PodPhase,
fixArgs,
mergeContainerWithOptions,
readExtensionFromFile,
fixArgs
PodPhase,
readExtensionFromFile
} from '../k8s/utils'
import { JOB_CONTAINER_EXTENSION_NAME, JOB_CONTAINER_NAME } from './constants'
@@ -28,7 +28,13 @@ export async function runContainerStep(
let secretName: string | undefined = undefined
if (stepContainer.environmentVariables) {
secretName = await createSecretForEnvs(stepContainer.environmentVariables)
try {
secretName = await createSecretForEnvs(stepContainer.environmentVariables)
} catch (err) {
core.debug(`createSecretForEnvs failed: ${JSON.stringify(err)}`)
const message = (err as any)?.response?.body?.message || err
throw new Error(`failed to create script environment: ${message}`)
}
}
const extension = readExtensionFromFile()
@@ -65,7 +71,12 @@ export async function runContainerStep(
await waitForPodPhases(
podName,
new Set([PodPhase.COMPLETED, PodPhase.RUNNING, PodPhase.SUCCEEDED]),
new Set([
PodPhase.COMPLETED,
PodPhase.RUNNING,
PodPhase.SUCCEEDED,
PodPhase.FAILED
]),
new Set([PodPhase.PENDING, PodPhase.UNKNOWN])
)
core.debug('Container step is running or complete, pulling logs')

View File

@@ -95,10 +95,12 @@ export async function createPod(
appPod.spec.containers = containers
appPod.spec.restartPolicy = 'Never'
if (!useKubeScheduler()) {
appPod.spec.nodeName = await getCurrentNodeName()
const nodeName = await getCurrentNodeName()
if (useKubeScheduler()) {
appPod.spec.affinity = await getPodAffinity(nodeName)
} else {
appPod.spec.nodeName = nodeName
}
const claimName = getVolumeClaimName()
appPod.spec.volumes = [
{
@@ -125,8 +127,10 @@ export async function createPod(
mergePodSpecWithOptions(appPod.spec, extension.spec)
}
const { body } = await k8sApi.createNamespacedPod(namespace(), appPod)
return body
return await k8sApi.createNamespacedPod({
namespace: namespace(),
body: appPod
})
}
export async function createJob(
@@ -155,8 +159,11 @@ export async function createJob(
job.spec.template.spec.containers = [container]
job.spec.template.spec.restartPolicy = 'Never'
if (!useKubeScheduler()) {
job.spec.template.spec.nodeName = await getCurrentNodeName()
const nodeName = await getCurrentNodeName()
if (useKubeScheduler()) {
job.spec.template.spec.affinity = await getPodAffinity(nodeName)
} else {
job.spec.template.spec.nodeName = nodeName
}
const claimName = getVolumeClaimName()
@@ -178,46 +185,42 @@ export async function createJob(
}
}
const { body } = await k8sBatchV1Api.createNamespacedJob(namespace(), job)
return body
return await k8sBatchV1Api.createNamespacedJob({
namespace: namespace(),
body: job
})
}
export async function getContainerJobPodName(jobName: string): Promise<string> {
const selector = `job-name=${jobName}`
const backOffManager = new BackOffManager(60)
while (true) {
const podList = await k8sApi.listNamespacedPod(
namespace(),
undefined,
undefined,
undefined,
undefined,
selector,
1
)
const podList = await k8sApi.listNamespacedPod({
namespace: namespace(),
labelSelector: selector,
limit: 1
})
if (!podList.body.items?.length) {
if (!podList.items?.length) {
await backOffManager.backOff()
continue
}
if (!podList.body.items[0].metadata?.name) {
if (!podList.items[0].metadata?.name) {
throw new Error(
`Failed to determine the name of the pod for job ${jobName}`
)
}
return podList.body.items[0].metadata.name
return podList.items[0].metadata.name
}
}
export async function deletePod(podName: string): Promise<void> {
await k8sApi.deleteNamespacedPod(
podName,
namespace(),
undefined,
undefined,
0
)
await k8sApi.deleteNamespacedPod({
name: podName,
namespace: namespace(),
gracePeriodSeconds: 0
})
}
export async function execPodStep(
@@ -269,7 +272,7 @@ export async function waitForJobToComplete(jobName: string): Promise<void> {
return
}
} catch (error) {
throw new Error(`job ${jobName} has failed`)
throw new Error(`job ${jobName} has failed: ${JSON.stringify(error)}`)
}
await backOffManager.backOff()
}
@@ -310,8 +313,10 @@ export async function createDockerSecret(
)
}
const { body } = await k8sApi.createNamespacedSecret(namespace(), secret)
return body
return await k8sApi.createNamespacedSecret({
namespace: namespace(),
body: secret
})
}
export async function createSecretForEnvs(envs: {
@@ -335,30 +340,30 @@ export async function createSecretForEnvs(envs: {
secret.data[key] = Buffer.from(value).toString('base64')
}
await k8sApi.createNamespacedSecret(namespace(), secret)
await k8sApi.createNamespacedSecret({ namespace: namespace(), body: secret })
return secretName
}
export async function deleteSecret(secretName: string): Promise<void> {
await k8sApi.deleteNamespacedSecret(secretName, namespace())
await k8sApi.deleteNamespacedSecret({
name: secretName,
namespace: namespace()
})
}
export async function pruneSecrets(): Promise<void> {
const secretList = await k8sApi.listNamespacedSecret(
namespace(),
undefined,
undefined,
undefined,
undefined,
new RunnerInstanceLabel().toString()
)
if (!secretList.body.items.length) {
const secretList = await k8sApi.listNamespacedSecret({
namespace: namespace(),
labelSelector: new RunnerInstanceLabel().toString()
})
if (!secretList.items.length) {
return
}
await Promise.all(
secretList.body.items.map(
secret => secret.metadata?.name && deleteSecret(secret.metadata.name)
secretList.items.map(
async secret =>
secret.metadata?.name && deleteSecret(secret.metadata.name)
)
)
}
@@ -386,7 +391,9 @@ export async function waitForPodPhases(
await backOffManager.backOff()
}
} catch (error) {
throw new Error(`Pod ${podName} is unhealthy with phase status ${phase}`)
throw new Error(
`Pod ${podName} is unhealthy with phase status ${phase}: ${JSON.stringify(error)}`
)
}
}
@@ -417,8 +424,10 @@ async function getPodPhase(podName: string): Promise<PodPhase> {
PodPhase.FAILED,
PodPhase.UNKNOWN
])
const { body } = await k8sApi.readNamespacedPod(podName, namespace())
const pod = body
const pod = await k8sApi.readNamespacedPod({
name: podName,
namespace: namespace()
})
if (!pod.status?.phase || !podPhaseLookup.has(pod.status.phase)) {
return PodPhase.UNKNOWN
@@ -427,8 +436,10 @@ async function getPodPhase(podName: string): Promise<PodPhase> {
}
async function isJobSucceeded(jobName: string): Promise<boolean> {
const { body } = await k8sBatchV1Api.readNamespacedJob(jobName, namespace())
const job = body
const job = await k8sBatchV1Api.readNamespacedJob({
name: jobName,
namespace: namespace()
})
if (job.status?.failed) {
throw new Error(`job ${jobName} has failed`)
}
@@ -450,31 +461,26 @@ export async function getPodLogs(
process.stderr.write(err.message)
})
const r = await log.log(namespace(), podName, containerName, logStream, {
await log.log(namespace(), podName, containerName, logStream, {
follow: true,
tailLines: 50,
pretty: false,
timestamps: false
})
await new Promise(resolve => r.on('close', () => resolve(null)))
await new Promise(resolve => logStream.on('close', () => resolve(null)))
}
export async function prunePods(): Promise<void> {
const podList = await k8sApi.listNamespacedPod(
namespace(),
undefined,
undefined,
undefined,
undefined,
new RunnerInstanceLabel().toString()
)
if (!podList.body.items.length) {
const podList = await k8sApi.listNamespacedPod({
namespace: namespace(),
labelSelector: new RunnerInstanceLabel().toString()
})
if (!podList.items.length) {
return
}
await Promise.all(
podList.body.items.map(
pod => pod.metadata?.name && deletePod(pod.metadata.name)
podList.items.map(
async pod => pod.metadata?.name && deletePod(pod.metadata.name)
)
)
}
@@ -482,16 +488,13 @@ export async function prunePods(): Promise<void> {
export async function getPodStatus(
name: string
): Promise<k8s.V1PodStatus | undefined> {
const { body } = await k8sApi.readNamespacedPod(name, namespace())
return body.status
const pod = await k8sApi.readNamespacedPod({ name, namespace: namespace() })
return pod.status
}
export async function isAuthPermissionsOK(): Promise<boolean> {
const sar = new k8s.V1SelfSubjectAccessReview()
const asyncs: Promise<{
response: unknown
body: k8s.V1SelfSubjectAccessReview
}>[] = []
const asyncs: Promise<k8s.V1SelfSubjectAccessReview>[] = []
for (const resource of requiredPermissions) {
for (const verb of resource.verbs) {
sar.spec = new k8s.V1SelfSubjectAccessReviewSpec()
@@ -501,11 +504,13 @@ export async function isAuthPermissionsOK(): Promise<boolean> {
sar.spec.resourceAttributes.group = resource.group
sar.spec.resourceAttributes.resource = resource.resource
sar.spec.resourceAttributes.subresource = resource.subresource
asyncs.push(k8sAuthorizationV1Api.createSelfSubjectAccessReview(sar))
asyncs.push(
k8sAuthorizationV1Api.createSelfSubjectAccessReview({ body: sar })
)
}
}
const responses = await Promise.all(asyncs)
return responses.every(resp => resp.body.status?.allowed)
return responses.every(resp => resp.status?.allowed)
}
export async function isPodContainerAlpine(
@@ -523,7 +528,7 @@ export async function isPodContainerAlpine(
podName,
containerName
)
} catch (err) {
} catch {
isAlpine = false
}
@@ -531,15 +536,38 @@ export async function isPodContainerAlpine(
}
async function getCurrentNodeName(): Promise<string> {
const resp = await k8sApi.readNamespacedPod(getRunnerPodName(), namespace())
const resp = await k8sApi.readNamespacedPod({
name: getRunnerPodName(),
namespace: namespace()
})
const nodeName = resp.body.spec?.nodeName
const nodeName = resp.spec?.nodeName
if (!nodeName) {
throw new Error('Failed to determine node name')
}
return nodeName
}
async function getPodAffinity(nodeName: string): Promise<k8s.V1Affinity> {
const affinity = new k8s.V1Affinity()
affinity.nodeAffinity = new k8s.V1NodeAffinity()
affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution =
new k8s.V1NodeSelector()
affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms =
[
{
matchExpressions: [
{
key: 'kubernetes.io/hostname',
operator: 'In',
values: [nodeName]
}
]
}
]
return affinity
}
export function namespace(): string {
if (process.env['ACTIONS_RUNNER_KUBERNETES_NAMESPACE']) {
return process.env['ACTIONS_RUNNER_KUBERNETES_NAMESPACE']
@@ -623,6 +651,5 @@ export function containerPorts(
}
export async function getPodByName(name): Promise<k8s.V1Pod> {
const { body } = await k8sApi.readNamespacedPod(name, namespace())
return body
return await k8sApi.readNamespacedPod({ name, namespace: namespace() })
}

View File

@@ -6,7 +6,7 @@ import { Mount } from 'hooklib'
import * as path from 'path'
import { v1 as uuidv4 } from 'uuid'
import { POD_VOLUME_NAME } from './index'
import { JOB_CONTAINER_EXTENSION_NAME } from '../hooks/constants'
import { CONTAINER_EXTENSION_PREFIX } from '../hooks/constants'
import * as shlex from 'shlex'
export const DEFAULT_CONTAINER_ENTRY_POINT_ARGS = [`-f`, `/dev/null`]
@@ -41,6 +41,16 @@ export function containerVolumes(
name: POD_VOLUME_NAME,
mountPath: '/github/file_commands',
subPath: '_temp/_runner_file_commands'
},
{
name: POD_VOLUME_NAME,
mountPath: '/github/home',
subPath: '_temp/_github_home'
},
{
name: POD_VOLUME_NAME,
mountPath: '/github/workflow',
subPath: '_temp/_github_workflow'
}
)
return mounts
@@ -180,7 +190,7 @@ export function mergeContainerWithOptions(
): void {
for (const [key, value] of Object.entries(from)) {
if (key === 'name') {
if (value !== base.name && value !== JOB_CONTAINER_EXTENSION_NAME) {
if (value !== CONTAINER_EXTENSION_PREFIX + base.name) {
core.warning("Skipping name override: name can't be overwritten")
}
continue
@@ -209,7 +219,9 @@ export function mergePodSpecWithOptions(
for (const [key, value] of Object.entries(from)) {
if (key === 'containers') {
base.containers.push(
...from.containers.filter(e => !e.name?.startsWith('$'))
...from.containers.filter(
e => !e.name?.startsWith(CONTAINER_EXTENSION_PREFIX)
)
)
} else if (key === 'volumes' && value) {
const volumes = value as k8s.V1Volume[]

View File

@@ -32,16 +32,12 @@ describe('Cleanup Job', () => {
kc.loadFromDefault()
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)
const podList = await k8sApi.listNamespacedPod(
namespace(),
undefined,
undefined,
undefined,
undefined,
new RunnerInstanceLabel().toString()
)
const podList = await k8sApi.listNamespacedPod({
namespace: namespace(),
labelSelector: new RunnerInstanceLabel().toString()
})
expect(podList.body.items.length).toBe(0)
expect(podList.items.length).toBe(0)
})
it('should have no runner linked secrets', async () => {
@@ -51,15 +47,11 @@ describe('Cleanup Job', () => {
kc.loadFromDefault()
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)
const secretList = await k8sApi.listNamespacedSecret(
namespace(),
undefined,
undefined,
undefined,
undefined,
new RunnerInstanceLabel().toString()
)
const secretList = await k8sApi.listNamespacedSecret({
namespace: namespace(),
labelSelector: new RunnerInstanceLabel().toString()
})
expect(secretList.body.items.length).toBe(0)
expect(secretList.items.length).toBe(0)
})
})

View File

@@ -185,6 +185,20 @@ describe('k8s utils', () => {
expect(volumes.find(e => e.mountPath === '/__w')).toBeTruthy()
})
it('should always have /github/workflow mount if working on container job or container action', () => {
let volumes = containerVolumes([], true, true)
expect(volumes.find(e => e.mountPath === '/github/workflow')).toBeTruthy()
volumes = containerVolumes([], true, false)
expect(volumes.find(e => e.mountPath === '/github/workflow')).toBeTruthy()
volumes = containerVolumes([], false, true)
expect(volumes.find(e => e.mountPath === '/github/workflow')).toBeTruthy()
volumes = containerVolumes([], false, false)
expect(
volumes.find(e => e.mountPath === '/github/workflow')
).toBeUndefined()
})
it('should have container action volumes', () => {
let volumes = containerVolumes([], true, true)
let workspace = volumes.find(e => e.mountPath === '/github/workspace')
@@ -205,11 +219,10 @@ describe('k8s utils', () => {
expect(fileCommands?.subPath).toBe('_temp/_runner_file_commands')
})
it('should have externals, github home and github workflow mounts if job container', () => {
it('should have externals, github home mounts if job container', () => {
const volumes = containerVolumes()
expect(volumes.find(e => e.mountPath === '/__e')).toBeTruthy()
expect(volumes.find(e => e.mountPath === '/github/home')).toBeTruthy()
expect(volumes.find(e => e.mountPath === '/github/workflow')).toBeTruthy()
})
it('should throw if user volume source volume path is not in workspace', () => {

View File

@@ -133,6 +133,13 @@ describe('Prepare job', () => {
expect(got.spec?.containers[1].image).toBe('redis')
expect(got.spec?.containers[1].command).toBeFalsy()
expect(got.spec?.containers[1].args).toBeFalsy()
expect(got.spec?.containers[1].env).toEqual([
{ name: 'ENV2', value: 'value2' }
])
expect(got.spec?.containers[1].resources).toEqual({
requests: { memory: '1Mi', cpu: '1' },
limits: { memory: '1Gi', cpu: '2' }
})
// side-car
expect(got.spec?.containers[2].name).toBe('side-car')
expect(got.spec?.containers[2].image).toBe('ubuntu:latest')
@@ -140,6 +147,26 @@ describe('Prepare job', () => {
expect(got.spec?.containers[2].args).toEqual(['-c', 'sleep 60'])
})
it('should put only job and services in output context file', async () => {
process.env[ENV_HOOK_TEMPLATE_PATH] = path.join(
__dirname,
'../../../examples/extension.yaml'
)
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).toBeTruthy()
expect(content.context.services).toBeTruthy()
expect(content.context.services.length).toBe(1)
})
it('should not throw exception using kube scheduler', async () => {
// only for ReadWriteMany volumes or single node cluster
process.env[ENV_USE_KUBE_SCHEDULER] = 'true'

View File

@@ -1,11 +1,10 @@
<!-- ## Features -->
## Bugs
- K8s: Try to get response body message and log entire error response in debug log [#123]
- Switch exec pod promise to reject on websocket error [#127]
- Fix is alpine check using shlex [#130]
<!-- ## Bugs -->
<!-- ## Misc -->
## Misc
- Bump `@kubernetes/client-node` from 0.18.1 to 0.22.0 in /packages/k8s [#182]
## SHA-256 Checksums