mirror of
https://github.com/actions/runner-container-hooks.git
synced 2025-12-13 16:16:46 +00:00
Bump all dependencies (#234)
* Bump all dependencies * build and reformat * lint * format
This commit is contained in:
@@ -1,4 +0,0 @@
|
|||||||
dist/
|
|
||||||
lib/
|
|
||||||
node_modules/
|
|
||||||
**/tests/**
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
122
eslint.config.js
Normal file
122
eslint.config.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
const eslint = require('@eslint/js');
|
||||||
|
const tseslint = require('@typescript-eslint/eslint-plugin');
|
||||||
|
const tsparser = require('@typescript-eslint/parser');
|
||||||
|
const globals = require('globals');
|
||||||
|
const pluginJest = require('eslint-plugin-jest');
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
sourceType: 'module',
|
||||||
|
project: ['./tsconfig.json', './packages/*/tsconfig.json']
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.es6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': tseslint,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Disabled rules from original config
|
||||||
|
'eslint-comments/no-use': 'off',
|
||||||
|
'import/no-namespace': 'off',
|
||||||
|
'no-constant-condition': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'i18n-text/no-en': 'off',
|
||||||
|
'camelcase': 'off',
|
||||||
|
'semi': 'off',
|
||||||
|
'no-shadow': 'off',
|
||||||
|
|
||||||
|
// TypeScript ESLint rules
|
||||||
|
'@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',
|
||||||
|
'@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': 'off', // Fixed: removed duplicate and kept only this one
|
||||||
|
'@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',
|
||||||
|
'@typescript-eslint/no-shadow': ['error']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Test files configuration - Fixed file pattern to match .ts files
|
||||||
|
files: ['**/*test*.ts', '**/*spec*.ts', '**/tests/**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
sourceType: 'module',
|
||||||
|
project: ['./tsconfig.json', './packages/*/tsconfig.json']
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.es6,
|
||||||
|
// Fixed Jest globals
|
||||||
|
describe: 'readonly',
|
||||||
|
it: 'readonly',
|
||||||
|
test: 'readonly',
|
||||||
|
expect: 'readonly',
|
||||||
|
beforeEach: 'readonly',
|
||||||
|
afterEach: 'readonly',
|
||||||
|
beforeAll: 'readonly',
|
||||||
|
afterAll: 'readonly',
|
||||||
|
jest: 'readonly'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': tseslint,
|
||||||
|
jest: pluginJest
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Disable no-undef for test files since Jest globals are handled above
|
||||||
|
'no-undef': 'off',
|
||||||
|
// Relax some rules for test files
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/jest.config.js', '**/jest.setup.js'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
jest: 'readonly',
|
||||||
|
module: 'writable'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
'import/no-commonjs': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
6703
package-lock.json
generated
6703
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -25,12 +25,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/actions/runner-container-hooks#readme",
|
"homepage": "https://github.com/actions/runner-container-hooks#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^27.5.1",
|
"@eslint/js": "^9.31.0",
|
||||||
"@types/node": "^17.0.23",
|
"@types/jest": "^30.0.0",
|
||||||
"@typescript-eslint/parser": "^5.18.0",
|
"@types/node": "^24.0.14",
|
||||||
"eslint": "^8.12.0",
|
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||||
"eslint-plugin-github": "^4.3.6",
|
"@typescript-eslint/parser": "^8.37.0",
|
||||||
"prettier": "^2.6.2",
|
"eslint": "^9.31.0",
|
||||||
"typescript": "^4.6.3"
|
"eslint-plugin-github": "^6.0.0",
|
||||||
|
"globals": "^15.12.0",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint-plugin-jest": "^29.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
// eslint-disable-next-line import/no-commonjs
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
clearMocks: true,
|
clearMocks: true,
|
||||||
|
preset: 'ts-jest',
|
||||||
moduleFileExtensions: ['js', 'ts'],
|
moduleFileExtensions: ['js', 'ts'],
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
testMatch: ['**/*-test.ts'],
|
testMatch: ['**/*-test.ts'],
|
||||||
testRunner: 'jest-circus/runner',
|
testRunner: 'jest-circus/runner',
|
||||||
|
verbose: true,
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.ts$': 'ts-jest'
|
'^.+\\.ts$': [
|
||||||
|
'ts-jest',
|
||||||
|
{
|
||||||
|
tsconfig: 'tsconfig.test.json'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Transform ESM modules to CommonJS
|
||||||
|
'^.+\\.(js|mjs)$': ['babel-jest', {
|
||||||
|
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
setupFilesAfterEnv: ['./jest.setup.js'],
|
transformIgnorePatterns: [
|
||||||
verbose: true
|
// Transform these ESM packages
|
||||||
|
'node_modules/(?!(shlex|@kubernetes/client-node|openid-client|oauth4webapi|jose|uuid)/)'
|
||||||
|
],
|
||||||
|
setupFilesAfterEnv: ['./jest.setup.js']
|
||||||
}
|
}
|
||||||
|
|||||||
11869
packages/docker/package-lock.json
generated
11869
packages/docker/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,21 +13,23 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.9.1",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^1.1.1",
|
||||||
"hooklib": "file:../hooklib",
|
"hooklib": "file:../hooklib",
|
||||||
"shlex": "^2.1.2",
|
"shlex": "^3.0.0",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^27.4.1",
|
"@babel/core": "^7.25.2",
|
||||||
"@types/node": "^17.0.23",
|
"@babel/preset-env": "^7.25.4",
|
||||||
"@typescript-eslint/parser": "^5.18.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@vercel/ncc": "^0.33.4",
|
"@types/node": "^24.0.14",
|
||||||
"jest": "^27.5.1",
|
"@typescript-eslint/parser": "^8.37.0",
|
||||||
"ts-jest": "^27.1.4",
|
"@vercel/ncc": "^0.38.3",
|
||||||
"ts-node": "^10.7.0",
|
"jest": "^30.0.4",
|
||||||
"tsconfig-paths": "^3.14.1",
|
"ts-jest": "^29.4.0",
|
||||||
"typescript": "^4.6.3"
|
"ts-node": "^10.9.2",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
/* eslint-disable import/no-commonjs */
|
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import { env } from 'process'
|
import { env } from 'process'
|
||||||
// Import this way otherwise typescript has errors
|
// Import this way otherwise typescript has errors
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default class TestSetup {
|
|||||||
private get allTestDirectories() {
|
private get allTestDirectories() {
|
||||||
const resp = [this.testdir, this.runnerMockDir, this.runnerOutputDir]
|
const resp = [this.testdir, this.runnerMockDir, this.runnerOutputDir]
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(this.runnerMockSubdirs)) {
|
for (const [, value] of Object.entries(this.runnerMockSubdirs)) {
|
||||||
resp.push(`${this.runnerMockDir}/${value}`)
|
resp.push(`${this.runnerMockDir}/${value}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +42,11 @@ export default class TestSetup {
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
public initialize(): void {
|
initialize(): void {
|
||||||
env['GITHUB_WORKSPACE'] = this.workingDirectory
|
env['GITHUB_WORKSPACE'] = this.workingDirectory
|
||||||
env['RUNNER_NAME'] = 'test'
|
env['RUNNER_NAME'] = 'test'
|
||||||
env[
|
env['RUNNER_TEMP'] =
|
||||||
'RUNNER_TEMP'
|
`${this.runnerMockDir}/${this.runnerMockSubdirs.workTemp}`
|
||||||
] = `${this.runnerMockDir}/${this.runnerMockSubdirs.workTemp}`
|
|
||||||
|
|
||||||
for (const dir of this.allTestDirectories) {
|
for (const dir of this.allTestDirectories) {
|
||||||
fs.mkdirSync(dir, { recursive: true })
|
fs.mkdirSync(dir, { recursive: true })
|
||||||
@@ -59,7 +58,7 @@ export default class TestSetup {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public teardown(): void {
|
teardown(): void {
|
||||||
fs.rmdirSync(this.testdir, { recursive: true })
|
fs.rmdirSync(this.testdir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,21 +107,21 @@ export default class TestSetup {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
public createOutputFile(name: string): string {
|
createOutputFile(name: string): string {
|
||||||
let filePath = path.join(this.runnerOutputDir, name || `${uuidv4()}.json`)
|
let filePath = path.join(this.runnerOutputDir, name || `${uuidv4()}.json`)
|
||||||
fs.writeFileSync(filePath, '')
|
fs.writeFileSync(filePath, '')
|
||||||
return filePath
|
return filePath
|
||||||
}
|
}
|
||||||
|
|
||||||
public get workingDirectory(): string {
|
get workingDirectory(): string {
|
||||||
return `${this.runnerMockDir}/_work/${this.projectName}/${this.projectName}`
|
return `${this.runnerMockDir}/_work/${this.projectName}/${this.projectName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
public get containerWorkingDirectory(): string {
|
get containerWorkingDirectory(): string {
|
||||||
return `/__w/${this.projectName}/${this.projectName}`
|
return `/__w/${this.projectName}/${this.projectName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
public initializeDockerAction(): string {
|
initializeDockerAction(): string {
|
||||||
const actionPath = `${this.testdir}/_actions/example-handle/example-repo/example-branch/mock-directory`
|
const actionPath = `${this.testdir}/_actions/example-handle/example-repo/example-branch/mock-directory`
|
||||||
fs.mkdirSync(actionPath, { recursive: true })
|
fs.mkdirSync(actionPath, { recursive: true })
|
||||||
this.writeDockerfile(actionPath)
|
this.writeDockerfile(actionPath)
|
||||||
@@ -147,7 +146,7 @@ echo "::set-output name=time::$time"`
|
|||||||
fs.chmodSync(entryPointPath, 0o755)
|
fs.chmodSync(entryPointPath, 0o755)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPrepareJobDefinition(): HookData {
|
getPrepareJobDefinition(): HookData {
|
||||||
const prepareJob = JSON.parse(
|
const prepareJob = JSON.parse(
|
||||||
fs.readFileSync(
|
fs.readFileSync(
|
||||||
path.resolve(__dirname + '/../../../examples/prepare-job.json'),
|
path.resolve(__dirname + '/../../../examples/prepare-job.json'),
|
||||||
@@ -166,7 +165,7 @@ echo "::set-output name=time::$time"`
|
|||||||
return prepareJob
|
return prepareJob
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRunScriptStepDefinition(): HookData {
|
getRunScriptStepDefinition(): HookData {
|
||||||
const runScriptStep = JSON.parse(
|
const runScriptStep = JSON.parse(
|
||||||
fs.readFileSync(
|
fs.readFileSync(
|
||||||
path.resolve(__dirname + '/../../../examples/run-script-step.json'),
|
path.resolve(__dirname + '/../../../examples/run-script-step.json'),
|
||||||
@@ -178,7 +177,7 @@ echo "::set-output name=time::$time"`
|
|||||||
return runScriptStep
|
return runScriptStep
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRunContainerStepDefinition(): HookData {
|
getRunContainerStepDefinition(): HookData {
|
||||||
const runContainerStep = JSON.parse(
|
const runContainerStep = JSON.parse(
|
||||||
fs.readFileSync(
|
fs.readFileSync(
|
||||||
path.resolve(__dirname + '/../../../examples/run-container-step.json'),
|
path.resolve(__dirname + '/../../../examples/run-container-step.json'),
|
||||||
|
|||||||
6
packages/docker/tsconfig.test.json
Normal file
6
packages/docker/tsconfig.test.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true
|
||||||
|
},
|
||||||
|
"extends": "./tsconfig.json"
|
||||||
|
}
|
||||||
5685
packages/hooklib/package-lock.json
generated
5685
packages/hooklib/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
@@ -14,15 +14,14 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.23",
|
"@types/node": "^24.0.14",
|
||||||
"@typescript-eslint/parser": "^5.18.0",
|
|
||||||
"@zeit/ncc": "^0.22.3",
|
"@zeit/ncc": "^0.22.3",
|
||||||
"eslint": "^8.12.0",
|
"eslint": "^9.31.0",
|
||||||
"eslint-plugin-github": "^4.3.6",
|
"eslint-plugin-github": "^6.0.0",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^3.6.2",
|
||||||
"typescript": "^4.6.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.9.1"
|
"@actions/core": "^1.11.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
// eslint-disable-next-line import/no-commonjs
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
clearMocks: true,
|
clearMocks: true,
|
||||||
|
preset: 'ts-jest',
|
||||||
moduleFileExtensions: ['js', 'ts'],
|
moduleFileExtensions: ['js', 'ts'],
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
testMatch: ['**/*-test.ts'],
|
testMatch: ['**/*-test.ts'],
|
||||||
testRunner: 'jest-circus/runner',
|
testRunner: 'jest-circus/runner',
|
||||||
|
verbose: true,
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.ts$': 'ts-jest'
|
'^.+\\.ts$': [
|
||||||
|
'ts-jest',
|
||||||
|
{
|
||||||
|
tsconfig: 'tsconfig.test.json'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Transform ESM modules to CommonJS
|
||||||
|
'^.+\\.(js|mjs)$': ['babel-jest', {
|
||||||
|
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
setupFilesAfterEnv: ['./jest.setup.js'],
|
transformIgnorePatterns: [
|
||||||
verbose: true
|
// Transform these ESM packages
|
||||||
|
'node_modules/(?!(shlex|@kubernetes/client-node|openid-client|oauth4webapi|jose|uuid)/)'
|
||||||
|
],
|
||||||
|
setupFilesAfterEnv: ['./jest.setup.js']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
// eslint-disable-next-line filenames/match-regex, no-undef
|
||||||
jest.setTimeout(500000)
|
jest.setTimeout(500000)
|
||||||
|
|||||||
11662
packages/k8s/package-lock.json
generated
11662
packages/k8s/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,20 +13,24 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.9.1",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/io": "^1.1.2",
|
"@actions/io": "^1.1.3",
|
||||||
"@kubernetes/client-node": "^0.22.2",
|
"@kubernetes/client-node": "^1.3.0",
|
||||||
"hooklib": "file:../hooklib",
|
"hooklib": "file:../hooklib",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"shlex": "^2.1.2"
|
"shlex": "^3.0.0",
|
||||||
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^27.4.1",
|
"@babel/core": "^7.25.2",
|
||||||
"@types/node": "^17.0.23",
|
"@babel/preset-env": "^7.25.4",
|
||||||
"@vercel/ncc": "^0.33.4",
|
"@types/jest": "^30.0.0",
|
||||||
"jest": "^27.5.1",
|
"@types/node": "^24.0.14",
|
||||||
"ts-jest": "^27.1.4",
|
"@vercel/ncc": "^0.38.3",
|
||||||
"typescript": "^4.6.3"
|
"babel-jest": "^30.0.4",
|
||||||
|
"jest": "^30.0.4",
|
||||||
|
"ts-jest": "^29.4.0",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import {
|
|||||||
JobContainerInfo,
|
JobContainerInfo,
|
||||||
ContextPorts,
|
ContextPorts,
|
||||||
PrepareJobArgs,
|
PrepareJobArgs,
|
||||||
writeToResponseFile
|
writeToResponseFile,
|
||||||
|
ServiceContainerInfo
|
||||||
} from 'hooklib'
|
} from 'hooklib'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {
|
import {
|
||||||
@@ -167,7 +168,9 @@ function generateResponseFile(
|
|||||||
const ctxPorts: ContextPorts = {}
|
const ctxPorts: ContextPorts = {}
|
||||||
if (c.ports?.length) {
|
if (c.ports?.length) {
|
||||||
for (const port of c.ports) {
|
for (const port of c.ports) {
|
||||||
ctxPorts[port.containerPort] = port.hostPort
|
if (port.containerPort && port.hostPort) {
|
||||||
|
ctxPorts[port.containerPort.toString()] = port.hostPort.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +196,7 @@ async function copyExternalsToRoot(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createContainerSpec(
|
export function createContainerSpec(
|
||||||
container: JobContainerInfo,
|
container: JobContainerInfo | ServiceContainerInfo,
|
||||||
name: string,
|
name: string,
|
||||||
jobContainer = false,
|
jobContainer = false,
|
||||||
extension?: k8s.V1PodTemplateSpec
|
extension?: k8s.V1PodTemplateSpec
|
||||||
@@ -208,24 +211,24 @@ export function createContainerSpec(
|
|||||||
image: container.image,
|
image: container.image,
|
||||||
ports: containerPorts(container)
|
ports: containerPorts(container)
|
||||||
} as k8s.V1Container
|
} as k8s.V1Container
|
||||||
if (container.workingDirectory) {
|
if (container['workingDirectory']) {
|
||||||
podContainer.workingDir = container.workingDirectory
|
podContainer.workingDir = container['workingDirectory']
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.entryPoint) {
|
if (container.entryPoint) {
|
||||||
podContainer.command = [container.entryPoint]
|
podContainer.command = [container.entryPoint]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.entryPointArgs?.length > 0) {
|
if (container.entryPointArgs && container.entryPointArgs.length > 0) {
|
||||||
podContainer.args = fixArgs(container.entryPointArgs)
|
podContainer.args = fixArgs(container.entryPointArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
podContainer.env = []
|
podContainer.env = []
|
||||||
for (const [key, value] of Object.entries(
|
for (const [key, value] of Object.entries(
|
||||||
container['environmentVariables']
|
container['environmentVariables'] || {}
|
||||||
)) {
|
)) {
|
||||||
if (value && key !== 'HOME') {
|
if (value && key !== 'HOME') {
|
||||||
podContainer.env.push({ name: key, value: value as string })
|
podContainer.env.push({ name: key, value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +237,7 @@ export function createContainerSpec(
|
|||||||
value: 'true'
|
value: 'true'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!('CI' in container['environmentVariables'])) {
|
if (!('CI' in (container['environmentVariables'] || {}))) {
|
||||||
podContainer.env.push({
|
podContainer.env.push({
|
||||||
name: 'CI',
|
name: 'CI',
|
||||||
value: 'true'
|
value: 'true'
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import { Command, getInputFromStdin, prepareJobArgs } from 'hooklib'
|
import {
|
||||||
|
Command,
|
||||||
|
getInputFromStdin,
|
||||||
|
PrepareJobArgs,
|
||||||
|
RunContainerStepArgs,
|
||||||
|
RunScriptStepArgs
|
||||||
|
} from 'hooklib'
|
||||||
import {
|
import {
|
||||||
cleanupJob,
|
cleanupJob,
|
||||||
prepareJob,
|
prepareJob,
|
||||||
@@ -27,16 +33,16 @@ async function run(): Promise<void> {
|
|||||||
let exitCode = 0
|
let exitCode = 0
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case Command.PrepareJob:
|
case Command.PrepareJob:
|
||||||
await prepareJob(args as prepareJobArgs, responseFile)
|
await prepareJob(args as PrepareJobArgs, responseFile)
|
||||||
return process.exit(0)
|
return process.exit(0)
|
||||||
case Command.CleanupJob:
|
case Command.CleanupJob:
|
||||||
await cleanupJob()
|
await cleanupJob()
|
||||||
return process.exit(0)
|
return process.exit(0)
|
||||||
case Command.RunScriptStep:
|
case Command.RunScriptStep:
|
||||||
await runScriptStep(args, state, null)
|
await runScriptStep(args as RunScriptStepArgs, state, null)
|
||||||
return process.exit(0)
|
return process.exit(0)
|
||||||
case Command.RunContainerStep:
|
case Command.RunContainerStep:
|
||||||
exitCode = await runContainerStep(args)
|
exitCode = await runContainerStep(args as RunContainerStepArgs)
|
||||||
return process.exit(exitCode)
|
return process.exit(exitCode)
|
||||||
default:
|
default:
|
||||||
throw new Error(`Command not recognized: ${command}`)
|
throw new Error(`Command not recognized: ${command}`)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as k8s from '@kubernetes/client-node'
|
import * as k8s from '@kubernetes/client-node'
|
||||||
import { ContainerInfo, Registry } from 'hooklib'
|
|
||||||
import * as stream from 'stream'
|
import * as stream from 'stream'
|
||||||
|
import type { ContainerInfo, Registry } from 'hooklib'
|
||||||
import {
|
import {
|
||||||
getJobPodName,
|
getJobPodName,
|
||||||
getRunnerPodName,
|
getRunnerPodName,
|
||||||
@@ -127,8 +127,10 @@ export async function createPod(
|
|||||||
mergePodSpecWithOptions(appPod.spec, extension.spec)
|
mergePodSpecWithOptions(appPod.spec, extension.spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { body } = await k8sApi.createNamespacedPod(namespace(), appPod)
|
return await k8sApi.createNamespacedPod({
|
||||||
return body
|
namespace: namespace(),
|
||||||
|
body: appPod
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createJob(
|
export async function createJob(
|
||||||
@@ -183,46 +185,42 @@ export async function createJob(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { body } = await k8sBatchV1Api.createNamespacedJob(namespace(), job)
|
return await k8sBatchV1Api.createNamespacedJob({
|
||||||
return body
|
namespace: namespace(),
|
||||||
|
body: job
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getContainerJobPodName(jobName: string): Promise<string> {
|
export async function getContainerJobPodName(jobName: string): Promise<string> {
|
||||||
const selector = `job-name=${jobName}`
|
const selector = `job-name=${jobName}`
|
||||||
const backOffManager = new BackOffManager(60)
|
const backOffManager = new BackOffManager(60)
|
||||||
while (true) {
|
while (true) {
|
||||||
const podList = await k8sApi.listNamespacedPod(
|
const podList = await k8sApi.listNamespacedPod({
|
||||||
namespace(),
|
namespace: namespace(),
|
||||||
undefined,
|
labelSelector: selector,
|
||||||
undefined,
|
limit: 1
|
||||||
undefined,
|
})
|
||||||
undefined,
|
|
||||||
selector,
|
|
||||||
1
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!podList.body.items?.length) {
|
if (!podList.items?.length) {
|
||||||
await backOffManager.backOff()
|
await backOffManager.backOff()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!podList.body.items[0].metadata?.name) {
|
if (!podList.items[0].metadata?.name) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to determine the name of the pod for job ${jobName}`
|
`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> {
|
export async function deletePod(name: string): Promise<void> {
|
||||||
await k8sApi.deleteNamespacedPod(
|
await k8sApi.deleteNamespacedPod({
|
||||||
podName,
|
name,
|
||||||
namespace(),
|
namespace: namespace(),
|
||||||
undefined,
|
gracePeriodSeconds: 0
|
||||||
undefined,
|
})
|
||||||
0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function execPodStep(
|
export async function execPodStep(
|
||||||
@@ -261,7 +259,6 @@ export async function execPodStep(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
// If exec.exec fails, explicitly reject the outer promise
|
// If exec.exec fails, explicitly reject the outer promise
|
||||||
// eslint-disable-next-line github/no-then
|
|
||||||
.catch(e => reject(e))
|
.catch(e => reject(e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -274,7 +271,7 @@ export async function waitForJobToComplete(jobName: string): Promise<void> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`job ${jobName} has failed`)
|
throw new Error(`job ${jobName} has failed: ${JSON.stringify(error)}`)
|
||||||
}
|
}
|
||||||
await backOffManager.backOff()
|
await backOffManager.backOff()
|
||||||
}
|
}
|
||||||
@@ -315,8 +312,10 @@ export async function createDockerSecret(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { body } = await k8sApi.createNamespacedSecret(namespace(), secret)
|
return await k8sApi.createNamespacedSecret({
|
||||||
return body
|
namespace: namespace(),
|
||||||
|
body: secret
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createSecretForEnvs(envs: {
|
export async function createSecretForEnvs(envs: {
|
||||||
@@ -340,30 +339,33 @@ export async function createSecretForEnvs(envs: {
|
|||||||
secret.data[key] = Buffer.from(value).toString('base64')
|
secret.data[key] = Buffer.from(value).toString('base64')
|
||||||
}
|
}
|
||||||
|
|
||||||
await k8sApi.createNamespacedSecret(namespace(), secret)
|
await k8sApi.createNamespacedSecret({
|
||||||
|
namespace: namespace(),
|
||||||
|
body: secret
|
||||||
|
})
|
||||||
return secretName
|
return secretName
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteSecret(secretName: string): Promise<void> {
|
export async function deleteSecret(name: string): Promise<void> {
|
||||||
await k8sApi.deleteNamespacedSecret(secretName, namespace())
|
await k8sApi.deleteNamespacedSecret({
|
||||||
|
name,
|
||||||
|
namespace: namespace()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pruneSecrets(): Promise<void> {
|
export async function pruneSecrets(): Promise<void> {
|
||||||
const secretList = await k8sApi.listNamespacedSecret(
|
const secretList = await k8sApi.listNamespacedSecret({
|
||||||
namespace(),
|
namespace: namespace(),
|
||||||
undefined,
|
labelSelector: new RunnerInstanceLabel().toString()
|
||||||
undefined,
|
})
|
||||||
undefined,
|
if (!secretList.items.length) {
|
||||||
undefined,
|
|
||||||
new RunnerInstanceLabel().toString()
|
|
||||||
)
|
|
||||||
if (!secretList.body.items.length) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
secretList.body.items.map(
|
secretList.items.map(
|
||||||
secret => secret.metadata?.name && deleteSecret(secret.metadata.name)
|
async secret =>
|
||||||
|
secret.metadata?.name && (await deleteSecret(secret.metadata.name))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -391,7 +393,9 @@ export async function waitForPodPhases(
|
|||||||
await backOffManager.backOff()
|
await backOffManager.backOff()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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)}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,7 +418,7 @@ export function getPrepareJobTimeoutSeconds(): number {
|
|||||||
return timeoutSeconds
|
return timeoutSeconds
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPodPhase(podName: string): Promise<PodPhase> {
|
async function getPodPhase(name: string): Promise<PodPhase> {
|
||||||
const podPhaseLookup = new Set<string>([
|
const podPhaseLookup = new Set<string>([
|
||||||
PodPhase.PENDING,
|
PodPhase.PENDING,
|
||||||
PodPhase.RUNNING,
|
PodPhase.RUNNING,
|
||||||
@@ -422,8 +426,10 @@ async function getPodPhase(podName: string): Promise<PodPhase> {
|
|||||||
PodPhase.FAILED,
|
PodPhase.FAILED,
|
||||||
PodPhase.UNKNOWN
|
PodPhase.UNKNOWN
|
||||||
])
|
])
|
||||||
const { body } = await k8sApi.readNamespacedPod(podName, namespace())
|
const pod = await k8sApi.readNamespacedPod({
|
||||||
const pod = body
|
name,
|
||||||
|
namespace: namespace()
|
||||||
|
})
|
||||||
|
|
||||||
if (!pod.status?.phase || !podPhaseLookup.has(pod.status.phase)) {
|
if (!pod.status?.phase || !podPhaseLookup.has(pod.status.phase)) {
|
||||||
return PodPhase.UNKNOWN
|
return PodPhase.UNKNOWN
|
||||||
@@ -431,11 +437,13 @@ async function getPodPhase(podName: string): Promise<PodPhase> {
|
|||||||
return pod.status?.phase as PodPhase
|
return pod.status?.phase as PodPhase
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isJobSucceeded(jobName: string): Promise<boolean> {
|
async function isJobSucceeded(name: string): Promise<boolean> {
|
||||||
const { body } = await k8sBatchV1Api.readNamespacedJob(jobName, namespace())
|
const job = await k8sBatchV1Api.readNamespacedJob({
|
||||||
const job = body
|
name,
|
||||||
|
namespace: namespace()
|
||||||
|
})
|
||||||
if (job.status?.failed) {
|
if (job.status?.failed) {
|
||||||
throw new Error(`job ${jobName} has failed`)
|
throw new Error(`job ${name} has failed`)
|
||||||
}
|
}
|
||||||
return !!job.status?.succeeded
|
return !!job.status?.succeeded
|
||||||
}
|
}
|
||||||
@@ -455,30 +463,26 @@ export async function getPodLogs(
|
|||||||
process.stderr.write(err.message)
|
process.stderr.write(err.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
const r = await log.log(namespace(), podName, containerName, logStream, {
|
await log.log(namespace(), podName, containerName, logStream, {
|
||||||
follow: true,
|
follow: true,
|
||||||
pretty: false,
|
pretty: false,
|
||||||
timestamps: false
|
timestamps: false
|
||||||
})
|
})
|
||||||
await new Promise(resolve => r.on('close', () => resolve(null)))
|
await new Promise(resolve => logStream.on('end', () => resolve(null)))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prunePods(): Promise<void> {
|
export async function prunePods(): Promise<void> {
|
||||||
const podList = await k8sApi.listNamespacedPod(
|
const podList = await k8sApi.listNamespacedPod({
|
||||||
namespace(),
|
namespace: namespace(),
|
||||||
undefined,
|
labelSelector: new RunnerInstanceLabel().toString()
|
||||||
undefined,
|
})
|
||||||
undefined,
|
if (!podList.items.length) {
|
||||||
undefined,
|
|
||||||
new RunnerInstanceLabel().toString()
|
|
||||||
)
|
|
||||||
if (!podList.body.items.length) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
podList.body.items.map(
|
podList.items.map(
|
||||||
pod => pod.metadata?.name && deletePod(pod.metadata.name)
|
async pod => pod.metadata?.name && (await deletePod(pod.metadata.name))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -486,16 +490,16 @@ export async function prunePods(): Promise<void> {
|
|||||||
export async function getPodStatus(
|
export async function getPodStatus(
|
||||||
name: string
|
name: string
|
||||||
): Promise<k8s.V1PodStatus | undefined> {
|
): Promise<k8s.V1PodStatus | undefined> {
|
||||||
const { body } = await k8sApi.readNamespacedPod(name, namespace())
|
const pod = await k8sApi.readNamespacedPod({
|
||||||
return body.status
|
name,
|
||||||
|
namespace: namespace()
|
||||||
|
})
|
||||||
|
return pod.status
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isAuthPermissionsOK(): Promise<boolean> {
|
export async function isAuthPermissionsOK(): Promise<boolean> {
|
||||||
const sar = new k8s.V1SelfSubjectAccessReview()
|
const sar = new k8s.V1SelfSubjectAccessReview()
|
||||||
const asyncs: Promise<{
|
const asyncs: Promise<k8s.V1SelfSubjectAccessReview>[] = []
|
||||||
response: unknown
|
|
||||||
body: k8s.V1SelfSubjectAccessReview
|
|
||||||
}>[] = []
|
|
||||||
for (const resource of requiredPermissions) {
|
for (const resource of requiredPermissions) {
|
||||||
for (const verb of resource.verbs) {
|
for (const verb of resource.verbs) {
|
||||||
sar.spec = new k8s.V1SelfSubjectAccessReviewSpec()
|
sar.spec = new k8s.V1SelfSubjectAccessReviewSpec()
|
||||||
@@ -505,11 +509,13 @@ export async function isAuthPermissionsOK(): Promise<boolean> {
|
|||||||
sar.spec.resourceAttributes.group = resource.group
|
sar.spec.resourceAttributes.group = resource.group
|
||||||
sar.spec.resourceAttributes.resource = resource.resource
|
sar.spec.resourceAttributes.resource = resource.resource
|
||||||
sar.spec.resourceAttributes.subresource = resource.subresource
|
sar.spec.resourceAttributes.subresource = resource.subresource
|
||||||
asyncs.push(k8sAuthorizationV1Api.createSelfSubjectAccessReview(sar))
|
asyncs.push(
|
||||||
|
k8sAuthorizationV1Api.createSelfSubjectAccessReview({ body: sar })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const responses = await Promise.all(asyncs)
|
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(
|
export async function isPodContainerAlpine(
|
||||||
@@ -527,7 +533,7 @@ export async function isPodContainerAlpine(
|
|||||||
podName,
|
podName,
|
||||||
containerName
|
containerName
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch {
|
||||||
isAlpine = false
|
isAlpine = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,9 +541,12 @@ export async function isPodContainerAlpine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getCurrentNodeName(): Promise<string> {
|
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) {
|
if (!nodeName) {
|
||||||
throw new Error('Failed to determine node name')
|
throw new Error('Failed to determine node name')
|
||||||
}
|
}
|
||||||
@@ -647,6 +656,8 @@ export function containerPorts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getPodByName(name): Promise<k8s.V1Pod> {
|
export async function getPodByName(name): Promise<k8s.V1Pod> {
|
||||||
const { body } = await k8sApi.readNamespacedPod(name, namespace())
|
return await k8sApi.readNamespacedPod({
|
||||||
return body
|
name,
|
||||||
|
namespace: namespace()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ exec ${environmentPrefix} ${entryPoint} ${
|
|||||||
|
|
||||||
export function generateContainerName(image: string): string {
|
export function generateContainerName(image: string): string {
|
||||||
const nameWithTag = image.split('/').pop()
|
const nameWithTag = image.split('/').pop()
|
||||||
const name = nameWithTag?.split(':').at(0)
|
const name = nameWithTag?.split(':')[0]
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
throw new Error(`Image definition '${image}' is invalid`)
|
throw new Error(`Image definition '${image}' is invalid`)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { cleanupJob, prepareJob } from '../src/hooks'
|
|||||||
import { RunnerInstanceLabel } from '../src/hooks/constants'
|
import { RunnerInstanceLabel } from '../src/hooks/constants'
|
||||||
import { namespace } from '../src/k8s'
|
import { namespace } from '../src/k8s'
|
||||||
import { TestHelper } from './test-setup'
|
import { TestHelper } from './test-setup'
|
||||||
|
import { PrepareJobArgs } from 'hooklib'
|
||||||
|
|
||||||
let testHelper: TestHelper
|
let testHelper: TestHelper
|
||||||
|
|
||||||
@@ -14,7 +15,10 @@ describe('Cleanup Job', () => {
|
|||||||
const prepareJobOutputFilePath = testHelper.createFile(
|
const prepareJobOutputFilePath = testHelper.createFile(
|
||||||
'prepare-job-output.json'
|
'prepare-job-output.json'
|
||||||
)
|
)
|
||||||
await prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
await prepareJob(
|
||||||
|
prepareJobData.args as PrepareJobArgs,
|
||||||
|
prepareJobOutputFilePath
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -32,16 +36,12 @@ describe('Cleanup Job', () => {
|
|||||||
kc.loadFromDefault()
|
kc.loadFromDefault()
|
||||||
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)
|
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)
|
||||||
|
|
||||||
const podList = await k8sApi.listNamespacedPod(
|
const podList = await k8sApi.listNamespacedPod({
|
||||||
namespace(),
|
namespace: namespace(),
|
||||||
undefined,
|
labelSelector: new RunnerInstanceLabel().toString()
|
||||||
undefined,
|
})
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
new RunnerInstanceLabel().toString()
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(podList.body.items.length).toBe(0)
|
expect(podList.items.length).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should have no runner linked secrets', async () => {
|
it('should have no runner linked secrets', async () => {
|
||||||
@@ -51,15 +51,11 @@ describe('Cleanup Job', () => {
|
|||||||
kc.loadFromDefault()
|
kc.loadFromDefault()
|
||||||
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)
|
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)
|
||||||
|
|
||||||
const secretList = await k8sApi.listNamespacedSecret(
|
const secretList = await k8sApi.listNamespacedSecret({
|
||||||
namespace(),
|
namespace: namespace(),
|
||||||
undefined,
|
labelSelector: new RunnerInstanceLabel().toString()
|
||||||
undefined,
|
})
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
new RunnerInstanceLabel().toString()
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(secretList.body.items.length).toBe(0)
|
expect(secretList.items.length).toBe(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
runScriptStep
|
runScriptStep
|
||||||
} from '../src/hooks'
|
} from '../src/hooks'
|
||||||
import { TestHelper } from './test-setup'
|
import { TestHelper } from './test-setup'
|
||||||
|
import { RunContainerStepArgs, RunScriptStepArgs } from 'hooklib'
|
||||||
|
|
||||||
jest.useRealTimers()
|
jest.useRealTimers()
|
||||||
|
|
||||||
@@ -36,13 +37,17 @@ describe('e2e', () => {
|
|||||||
const prepareJobOutputData = JSON.parse(prepareJobOutputJson.toString())
|
const prepareJobOutputData = JSON.parse(prepareJobOutputJson.toString())
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
runScriptStep(scriptStepData.args, prepareJobOutputData.state, null)
|
runScriptStep(
|
||||||
|
scriptStepData.args as RunScriptStepArgs,
|
||||||
|
prepareJobOutputData.state,
|
||||||
|
null
|
||||||
|
)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
|
|
||||||
const runContainerStepData = testHelper.getRunContainerStepDefinition()
|
const runContainerStepData = testHelper.getRunContainerStepDefinition()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
runContainerStep(runContainerStepData.args)
|
runContainerStep(runContainerStepData.args as RunContainerStepArgs)
|
||||||
).resolves.not.toThrow()
|
).resolves.not.toThrow()
|
||||||
|
|
||||||
await expect(cleanupJob()).resolves.not.toThrow()
|
await expect(cleanupJob()).resolves.not.toThrow()
|
||||||
|
|||||||
@@ -230,7 +230,9 @@ describe('k8s utils', () => {
|
|||||||
containerVolumes(
|
containerVolumes(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
sourceVolumePath: '/outside/of/workdir'
|
sourceVolumePath: '/outside/of/workdir',
|
||||||
|
targetVolumePath: '/some/target/path',
|
||||||
|
readOnly: false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import { TestHelper } from './test-setup'
|
|||||||
import {
|
import {
|
||||||
ENV_HOOK_TEMPLATE_PATH,
|
ENV_HOOK_TEMPLATE_PATH,
|
||||||
ENV_USE_KUBE_SCHEDULER,
|
ENV_USE_KUBE_SCHEDULER,
|
||||||
generateContainerName,
|
generateContainerName
|
||||||
readExtensionFromFile
|
|
||||||
} from '../src/k8s/utils'
|
} from '../src/k8s/utils'
|
||||||
import { getPodByName } from '../src/k8s'
|
import { getPodByName } from '../src/k8s'
|
||||||
import { V1Container } from '@kubernetes/client-node'
|
import { V1Container } from '@kubernetes/client-node'
|
||||||
import * as yaml from 'js-yaml'
|
|
||||||
import { JOB_CONTAINER_NAME } from '../src/hooks/constants'
|
import { JOB_CONTAINER_NAME } from '../src/hooks/constants'
|
||||||
|
|
||||||
jest.useRealTimers()
|
jest.useRealTimers()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { cleanupJob, prepareJob, runScriptStep } from '../src/hooks'
|
import { cleanupJob, prepareJob, runScriptStep } from '../src/hooks'
|
||||||
import { TestHelper } from './test-setup'
|
import { TestHelper } from './test-setup'
|
||||||
|
import { PrepareJobArgs } from 'hooklib'
|
||||||
|
|
||||||
jest.useRealTimers()
|
jest.useRealTimers()
|
||||||
|
|
||||||
@@ -21,7 +22,10 @@ describe('Run script step', () => {
|
|||||||
const prepareJobData = testHelper.getPrepareJobDefinition()
|
const prepareJobData = testHelper.getPrepareJobDefinition()
|
||||||
runScriptStepDefinition = testHelper.getRunScriptStepDefinition()
|
runScriptStepDefinition = testHelper.getRunScriptStepDefinition()
|
||||||
|
|
||||||
await prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
await prepareJob(
|
||||||
|
prepareJobData.args as PrepareJobArgs,
|
||||||
|
prepareJobOutputFilePath
|
||||||
|
)
|
||||||
const outputContent = fs.readFileSync(prepareJobOutputFilePath)
|
const outputContent = fs.readFileSync(prepareJobOutputFilePath)
|
||||||
prepareJobOutputData = JSON.parse(outputContent.toString())
|
prepareJobOutputData = JSON.parse(outputContent.toString())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class TestHelper {
|
|||||||
this.podName = uuidv4().replace(/-/g, '')
|
this.podName = uuidv4().replace(/-/g, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
public async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
process.env['ACTIONS_RUNNER_POD_NAME'] = `${this.podName}`
|
process.env['ACTIONS_RUNNER_POD_NAME'] = `${this.podName}`
|
||||||
process.env['RUNNER_WORKSPACE'] = `${this.tempDirPath}/_work/repo`
|
process.env['RUNNER_WORKSPACE'] = `${this.tempDirPath}/_work/repo`
|
||||||
process.env['RUNNER_TEMP'] = `${this.tempDirPath}/_work/_temp`
|
process.env['RUNNER_TEMP'] = `${this.tempDirPath}/_work/_temp`
|
||||||
@@ -44,49 +44,66 @@ export class TestHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async cleanup(): Promise<void> {
|
async cleanup(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await this.cleanupK8sResources()
|
await this.cleanupK8sResources()
|
||||||
fs.rmSync(this.tempDirPath, { recursive: true })
|
fs.rmSync(this.tempDirPath, { recursive: true })
|
||||||
} catch {}
|
} catch {
|
||||||
|
// Ignore errors during cleanup
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public async cleanupK8sResources() {
|
|
||||||
|
async cleanupK8sResources(): Promise<void> {
|
||||||
await k8sApi
|
await k8sApi
|
||||||
.deleteNamespacedPersistentVolumeClaim(
|
.deleteNamespacedPersistentVolumeClaim({
|
||||||
`${this.podName}-work`,
|
name: `${this.podName}-work`,
|
||||||
'default',
|
namespace: 'default',
|
||||||
undefined,
|
gracePeriodSeconds: 0
|
||||||
undefined,
|
})
|
||||||
0
|
.catch(e => {
|
||||||
)
|
console.error(e)
|
||||||
.catch(e => {})
|
})
|
||||||
await k8sApi.deletePersistentVolume(`${this.podName}-pv`).catch(e => {})
|
|
||||||
await k8sStorageApi.deleteStorageClass('local-storage').catch(e => {})
|
|
||||||
await k8sApi
|
await k8sApi
|
||||||
.deleteNamespacedPod(this.podName, 'default', undefined, undefined, 0)
|
.deletePersistentVolume({ name: `${this.podName}-pv` })
|
||||||
.catch(e => {})
|
.catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
})
|
||||||
|
await k8sStorageApi
|
||||||
|
.deleteStorageClass({ name: 'local-storage' })
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
})
|
||||||
await k8sApi
|
await k8sApi
|
||||||
.deleteNamespacedPod(
|
.deleteNamespacedPod({
|
||||||
`${this.podName}-workflow`,
|
name: this.podName,
|
||||||
'default',
|
namespace: 'default',
|
||||||
undefined,
|
gracePeriodSeconds: 0
|
||||||
undefined,
|
})
|
||||||
0
|
.catch(e => {
|
||||||
)
|
console.error(e)
|
||||||
.catch(e => {})
|
})
|
||||||
|
await k8sApi
|
||||||
|
.deleteNamespacedPod({
|
||||||
|
name: `${this.podName}-workflow`,
|
||||||
|
namespace: 'default',
|
||||||
|
gracePeriodSeconds: 0
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
public createFile(fileName?: string): string {
|
createFile(fileName?: string): string {
|
||||||
const filePath = `${this.tempDirPath}/${fileName || uuidv4()}`
|
const filePath = `${this.tempDirPath}/${fileName || uuidv4()}`
|
||||||
fs.writeFileSync(filePath, '')
|
fs.writeFileSync(filePath, '')
|
||||||
return filePath
|
return filePath
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFile(fileName: string): void {
|
removeFile(fileName: string): void {
|
||||||
const filePath = `${this.tempDirPath}/${fileName}`
|
const filePath = `${this.tempDirPath}/${fileName}`
|
||||||
fs.rmSync(filePath)
|
fs.rmSync(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createTestJobPod() {
|
async createTestJobPod(): Promise<void> {
|
||||||
const container = {
|
const container = {
|
||||||
name: 'nginx',
|
name: 'nginx',
|
||||||
image: 'nginx:latest',
|
image: 'nginx:latest',
|
||||||
@@ -102,10 +119,10 @@ export class TestHelper {
|
|||||||
containers: [container]
|
containers: [container]
|
||||||
}
|
}
|
||||||
} as k8s.V1Pod
|
} as k8s.V1Pod
|
||||||
await k8sApi.createNamespacedPod('default', pod)
|
await k8sApi.createNamespacedPod({ namespace: 'default', body: pod })
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createTestVolume() {
|
async createTestVolume(): Promise<void> {
|
||||||
var sc: k8s.V1StorageClass = {
|
var sc: k8s.V1StorageClass = {
|
||||||
metadata: {
|
metadata: {
|
||||||
name: 'local-storage'
|
name: 'local-storage'
|
||||||
@@ -113,7 +130,7 @@ export class TestHelper {
|
|||||||
provisioner: 'kubernetes.io/no-provisioner',
|
provisioner: 'kubernetes.io/no-provisioner',
|
||||||
volumeBindingMode: 'Immediate'
|
volumeBindingMode: 'Immediate'
|
||||||
}
|
}
|
||||||
await k8sStorageApi.createStorageClass(sc)
|
await k8sStorageApi.createStorageClass({ body: sc })
|
||||||
|
|
||||||
var volume: k8s.V1PersistentVolume = {
|
var volume: k8s.V1PersistentVolume = {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -131,7 +148,7 @@ export class TestHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await k8sApi.createPersistentVolume(volume)
|
await k8sApi.createPersistentVolume({ body: volume })
|
||||||
var volumeClaim: k8s.V1PersistentVolumeClaim = {
|
var volumeClaim: k8s.V1PersistentVolumeClaim = {
|
||||||
metadata: {
|
metadata: {
|
||||||
name: `${this.podName}-work`
|
name: `${this.podName}-work`
|
||||||
@@ -148,10 +165,13 @@ export class TestHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await k8sApi.createNamespacedPersistentVolumeClaim('default', volumeClaim)
|
await k8sApi.createNamespacedPersistentVolumeClaim({
|
||||||
|
namespace: 'default',
|
||||||
|
body: volumeClaim
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPrepareJobDefinition(): HookData {
|
getPrepareJobDefinition(): HookData {
|
||||||
const prepareJob = JSON.parse(
|
const prepareJob = JSON.parse(
|
||||||
fs.readFileSync(
|
fs.readFileSync(
|
||||||
path.resolve(__dirname + '/../../../examples/prepare-job.json'),
|
path.resolve(__dirname + '/../../../examples/prepare-job.json'),
|
||||||
@@ -168,7 +188,7 @@ export class TestHelper {
|
|||||||
return prepareJob
|
return prepareJob
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRunScriptStepDefinition(): HookData {
|
getRunScriptStepDefinition(): HookData {
|
||||||
const runScriptStep = JSON.parse(
|
const runScriptStep = JSON.parse(
|
||||||
fs.readFileSync(
|
fs.readFileSync(
|
||||||
path.resolve(__dirname + '/../../../examples/run-script-step.json'),
|
path.resolve(__dirname + '/../../../examples/run-script-step.json'),
|
||||||
@@ -180,7 +200,7 @@ export class TestHelper {
|
|||||||
return runScriptStep
|
return runScriptStep
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRunContainerStepDefinition(): HookData {
|
getRunContainerStepDefinition(): HookData {
|
||||||
const runContainerStep = JSON.parse(
|
const runContainerStep = JSON.parse(
|
||||||
fs.readFileSync(
|
fs.readFileSync(
|
||||||
path.resolve(__dirname + '/../../../examples/run-container-step.json'),
|
path.resolve(__dirname + '/../../../examples/run-container-step.json'),
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"rootDir": "./src"
|
"rootDir": "./src"
|
||||||
},
|
},
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
"include": [
|
"include": [
|
||||||
"./src"
|
"src/**/*",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
6
packages/k8s/tsconfig.test.json
Normal file
6
packages/k8s/tsconfig.test.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true
|
||||||
|
},
|
||||||
|
"extends": "./tsconfig.json"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user