mirror of
https://github.com/actions/runner.git
synced 2025-12-12 15:13:30 +00:00
Compare commits
1 Commits
users/tako
...
thboop/con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f71edd2af |
4
hooks/container/.eslintignore
Normal file
4
hooks/container/.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
|
**/__tests__/**
|
||||||
56
hooks/container/.eslintrc.json
Normal file
56
hooks/container/.eslintrc.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
3
hooks/container/.gitignore
vendored
Normal file
3
hooks/container/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
lib/
|
||||||
|
dist/
|
||||||
3
hooks/container/.prettierignore
Normal file
3
hooks/container/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
11
hooks/container/.prettierrc.json
Normal file
11
hooks/container/.prettierrc.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"parser": "typescript"
|
||||||
|
}
|
||||||
34
hooks/container/CONTRIBUTING.md
Normal file
34
hooks/container/CONTRIBUTING.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Basic setup
|
||||||
|
You'll need a runner compatible with hooks, a repository with container workflows to which you can register the runner and the hooks from this repository.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
- Run ` npm install && npm run bootstrap` to setup your environment and install all the needed packages
|
||||||
|
- Run `npm run lint` and `npm run format` to ensure your charges will pass CI
|
||||||
|
- Run `npm run build-all` to build and test end to end.
|
||||||
|
|
||||||
|
|
||||||
|
## E2E
|
||||||
|
- You'll need a runner compatible with hooks, a repository with container workflows to which you can register the runner and the hooks from this repository.
|
||||||
|
- See [the runner contributing.md](../../github/CONTRIBUTING.MD) for how to get started with runner development.
|
||||||
|
- Build your hook using `npm run build`
|
||||||
|
- Enable the hooks by setting `ACTIONS_RUNNER_CONTAINER_HOOK=./src/{libraryname}/dist/index.js` file generated by [ncc](https://github.com/vercel/ncc)
|
||||||
|
- Configure your self hosted runner against the a repository you have admin access
|
||||||
|
- Run a workflow with a container job, for example
|
||||||
|
```
|
||||||
|
name: myjob
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
jobs:
|
||||||
|
my_job:
|
||||||
|
runs-on: self-hosted
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
container:
|
||||||
|
image: alpine:3.15
|
||||||
|
options: --cpus 1
|
||||||
|
steps:
|
||||||
|
- run: pwd
|
||||||
|
```
|
||||||
10
hooks/container/README.md
Normal file
10
hooks/container/README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Container Hooks
|
||||||
|
This repo contains example implementation of the container hook feature across various container providers. More information on how to implement your own hooks can be found in the [github docs]().
|
||||||
|
|
||||||
|
Three projects are included in the `src` folder
|
||||||
|
- k8s: A kubernetes hook implementation that spins up pods dynamically to run a job
|
||||||
|
- docker: A hook implementation of the runner's docker implementation
|
||||||
|
- hooklib: a shared library which contains typescript definitions and utilities that the other projects consume
|
||||||
|
|
||||||
|
### Want to contribute
|
||||||
|
We welcome contributions. See [how to contribute](CONTRIBUTING.md).
|
||||||
3
hooks/container/hooklib/.eslintignore
Normal file
3
hooks/container/hooklib/.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
4350
hooks/container/hooklib/package-lock.json
generated
Normal file
4350
hooks/container/hooklib/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
hooks/container/hooklib/package.json
Normal file
28
hooks/container/hooklib/package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "hooklib",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "tsc && ncc build",
|
||||||
|
"format": "prettier --write '**/*.ts'",
|
||||||
|
"format-check": "prettier --check '**/*.ts'",
|
||||||
|
"lint": "eslint src/**/*.ts"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^17.0.23",
|
||||||
|
"@typescript-eslint/parser": "^5.18.0",
|
||||||
|
"@zeit/ncc": "^0.22.3",
|
||||||
|
"eslint": "^8.12.0",
|
||||||
|
"eslint-plugin-github": "^4.3.6",
|
||||||
|
"prettier": "^2.6.2",
|
||||||
|
"typescript": "^4.6.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/core": "^1.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
2
hooks/container/hooklib/src/index.ts
Normal file
2
hooks/container/hooklib/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './interfaces'
|
||||||
|
export * from './utils'
|
||||||
87
hooks/container/hooklib/src/interfaces.ts
Normal file
87
hooks/container/hooklib/src/interfaces.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
export enum Command {
|
||||||
|
PrepareJob = 'prepare_job',
|
||||||
|
CleanupJob = 'cleanup_job',
|
||||||
|
RunContainerStep = 'run_container_step',
|
||||||
|
RunScriptStep = 'run_script_step'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HookData {
|
||||||
|
command: Command
|
||||||
|
responseFile: string
|
||||||
|
args?: PrepareJobArgs | RunContainerStepArgs | RunScriptStepArgs
|
||||||
|
state?: object
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrepareJobArgs {
|
||||||
|
container?: JobContainerInfo
|
||||||
|
services?: ServiceContainerInfo[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RunContainerStepArgs = StepContainerInfo
|
||||||
|
|
||||||
|
export interface RunScriptStepArgs {
|
||||||
|
entrypoint: string
|
||||||
|
entrypointArgs: string[]
|
||||||
|
environmentVariables?: {[key: string]: string}
|
||||||
|
prependPath?: string[]
|
||||||
|
workingDirectory: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContainerInfo {
|
||||||
|
entrypoint?: string
|
||||||
|
entrypointArgs?: string[]
|
||||||
|
createOptions?: string
|
||||||
|
environmentVariables?: {[key: string]: string}
|
||||||
|
userMountVolumes?: Mount[]
|
||||||
|
registry?: Registry
|
||||||
|
portMappings?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceContainerInfo extends ContainerInfo {
|
||||||
|
contextName: string
|
||||||
|
image: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobContainerInfo extends ContainerInfo {
|
||||||
|
image: string
|
||||||
|
workingDirectory: string
|
||||||
|
systemMountVolumes: Mount[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StepContainerInfo extends ContainerInfo {
|
||||||
|
prependPath?: string[]
|
||||||
|
workingDirectory: string
|
||||||
|
dockerfile?: string
|
||||||
|
image?: string
|
||||||
|
systemMountVolumes: Mount[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Mount {
|
||||||
|
sourceVolumePath: string
|
||||||
|
targetVolumePath: string
|
||||||
|
readOnly: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Registry {
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
serverUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Protocol {
|
||||||
|
TCP = 'tcp',
|
||||||
|
UDP = 'udp'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrepareJobResponse {
|
||||||
|
state?: object
|
||||||
|
context?: ContainerContext
|
||||||
|
services?: {[key: string]: ContainerContext}
|
||||||
|
alpine: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContainerContext {
|
||||||
|
id?: string
|
||||||
|
network?: string
|
||||||
|
ports?: {[key: string]: string}
|
||||||
|
}
|
||||||
44
hooks/container/hooklib/src/utils.ts
Normal file
44
hooks/container/hooklib/src/utils.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as events from 'events'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as os from 'os'
|
||||||
|
import * as readline from 'readline'
|
||||||
|
import {HookData} from './interfaces'
|
||||||
|
|
||||||
|
export async function getInputFromStdin(): Promise<HookData> {
|
||||||
|
let input = ''
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin
|
||||||
|
})
|
||||||
|
|
||||||
|
rl.on('line', line => {
|
||||||
|
core.debug(`Line from STDIN: ${line}`)
|
||||||
|
input = line
|
||||||
|
})
|
||||||
|
await events.default.once(rl, 'close')
|
||||||
|
const inputJson = JSON.parse(input)
|
||||||
|
return inputJson as HookData
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeToResponseFile(filePath: string, message: any): void {
|
||||||
|
if (!filePath) {
|
||||||
|
throw new Error(`Expected file path`)
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
throw new Error(`Missing file at path: ${filePath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.appendFileSync(filePath, `${toCommandValue(message)}${os.EOL}`, {
|
||||||
|
encoding: 'utf8'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function toCommandValue(input: any): string {
|
||||||
|
if (input === null || input === undefined) {
|
||||||
|
return ''
|
||||||
|
} else if (typeof input === 'string' || input instanceof String) {
|
||||||
|
return input as string
|
||||||
|
}
|
||||||
|
return JSON.stringify(input)
|
||||||
|
}
|
||||||
11
hooks/container/hooklib/tsconfig.json
Normal file
11
hooks/container/hooklib/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"outDir": "./lib",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src"
|
||||||
|
]
|
||||||
|
}
|
||||||
4368
hooks/container/package-lock.json
generated
Normal file
4368
hooks/container/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
hooks/container/package.json
Normal file
37
hooks/container/package.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "hooks",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Three projects are included - k8s: a kubernetes hook implementation that spins up pods dynamically to run a job - docker: A hook implementation of the runner's docker implementation - A hook lib, which contains shared typescript definitions and utilities that the other packages consume",
|
||||||
|
"main": "",
|
||||||
|
"directories": {
|
||||||
|
"doc": "docs"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"bootstrap": "npm install --prefix src/hooklib && npm install --prefix src/k8s && npm install --prefix src/docker",
|
||||||
|
"format": "prettier --write '**/*.ts'",
|
||||||
|
"format-check": "prettier --check '**/*.ts'",
|
||||||
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"build-all": "npm run build --prefix src/hooklib && npm run build --prefix src/k8s && npm run build --prefix src/docker"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/actions/runner.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/actions/runner/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/actions/runner#readme",
|
||||||
|
"dependencies": {
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@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"
|
||||||
|
}
|
||||||
|
}
|
||||||
70
hooks/container/tsconfig.json
Normal file
70
hooks/container/tsconfig.json
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
"outDir": "./lib",
|
||||||
|
"rootDir": "./src",
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
// "outDir": "./", /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -227,7 +227,6 @@ namespace GitHub.Runner.Common
|
|||||||
//
|
//
|
||||||
public static readonly string AllowUnsupportedCommands = "ACTIONS_ALLOW_UNSECURE_COMMANDS";
|
public static readonly string AllowUnsupportedCommands = "ACTIONS_ALLOW_UNSECURE_COMMANDS";
|
||||||
public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS";
|
public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS";
|
||||||
public static readonly string RequireJobContainer = "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER";
|
|
||||||
public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG";
|
public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG";
|
||||||
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
|
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
|
||||||
public static readonly string AllowActionsUseUnsecureNodeVersion = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
|
public static readonly string AllowActionsUseUnsecureNodeVersion = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using GitHub.Services.Common;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
|
||||||
{
|
|
||||||
[ServiceLocator(Default = typeof(RunServer))]
|
|
||||||
public interface IRunServer : IRunnerService
|
|
||||||
{
|
|
||||||
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
|
|
||||||
|
|
||||||
Task<AgentJobRequestMessage> GetJobMessageAsync(string id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class RunServer : RunnerService, IRunServer
|
|
||||||
{
|
|
||||||
private bool _hasConnection;
|
|
||||||
private VssConnection _connection;
|
|
||||||
private TaskAgentHttpClient _taskAgentClient;
|
|
||||||
|
|
||||||
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
|
|
||||||
{
|
|
||||||
_connection = await EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
|
|
||||||
_taskAgentClient = _connection.GetClient<TaskAgentHttpClient>();
|
|
||||||
_hasConnection = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<VssConnection> EstablishVssConnection(Uri serverUrl, VssCredentials credentials, TimeSpan timeout)
|
|
||||||
{
|
|
||||||
Trace.Info($"EstablishVssConnection");
|
|
||||||
Trace.Info($"Establish connection with {timeout.TotalSeconds} seconds timeout.");
|
|
||||||
int attemptCount = 5;
|
|
||||||
while (attemptCount-- > 0)
|
|
||||||
{
|
|
||||||
var connection = VssUtil.CreateConnection(serverUrl, credentials, timeout: timeout);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await connection.ConnectAsync();
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (attemptCount > 0)
|
|
||||||
{
|
|
||||||
Trace.Info($"Catch exception during connect. {attemptCount} attempt left.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
|
|
||||||
await HostContext.Delay(TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// should never reach here.
|
|
||||||
throw new InvalidOperationException(nameof(EstablishVssConnection));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckConnection()
|
|
||||||
{
|
|
||||||
if (!_hasConnection)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"SetConnection");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id)
|
|
||||||
{
|
|
||||||
CheckConnection();
|
|
||||||
return _taskAgentClient.GetJobMessageAsync(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,18 @@
|
|||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Listener.Check;
|
|
||||||
using GitHub.Runner.Listener.Configuration;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
using System.IO;
|
||||||
using GitHub.Services.Common;
|
using System.Reflection;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Net;
|
using GitHub.Runner.Common;
|
||||||
using System.Net.Http;
|
using GitHub.Runner.Sdk;
|
||||||
using System.Net.Http.Headers;
|
using System.Linq;
|
||||||
|
using GitHub.Runner.Listener.Check;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
namespace GitHub.Runner.Listener
|
||||||
{
|
{
|
||||||
@@ -328,7 +322,6 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// Should we try to cleanup ephemeral runners
|
// Should we try to cleanup ephemeral runners
|
||||||
bool runOnceJobCompleted = false;
|
bool runOnceJobCompleted = false;
|
||||||
bool skipSessionDeletion = false;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var notification = HostContext.GetService<IJobNotification>();
|
var notification = HostContext.GetService<IJobNotification>();
|
||||||
@@ -464,42 +457,6 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Broker flow
|
|
||||||
else if (string.Equals(message.MessageType, JobRequestMessageTypes.RunnerJobRequest, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (autoUpdateInProgress || runOnceJobReceived)
|
|
||||||
{
|
|
||||||
skipMessageDeletion = true;
|
|
||||||
Trace.Info($"Skip message deletion for job request message '{message.MessageId}'.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
|
|
||||||
|
|
||||||
// Create connection
|
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
|
||||||
var creds = credMgr.LoadCredentials();
|
|
||||||
|
|
||||||
// todo: add retries https://github.com/github/actions-broker/issues/49
|
|
||||||
var runServer = HostContext.CreateService<IRunServer>();
|
|
||||||
await runServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
|
||||||
|
|
||||||
var jobMessage = await RetriesHelper<AgentJobRequestMessage>.RetryWithTimeoutAsync(async () =>
|
|
||||||
{
|
|
||||||
return await runServer.GetJobMessageAsync(messageRef.RunnerRequestId);
|
|
||||||
},
|
|
||||||
TimeSpan.FromSeconds(5),
|
|
||||||
TimeSpan.FromSeconds(10),
|
|
||||||
5);
|
|
||||||
|
|
||||||
jobDispatcher.Run(jobMessage, runOnce);
|
|
||||||
if (runOnce)
|
|
||||||
{
|
|
||||||
Trace.Info("One time used runner received job message.");
|
|
||||||
runOnceJobReceived = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (string.Equals(message.MessageType, JobCancelMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(message.MessageType, JobCancelMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var cancelJobMessage = JsonUtility.FromString<JobCancelMessage>(message.Body);
|
var cancelJobMessage = JsonUtility.FromString<JobCancelMessage>(message.Body);
|
||||||
@@ -511,14 +468,6 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info($"Skip message deletion for cancellation message '{message.MessageId}'.");
|
Trace.Info($"Skip message deletion for cancellation message '{message.MessageId}'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (string.Equals(message.MessageType, Pipelines.HostedRunnerShutdownMessage.MessageType, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
var HostedRunnerShutdownMessage = JsonUtility.FromString<Pipelines.HostedRunnerShutdownMessage>(message.Body);
|
|
||||||
skipMessageDeletion = true;
|
|
||||||
skipSessionDeletion = true;
|
|
||||||
Trace.Info($"Service requests the hosted runner to shutdown. Reason: '{HostedRunnerShutdownMessage.Reason}'.");
|
|
||||||
return Constants.Runner.ReturnCode.Success;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Trace.Error($"Received message {message.MessageId} with unsupported message type {message.MessageType}.");
|
Trace.Error($"Received message {message.MessageId} with unsupported message type {message.MessageType}.");
|
||||||
@@ -552,18 +501,15 @@ namespace GitHub.Runner.Listener
|
|||||||
await jobDispatcher.ShutdownAsync();
|
await jobDispatcher.ShutdownAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skipSessionDeletion)
|
try
|
||||||
{
|
{
|
||||||
try
|
await _listener.DeleteSessionAsync();
|
||||||
{
|
}
|
||||||
await _listener.DeleteSessionAsync();
|
catch (Exception ex) when (runOnce)
|
||||||
}
|
{
|
||||||
catch (Exception ex) when (runOnce)
|
// ignore exception during delete session for ephemeral runner since the runner might already be deleted from the server side
|
||||||
{
|
// and the delete session call will ends up with 401.
|
||||||
// ignore exception during delete session for ephemeral runner since the runner might already be deleted from the server side
|
Trace.Info($"Ignore any exception during DeleteSession for an ephemeral runner. {ex}");
|
||||||
// and the delete session call will ends up with 401.
|
|
||||||
Trace.Info($"Ignore any exception during DeleteSession for an ephemeral runner. {ex}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
messageQueueLoopTokenSource.Dispose();
|
messageQueueLoopTokenSource.Dispose();
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
using System.Runtime.Serialization;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public sealed class RunnerJobRequestRef
|
|
||||||
{
|
|
||||||
[DataMember(Name = "id")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
[DataMember(Name = "runner_request_id")]
|
|
||||||
public string RunnerRequestId { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -57,10 +57,6 @@ namespace GitHub.Runner.Sdk
|
|||||||
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
|
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_ALLOW_REDIRECT")))
|
|
||||||
{
|
|
||||||
settings.AllowAutoRedirect = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove Invariant from the list of accepted languages.
|
// Remove Invariant from the list of accepted languages.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
namespace GitHub.Runner.Sdk
|
namespace GitHub.Runner.Sdk
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -275,6 +275,12 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container)
|
public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container)
|
||||||
{
|
{
|
||||||
|
if (!context.Global.Variables.GetBoolean("DistributedTask.UploadStepSummary") ?? true)
|
||||||
|
{
|
||||||
|
Trace.Info("Step Summary is disabled; skipping attachment upload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(filePath) || !File.Exists(filePath))
|
if (String.IsNullOrEmpty(filePath) || !File.Exists(filePath))
|
||||||
{
|
{
|
||||||
Trace.Info($"Step Summary file ({filePath}) does not exist; skipping attachment upload");
|
Trace.Info($"Step Summary file ({filePath}) does not exist; skipping attachment upload");
|
||||||
|
|||||||
@@ -202,32 +202,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// For these shells, we want to use system binaries
|
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
||||||
var systemShells = new string[] { "bash", "sh", "powershell", "pwsh" };
|
shellCommand = parsed.shellCommand;
|
||||||
if (!IsActionStep && systemShells.Contains(shell))
|
// For non-ContainerStepHost, the command must be located on the host by Which
|
||||||
|
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||||
|
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||||
|
if (string.IsNullOrEmpty(argFormat))
|
||||||
{
|
{
|
||||||
shellCommand = shell;
|
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||||
commandPath = WhichUtil.Which(shell, !isContainerStepHost, Trace, prependPath);
|
|
||||||
if (shell == "bash")
|
|
||||||
{
|
|
||||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat("sh");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
|
||||||
shellCommand = parsed.shellCommand;
|
|
||||||
// For non-ContainerStepHost, the command must be located on the host by Which
|
|
||||||
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
|
||||||
argFormat = $"{parsed.shellArgs}".TrimStart();
|
|
||||||
if (string.IsNullOrEmpty(argFormat))
|
|
||||||
{
|
|
||||||
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +229,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
// We do not not the full path until we know what shell is being used, so that we can determine the file extension
|
// We do not not the full path until we know what shell is being used, so that we can determine the file extension
|
||||||
scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}");
|
scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}");
|
||||||
resolvedScriptPath = StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"");
|
resolvedScriptPath = $"{StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"")}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,23 +82,18 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetDefaultShellNameForScript(string path, Common.Tracing trace, string prependPath)
|
internal static string GetDefaultShellForScript(string path, Common.Tracing trace, string prependPath)
|
||||||
{
|
{
|
||||||
|
var format = "{0} {1}";
|
||||||
switch (Path.GetExtension(path))
|
switch (Path.GetExtension(path))
|
||||||
{
|
{
|
||||||
case ".sh":
|
case ".sh":
|
||||||
// use 'sh' args but prefer bash
|
// use 'sh' args but prefer bash
|
||||||
if (WhichUtil.Which("bash", false, trace, prependPath) != null)
|
var pathToShell = WhichUtil.Which("bash", false, trace, prependPath) ?? WhichUtil.Which("sh", true, trace, prependPath);
|
||||||
{
|
return string.Format(format, pathToShell, _defaultArguments["sh"]);
|
||||||
return "bash";
|
|
||||||
}
|
|
||||||
return "sh";
|
|
||||||
case ".ps1":
|
case ".ps1":
|
||||||
if (WhichUtil.Which("pwsh", false, trace, prependPath) != null)
|
var pathToPowershell = WhichUtil.Which("pwsh", false, trace, prependPath) ?? WhichUtil.Which("powershell", true, trace, prependPath);
|
||||||
{
|
return string.Format(format, pathToPowershell, _defaultArguments["powershell"]);
|
||||||
return "pwsh";
|
|
||||||
}
|
|
||||||
return "powershell";
|
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"{path} is not a valid path to a script. Make sure it ends in '.sh' or '.ps1'.");
|
throw new ArgumentException($"{path} is not a valid path to a script. Make sure it ends in '.sh' or '.ps1'.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
@@ -207,7 +206,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// Evaluate the job container
|
// Evaluate the job container
|
||||||
context.Debug("Evaluating job container");
|
context.Debug("Evaluating job container");
|
||||||
var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||||
ValidateJobContainer(container);
|
|
||||||
if (container != null)
|
if (container != null)
|
||||||
{
|
{
|
||||||
jobContext.Global.Container = new Container.ContainerInfo(HostContext, container);
|
jobContext.Global.Container = new Container.ContainerInfo(HostContext, container);
|
||||||
@@ -674,13 +672,5 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Total accessible running process: {snapshot.Count}.");
|
Trace.Info($"Total accessible running process: {snapshot.Count}.");
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateJobContainer(JobContainer container)
|
|
||||||
{
|
|
||||||
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.RequireJobContainer)) && container == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Jobs without a job container are forbidden on this runner, please add a 'container:' to your job or contact your self-hosted runner administrator.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public class JobHookData
|
public class JobHookData
|
||||||
{
|
{
|
||||||
public string Path { get; private set; }
|
public string Path {get; private set;}
|
||||||
public ActionRunStage Stage { get; private set; }
|
public ActionRunStage Stage {get; private set;}
|
||||||
|
|
||||||
public JobHookData(ActionRunStage stage, string path)
|
public JobHookData(ActionRunStage stage, string path)
|
||||||
{
|
{
|
||||||
@@ -60,7 +60,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Dictionary<string, string> inputs = new()
|
Dictionary<string, string> inputs = new()
|
||||||
{
|
{
|
||||||
["path"] = hookData.Path,
|
["path"] = hookData.Path,
|
||||||
["shell"] = ScriptHandlerHelpers.GetDefaultShellNameForScript(hookData.Path, Trace, prependPath)
|
["shell"] = ScriptHandlerHelpers.GetDefaultShellForScript(hookData.Path, Trace, prependPath)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the handler
|
// Create the handler
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace GitHub.Services.Common
|
|
||||||
{
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
|
|
||||||
public static class RetriesHelper<T>
|
|
||||||
{
|
|
||||||
public static async Task<T> RetryWithTimeoutAsync(
|
|
||||||
Func<Task<T>> retriableAction,
|
|
||||||
TimeSpan minBackoff,
|
|
||||||
TimeSpan maxBackoff,
|
|
||||||
int maxTimeoutMinutes = 5
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var remainingTime = TimeSpan.FromMinutes(maxTimeoutMinutes);
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await retriableAction();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
if (remainingTime > TimeSpan.Zero)
|
|
||||||
{
|
|
||||||
var backOff = BackoffTimerHelper.GetRandomBackoff(minBackoff, maxBackoff);
|
|
||||||
remainingTime -= backOff;
|
|
||||||
await Task.Delay(backOff);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines
|
|
||||||
{
|
|
||||||
[DataContract]
|
|
||||||
public sealed class HostedRunnerShutdownMessage
|
|
||||||
{
|
|
||||||
public static readonly String MessageType = "RunnerShutdown";
|
|
||||||
|
|
||||||
[JsonConstructor]
|
|
||||||
internal HostedRunnerShutdownMessage()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public HostedRunnerShutdownMessage(String reason)
|
|
||||||
{
|
|
||||||
this.Reason = reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DataMember]
|
|
||||||
public String Reason
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebApi.TaskAgentMessage GetAgentMessage()
|
|
||||||
{
|
|
||||||
return new WebApi.TaskAgentMessage
|
|
||||||
{
|
|
||||||
Body = JsonUtility.ToString(this),
|
|
||||||
MessageType = HostedRunnerShutdownMessage.MessageType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,5 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
public static class JobRequestMessageTypes
|
public static class JobRequestMessageTypes
|
||||||
{
|
{
|
||||||
public const String PipelineAgentJobRequest = "PipelineAgentJobRequest";
|
public const String PipelineAgentJobRequest = "PipelineAgentJobRequest";
|
||||||
public const String RunnerJobRequest = "RunnerJobRequest";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,24 +141,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
return ReplaceAgentAsync(poolId, agent.Id, agent, userState, cancellationToken);
|
return ReplaceAgentAsync(poolId, agent.Id, agent, userState, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Pipelines.AgentJobRequestMessage> GetJobMessageAsync(
|
|
||||||
string messageId,
|
|
||||||
object userState = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
HttpMethod httpMethod = new HttpMethod("GET");
|
|
||||||
Guid locationId = new Guid("25adab70-1379-4186-be8e-b643061ebe3a");
|
|
||||||
object routeValues = new { messageId = messageId };
|
|
||||||
|
|
||||||
return SendAsync<Pipelines.AgentJobRequestMessage>(
|
|
||||||
httpMethod,
|
|
||||||
locationId,
|
|
||||||
routeValues: routeValues,
|
|
||||||
version: new ApiResourceVersion(6.0, 1),
|
|
||||||
userState: userState,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Task<T> SendAsync<T>(
|
protected Task<T> SendAsync<T>(
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
Guid locationId,
|
Guid locationId,
|
||||||
|
|||||||
@@ -25,6 +25,23 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
private CreateStepSummaryCommand _createStepCommand;
|
private CreateStepSummaryCommand _createStepCommand;
|
||||||
private ITraceWriter _trace;
|
private ITraceWriter _trace;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void CreateStepSummaryCommand_FeatureDisabled()
|
||||||
|
{
|
||||||
|
using (var hostContext = Setup(featureFlagState: "false"))
|
||||||
|
{
|
||||||
|
var stepSummaryFile = Path.Combine(_rootDirectory, "feature-off");
|
||||||
|
|
||||||
|
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||||
|
_jobExecutionContext.Complete();
|
||||||
|
|
||||||
|
_jobServerQueue.Verify(x => x.QueueFileUpload(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<bool>()), Times.Never());
|
||||||
|
Assert.Equal(0, _issues.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -182,7 +199,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
File.WriteAllText(path, contentStr, encoding);
|
File.WriteAllText(path, contentStr, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestHostContext Setup([CallerMemberName] string name = "")
|
private TestHostContext Setup([CallerMemberName] string name = "", string featureFlagState = "true")
|
||||||
{
|
{
|
||||||
var hostContext = new TestHostContext(this, name);
|
var hostContext = new TestHostContext(this, name);
|
||||||
|
|
||||||
@@ -224,6 +241,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_variables = new Variables(hostContext, new Dictionary<string, VariableValue>
|
_variables = new Variables(hostContext, new Dictionary<string, VariableValue>
|
||||||
{
|
{
|
||||||
{ "MySecretName", new VariableValue("My secret value", true) },
|
{ "MySecretName", new VariableValue("My secret value", true) },
|
||||||
|
{ "DistributedTask.UploadStepSummary", featureFlagState },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Directory for test data
|
// Directory for test data
|
||||||
|
|||||||
@@ -211,24 +211,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public async Task JobExtensionBuildFailsWithoutContainerIfRequired()
|
|
||||||
{
|
|
||||||
Environment.SetEnvironmentVariable(Constants.Variables.Actions.RequireJobContainer, "true");
|
|
||||||
using (TestHostContext hc = CreateTestContext())
|
|
||||||
{
|
|
||||||
var jobExtension = new JobExtension();
|
|
||||||
jobExtension.Initialize(hc);
|
|
||||||
|
|
||||||
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>(), It.IsAny<Guid>()))
|
|
||||||
.Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>() { new JobExtensionRunner(null, "", "prepare1", null), new JobExtensionRunner(null, "", "prepare2", null) }, new Dictionary<Guid, IActionRunner>())));
|
|
||||||
|
|
||||||
await Assert.ThrowsAsync<ArgumentException>(() => jobExtension.InitializeJob(_jobEc, _message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
|
|||||||
Reference in New Issue
Block a user