Compare commits

...

43 Commits

Author SHA1 Message Date
eric sciple
04ece46c6a temp tracing for client auth 2022-05-10 18:20:49 +00:00
eric sciple
e75d502ab1 use token for broker and actions service 2022-05-09 06:20:08 +00:00
eric sciple
5686904fbe temporarily dont renew the job lock 2022-04-28 03:04:35 +00:00
eric sciple
c278fb1736 working on running job 2022-04-28 02:32:40 +00:00
eric sciple
332b8f9240 send runner info on getNextMessage 2022-04-09 20:04:55 +00:00
eric sciple
25f6cc100f print retrieved message 2022-04-09 00:31:59 +00:00
eric sciple
6ec30ea522 adding compile-time const whether to use the broker service 2022-04-08 23:23:27 +00:00
Thomas Boop
f48f314a70 fix an issue where container hooks used the job default working directory (#1809) 2022-04-06 14:33:32 +02:00
Soe Tun
7b677e0618 Mark run as Cancelled/Failed upon HostContext.RunnerShutdownToken state (#1792)
- github/c2c-actions-support#883
2022-04-04 13:46:03 -04:00
Nikola Jokic
d70f9f6174 Continue on error for the composite actions (#1763)
* Added continue on error to composite action

* changed from boolean-strategy-context -> boolean-steps-context for action_yaml

* refactored composite handler to always set outcome

* retrigger checks

* fixed typo in ??= operator

* boolean-steps-context accepts the same context as string-steps-context

* setting the outcome only on continue-on-error

* moved continue on error logic to the execution context

* Added L0 table tests for continue-on-error ExecutionContext

* Added missing mocks on StepsRunnerL0 for this update

* removed empty line and added one line separating the call

* Removed empty line

Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>

Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>
2022-04-01 09:18:53 -04:00
dependabot[bot]
0343e76789 Bump minimist from 1.2.5 to 1.2.6 in /src/Misc/expressionFunc/hashFiles (#1783)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

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

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-29 22:40:32 -04:00
Yashwanth Anantharaju
909b05eb66 FeedStream: handle websocket close failures (#1789)
* handle close failures

* handle in other place as well

* refactor

* bump runner version

* update release notes
2022-03-28 14:41:21 -04:00
Yashwanth Anantharaju
2e3976cf97 Feedstream websocket: set user agent (#1791)
* set user agent

* let's also add prefix
2022-03-28 14:31:23 -04:00
ruvceskistefan
052ac521b0 Issue 1739: Fixing null reference exception during configuring runner with invalid repo URL or token (#1741)
* Fixing null reference exception when configuring runner with invalid repo URL or token

* Throw exception instead of ConvertFromJson

* Storing the response code
2022-03-28 09:06:24 -04:00
Ferenc Hammerl
408d6c579c Add annotations if Node 12 action is found and FF is on (#1735)
* Add annotations if node 12 action is found

* Better placeholder

* Only warn if FF is on

* Move annotation logic

* Pass in the LTS Url

* Raise annotation right before executing the action

* Match server side FF name

* Change name back to features

* Better warning text

* Update src/Runner.Common/Constants.cs

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
2022-03-22 10:43:25 +01:00
Thomas Boop
46258428cd 2.289.1 release notes (#1771) 2022-03-18 14:22:24 -04:00
Thomas Boop
eb9a604b63 Revert "Added repository name and workflow file name to console output (#1761)" (#1770)
98aa9c1152
2022-03-18 14:09:01 -04:00
Thomas Boop
8792d8e5ee cleanup message displayed on job started/completed hooks (#1769) 2022-03-18 14:08:50 -04:00
Thomas Boop
87e86e3d72 2.289.0 release notes (#1766) 2022-03-18 16:12:50 +01:00
Thomas Boop
48b6cd9a42 Update dependencies to latest versions (#1756) 2022-03-17 23:21:35 -04:00
Yashwanth Anantharaju
d081289ed5 postlines: refactor per feedback (#1755)
* refactor per feedback

* feedback

* nit

* commentify

* feedback

* feedback
2022-03-17 21:35:20 -04:00
Ferenc Hammerl
7d5e9cd70f Runner Job Started/Completed Hooks (#1737)
* Prototype for pre job hook

* Remove debug log

* Enable hooks again

* Initialize with hostContext

* Add event_path, fix no-path bug

* Allow script post steps

* Call script handler with correct pre post stage

* Add job completed hook

* Make filecommand work and hardcode shell

* Conditionally print step details and no telemetry for hooks

* Figure out whih script to use

* Only check path for managed scripts

* Resture win dependency

* Nits

* Remove unused, add named params

* Telemetry + refactoring

* add message to job

* rename hooks remove stale comment

* cleanup

* Use .CreateService to create step

* Add L0s

* pr feedback

* update tests

* add disclaimer, clean up code

* spacing fix

* little more cleanup

* pr fix

* pr feedback

* Refactor to use JobExtension

* fix tests

* fix typo

* cleanup code

* more cleanup

* little more cleanup

* last bit of cleanup

* fix tests

* nit fix

* Update src/Runner.Worker/JobHookProvider.cs

Co-authored-by: Edward Thomson <ethomson@github.com>

* don't override runner telemtry

* pr feedback

* pr feedback

* pr feedback

Co-authored-by: Thomas Boop <thboop@github.com>
Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
Co-authored-by: Edward Thomson <ethomson@github.com>
2022-03-17 21:35:04 -04:00
ruvceskistefan
98aa9c1152 Added repository name and workflow file name to console output (#1761)
* Adding repo name and workflow file to console output

* Add guard for empty workflow file name
2022-03-17 13:25:28 -04:00
Yashwanth Anantharaju
ddc700e9eb Send postlines via websocket if we can (#1730)
* feed via websocket

* feed via websocket

* feedback

* ensure right schema is used

* fix resiliency

* some fixes

* fix sending message

* chunk data

* let's abort, which will also dispose

* close gracefully
2022-03-15 14:01:18 -04:00
Konrad Pabjan
a0458aebfe Save record order for annotation links when creating issues (#1744)
* Save record order for annotation links when creating issues

* PR feedback

* Add tests for step and line numbers
2022-03-14 11:20:11 -04:00
Tingluo Huang
b2c6d093b2 Validate packages hash before uploading to github release in CD workflow. (#1745) 2022-03-14 09:21:13 -04:00
Thomas Boop
292a2e0ab3 Fix spelling (#1747) 2022-03-11 09:41:54 -05:00
Nikola Jokic
29cee52276 Prefer user who initiated install before (#1714) 2022-03-10 14:08:19 +01:00
Antoine Grondin
ad0d0c4d0a worker: expose github.triggering_actor as an env-var (#1726)
* worker: expose `github.triggering_actor` as an env-var

* worker: sort the allow list
2022-03-02 16:49:26 -05:00
Thomas Boop
2c6064a655 Update to v2.288.1 (#1723)
We hotfixed the releases/m288 branch to update to v2.288.1. This PR brings us to parity on the main branch
2022-03-01 18:19:56 +00:00
ruvceskistefan
af6c8e6edd Issue 1698: Use safe_sleep executable in bash scripts (#1707)
* use safe_sleep executable in bash scripts

* new line at the end of safe_sleep bash script

* Replacing relative paths with absolute paths and changing location of safe_sleep

Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>
2022-03-01 13:08:52 +00:00
Nikola Jokic
c15d3f10b2 Enhancement: RunnerService.js added logic to fail on N attempts if env variable exported (#1693)
* RunnerService.js added logic to fail on N attempts

* removed code grafeculShutdown, removed unused import
2022-03-01 13:55:25 +01:00
TingluoHuang
bdf1e90503 Prepare 2.288.0 runner releases. 2022-02-25 15:08:26 -05:00
Ferenc Hammerl
100c99f263 Force JS Actions Node version to 16 if FF is on unless user opted out (#1716)
* Set GH actions Node version to 16 if FF is on unless user opted out

* Add L0s (WIP)

* Wrap tests into theory

* Only check for node12 actions

* Refactor node version picking
2022-02-25 14:59:16 -05:00
Ferenc Hammerl
e8ccafea63 Add internal to node version function and use better env var name (#1715) 2022-02-25 14:59:02 -05:00
Thomas Boop
02d2cb8fcd Lets allow up to 150 characters for services on linux/mac (#1710)
* Lets allow up to 150 characters on linux/mac, just to avoid some issues with runner naming

* Add 4 randomized digits on mac/linux

* fix pragma issue

* fix test

* Address pr feedback

* reduce complexity

* lets make it cleaner!

* fix test

* fix logic
2022-02-24 21:05:51 -05:00
Rob Herley
0cbf3351f4 Update summary max file size annotation error text (#1712)
* add doc link to summary file size err annotation

* Update src/Runner.Common/Constants.cs

Co-authored-by: Konrad Pabjan <konradpabjan@github.com>

* remove i18n from url

Co-authored-by: Konrad Pabjan <konradpabjan@github.com>
2022-02-24 21:01:49 -05:00
Ferenc Hammerl
6abef8199f Use better exit codes and comparison (#1708) 2022-02-24 21:10:52 +01:00
ruvceskistefan
ec9830836b Issue 1596: Runner throws null ref exception when new line after EOF is missing (#1687)
* Issue 1596: runner throws nullref exception when writting env var

* Adding tests for missing new line after EOF marker

* Changing newline to new line
2022-02-23 09:55:59 -05:00
Nikola Jokic
460c32a337 Repaired hashFiles call so if error was thrown, it was returned to process invoker (#1678)
* hashFiles.ts added exit status on promise action

* generated layoutbin/hashfiles/index.js
2022-02-23 09:51:09 -05:00
ruvceskistefan
934027da60 Issue 1261: inconsistency of outputs (both canceled and cancelled are used) (#1624)
* Issue 1261: inconsistency of outputs

* Changing cancelled to canceled in one error message
2022-02-23 09:48:24 -05:00
Tingluo Huang
28f0027938 Add SHA to useragent. (#1694) 2022-02-17 20:01:48 +00:00
Thomas Boop
17153c9b29 Revert "revert node12 version due to fs.copyFileSync hang https://git… (#1651)
* Revert "revert node12 version due to fs.copyFileSync hang https://github.com/actions/runner/issues/1536 (#1537)"

bef164a12f

* check hashs before tests because tests rely on right values + update hashes

* fix tests

* use hc trace
2022-02-17 09:54:13 -05:00
84 changed files with 4077 additions and 4315 deletions

View File

@@ -50,13 +50,6 @@ jobs:
${{ matrix.devScript }} layout Release ${{ matrix.runtime }} ${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
working-directory: src working-directory: src
# Run tests
- name: L0
run: |
${{ matrix.devScript }} test
working-directory: src
if: matrix.runtime != 'linux-arm64' && matrix.runtime != 'linux-arm'
# Check runtime/externals hash # Check runtime/externals hash
- name: Compute/Compare runtime and externals Hash - name: Compute/Compare runtime and externals Hash
shell: bash shell: bash
@@ -80,6 +73,13 @@ jobs:
DOTNET_RUNTIME_HASH: ${{hashFiles('**/_layout_trims/runtime/**/*')}} DOTNET_RUNTIME_HASH: ${{hashFiles('**/_layout_trims/runtime/**/*')}}
EXTERNALS_HASH: ${{hashFiles('**/_layout_trims/externals/**/*')}} EXTERNALS_HASH: ${{hashFiles('**/_layout_trims/externals/**/*')}}
# Run tests
- name: L0
run: |
${{ matrix.devScript }} test
working-directory: src
if: matrix.runtime != 'linux-arm64' && matrix.runtime != 'linux-arm'
# Create runner package tar.gz/zip # Create runner package tar.gz/zip
- name: Package Release - name: Package Release
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'

View File

@@ -260,6 +260,17 @@ jobs:
console.log(releaseNote) console.log(releaseNote)
core.setOutput('version', runnerVersion); core.setOutput('version', runnerVersion);
core.setOutput('note', releaseNote); core.setOutput('note', releaseNote);
- name: Validate Packages HASH
working-directory: _package
run: |
ls -l
echo "${{needs.build.outputs.win-x64-sha}} actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}.zip" | shasum -a 256 -c
echo "${{needs.build.outputs.osx-x64-sha}} actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
echo "${{needs.build.outputs.linux-x64-sha}} actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
echo "${{needs.build.outputs.linux-arm-sha}} actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
echo "${{needs.build.outputs.linux-arm64-sha}} actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz" | shasum -a 256 -c
# Create GitHub release # Create GitHub release
- uses: actions/create-release@master - uses: actions/create-release@master
id: createRelease id: createRelease

View File

@@ -1,19 +1,7 @@
## Features ## Features
- Add Runner Configuration option to disable auto update `--disableupdate` (#1558)
- Introduce `GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY` env variable to skip SSL Cert Verification on the Runner (#1616)
- Adds support for downloading trimmed versions of the runner when the entire package does not need to be upgraded (#1568)
## Bugs ## Bugs
- Set Outcome/Conclusion for composite action steps (#1600) - Fixed an issue where websockets failed to successfully close when posting log lines (#1790)
## Misc
- Update `run.sh` to more gracefully handle updates (#1494)
- Use 8Mb default chunking for File Container Uploads (#1626)
- Performance improvements in handling large amounts of live logs (#1592)
- Allow `./svc.sh stop` to exit as soon as runner process exits (#1580)
- Add additional tracing to help troubleshoot job message corruption (#1587)
## Windows x64 ## Windows x64

View File

@@ -47,6 +47,11 @@
<DefineConstants>$(DefineConstants);DEBUG</DefineConstants> <DefineConstants>$(DefineConstants);DEBUG</DefineConstants>
</PropertyGroup> </PropertyGroup>
<!-- Set USE_BROKER vars -->
<PropertyGroup Condition="'$(USE_BROKER)' == 'true'">
<DefineConstants>$(DefineConstants);USE_BROKER</DefineConstants>
</PropertyGroup>
<!-- Set Treat tarnings as errors --> <!-- Set Treat tarnings as errors -->
<PropertyGroup> <PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>

View File

@@ -1 +1 @@
6ca4a0e1c50b7079ead05321dcf5835c1c25f23dc632add8c1c4667d416d103e 6ed30a2c1ee403a610d63e82bb230b9ba846a9c25cec9e4ea8672fb6ed4e1a51

View File

@@ -1 +1 @@
b5951dc607d782d9c7571a7224e940eb0975bb23c54ff25c7afdbf959a417081 711c30c51ec52c9b7a9a2eb399d6ab2ab5ee1dc72de11879f2f36f919f163d78

View File

@@ -1 +1 @@
af819e92011cc9cbca90e8299f9f7651f2cf6bf45b42920f9a4ca22795486147 a49479ca4b4988a06c097e8d22c51fd08a11c13f40807366236213d0e008cf6a

View File

@@ -1 +1 @@
aa0e6bf4bfaabf48c962ea3b262dca042629ab332005f73d282faec908847036 8e97df75230b843462a9b4c578ccec604ee4b4a1066120c85b04374317fa372b

View File

@@ -1 +1 @@
40328cff2b8229f9b578f32739183bd8f6aab481c21dadc052b09f1c7e8e4665 f75a671e5a188c76680739689aa75331a2c09d483dce9c80023518c48fd67a18

View File

@@ -1,6 +1,6 @@
{ {
"plugins": ["jest", "@typescript-eslint"], "plugins": ["@typescript-eslint"],
"extends": ["plugin:github/es6"], "extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": 9, "ecmaVersion": 9,
@@ -17,13 +17,16 @@
"@typescript-eslint/no-require-imports": "error", "@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error", "@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error", "@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-ignore": "error", "@typescript-eslint/naming-convention": [
"error",
{
"selector": "default",
"format": ["camelCase"]
}
],
"camelcase": "off", "camelcase": "off",
"@typescript-eslint/camelcase": "error",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}], "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"], "@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
"@typescript-eslint/no-array-constructor": "error", "@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
@@ -33,7 +36,6 @@
"@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-object-literal-type-assertion": "error",
"@typescript-eslint/no-unnecessary-qualifier": "error", "@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error", "@typescript-eslint/no-useless-constructor": "error",
@@ -41,19 +43,19 @@
"@typescript-eslint/prefer-for-of": "warn", "@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn", "@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error", "@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-interface": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error", "@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error", "@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error", "@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error", "@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "never"], "@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error", "@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error" "@typescript-eslint/unbound-method": "error",
"filenames/match-regex" : "off",
"github/no-then" : 1, // warning
"semi": "off"
}, },
"env": { "env": {
"node": true, "node": true,
"es6": true, "es6": true
"jest/globals": true
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -25,10 +25,10 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.7.12", "@types/node": "^12.7.12",
"@typescript-eslint/parser": "^2.8.0", "@typescript-eslint/parser": "^5.15.0",
"@zeit/ncc": "^0.20.5", "@zeit/ncc": "^0.20.5",
"eslint": "^6.8.0", "eslint": "^8.11.0",
"eslint-plugin-github": "^2.0.0", "eslint-plugin-github": "^4.3.5",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"typescript": "^3.6.4" "typescript": "^3.6.4"
} }

View File

@@ -1,9 +1,9 @@
import * as glob from '@actions/glob'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
import * as glob from '@actions/glob'
import * as path from 'path'
import * as stream from 'stream' import * as stream from 'stream'
import * as util from 'util' import * as util from 'util'
import * as path from 'path'
async function run(): Promise<void> { async function run(): Promise<void> {
// arg0 -> node // arg0 -> node
@@ -53,3 +53,11 @@ async function run(): Promise<void> {
} }
run() run()
.then(out => {
console.log(out)
process.exit(0)
})
.catch(err => {
console.error(err)
process.exit(1)
})

View File

@@ -3,7 +3,7 @@ PACKAGERUNTIME=$1
PRECACHE=$2 PRECACHE=$2
NODE_URL=https://nodejs.org/dist NODE_URL=https://nodejs.org/dist
NODE12_VERSION="12.13.1" NODE12_VERSION="12.22.7"
NODE16_VERSION="16.13.0" NODE16_VERSION="16.13.0"
get_abs_path() { get_abs_path() {
@@ -143,7 +143,7 @@ fi
# Download the external tools for Linux PACKAGERUNTIMEs. # Download the external tools for Linux PACKAGERUNTIMEs.
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-x64.tar.gz" node12 fix_nested_dir acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-x64.tar.gz" node12 fix_nested_dir
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE12_VERSION}/alpine/x64/node-${NODE12_VERSION}-alpine-x64.tar.gz" node12_alpine acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE12_VERSION}/alpine/x64/node-v${NODE12_VERSION}-alpine-x64.tar.gz" node12_alpine
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-x64.tar.gz" node16 fix_nested_dir acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-x64.tar.gz" node16 fix_nested_dir
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE16_VERSION}/alpine/x64/node-v${NODE16_VERSION}-alpine-x64.tar.gz" node16_alpine acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE16_VERSION}/alpine/x64/node-v${NODE16_VERSION}-alpine-x64.tar.gz" node16_alpine
fi fi

View File

@@ -3,94 +3,135 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
var childProcess = require("child_process"); var childProcess = require("child_process");
var path = require("path") var path = require("path");
var supported = ['linux', 'darwin'] var supported = ["linux", "darwin"];
if (supported.indexOf(process.platform) == -1) { if (supported.indexOf(process.platform) == -1) {
console.log('Unsupported platform: ' + process.platform); console.log("Unsupported platform: " + process.platform);
console.log('Supported platforms are: ' + supported.toString()); console.log("Supported platforms are: " + supported.toString());
process.exit(1); process.exit(1);
} }
var stopping = false; var stopping = false;
var listener = null; var listener = null;
var runService = function () { var exitServiceAfterNFailures = Number(
var listenerExePath = path.join(__dirname, '../bin/Runner.Listener'); process.env.GITHUB_ACTIONS_SERVICE_EXIT_AFTER_N_FAILURES
var interactive = process.argv[2] === "interactive"; );
if (!stopping) { if (exitServiceAfterNFailures <= 0) {
try { exitServiceAfterNFailures = NaN;
if (interactive) {
console.log('Starting Runner listener interactively');
listener = childProcess.spawn(listenerExePath, ['run'], { env: process.env });
} else {
console.log('Starting Runner listener with startup type: service');
listener = childProcess.spawn(listenerExePath, ['run', '--startuptype', 'service'], { env: process.env });
}
console.log(`Started listener process, pid: ${listener.pid}`);
listener.stdout.on('data', (data) => {
process.stdout.write(data.toString('utf8'));
});
listener.stderr.on('data', (data) => {
process.stdout.write(data.toString('utf8'));
});
listener.on("error", (err) => {
console.log(`Runner listener fail to start with error ${err.message}`);
});
listener.on('close', (code) => {
console.log(`Runner listener exited with error code ${code}`);
if (code === 0) {
console.log('Runner listener exit with 0 return code, stop the service, no retry needed.');
stopping = true;
} else if (code === 1) {
console.log('Runner listener exit with terminated error, stop the service, no retry needed.');
stopping = true;
} else if (code === 2) {
console.log('Runner listener exit with retryable error, re-launch runner in 5 seconds.');
} else if (code === 3) {
console.log('Runner listener exit because of updating, re-launch runner in 5 seconds.');
} else {
console.log('Runner listener exit with undefined return code, re-launch runner in 5 seconds.');
}
if (!stopping) {
setTimeout(runService, 5000);
}
});
} catch (ex) {
console.log(ex);
}
}
} }
var consecutiveFailureCount = 0;
var gracefulShutdown = function () {
console.log("Shutting down runner listener");
stopping = true;
if (listener) {
console.log("Sending SIGINT to runner listener to stop");
listener.kill("SIGINT");
console.log("Sending SIGKILL to runner listener");
setTimeout(() => listener.kill("SIGKILL"), 30000).unref();
}
};
var runService = function () {
var listenerExePath = path.join(__dirname, "../bin/Runner.Listener");
var interactive = process.argv[2] === "interactive";
if (!stopping) {
try {
if (interactive) {
console.log("Starting Runner listener interactively");
listener = childProcess.spawn(listenerExePath, ["run"], {
env: process.env,
});
} else {
console.log("Starting Runner listener with startup type: service");
listener = childProcess.spawn(
listenerExePath,
["run", "--startuptype", "service"],
{ env: process.env }
);
}
console.log(`Started listener process, pid: ${listener.pid}`);
listener.stdout.on("data", (data) => {
if (data.toString("utf8").includes("Listening for Jobs")) {
consecutiveFailureCount = 0;
}
process.stdout.write(data.toString("utf8"));
});
listener.stderr.on("data", (data) => {
process.stdout.write(data.toString("utf8"));
});
listener.on("error", (err) => {
console.log(`Runner listener fail to start with error ${err.message}`);
});
listener.on("close", (code) => {
console.log(`Runner listener exited with error code ${code}`);
if (code === 0) {
console.log(
"Runner listener exit with 0 return code, stop the service, no retry needed."
);
stopping = true;
} else if (code === 1) {
console.log(
"Runner listener exit with terminated error, stop the service, no retry needed."
);
stopping = true;
} else if (code === 2) {
console.log(
"Runner listener exit with retryable error, re-launch runner in 5 seconds."
);
consecutiveFailureCount = 0;
} else if (code === 3 || code === 4) {
console.log(
"Runner listener exit because of updating, re-launch runner in 5 seconds."
);
consecutiveFailureCount = 0;
} else {
var messagePrefix = "Runner listener exit with undefined return code";
consecutiveFailureCount++;
if (
!isNaN(exitServiceAfterNFailures) &&
consecutiveFailureCount >= exitServiceAfterNFailures
) {
console.error(
`${messagePrefix}, exiting service after ${consecutiveFailureCount} consecutive failures`
);
gracefulShutdown();
return;
} else {
console.log(`${messagePrefix}, re-launch runner in 5 seconds.`);
}
}
if (!stopping) {
setTimeout(runService, 5000);
}
});
} catch (ex) {
console.log(ex);
}
}
};
runService(); runService();
console.log('Started running service'); console.log("Started running service");
var gracefulShutdown = function (code) { process.on("SIGINT", () => {
console.log('Shutting down runner listener'); gracefulShutdown();
stopping = true;
if (listener) {
console.log('Sending SIGINT to runner listener to stop');
listener.kill('SIGINT');
console.log('Sending SIGKILL to runner listener');
setTimeout(() => listener.kill('SIGKILL'), 30000).unref();
}
}
process.on('SIGINT', () => {
gracefulShutdown(0);
}); });
process.on('SIGTERM', () => { process.on("SIGTERM", () => {
gracefulShutdown(0); gracefulShutdown();
}); });

View File

@@ -63,7 +63,7 @@ function install()
mkdir -p "${log_path}" || failed "failed to create ${log_path}" mkdir -p "${log_path}" || failed "failed to create ${log_path}"
echo Creating ${PLIST_PATH} echo Creating ${PLIST_PATH}
sed "s/{{User}}/${SUDO_USER:-$USER}/g; s/{{SvcName}}/$SVC_NAME/g; s@{{RunnerRoot}}@${RUNNER_ROOT}@g; s@{{UserHome}}@$HOME@g;" "${TEMPLATE_PATH}" > "${TEMP_PATH}" || failed "failed to create replacement temp file" sed "s/{{User}}/${USER:-$SUDO_USER}/g; s/{{SvcName}}/$SVC_NAME/g; s@{{RunnerRoot}}@${RUNNER_ROOT}@g; s@{{UserHome}}@$HOME@g;" "${TEMPLATE_PATH}" > "${TEMP_PATH}" || failed "failed to create replacement temp file"
mv "${TEMP_PATH}" "${PLIST_PATH}" || failed "failed to copy plist" mv "${TEMP_PATH}" "${PLIST_PATH}" || failed "failed to copy plist"
# Since we started with sudo, runsvc.sh will be owned by root. Change this to current login user. # Since we started with sudo, runsvc.sh will be owned by root. Change this to current login user.

View File

@@ -1557,12 +1557,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result; return result;
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const glob = __importStar(__webpack_require__(281));
const crypto = __importStar(__webpack_require__(417)); const crypto = __importStar(__webpack_require__(417));
const fs = __importStar(__webpack_require__(747)); const fs = __importStar(__webpack_require__(747));
const glob = __importStar(__webpack_require__(281));
const path = __importStar(__webpack_require__(622));
const stream = __importStar(__webpack_require__(413)); const stream = __importStar(__webpack_require__(413));
const util = __importStar(__webpack_require__(669)); const util = __importStar(__webpack_require__(669));
const path = __importStar(__webpack_require__(622));
function run() { function run() {
var e_1, _a; var e_1, _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
@@ -1621,7 +1621,15 @@ function run() {
} }
}); });
} }
run(); run()
.then(out => {
console.log(out);
process.exit(0);
})
.catch(err => {
console.error(err);
process.exit(1);
});
/***/ }), /***/ }),

View File

@@ -30,13 +30,13 @@ date "+[%F %T-%4N] Waiting for $runnerprocessname ($runnerpid) to complete" >> "
while [ -e /proc/$runnerpid ] while [ -e /proc/$runnerpid ]
do do
date "+[%F %T-%4N] Process $runnerpid still running" >> "$logfile" 2>&1 date "+[%F %T-%4N] Process $runnerpid still running" >> "$logfile" 2>&1
sleep 2 "$rootfolder"/safe_sleep.sh 2
done done
date "+[%F %T-%4N] Process $runnerpid finished running" >> "$logfile" 2>&1 date "+[%F %T-%4N] Process $runnerpid finished running" >> "$logfile" 2>&1
# start re-organize folders # start re-organize folders
date "+[%F %T-%4N] Sleep 1 more second to make sure process exited" >> "$logfile" 2>&1 date "+[%F %T-%4N] Sleep 1 more second to make sure process exited" >> "$logfile" 2>&1
sleep 1 "$rootfolder"/safe_sleep.sh 1
# the folder structure under runner root will be # the folder structure under runner root will be
# ./bin -> bin.2.100.0 (junction folder) # ./bin -> bin.2.100.0 (junction folder)

View File

@@ -10,22 +10,6 @@ fi
# Run # Run
shopt -s nocasematch shopt -s nocasematch
safe_sleep() {
if [ ! -x "$(command -v sleep)" ]; then
if [ ! -x "$(command -v ping)" ]; then
COUNT="0"
while [[ $COUNT != 5000 ]]; do
echo "SLEEP" > /dev/null
COUNT=$[$COUNT+1]
done
else
ping -c 5 127.0.0.1 > /dev/null
fi
else
sleep 5
fi
}
SOURCE="${BASH_SOURCE[0]}" SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
@@ -44,18 +28,18 @@ elif [[ $returnCode == 1 ]]; then
exit 0 exit 0
elif [[ $returnCode == 2 ]]; then elif [[ $returnCode == 2 ]]; then
echo "Runner listener exit with retryable error, re-launch runner in 5 seconds." echo "Runner listener exit with retryable error, re-launch runner in 5 seconds."
safe_sleep "$DIR"/safe_sleep.sh 5
exit 1 exit 2
elif [[ $returnCode == 3 ]]; then elif [[ $returnCode == 3 ]]; then
# Sleep 5 seconds to wait for the runner update process finish # Sleep 5 seconds to wait for the runner update process finish
echo "Runner listener exit because of updating, re-launch runner in 5 seconds" echo "Runner listener exit because of updating, re-launch runner in 5 seconds"
safe_sleep "$DIR"/safe_sleep.sh 5
exit 1 exit 2
elif [[ $returnCode == 4 ]]; then elif [[ $returnCode == 4 ]]; then
# Sleep 5 seconds to wait for the ephemeral runner update process finish # Sleep 5 seconds to wait for the ephemeral runner update process finish
echo "Runner listener exit because of updating, re-launch ephemeral runner in 5 seconds" echo "Runner listener exit because of updating, re-launch ephemeral runner in 5 seconds"
safe_sleep "$DIR"/safe_sleep.sh 5
exit 1 exit 2
else else
echo "Exiting with unknown error code: ${returnCode}" echo "Exiting with unknown error code: ${returnCode}"
exit 0 exit 0

View File

@@ -15,7 +15,7 @@ while :;
do do
"$DIR"/run-helper.sh $* "$DIR"/run-helper.sh $*
returnCode=$? returnCode=$?
if [[ $returnCode == 1 ]]; then if [[ $returnCode -eq 2 ]]; then
echo "Restarting runner..." echo "Restarting runner..."
else else
echo "Exiting runner..." echo "Exiting runner..."

View File

@@ -0,0 +1,6 @@
#!/bin/bash
SECONDS=0
while [[ $SECONDS != $1 ]]; do
:
done

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using GitHub.DistributedTask.WebApi;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Runner.Common.Util;
using GitHub.Services.WebApi;
using GitHub.Services.Common;
using GitHub.Runner.Sdk;
using System.Net;
using System.Net.Http;
namespace GitHub.Runner.Common
{
[ServiceLocator(Default = typeof(BrokerServer))]
public interface IBrokerServer : IRunnerService
{
Task ConnectAsync(Uri serverUrl, CancellationToken cancellationToken);
Task<string> GetMessageAsync(TaskAgentSession session, RunnerSettings settings, long? lastMessageId, CancellationToken cancellationToken);
}
public sealed class BrokerServer : RunnerService, IBrokerServer
{
private HttpClient _httpClient;
public async Task ConnectAsync(Uri serverUrl, CancellationToken cancellationToken)
{
_httpClient = new HttpClient();
_httpClient.BaseAddress = serverUrl;
_httpClient.Timeout = TimeSpan.FromSeconds(100);
await _httpClient.GetAsync("health", cancellationToken);
}
public async Task<string> GetMessageAsync(TaskAgentSession session, RunnerSettings settings, long? lastMessageId, CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync($"message?tenant=org:github&root_tenant=org:github&group_id={settings.PoolId}&group_name={settings.PoolName}&runner_id={settings.AgentId}&runner_name={settings.AgentName}&labels=self-hosted,linux", cancellationToken);
if (!response.IsSuccessStatusCode)
{
var content = default(string);
try
{
content = await response.Content.ReadAsStringAsync();
}
catch
{
}
var error = $"HTTP {(int)response.StatusCode} {Enum.GetName(typeof(HttpStatusCode), response.StatusCode)}";
if (!string.IsNullOrEmpty(content))
{
error = $"{error}: {content}";
}
throw new Exception(error);
}
return await response.Content.ReadAsStringAsync();
}
}
}

View File

@@ -149,6 +149,7 @@ namespace GitHub.Runner.Common
public static class Features public static class Features
{ {
public static readonly string DiskSpaceWarning = "runner.diskspace.warning"; public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
public static readonly string Node12Warning = "DistributedTask.AddWarningToNode12Action";
} }
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry"; public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
@@ -156,7 +157,9 @@ namespace GitHub.Runner.Common
public static readonly string LowDiskSpace = "LOW_DISK_SPACE"; public static readonly string LowDiskSpace = "LOW_DISK_SPACE";
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND"; public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/"; public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`."; public static readonly string UnsupportedStopCommandTokenDisabled = "You cannot use a endToken that is an empty string, the string 'pause-logging', or another workflow command. For more information see: https://docs.github.com/actions/learn-github-actions/workflow-commands-for-github-actions#example-stopping-and-starting-workflow-commands or opt into insecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS` environment variable to `true`.";
public static readonly string UnsupportedSummarySize = "$GITHUB_STEP_SUMMARY upload aborted, supports content up to a size of {0}k, got {1}k. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
public static readonly string Node12DetectedAfterEndOfLife = "Node.js 12 actions are deprecated. Please update the following actions to use Node.js 16: {0}";
} }
public static class RunnerEvent public static class RunnerEvent
@@ -188,6 +191,12 @@ namespace GitHub.Runner.Common
public static readonly string Success = "success"; public static readonly string Success = "success";
} }
public static class Hooks
{
public static readonly string JobStartedStepName = "Set up runner";
public static readonly string JobCompletedStepName = "Complete runner";
}
public static class Path public static class Path
{ {
public static readonly string ActionsDirectory = "_actions"; public static readonly string ActionsDirectory = "_actions";
@@ -219,14 +228,15 @@ namespace GitHub.Runner.Common
public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS"; public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS";
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 class Agent public static class Agent
{ {
public static readonly string ToolsDirectory = "agent.ToolsDirectory"; public static readonly string ToolsDirectory = "agent.ToolsDirectory";
// Set this env var to force a node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions. // Set this env var to "node12" to downgrade the node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
public static readonly string ForcedNodeVersion = "GITHUB_ACTIONS_RUNNER_FORCED_NODE_VERSION"; public static readonly string ForcedInternalNodeVersion = "ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION";
} }
public static class System public static class System

View File

@@ -216,6 +216,8 @@ namespace GitHub.Runner.Common
_userAgents.Add(new ProductInfoHeaderValue("RunnerId", runnerSettings.AgentId.ToString(CultureInfo.InvariantCulture))); _userAgents.Add(new ProductInfoHeaderValue("RunnerId", runnerSettings.AgentId.ToString(CultureInfo.InvariantCulture)));
_userAgents.Add(new ProductInfoHeaderValue("GroupId", runnerSettings.PoolId.ToString(CultureInfo.InvariantCulture))); _userAgents.Add(new ProductInfoHeaderValue("GroupId", runnerSettings.PoolId.ToString(CultureInfo.InvariantCulture)));
} }
_userAgents.Add(new ProductInfoHeaderValue("CommitSHA", BuildConstants.Source.CommitHash));
} }
public string GetDirectory(WellKnownDirectory directory) public string GetDirectory(WellKnownDirectory directory)

View File

@@ -2,24 +2,31 @@ using GitHub.DistributedTask.WebApi;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.WebSockets;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Services.Common; using GitHub.Services.Common;
using GitHub.Services.WebApi; using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Utilities.Internal;
using Newtonsoft.Json;
namespace GitHub.Runner.Common namespace GitHub.Runner.Common
{ {
[ServiceLocator(Default = typeof(JobServer))] [ServiceLocator(Default = typeof(JobServer))]
public interface IJobServer : IRunnerService public interface IJobServer : IRunnerService, IAsyncDisposable
{ {
Task ConnectAsync(VssConnection jobConnection); Task ConnectAsync(VssConnection jobConnection);
void InitializeWebsocketClient(ServiceEndpoint serviceEndpoint);
// logging and console // logging and console
Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken); Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken);
Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, CancellationToken cancellationToken); Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken);
Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long startLine, CancellationToken cancellationToken);
Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken); Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, String type, String name, Stream uploadStream, CancellationToken cancellationToken);
Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken); Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken);
Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken); Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
@@ -34,6 +41,20 @@ namespace GitHub.Runner.Common
private bool _hasConnection; private bool _hasConnection;
private VssConnection _connection; private VssConnection _connection;
private TaskHttpClient _taskClient; private TaskHttpClient _taskClient;
private ClientWebSocket _websocketClient;
private ServiceEndpoint _serviceEndpoint;
private int totalBatchedLinesAttemptedByWebsocket = 0;
private int failedAttemptsToPostBatchedLinesByWebsocket = 0;
private static readonly TimeSpan _minDelayForWebsocketReconnect = TimeSpan.FromMilliseconds(100);
private static readonly TimeSpan _maxDelayForWebsocketReconnect = TimeSpan.FromMilliseconds(500);
private static readonly int _minWebsocketFailurePercentageAllowed = 50;
private static readonly int _minWebsocketBatchedLinesCountToConsider = 5;
private Task _websocketConnectTask;
public async Task ConnectAsync(VssConnection jobConnection) public async Task ConnectAsync(VssConnection jobConnection)
{ {
@@ -117,6 +138,21 @@ namespace GitHub.Runner.Common
} }
} }
public void InitializeWebsocketClient(ServiceEndpoint serviceEndpoint)
{
this._serviceEndpoint = serviceEndpoint;
InitializeWebsocketClient(TimeSpan.Zero);
}
public ValueTask DisposeAsync()
{
CloseWebSocket(WebSocketCloseStatus.NormalClosure, CancellationToken.None);
GC.SuppressFinalize(this);
return ValueTask.CompletedTask;
}
private void CheckConnection() private void CheckConnection()
{ {
if (!_hasConnection) if (!_hasConnection)
@@ -125,6 +161,53 @@ namespace GitHub.Runner.Common
} }
} }
private void InitializeWebsocketClient(TimeSpan delay)
{
if (_serviceEndpoint.Authorization != null &&
_serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out var accessToken) &&
!string.IsNullOrEmpty(accessToken))
{
if (_serviceEndpoint.Data.TryGetValue("FeedStreamUrl", out var feedStreamUrl) && !string.IsNullOrEmpty(feedStreamUrl))
{
// let's ensure we use the right scheme
feedStreamUrl = feedStreamUrl.Replace("https://", "wss://").Replace("http://", "ws://");
Trace.Info($"Creating websocket client ..." + feedStreamUrl);
this._websocketClient = new ClientWebSocket();
this._websocketClient.Options.SetRequestHeader("Authorization", $"Bearer {accessToken}");
var userAgentValues = new List<ProductInfoHeaderValue>();
userAgentValues.AddRange(UserAgentUtility.GetDefaultRestUserAgent());
userAgentValues.AddRange(HostContext.UserAgents);
this._websocketClient.Options.SetRequestHeader("User-Agent", string.Join(" ", userAgentValues.Select(x=>x.ToString())));
this._websocketConnectTask = ConnectWebSocketClient(feedStreamUrl, delay);
}
else
{
Trace.Info($"No FeedStreamUrl found, so we will use Rest API calls for sending feed data");
}
}
else
{
Trace.Info($"No access token from the service endpoint");
}
}
private async Task ConnectWebSocketClient(string feedStreamUrl, TimeSpan delay)
{
try
{
Trace.Info($"Attempting to start websocket client with delay {delay}.");
await Task.Delay(delay);
await this._websocketClient.ConnectAsync(new Uri(feedStreamUrl), default(CancellationToken));
Trace.Info($"Successfully started websocket client.");
}
catch(Exception ex)
{
Trace.Info("Exception caught during websocket client connect, fallback of HTTP would be used now instead of websocket.");
Trace.Error(ex);
}
}
//----------------------------------------------------------------- //-----------------------------------------------------------------
// Feedback: WebConsole, TimelineRecords and Logs // Feedback: WebConsole, TimelineRecords and Logs
//----------------------------------------------------------------- //-----------------------------------------------------------------
@@ -135,16 +218,86 @@ namespace GitHub.Runner.Common
return _taskClient.AppendLogContentAsync(scopeIdentifier, hubName, planId, logId, uploadStream, cancellationToken: cancellationToken); return _taskClient.AppendLogContentAsync(scopeIdentifier, hubName, planId, logId, uploadStream, cancellationToken: cancellationToken);
} }
public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, CancellationToken cancellationToken) public async Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken)
{ {
CheckConnection(); CheckConnection();
return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken); var pushedLinesViaWebsocket = false;
if (_websocketConnectTask != null)
{
await _websocketConnectTask;
}
// "_websocketClient != null" implies either: We have a successful connection OR we have to attempt sending again and then reconnect
// ...in other words, if websocket client is null, we will skip sending to websocket and just use rest api calls to send data
if (_websocketClient != null)
{
var linesWrapper = startLine.HasValue? new TimelineRecordFeedLinesWrapper(stepId, lines, startLine.Value): new TimelineRecordFeedLinesWrapper(stepId, lines);
var jsonData = StringUtil.ConvertToJson(linesWrapper);
try
{
totalBatchedLinesAttemptedByWebsocket++;
var jsonDataBytes = Encoding.UTF8.GetBytes(jsonData);
// break the message into chunks of 1024 bytes
for (var i = 0; i < jsonDataBytes.Length; i += 1 * 1024)
{
var lastChunk = i + (1 * 1024) >= jsonDataBytes.Length;
var chunk = new ArraySegment<byte>(jsonDataBytes, i, Math.Min(1 * 1024, jsonDataBytes.Length - i));
await _websocketClient.SendAsync(chunk, WebSocketMessageType.Text, endOfMessage:lastChunk, cancellationToken);
}
pushedLinesViaWebsocket = true;
}
catch (Exception ex)
{
failedAttemptsToPostBatchedLinesByWebsocket++;
Trace.Info($"Caught exception during append web console line to websocket, let's fallback to sending via non-websocket call (total calls: {totalBatchedLinesAttemptedByWebsocket}, failed calls: {failedAttemptsToPostBatchedLinesByWebsocket}, websocket state: {this._websocketClient?.State}).");
Trace.Error(ex);
if (totalBatchedLinesAttemptedByWebsocket > _minWebsocketBatchedLinesCountToConsider)
{
// let's consider failure percentage
if (failedAttemptsToPostBatchedLinesByWebsocket * 100 / totalBatchedLinesAttemptedByWebsocket > _minWebsocketFailurePercentageAllowed)
{
Trace.Info($"Exhausted websocket allowed retries, we will not attempt websocket connection for this job to post lines again.");
CloseWebSocket(WebSocketCloseStatus.InternalServerError, cancellationToken);
// By setting it to null, we will ensure that we never try websocket path again for this job
_websocketClient = null;
}
}
if (_websocketClient != null)
{
var delay = BackoffTimerHelper.GetRandomBackoff(_minDelayForWebsocketReconnect, _maxDelayForWebsocketReconnect);
Trace.Info($"Websocket is not open, let's attempt to connect back again with random backoff {delay} ms (total calls: {totalBatchedLinesAttemptedByWebsocket}, failed calls: {failedAttemptsToPostBatchedLinesByWebsocket}).");
InitializeWebsocketClient(delay);
}
}
}
if (!pushedLinesViaWebsocket)
{
if (startLine.HasValue)
{
await _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, startLine.Value, cancellationToken: cancellationToken);
}
else
{
await _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken);
}
}
} }
public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long startLine, CancellationToken cancellationToken) private void CloseWebSocket(WebSocketCloseStatus closeStatus, CancellationToken cancellationToken)
{ {
CheckConnection(); try
return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, startLine, cancellationToken: cancellationToken); {
_websocketClient?.CloseOutputAsync(closeStatus, "Closing websocket", cancellationToken);
}
catch (Exception websocketEx)
{
// In some cases this might be okay since the websocket might be open yet, so just close and don't trace exceptions
Trace.Info($"Failed to close websocket gracefully {websocketEx.GetType().Name}");
}
} }
public Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, string type, string name, Stream uploadStream, CancellationToken cancellationToken) public Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, string type, string name, Stream uploadStream, CancellationToken cancellationToken)

View File

@@ -71,7 +71,7 @@ namespace GitHub.Runner.Common
// Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds). // Web console dequeue will start with process queue every 250ms for the first 60*4 times (~60 seconds).
// Then the dequeue will happen every 500ms. // Then the dequeue will happen every 500ms.
// In this way, customer still can get instance live console output on job start, // In this way, customer still can get instance live console output on job start,
// at the same time we can cut the load to server after the build run for more than 60s // at the same time we can cut the load to server after the build run for more than 60s
private int _webConsoleLineAggressiveDequeueCount = 0; private int _webConsoleLineAggressiveDequeueCount = 0;
private const int _webConsoleLineAggressiveDequeueLimit = 4 * 60; private const int _webConsoleLineAggressiveDequeueLimit = 4 * 60;
@@ -89,6 +89,10 @@ namespace GitHub.Runner.Common
{ {
Trace.Entering(); Trace.Entering();
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
_jobServer.InitializeWebsocketClient(serviceEndPoint);
if (_queueInProcess) if (_queueInProcess)
{ {
Trace.Info("No-opt, all queue process tasks are running."); Trace.Info("No-opt, all queue process tasks are running.");
@@ -156,6 +160,9 @@ namespace GitHub.Runner.Common
await ProcessTimelinesUpdateQueueAsync(runOnce: true); await ProcessTimelinesUpdateQueueAsync(runOnce: true);
Trace.Info("Timeline update queue drained."); Trace.Info("Timeline update queue drained.");
Trace.Info($"Disposing job server ...");
await _jobServer.DisposeAsync();
Trace.Info("All queue process tasks have been stopped, and all queues are drained."); Trace.Info("All queue process tasks have been stopped, and all queues are drained.");
} }
@@ -292,15 +299,7 @@ namespace GitHub.Runner.Common
{ {
try try
{ {
// we will not requeue failed batch, since the web console lines are time sensitive. await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber, default(CancellationToken));
if (batch[0].LineNumber.HasValue)
{
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), batch[0].LineNumber.Value, default(CancellationToken));
}
else
{
await _jobServer.AppendTimelineRecordFeedAsync(_scopeIdentifier, _hubName, _planId, _jobTimelineId, _jobTimelineRecordId, stepRecordId, batch.Select(logLine => logLine.Line).ToList(), default(CancellationToken));
}
if (_firstConsoleOutputs) if (_firstConsoleOutputs)
{ {
@@ -489,8 +488,8 @@ namespace GitHub.Runner.Common
if (runOnce) if (runOnce)
{ {
// continue process timeline records update, // continue process timeline records update,
// we might have more records need update, // we might have more records need update,
// since we just create a new sub-timeline // since we just create a new sub-timeline
if (pendingSubtimelineUpdate) if (pendingSubtimelineUpdate)
{ {

View File

@@ -0,0 +1,120 @@
using GitHub.DistributedTask.WebApi;
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Runner.Common.Util;
using GitHub.Services.WebApi;
using GitHub.Services.Common;
using GitHub.Runner.Sdk;
using GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Common
{
[ServiceLocator(Default = typeof(RunServer))]
public interface IRunServer : IRunnerService
{
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
Task<AgentJobRequestMessage> GetJobMessageAsync(Guid scopeId, Guid hostId, string planType, string planGroup, Guid planId, IList<InstanceRef> instanceRefsJson);
}
public sealed class RunServer : RunnerService, IRunServer
{
private bool _hasConnection;
private VssConnection _connection;
private TaskAgentHttpClient _taskAgentClient;
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
{
// System.Console.WriteLine("RunServer.ConnectAsync");
_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)
{
// System.Console.WriteLine("EstablishVssConnection");
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(
Guid scopeId,
Guid hostId,
string planType,
string planGroup,
Guid planId,
IList<InstanceRef> instanceRefsJson)
{
// System.Console.WriteLine("RunServer.GetMessageAsync");
CheckConnection();
return _taskAgentClient.GetJobMessageAsync(scopeId, hostId, planType, planGroup, planId, StringUtil.ConvertToJson(instanceRefsJson, Newtonsoft.Json.Formatting.None));
}
}
// todo: move to SDK?
[DataContract]
public sealed class MessageRef
{
[DataMember(Name = "url")]
public string Url { get; set; }
[DataMember(Name = "token")]
public string Token { get; set; }
[DataMember(Name = "scopeId")]
public Guid ScopeId { get; set; }
[DataMember(Name = "hostId")]
public Guid HostId { get; set; }
[DataMember(Name = "planType")]
public string PlanType { get; set; }
[DataMember(Name = "planGroup")]
public string PlanGroup { get; set; }
[DataMember(Name = "planId")]
public Guid PlanId { get; set; }
[DataMember(Name = "instanceRefs")]
public InstanceRef[] InstanceRefs { get; set; }
[DataMember(Name = "labels")]
public string[] Labels { get; set; }
}
[DataContract]
public sealed class InstanceRef
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "instanceType")]
public string InstanceType { get; set; }
[DataMember(Name = "attempt")]
public int Attempt { get; set; }
}
}

View File

@@ -44,7 +44,7 @@ namespace GitHub.Runner.Common
// job request // job request
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken); Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId, CancellationToken cancellationToken); Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId, CancellationToken cancellationToken);
Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken); Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, Guid targetHostId, CancellationToken cancellationToken);
// agent package // agent package
Task<List<PackageMetadata>> GetPackagesAsync(string packageType, string platform, int top, bool includeToken, CancellationToken cancellationToken); Task<List<PackageMetadata>> GetPackagesAsync(string packageType, string platform, int top, bool includeToken, CancellationToken cancellationToken);
@@ -68,11 +68,23 @@ namespace GitHub.Runner.Common
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials) public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
{ {
var createGenericConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100)); // System.Console.WriteLine("RunnerServer.ConnectAsync: Create message connection");
var createMessageConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60)); var createMessageConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
var createRequestConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60)); await Task.WhenAll(createMessageConnection);
await Task.WhenAll(createGenericConnection, createMessageConnection, createRequestConnection); // System.Console.WriteLine("RunnerServer.ConnectAsync: Create generic connection");
var createGenericConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
await Task.WhenAll(createGenericConnection);
// System.Console.WriteLine("RunnerServer.ConnectAsync: Create request connection");
var createRequestConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
await Task.WhenAll(createRequestConnection);
// var createGenericConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
// var createMessageConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
// var createRequestConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
// await Task.WhenAll(createGenericConnection, createMessageConnection, createRequestConnection);
_genericConnection = await createGenericConnection; _genericConnection = await createGenericConnection;
_messageConnection = await createMessageConnection; _messageConnection = await createMessageConnection;
@@ -182,6 +194,8 @@ namespace GitHub.Runner.Common
private async Task<VssConnection> EstablishVssConnection(Uri serverUrl, VssCredentials credentials, TimeSpan timeout) private async Task<VssConnection> EstablishVssConnection(Uri serverUrl, VssCredentials credentials, TimeSpan timeout)
{ {
// System.Console.WriteLine("EstablishVssConnection");
Trace.Info($"EstablishVssConnection");
Trace.Info($"Establish connection with {timeout.TotalSeconds} seconds timeout."); Trace.Info($"Establish connection with {timeout.TotalSeconds} seconds timeout.");
int attemptCount = 5; int attemptCount = 5;
while (attemptCount-- > 0) while (attemptCount-- > 0)
@@ -238,41 +252,48 @@ namespace GitHub.Runner.Common
public Task<List<TaskAgentPool>> GetAgentPoolsAsync(string agentPoolName = null, TaskAgentPoolType poolType = TaskAgentPoolType.Automation) public Task<List<TaskAgentPool>> GetAgentPoolsAsync(string agentPoolName = null, TaskAgentPoolType poolType = TaskAgentPoolType.Automation)
{ {
// System.Console.WriteLine("RunnerServer.GetAgentPoolsAsync");
CheckConnection(RunnerConnectionType.Generic); CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.GetAgentPoolsAsync(agentPoolName, poolType: poolType); return _genericTaskAgentClient.GetAgentPoolsAsync(agentPoolName, poolType: poolType);
} }
public Task<TaskAgent> AddAgentAsync(Int32 agentPoolId, TaskAgent agent) public Task<TaskAgent> AddAgentAsync(Int32 agentPoolId, TaskAgent agent)
{ {
// System.Console.WriteLine("RunnerServer.AddAgentAsync");
CheckConnection(RunnerConnectionType.Generic); CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.AddAgentAsync(agentPoolId, agent); return _genericTaskAgentClient.AddAgentAsync(agentPoolId, agent);
} }
public Task<List<TaskAgent>> GetAgentsAsync(int agentPoolId, string agentName = null) public Task<List<TaskAgent>> GetAgentsAsync(int agentPoolId, string agentName = null)
{ {
// System.Console.WriteLine("RunnerServer.GetAgentsAsync 1");
CheckConnection(RunnerConnectionType.Generic); CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.GetAgentsAsync(agentPoolId, agentName, false); return _genericTaskAgentClient.GetAgentsAsync(agentPoolId, agentName, false);
} }
public Task<List<TaskAgent>> GetAgentsAsync(string agentName) public Task<List<TaskAgent>> GetAgentsAsync(string agentName)
{ {
// System.Console.WriteLine("RunnerServer.GetAgentsAsync 2");
return GetAgentsAsync(0, agentName); // search in all all agentPools return GetAgentsAsync(0, agentName); // search in all all agentPools
} }
public Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent) public Task<TaskAgent> ReplaceAgentAsync(int agentPoolId, TaskAgent agent)
{ {
// System.Console.WriteLine("RunnerServer.ReplaceAgentAsync");
CheckConnection(RunnerConnectionType.Generic); CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.ReplaceAgentAsync(agentPoolId, agent); return _genericTaskAgentClient.ReplaceAgentAsync(agentPoolId, agent);
} }
public Task DeleteAgentAsync(int agentPoolId, int agentId) public Task DeleteAgentAsync(int agentPoolId, int agentId)
{ {
// System.Console.WriteLine("RunnerServer.DeleteAgentAsync");
CheckConnection(RunnerConnectionType.Generic); CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.DeleteAgentAsync(agentPoolId, agentId); return _genericTaskAgentClient.DeleteAgentAsync(agentPoolId, agentId);
} }
public Task DeleteAgentAsync(int agentId) public Task DeleteAgentAsync(int agentId)
{ {
// System.Console.WriteLine("RunnerServer.DeleteAgentAsync");
return DeleteAgentAsync(0, agentId); // agentPool is ignored server side return DeleteAgentAsync(0, agentId); // agentPool is ignored server side
} }
@@ -282,24 +303,28 @@ namespace GitHub.Runner.Common
public Task<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken) public Task<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken)
{ {
// System.Console.WriteLine("RunnerServer.CreateAgentSessionAsync");
CheckConnection(RunnerConnectionType.MessageQueue); CheckConnection(RunnerConnectionType.MessageQueue);
return _messageTaskAgentClient.CreateAgentSessionAsync(poolId, session, cancellationToken: cancellationToken); return _messageTaskAgentClient.CreateAgentSessionAsync(poolId, session, cancellationToken: cancellationToken);
} }
public Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken) public Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken)
{ {
// System.Console.WriteLine("RunnerServer.DeleteAgentMessageAsync");
CheckConnection(RunnerConnectionType.MessageQueue); CheckConnection(RunnerConnectionType.MessageQueue);
return _messageTaskAgentClient.DeleteMessageAsync(poolId, messageId, sessionId, cancellationToken: cancellationToken); return _messageTaskAgentClient.DeleteMessageAsync(poolId, messageId, sessionId, cancellationToken: cancellationToken);
} }
public Task DeleteAgentSessionAsync(Int32 poolId, Guid sessionId, CancellationToken cancellationToken) public Task DeleteAgentSessionAsync(Int32 poolId, Guid sessionId, CancellationToken cancellationToken)
{ {
// System.Console.WriteLine("RunnerServer.DeleteAgentSessionAsync");
CheckConnection(RunnerConnectionType.MessageQueue); CheckConnection(RunnerConnectionType.MessageQueue);
return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken); return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken);
} }
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, CancellationToken cancellationToken) public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, CancellationToken cancellationToken)
{ {
// System.Console.WriteLine("RunnerServer.GetAgentMessageAsync");
CheckConnection(RunnerConnectionType.MessageQueue); CheckConnection(RunnerConnectionType.MessageQueue);
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, cancellationToken: cancellationToken); return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, cancellationToken: cancellationToken);
} }
@@ -310,18 +335,21 @@ namespace GitHub.Runner.Common
public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId = null, CancellationToken cancellationToken = default(CancellationToken)) public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId = null, CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("RunnerServer.RenewAgentRequestAsync");
CheckConnection(RunnerConnectionType.JobRequest); CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId: orchestrationId, cancellationToken: cancellationToken); return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId: orchestrationId, cancellationToken: cancellationToken);
} }
public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken)) public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, Guid targetHostId, CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("RunnerServer.FinishAgentRequestAsync");
CheckConnection(RunnerConnectionType.JobRequest); CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.FinishAgentRequestAsync(poolId, requestId, lockToken, finishTime, result, cancellationToken: cancellationToken); return _requestTaskAgentClient.FinishAgentRequestAsync(poolId, requestId, lockToken, finishTime, result, targetHostId, cancellationToken: cancellationToken);
} }
public Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken = default(CancellationToken)) public Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("RunnerServer.GetAgentRequestAsync");
CheckConnection(RunnerConnectionType.JobRequest); CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.GetAgentRequestAsync(poolId, requestId, cancellationToken: cancellationToken); return _requestTaskAgentClient.GetAgentRequestAsync(poolId, requestId, cancellationToken: cancellationToken);
} }
@@ -331,18 +359,21 @@ namespace GitHub.Runner.Common
//----------------------------------------------------------------- //-----------------------------------------------------------------
public Task<List<PackageMetadata>> GetPackagesAsync(string packageType, string platform, int top, bool includeToken, CancellationToken cancellationToken) public Task<List<PackageMetadata>> GetPackagesAsync(string packageType, string platform, int top, bool includeToken, CancellationToken cancellationToken)
{ {
// System.Console.WriteLine("RunnerServer.GetPackagesAsync");
CheckConnection(RunnerConnectionType.Generic); CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.GetPackagesAsync(packageType, platform, top, includeToken, cancellationToken: cancellationToken); return _genericTaskAgentClient.GetPackagesAsync(packageType, platform, top, includeToken, cancellationToken: cancellationToken);
} }
public Task<PackageMetadata> GetPackageAsync(string packageType, string platform, string version, bool includeToken, CancellationToken cancellationToken) public Task<PackageMetadata> GetPackageAsync(string packageType, string platform, string version, bool includeToken, CancellationToken cancellationToken)
{ {
// System.Console.WriteLine("RunnerServer.GetPackageAsync");
CheckConnection(RunnerConnectionType.Generic); CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.GetPackageAsync(packageType, platform, version, includeToken, cancellationToken: cancellationToken); return _genericTaskAgentClient.GetPackageAsync(packageType, platform, version, includeToken, cancellationToken: cancellationToken);
} }
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState, string trace) public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState, string trace)
{ {
// System.Console.WriteLine("RunnerServer.UpdateAgentUpdateStateAsync");
CheckConnection(RunnerConnectionType.Generic); CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace); return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace);
} }

View File

@@ -7,9 +7,9 @@ namespace GitHub.Runner.Common.Util
{ {
private const string _defaultNodeVersion = "node16"; private const string _defaultNodeVersion = "node16";
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] {"node12", "node16"}); public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] {"node12", "node16"});
public static string GetNodeVersion() public static string GetInternalNodeVersion()
{ {
var forcedNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedNodeVersion); var forcedNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion);
return !string.IsNullOrEmpty(forcedNodeVersion) && BuiltInNodeVersions.Contains(forcedNodeVersion) ? forcedNodeVersion : _defaultNodeVersion; return !string.IsNullOrEmpty(forcedNodeVersion) && BuiltInNodeVersions.Contains(forcedNodeVersion) ? forcedNodeVersion : _defaultNodeVersion;
} }
} }

View File

@@ -315,7 +315,7 @@ namespace GitHub.Runner.Listener.Check
}); });
var downloadCertScript = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "downloadCert"); var downloadCertScript = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "downloadCert");
var node = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); var node = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{downloadCertScript}\"' "); result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{downloadCertScript}\"' ");
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}"); result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
await processInvoker.ExecuteAsync( await processInvoker.ExecuteAsync(

View File

@@ -145,7 +145,7 @@ namespace GitHub.Runner.Listener.Check
}); });
var makeWebRequestScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "makeWebRequest.js"); var makeWebRequestScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "makeWebRequest.js");
var node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); var node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{makeWebRequestScript}\"' "); result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node} \"{makeWebRequestScript}\"' ");
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}"); result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
await processInvoker.ExecuteAsync( await processInvoker.ExecuteAsync(

View File

@@ -624,9 +624,12 @@ namespace GitHub.Runner.Listener.Configuration
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken);
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents); httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json"); httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json");
var responseStatus = System.Net.HttpStatusCode.OK;
try try
{ {
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty)); var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty));
responseStatus = response.StatusCode;
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@@ -634,11 +637,6 @@ namespace GitHub.Runner.Listener.Configuration
var jsonResponse = await response.Content.ReadAsStringAsync(); var jsonResponse = await response.Content.ReadAsStringAsync();
return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse); return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse);
} }
else if(response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
// It doesn't make sense to retry in this case, so just stop
break;
}
else else
{ {
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'"); _term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
@@ -647,15 +645,15 @@ namespace GitHub.Runner.Listener.Configuration
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
} }
catch(Exception ex) when (retryCount < 2) catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
{ {
retryCount++; retryCount++;
Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}"); Trace.Error($"Failed to get JIT runner token -- Atempt: {retryCount}");
Trace.Error(ex); Trace.Error(ex);
Trace.Info("Retrying in 5 seconds");
} }
} }
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
Trace.Info($"Retrying in {backOff.Seconds} seconds");
await Task.Delay(backOff); await Task.Delay(backOff);
} }
return null; return null;
@@ -689,9 +687,11 @@ namespace GitHub.Runner.Listener.Configuration
{"runner_event", runnerEvent} {"runner_event", runnerEvent}
}; };
var responseStatus = System.Net.HttpStatusCode.OK;
try try
{ {
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json")); var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"));
responseStatus = response.StatusCode;
if(response.IsSuccessStatusCode) if(response.IsSuccessStatusCode)
{ {
@@ -699,29 +699,23 @@ namespace GitHub.Runner.Listener.Configuration
var jsonResponse = await response.Content.ReadAsStringAsync(); var jsonResponse = await response.Content.ReadAsStringAsync();
return StringUtil.ConvertFromJson<GitHubAuthResult>(jsonResponse); return StringUtil.ConvertFromJson<GitHubAuthResult>(jsonResponse);
} }
else if(response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
// It doesn't make sense to retry in this case, so just stop
break;
}
else else
{ {
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'"); _term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
var errorResponse = await response.Content.ReadAsStringAsync(); var errorResponse = await response.Content.ReadAsStringAsync();
_term.WriteError(errorResponse); _term.WriteError(errorResponse);
// Something else bad happened, let's go to our retry logic
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
} }
} }
catch(Exception ex) when (retryCount < 2) catch(Exception ex) when (retryCount < 2 && responseStatus != System.Net.HttpStatusCode.NotFound)
{ {
retryCount++; retryCount++;
Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}"); Trace.Error($"Failed to get tenant credentials -- Atempt: {retryCount}");
Trace.Error(ex); Trace.Error(ex);
Trace.Info("Retrying in 5 seconds");
} }
} }
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5)); var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
Trace.Info($"Retrying in {backOff.Seconds} seconds");
await Task.Delay(backOff); await Task.Delay(backOff);
} }
return null; return null;

View File

@@ -40,6 +40,12 @@ namespace GitHub.Runner.Listener.Configuration
return creds; return creds;
} }
#if USE_BROKER
public VssCredentials LoadCredentials()
{
return new VssCredentials();
}
#else
public VssCredentials LoadCredentials() public VssCredentials LoadCredentials()
{ {
IConfigurationStore store = HostContext.GetService<IConfigurationStore>(); IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
@@ -69,6 +75,7 @@ namespace GitHub.Runner.Listener.Configuration
return creds; return creds;
} }
#endif
} }
[DataContract] [DataContract]

View File

@@ -48,13 +48,12 @@ namespace GitHub.Runner.Listener.Configuration
string repoOrOrgName = regex.Replace(settings.RepoOrOrgName, "-"); string repoOrOrgName = regex.Replace(settings.RepoOrOrgName, "-");
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgName, settings.AgentName); serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgName, settings.AgentName);
if (serviceName.Length > MaxServiceNameLength)
if (serviceName.Length > 80)
{ {
Trace.Verbose($"Calculated service name is too long (> 80 chars). Trying again by calculating a shorter name."); Trace.Verbose($"Calculated service name is too long (> {MaxServiceNameLength} chars). Trying again by calculating a shorter name.");
// Add 5 to add -xxxx random number on the end
int exceededCharLength = serviceName.Length - 80; int exceededCharLength = serviceName.Length - MaxServiceNameLength + 5;
string repoOrOrgNameSubstring = StringUtil.SubstringPrefix(repoOrOrgName, 45); string repoOrOrgNameSubstring = StringUtil.SubstringPrefix(repoOrOrgName, MaxRepoOrgCharacters);
exceededCharLength -= repoOrOrgName.Length - repoOrOrgNameSubstring.Length; exceededCharLength -= repoOrOrgName.Length - repoOrOrgNameSubstring.Length;
@@ -66,6 +65,10 @@ namespace GitHub.Runner.Listener.Configuration
runnerNameSubstring = StringUtil.SubstringPrefix(settings.AgentName, settings.AgentName.Length - exceededCharLength); runnerNameSubstring = StringUtil.SubstringPrefix(settings.AgentName, settings.AgentName.Length - exceededCharLength);
} }
// Lets add a suffix with a random number to reduce the chance of collisions between runner names once we truncate
var random = new Random();
var num = random.Next(1000, 9999).ToString();
runnerNameSubstring +=$"-{num}";
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgNameSubstring, runnerNameSubstring); serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgNameSubstring, runnerNameSubstring);
} }
@@ -73,5 +76,12 @@ namespace GitHub.Runner.Listener.Configuration
Trace.Info($"Service name '{serviceName}' display name '{serviceDisplayName}' will be used for service configuration."); Trace.Info($"Service name '{serviceName}' display name '{serviceDisplayName}' will be used for service configuration.");
} }
#if (OS_LINUX || OS_OSX)
const int MaxServiceNameLength = 150;
const int MaxRepoOrgCharacters = 70;
#elif OS_WINDOWS
const int MaxServiceNameLength = 80;
const int MaxRepoOrgCharacters = 45;
#endif
} }
} }

View File

@@ -23,7 +23,7 @@ namespace GitHub.Runner.Listener
{ {
bool Busy { get; } bool Busy { get; }
TaskCompletionSource<bool> RunOnceJobCompleted { get; } TaskCompletionSource<bool> RunOnceJobCompleted { get; }
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false); void Run(Guid targetHostId, Pipelines.AgentJobRequestMessage message, bool runOnce = false);
bool Cancel(JobCancelMessage message); bool Cancel(JobCancelMessage message);
Task WaitAsync(CancellationToken token); Task WaitAsync(CancellationToken token);
Task ShutdownAsync(); Task ShutdownAsync();
@@ -79,7 +79,7 @@ namespace GitHub.Runner.Listener
public bool Busy { get; private set; } public bool Busy { get; private set; }
public void Run(Pipelines.AgentJobRequestMessage jobRequestMessage, bool runOnce = false) public void Run(Guid targetHostId, Pipelines.AgentJobRequestMessage jobRequestMessage, bool runOnce = false)
{ {
Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received."); Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received.");
@@ -112,11 +112,11 @@ namespace GitHub.Runner.Listener
if (runOnce) if (runOnce)
{ {
Trace.Info("Start dispatcher for one time used runner."); Trace.Info("Start dispatcher for one time used runner.");
newDispatch.WorkerDispatch = RunOnceAsync(jobRequestMessage, orchestrationId, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token); newDispatch.WorkerDispatch = RunOnceAsync(targetHostId, jobRequestMessage, orchestrationId, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token);
} }
else else
{ {
newDispatch.WorkerDispatch = RunAsync(jobRequestMessage, orchestrationId, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token); newDispatch.WorkerDispatch = RunAsync(targetHostId, jobRequestMessage, orchestrationId, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token);
} }
_jobInfos.TryAdd(newDispatch.JobId, newDispatch); _jobInfos.TryAdd(newDispatch.JobId, newDispatch);
@@ -285,7 +285,7 @@ namespace GitHub.Runner.Listener
{ {
// at this point, the job execution might encounter some dead lock and even not able to be cancelled. // at this point, the job execution might encounter some dead lock and even not able to be cancelled.
// no need to localize the exception string should never happen. // no need to localize the exception string should never happen.
throw new InvalidOperationException($"Job dispatch process for {jobDispatch.JobId} has encountered unexpected error, the dispatch task is not able to be canceled within 45 seconds."); throw new InvalidOperationException($"Job dispatch process for {jobDispatch.JobId} has encountered unexpected error, the dispatch task is not able to be cancelled within 45 seconds.");
} }
} }
else else
@@ -317,11 +317,11 @@ namespace GitHub.Runner.Listener
} }
} }
private async Task RunOnceAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken) private async Task RunOnceAsync(Guid targetHostId, Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
{ {
try try
{ {
await RunAsync(message, orchestrationId, previousJobDispatch, jobRequestCancellationToken, workerCancelTimeoutKillToken); await RunAsync(targetHostId, message, orchestrationId, previousJobDispatch, jobRequestCancellationToken, workerCancelTimeoutKillToken);
} }
finally finally
{ {
@@ -330,7 +330,7 @@ namespace GitHub.Runner.Listener
} }
} }
private async Task RunAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken) private async Task RunAsync(Guid targetHostId, Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
{ {
Busy = true; Busy = true;
try try
@@ -363,7 +363,7 @@ namespace GitHub.Runner.Listener
Trace.Info($"Start renew job request {requestId} for job {message.JobId}."); Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token); Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
// wait till first renew succeed or job request is canceled // wait till first renew succeed or job request is cancelled
// not even start worker if the first renew fail // not even start worker if the first renew fail
await Task.WhenAny(firstJobRequestRenewed.Task, renewJobRequest, Task.Delay(-1, jobRequestCancellationToken)); await Task.WhenAny(firstJobRequestRenewed.Task, renewJobRequest, Task.Delay(-1, jobRequestCancellationToken));
@@ -383,7 +383,7 @@ namespace GitHub.Runner.Listener
await renewJobRequest; await renewJobRequest;
// complete job request with result Cancelled // complete job request with result Cancelled
await CompleteJobRequestAsync(_poolId, message, lockToken, TaskResult.Canceled); await CompleteJobRequestAsync(targetHostId, _poolId, message, lockToken, TaskResult.Canceled);
return; return;
} }
@@ -540,7 +540,7 @@ namespace GitHub.Runner.Listener
await renewJobRequest; await renewJobRequest;
// complete job request // complete job request
await CompleteJobRequestAsync(_poolId, message, lockToken, result, detailInfo); await CompleteJobRequestAsync(targetHostId, _poolId, message, lockToken, result, detailInfo);
// print out unhandled exception happened in worker after we complete job request. // print out unhandled exception happened in worker after we complete job request.
// when we run out of disk space, report back to server has higher priority. // when we run out of disk space, report back to server has higher priority.
@@ -637,7 +637,7 @@ namespace GitHub.Runner.Listener
await renewJobRequest; await renewJobRequest;
// complete job request // complete job request
await CompleteJobRequestAsync(_poolId, message, lockToken, resultOnAbandonOrCancel); await CompleteJobRequestAsync(targetHostId, _poolId, message, lockToken, resultOnAbandonOrCancel);
} }
finally finally
{ {
@@ -666,17 +666,25 @@ namespace GitHub.Runner.Listener
{ {
try try
{ {
request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId, token); // #if USE_BROKER
Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}");
if (!firstJobRequestRenewed.Task.IsCompleted) if (!firstJobRequestRenewed.Task.IsCompleted)
{ {
// fire first renew succeed event. // fire first renew succeed event.
firstJobRequestRenewed.TrySetResult(0); firstJobRequestRenewed.TrySetResult(0);
// Update settings if the runner name has been changed server-side
UpdateAgentNameIfNeeded(request.ReservedAgent?.Name);
} }
// #else
// request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId, token);
// Trace.Info($"Successfully renew job request {requestId}, job is valid till {request?.LockedUntil.Value}");
// if (!firstJobRequestRenewed.Task.IsCompleted)
// {
// // fire first renew succeed event.
// firstJobRequestRenewed.TrySetResult(0);
// // Update settings if the runner name has been changed server-side
// UpdateAgentNameIfNeeded(request.ReservedAgent?.Name);
// }
// #endif
if (encounteringError > 0) if (encounteringError > 0)
{ {
@@ -704,7 +712,7 @@ namespace GitHub.Runner.Listener
{ {
// OperationCanceledException may caused by http timeout or _lockRenewalTokenSource.Cance(); // OperationCanceledException may caused by http timeout or _lockRenewalTokenSource.Cance();
// Stop renew only on cancellation token fired. // Stop renew only on cancellation token fired.
Trace.Info($"job renew has been canceled, stop renew job request {requestId}."); Trace.Info($"job renew has been cancelled, stop renew job request {requestId}.");
return; return;
} }
catch (Exception ex) catch (Exception ex)
@@ -762,7 +770,7 @@ namespace GitHub.Runner.Listener
} }
catch (OperationCanceledException) when (token.IsCancellationRequested) catch (OperationCanceledException) when (token.IsCancellationRequested)
{ {
Trace.Info($"job renew has been canceled, stop renew job request {requestId}."); Trace.Info($"job renew has been cancelled, stop renew job request {requestId}.");
} }
} }
else else
@@ -911,7 +919,7 @@ namespace GitHub.Runner.Listener
} }
} }
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null) private async Task CompleteJobRequestAsync(Guid targetHostId, int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null)
{ {
Trace.Entering(); Trace.Entering();
@@ -928,7 +936,7 @@ namespace GitHub.Runner.Listener
{ {
try try
{ {
await runnerServer.FinishAgentRequestAsync(poolId, message.RequestId, lockToken, DateTime.UtcNow, result, CancellationToken.None); await runnerServer.FinishAgentRequestAsync(poolId, message.RequestId, lockToken, DateTime.UtcNow, result, targetHostId, CancellationToken.None);
return; return;
} }
catch (TaskAgentJobNotFoundException) catch (TaskAgentJobNotFoundException)

View File

@@ -2,7 +2,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@@ -27,10 +31,13 @@ namespace GitHub.Runner.Listener
public sealed class MessageListener : RunnerService, IMessageListener public sealed class MessageListener : RunnerService, IMessageListener
{ {
#if !USE_BROKER
private long? _lastMessageId; private long? _lastMessageId;
#endif
private RunnerSettings _settings; private RunnerSettings _settings;
private ITerminal _term; private ITerminal _term;
private IRunnerServer _runnerServer; private IRunnerServer _runnerServer;
private IBrokerServer _brokerServer;
private TaskAgentSession _session; private TaskAgentSession _session;
private TimeSpan _getNextMessageRetryInterval; private TimeSpan _getNextMessageRetryInterval;
private bool _accessTokenRevoked = false; private bool _accessTokenRevoked = false;
@@ -45,8 +52,44 @@ namespace GitHub.Runner.Listener
_term = HostContext.GetService<ITerminal>(); _term = HostContext.GetService<ITerminal>();
_runnerServer = HostContext.GetService<IRunnerServer>(); _runnerServer = HostContext.GetService<IRunnerServer>();
_brokerServer = HostContext.GetService<IBrokerServer>();
} }
#if USE_BROKER
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
{
Trace.Entering();
// Settings
var configManager = HostContext.GetService<IConfigurationManager>();
_settings = configManager.LoadSettings();
var serverUrl = _settings.ServerUrl;
Trace.Info(_settings);
// Connect
token.ThrowIfCancellationRequested();
Trace.Info($"Attempt to create session.");
Trace.Info("Connecting to the Runner Server...");
_term.WriteLine($"Connecting to {new Uri(serverUrl)}");
await _brokerServer.ConnectAsync(new Uri(serverUrl), token);
_term.WriteLine();
_term.WriteSuccessMessage("Connected to GitHub");
_term.WriteLine();
// Session info
var agent = new TaskAgentReference
{
Id = _settings.AgentId,
Name = _settings.AgentName,
Version = BuildConstants.RunnerPackage.Version,
OSDescription = RuntimeInformation.OSDescription,
};
string sessionName = $"{Environment.MachineName ?? "RUNNER"}";
_session = new TaskAgentSession(sessionName, agent);
return true;
}
#else
public async Task<Boolean> CreateSessionAsync(CancellationToken token) public async Task<Boolean> CreateSessionAsync(CancellationToken token)
{ {
Trace.Entering(); Trace.Entering();
@@ -81,6 +124,7 @@ namespace GitHub.Runner.Listener
Trace.Info($"Attempt to create session."); Trace.Info($"Attempt to create session.");
try try
{ {
Trace.Info("Connecting to the Runner Server...");
Trace.Info("Connecting to the Runner Server..."); Trace.Info("Connecting to the Runner Server...");
await _runnerServer.ConnectAsync(new Uri(serverUrl), creds); await _runnerServer.ConnectAsync(new Uri(serverUrl), creds);
Trace.Info("VssConnection created"); Trace.Info("VssConnection created");
@@ -151,6 +195,7 @@ namespace GitHub.Runner.Listener
} }
} }
} }
#endif
public async Task DeleteSessionAsync() public async Task DeleteSessionAsync()
{ {
@@ -170,6 +215,167 @@ namespace GitHub.Runner.Listener
} }
} }
#if USE_BROKER
[DataContract]
public sealed class MessageRef
{
[DataMember(Name = "url")]
public string Url { get; set; }
[DataMember(Name = "token")]
public string Token { get; set; }
[DataMember(Name = "scopeId")]
public string ScopeId { get; set; }
[DataMember(Name = "planType")]
public string PlanType { get; set; }
[DataMember(Name = "planGroup")]
public string PlanGroup { get; set; }
[DataMember(Name = "instanceRefs")]
public InstanceRef[] InstanceRefs { get; set; }
[DataMember(Name = "labels")]
public string[] Labels { get; set; }
}
[DataContract]
public sealed class InstanceRef
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "instanceType")]
public string InstanceType { get; set; }
[DataMember(Name = "attempt")]
public int Attempt { get; set; }
}
public async Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token)
{
Trace.Entering();
ArgUtil.NotNull(_session, nameof(_session));
ArgUtil.NotNull(_settings, nameof(_settings));
bool encounteringError = false;
int continuousError = 0;
string errorMessage = string.Empty;
Stopwatch heartbeat = new Stopwatch();
heartbeat.Restart();
while (true)
{
token.ThrowIfCancellationRequested();
string message = null;
try
{
message = await _brokerServer.GetMessageAsync(_session, _settings, null/*_lastMessageId*/, token);
_term.WriteLine($"{DateTime.UtcNow:u}: {message}");
if (!string.IsNullOrEmpty(message))
{
var messageRef = StringUtil.ConvertFromJson<MessageRef>(message);
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", messageRef.Token);
var response = await client.GetAsync(messageRef.Url, token);
if (!response.IsSuccessStatusCode)
{
var content = default(string);
try
{
content = await response.Content.ReadAsStringAsync();
}
catch
{
}
var error = $"HTTP {(int)response.StatusCode} {Enum.GetName(typeof(HttpStatusCode), response.StatusCode)}";
if (!string.IsNullOrEmpty(content))
{
error = $"{error}: {content}";
}
throw new Exception(error);
}
var fullMessage = await response.Content.ReadAsStringAsync();
return StringUtil.ConvertFromJson<TaskAgentMessage>(fullMessage);
}
if (encounteringError) //print the message once only if there was an error
{
_term.WriteLine($"{DateTime.UtcNow:u}: Runner reconnected.");
encounteringError = false;
continuousError = 0;
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
Trace.Info("Get next message has been cancelled.");
throw;
}
catch (TaskAgentAccessTokenExpiredException)
{
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
_accessTokenRevoked = true;
throw;
}
catch (Exception ex)
{
Trace.Error("Catch exception during get next message.");
Trace.Error(ex);
// don't retry if SkipSessionRecover = true, DT service will delete agent session to stop agent from taking more jobs.
if (ex is TaskAgentSessionExpiredException && !_settings.SkipSessionRecover && await CreateSessionAsync(token))
{
Trace.Info($"{nameof(TaskAgentSessionExpiredException)} received, recovered by recreate session.");
}
else if (!IsGetNextMessageExceptionRetriable(ex))
{
throw;
}
else
{
continuousError++;
//retry after a random backoff to avoid service throttling
//in case of there is a service error happened and all agents get kicked off of the long poll and all agent try to reconnect back at the same time.
if (continuousError <= 5)
{
// random backoff [15, 30]
_getNextMessageRetryInterval = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(30), _getNextMessageRetryInterval);
}
else
{
// more aggressive backoff [30, 60]
_getNextMessageRetryInterval = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(60), _getNextMessageRetryInterval);
}
if (!encounteringError)
{
//print error only on the first consecutive error
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
encounteringError = true;
}
// re-create VssConnection before next retry
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
Trace.Info("Sleeping for {0} seconds before retrying.", _getNextMessageRetryInterval.TotalSeconds);
await HostContext.Delay(_getNextMessageRetryInterval, token);
}
}
// if (message == null)
// {
// if (heartbeat.Elapsed > TimeSpan.FromMinutes(30))
// {
// Trace.Info($"No message retrieved from session '{_session.SessionId}' within last 30 minutes.");
// heartbeat.Restart();
// }
// else
// {
// Trace.Verbose($"No message retrieved from session '{_session.SessionId}'.");
// }
// continue;
// }
// Trace.Info($"Message '{message.MessageId}' received from session '{_session.SessionId}'.");
// return message;
}
}
#else
public async Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token) public async Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token)
{ {
Trace.Entering(); Trace.Entering();
@@ -281,6 +487,7 @@ namespace GitHub.Runner.Listener
return message; return message;
} }
} }
#endif
public async Task DeleteMessageAsync(TaskAgentMessage message) public async Task DeleteMessageAsync(TaskAgentMessage message)
{ {

View File

@@ -13,6 +13,10 @@ using GitHub.Runner.Sdk;
using System.Linq; using System.Linq;
using GitHub.Runner.Listener.Check; using GitHub.Runner.Listener.Check;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
namespace GitHub.Runner.Listener namespace GitHub.Runner.Listener
{ {
@@ -449,7 +453,37 @@ namespace GitHub.Runner.Listener
{ {
Trace.Info($"Received job message of length {message.Body.Length} from service, with hash '{IOUtil.GetSha256Hash(message.Body)}'"); Trace.Info($"Received job message of length {message.Body.Length} from service, with hash '{IOUtil.GetSha256Hash(message.Body)}'");
var jobMessage = StringUtil.ConvertFromJson<Pipelines.AgentJobRequestMessage>(message.Body); var jobMessage = StringUtil.ConvertFromJson<Pipelines.AgentJobRequestMessage>(message.Body);
jobDispatcher.Run(jobMessage, runOnce); jobDispatcher.Run(Guid.Empty, jobMessage, runOnce);
if (runOnce)
{
Trace.Info("One time used runner received job message.");
runOnceJobReceived = true;
}
}
}
// 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<MessageRef>(message.Body);
// Create connection
var credMgr = HostContext.GetService<ICredentialManager>();
var creds = credMgr.LoadCredentials();
// todo: add retries
var runServer = HostContext.CreateService<IRunServer>();
await runServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
var jobMessage = await runServer.GetJobMessageAsync(messageRef.ScopeId, messageRef.HostId, messageRef.PlanType, messageRef.PlanGroup, messageRef.PlanId, messageRef.InstanceRefs);
// todo: Trace.Info($"Received job message of length {message.Body.Length} from service, with hash '{IOUtil.GetSha256Hash(message.Body)}'");
jobDispatcher.Run(messageRef.HostId, jobMessage, runOnce);
if (runOnce) if (runOnce)
{ {
Trace.Info("One time used runner received job message."); Trace.Info("One time used runner received job message.");

View File

@@ -504,7 +504,7 @@ namespace GitHub.Runner.Listener
} }
catch (OperationCanceledException) when (token.IsCancellationRequested) catch (OperationCanceledException) when (token.IsCancellationRequested)
{ {
Trace.Info($"Runner download has been canceled."); Trace.Info($"Runner download has been cancelled.");
throw; throw;
} }
catch (Exception ex) catch (Exception ex)
@@ -1070,7 +1070,7 @@ namespace GitHub.Runner.Listener
var stopWatch = Stopwatch.StartNew(); var stopWatch = Stopwatch.StartNew();
string binDir = HostContext.GetDirectory(WellKnownDirectory.Bin); string binDir = HostContext.GetDirectory(WellKnownDirectory.Bin);
string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
string hashFilesScript = Path.Combine(binDir, "hashFiles"); string hashFilesScript = Path.Combine(binDir, "hashFiles");
var hashResult = string.Empty; var hashResult = string.Empty;

View File

@@ -166,7 +166,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
} }
else else
{ {
// delete the index.lock file left by previous canceled build or any operation cause git.exe crash last time. // delete the index.lock file left by previous cancelled build or any operation cause git.exe crash last time.
string lockFile = Path.Combine(targetPath, ".git\\index.lock"); string lockFile = Path.Combine(targetPath, ".git\\index.lock");
if (File.Exists(lockFile)) if (File.Exists(lockFile))
{ {
@@ -181,7 +181,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
} }
} }
// delete the shallow.lock file left by previous canceled build or any operation cause git.exe crash last time. // delete the shallow.lock file left by previous cancelled build or any operation cause git.exe crash last time.
string shallowLockFile = Path.Combine(targetPath, ".git\\shallow.lock"); string shallowLockFile = Path.Combine(targetPath, ".git\\shallow.lock");
if (File.Exists(shallowLockFile)) if (File.Exists(shallowLockFile))
{ {

View File

@@ -150,7 +150,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
} }
else else
{ {
// delete the index.lock file left by previous canceled build or any operation cause git.exe crash last time. // delete the index.lock file left by previous cancelled build or any operation cause git.exe crash last time.
string lockFile = Path.Combine(targetPath, ".git\\index.lock"); string lockFile = Path.Combine(targetPath, ".git\\index.lock");
if (File.Exists(lockFile)) if (File.Exists(lockFile))
{ {
@@ -165,7 +165,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
} }
} }
// delete the shallow.lock file left by previous canceled build or any operation cause git.exe crash last time. // delete the shallow.lock file left by previous cancelled build or any operation cause git.exe crash last time.
string shallowLockFile = Path.Combine(targetPath, ".git\\shallow.lock"); string shallowLockFile = Path.Combine(targetPath, ".git\\shallow.lock");
if (File.Exists(shallowLockFile)) if (File.Exists(shallowLockFile))
{ {

View File

@@ -108,7 +108,7 @@ namespace GitHub.Runner.Sdk
} }
// Create a new token source for the parallel query. The parallel query should be // Create a new token source for the parallel query. The parallel query should be
// canceled after the first error is encountered. Otherwise the number of exceptions // cancelled after the first error is encountered. Otherwise the number of exceptions
// could get out of control for a large directory with access denied on every file. // could get out of control for a large directory with access denied on every file.
using (var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) using (var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{ {

View File

@@ -36,6 +36,7 @@ namespace GitHub.Runner.Sdk
public static VssConnection CreateConnection(Uri serverUri, VssCredentials credentials, IEnumerable<DelegatingHandler> additionalDelegatingHandler = null, TimeSpan? timeout = null) public static VssConnection CreateConnection(Uri serverUri, VssCredentials credentials, IEnumerable<DelegatingHandler> additionalDelegatingHandler = null, TimeSpan? timeout = null)
{ {
// System.Console.WriteLine("VssUtil.CreateConnection");
VssClientHttpRequestSettings settings = VssClientHttpRequestSettings.Default.Clone(); VssClientHttpRequestSettings settings = VssClientHttpRequestSettings.Default.Clone();
int maxRetryRequest; int maxRetryRequest;

View File

@@ -654,7 +654,7 @@ namespace GitHub.Runner.Worker
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Global.Plan.ScopeIdentifier, executionContext.Global.Plan.PlanType, executionContext.Global.Plan.PlanId, executionContext.Root.Id, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken); actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Global.Plan.ScopeIdentifier, executionContext.Global.Plan.PlanType, executionContext.Global.Plan.PlanId, executionContext.Root.Id, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
break; break;
} }
catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is canceled. catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is cancelled.
{ {
// UnresolvableActionDownloadInfoException is a 422 client error, don't retry // UnresolvableActionDownloadInfoException is a 422 client error, don't retry
// Some possible cases are: // Some possible cases are:
@@ -817,6 +817,7 @@ namespace GitHub.Runner.Worker
{ {
// Something else bad happened, let's go to our retry logic // Something else bad happened, let's go to our retry logic
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
throw new Exception("Unexpected response code: " + response.StatusCode);
} }
} }
} }

View File

@@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.ObjectTemplating; using GitHub.DistributedTask.ObjectTemplating;
using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.ObjectTemplating.Tokens;
@@ -11,6 +9,7 @@ using GitHub.DistributedTask.Pipelines.ObjectTemplating;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Handlers; using GitHub.Runner.Worker.Handlers;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
@@ -141,21 +140,7 @@ namespace GitHub.Runner.Worker
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>(); IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
// Makes directory for event_path data ExecutionContext.WriteWebhookPayload();
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
var workflowDirectory = Path.Combine(tempDirectory, "_github_workflow");
Directory.CreateDirectory(workflowDirectory);
var gitHubEvent = ExecutionContext.GetGitHubContext("event");
// adds the GitHub event path/file if the event exists
if (gitHubEvent != null)
{
var workflowFile = Path.Combine(workflowDirectory, "event.json");
Trace.Info($"Write event payload to {workflowFile}");
File.WriteAllText(workflowFile, gitHubEvent, new UTF8Encoding(false));
ExecutionContext.SetGitHubContext("event_path", workflowFile);
}
// Set GITHUB_ACTION_REPOSITORY if this Action is from a repository // Set GITHUB_ACTION_REPOSITORY if this Action is from a repository
if (Action.Reference is Pipelines.RepositoryPathReference repoPathReferenceAction && if (Action.Reference is Pipelines.RepositoryPathReference repoPathReferenceAction &&

View File

@@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines; using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.Pipelines.ObjectTemplating; using GitHub.DistributedTask.Pipelines.ObjectTemplating;
@@ -109,6 +110,12 @@ namespace GitHub.Runner.Worker
void ForceTaskComplete(); void ForceTaskComplete();
void RegisterPostJobStep(IStep step); void RegisterPostJobStep(IStep step);
void PublishStepTelemetry(); void PublishStepTelemetry();
void ApplyContinueOnError(TemplateToken continueOnError);
void UpdateGlobalStepsContext();
void WriteWebhookPayload();
} }
public sealed class ExecutionContext : RunnerService, IExecutionContext public sealed class ExecutionContext : RunnerService, IExecutionContext
@@ -438,14 +445,19 @@ namespace GitHub.Runner.Worker
_logger.End(); _logger.End();
UpdateGlobalStepsContext();
return Result.Value;
}
public void UpdateGlobalStepsContext()
{
// Skip if generated context name. Generated context names start with "__". After 3.2 the server will never send an empty context name. // Skip if generated context name. Generated context names start with "__". After 3.2 the server will never send an empty context name.
if (!string.IsNullOrEmpty(ContextName) && !ContextName.StartsWith("__", StringComparison.Ordinal)) if (!string.IsNullOrEmpty(ContextName) && !ContextName.StartsWith("__", StringComparison.Ordinal))
{ {
Global.StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult()); Global.StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult());
Global.StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult()); Global.StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult());
} }
return Result.Value;
} }
public void SetRunnerContext(string name, string value) public void SetRunnerContext(string name, string value)
@@ -550,10 +562,15 @@ namespace GitHub.Runner.Worker
issue.Message = issue.Message[.._maxIssueMessageLength]; issue.Message = issue.Message[.._maxIssueMessageLength];
} }
// Tracking the line number (logFileLineNumber) and step number (stepNumber) for each issue that gets created
// Actions UI from the run summary page use both values to easily link to an exact locations in logs where annotations originate from
if (_record.Order != null)
{
issue.Data["stepNumber"] = _record.Order.ToString();
}
if (issue.Type == IssueType.Error) if (issue.Type == IssueType.Error)
{ {
// tracking line number for each issue in log file
// log UI use this to navigate from issue to log
if (!string.IsNullOrEmpty(logMessage)) if (!string.IsNullOrEmpty(logMessage))
{ {
long logLineNumber = Write(WellKnownTags.Error, logMessage); long logLineNumber = Write(WellKnownTags.Error, logMessage);
@@ -569,8 +586,6 @@ namespace GitHub.Runner.Worker
} }
else if (issue.Type == IssueType.Warning) else if (issue.Type == IssueType.Warning)
{ {
// tracking line number for each issue in log file
// log UI use this to navigate from issue to log
if (!string.IsNullOrEmpty(logMessage)) if (!string.IsNullOrEmpty(logMessage))
{ {
long logLineNumber = Write(WellKnownTags.Warning, logMessage); long logLineNumber = Write(WellKnownTags.Warning, logMessage);
@@ -586,9 +601,6 @@ namespace GitHub.Runner.Worker
} }
else if (issue.Type == IssueType.Notice) else if (issue.Type == IssueType.Notice)
{ {
// tracking line number for each issue in log file
// log UI use this to navigate from issue to log
if (!string.IsNullOrEmpty(logMessage)) if (!string.IsNullOrEmpty(logMessage))
{ {
long logLineNumber = Write(WellKnownTags.Notice, logMessage); long logLineNumber = Write(WellKnownTags.Notice, logMessage);
@@ -680,7 +692,7 @@ namespace GitHub.Runner.Worker
if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo12") ?? false) if (Global.Variables.GetBoolean("DistributedTask.ForceInternalNodeVersionOnRunnerTo12") ?? false)
{ {
Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedNodeVersion, "node12"); Environment.SetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion, "node12");
} }
// Environment variables shared across all actions // Environment variables shared across all actions
@@ -991,6 +1003,24 @@ namespace GitHub.Runner.Worker
} }
} }
public void WriteWebhookPayload()
{
// Makes directory for event_path data
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
var workflowDirectory = Path.Combine(tempDirectory, "_github_workflow");
Directory.CreateDirectory(workflowDirectory);
var gitHubEvent = GetGitHubContext("event");
// adds the GitHub event path/file if the event exists
if (gitHubEvent != null)
{
var workflowFile = Path.Combine(workflowDirectory, "event.json");
Trace.Info($"Write event payload to {workflowFile}");
File.WriteAllText(workflowFile, gitHubEvent, new UTF8Encoding(false));
SetGitHubContext("event_path", workflowFile);
}
}
private void InitializeTimelineRecord(Guid timelineId, Guid timelineRecordId, Guid? parentTimelineRecordId, string recordType, string displayName, string refName, int? order) private void InitializeTimelineRecord(Guid timelineId, Guid timelineRecordId, Guid? parentTimelineRecordId, string recordType, string displayName, string refName, int? order)
{ {
_mainTimelineId = timelineId; _mainTimelineId = timelineId;
@@ -1045,6 +1075,36 @@ namespace GitHub.Runner.Worker
var newGuid = Guid.NewGuid(); var newGuid = Guid.NewGuid();
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, ActionRunStage.Post, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName); return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, ActionRunStage.Post, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
} }
public void ApplyContinueOnError(TemplateToken continueOnErrorToken)
{
if (Result != TaskResult.Failed)
{
return;
}
var continueOnError = false;
try
{
var templateEvaluator = this.ToPipelineTemplateEvaluator();
continueOnError = templateEvaluator.EvaluateStepContinueOnError(continueOnErrorToken, ExpressionValues, ExpressionFunctions);
}
catch (Exception ex)
{
Trace.Info("The step failed and an error occurred when attempting to determine whether to continue on error.");
Trace.Error(ex);
this.Error("The step failed and an error occurred when attempting to determine whether to continue on error.");
this.Error(ex);
}
if (continueOnError)
{
Outcome = Result;
Result = TaskResult.Succeeded;
Trace.Info($"Updated step result (continue on error)");
}
UpdateGlobalStepsContext();
}
} }
// The Error/Warning/etc methods are created as extension methods to simplify unit testing. // The Error/Warning/etc methods are created as extension methods to simplify unit testing.
@@ -1066,7 +1126,6 @@ namespace GitHub.Runner.Worker
context.Error(ex.Message); context.Error(ex.Message);
context.Debug(ex.ToString()); context.Debug(ex.ToString());
} }
// Do not add a format string overload. See comment on ExecutionContext.Write(). // Do not add a format string overload. See comment on ExecutionContext.Write().
public static void Error(this IExecutionContext context, string message) public static void Error(this IExecutionContext context, string message)
{ {

View File

@@ -63,7 +63,7 @@ namespace GitHub.Runner.Worker.Expressions
string binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); string binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
string runnerRoot = new DirectoryInfo(binDir).Parent.FullName; string runnerRoot = new DirectoryInfo(binDir).Parent.FullName;
string node = Path.Combine(runnerRoot, "externals", NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); string node = Path.Combine(runnerRoot, "externals", NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
string hashFilesScript = Path.Combine(binDir, "hashFiles"); string hashFilesScript = Path.Combine(binDir, "hashFiles");
var hashResult = string.Empty; var hashResult = string.Empty;
var p = new ProcessInvoker(new HashFilesTrace(context.Trace)); var p = new ProcessInvoker(new HashFilesTrace(context.Trace));

View File

@@ -181,6 +181,10 @@ namespace GitHub.Runner.Worker
{ {
throw new Exception($"Invalid environment variable value. Matching delimiter not found '{delimiter}'"); throw new Exception($"Invalid environment variable value. Matching delimiter not found '{delimiter}'");
} }
if (newline == null)
{
throw new Exception($"Invalid environment variable value. EOF marker missing new line.");
}
endIndex = index - newline.Length; endIndex = index - newline.Length;
tempLine = ReadLine(text, ref index, out newline); tempLine = ReadLine(text, ref index, out newline);
} }
@@ -294,7 +298,7 @@ namespace GitHub.Runner.Worker
if (fileSize > _attachmentSizeLimit) if (fileSize > _attachmentSizeLimit)
{ {
context.Error($"$GITHUB_STEP_SUMMARY supports content up a size of {_attachmentSizeLimit / 1024}k got {fileSize / 1024}k"); context.Error(String.Format(Constants.Runner.UnsupportedSummarySize, _attachmentSizeLimit / 1024, fileSize / 1024));
Trace.Info($"Step Summary file ({filePath}) is too large ({fileSize} bytes); skipping attachment upload"); Trace.Info($"Step Summary file ({filePath}) is too large ({fileSize} bytes); skipping attachment upload");
return; return;

View File

@@ -8,10 +8,10 @@ namespace GitHub.Runner.Worker
{ {
private readonly HashSet<string> _contextEnvAllowlist = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private readonly HashSet<string> _contextEnvAllowlist = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
"action",
"action_path", "action_path",
"action_ref", "action_ref",
"action_repository", "action_repository",
"action",
"actor", "actor",
"api_url", "api_url",
"base_ref", "base_ref",
@@ -22,12 +22,12 @@ namespace GitHub.Runner.Worker
"head_ref", "head_ref",
"job", "job",
"path", "path",
"ref",
"ref_name", "ref_name",
"ref_protected", "ref_protected",
"ref_type", "ref_type",
"repository", "ref",
"repository_owner", "repository_owner",
"repository",
"retention_days", "retention_days",
"run_attempt", "run_attempt",
"run_id", "run_id",
@@ -35,6 +35,7 @@ namespace GitHub.Runner.Worker
"server_url", "server_url",
"sha", "sha",
"step_summary", "step_summary",
"triggering_actor",
"workflow", "workflow",
"workspace", "workspace",
}; };

View File

@@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Expressions2;
@@ -13,7 +11,6 @@ using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Expressions; using GitHub.Runner.Worker.Expressions;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
@@ -86,7 +83,7 @@ namespace GitHub.Runner.Worker.Handlers
ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre; ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre;
ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost; ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost;
ExecutionContext.StepTelemetry.HasRunsStep = hasRunsStep; ExecutionContext.StepTelemetry.HasRunsStep = hasRunsStep;
ExecutionContext.StepTelemetry.HasUsesStep = hasUsesStep; ExecutionContext.StepTelemetry.HasUsesStep = hasUsesStep;
ExecutionContext.StepTelemetry.StepCount = steps.Count; ExecutionContext.StepTelemetry.StepCount = steps.Count;
@@ -299,7 +296,7 @@ namespace GitHub.Runner.Worker.Handlers
// Register job cancellation call back only if job cancellation token not been fire before each step run // Register job cancellation call back only if job cancellation token not been fire before each step run
if (!ExecutionContext.Root.CancellationToken.IsCancellationRequested) if (!ExecutionContext.Root.CancellationToken.IsCancellationRequested)
{ {
// Test the condition again. The job was canceled after the condition was originally evaluated. // Test the condition again. The job was cancelled after the condition was originally evaluated.
jobCancelRegister = ExecutionContext.Root.CancellationToken.Register(() => jobCancelRegister = ExecutionContext.Root.CancellationToken.Register(() =>
{ {
// Mark job as cancelled // Mark job as cancelled
@@ -399,7 +396,7 @@ namespace GitHub.Runner.Worker.Handlers
jobCancelRegister = null; jobCancelRegister = null;
} }
} }
// Check failed or canceled // Check failed or cancelled
if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled) if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled)
{ {
Trace.Info($"Update job result with current composite step result '{step.ExecutionContext.Result}'."); Trace.Info($"Update job result with current composite step result '{step.ExecutionContext.Result}'.");
@@ -407,7 +404,7 @@ namespace GitHub.Runner.Worker.Handlers
} }
// Update context // Update context
SetStepsContext(step); step.ExecutionContext.UpdateGlobalStepsContext();
} }
} }
@@ -452,6 +449,8 @@ namespace GitHub.Runner.Worker.Handlers
SetStepConclusion(step, Common.Util.TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value)); SetStepConclusion(step, Common.Util.TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value));
} }
step.ExecutionContext.ApplyContinueOnError(step.ContinueOnError);
Trace.Info($"Step result: {step.ExecutionContext.Result}"); Trace.Info($"Step result: {step.ExecutionContext.Result}");
step.ExecutionContext.Debug($"Finished: {step.DisplayName}"); step.ExecutionContext.Debug($"Finished: {step.DisplayName}");
step.ExecutionContext.PublishStepTelemetry(); step.ExecutionContext.PublishStepTelemetry();
@@ -460,16 +459,7 @@ namespace GitHub.Runner.Worker.Handlers
private void SetStepConclusion(IStep step, TaskResult result) private void SetStepConclusion(IStep step, TaskResult result)
{ {
step.ExecutionContext.Result = result; step.ExecutionContext.Result = result;
SetStepsContext(step); step.ExecutionContext.UpdateGlobalStepsContext();
}
private void SetStepsContext(IStep step)
{
if (!string.IsNullOrEmpty(step.ExecutionContext.ContextName) && !step.ExecutionContext.ContextName.StartsWith("__", StringComparison.Ordinal))
{
// TODO: when we support continue on error, we may need to do logic here to change conclusion based on the continue on error result
step.ExecutionContext.Global.StepsContext.SetOutcome(step.ExecutionContext.ScopeName, step.ExecutionContext.ContextName, (step.ExecutionContext.Result ?? TaskResult.Succeeded).ToActionResult());
step.ExecutionContext.Global.StepsContext.SetConclusion(step.ExecutionContext.ScopeName, step.ExecutionContext.ContextName, (step.ExecutionContext.Result ?? TaskResult.Succeeded).ToActionResult());
}
} }
} }
} }

View File

@@ -36,6 +36,7 @@ namespace GitHub.Runner.Worker.Handlers
protected IActionCommandManager ActionCommandManager { get; private set; } protected IActionCommandManager ActionCommandManager { get; private set; }
public Pipelines.ActionStepDefinitionReference Action { get; set; } public Pipelines.ActionStepDefinitionReference Action { get; set; }
public bool IsActionStep => Action != null;
public Dictionary<string, string> Environment { get; set; } public Dictionary<string, string> Environment { get; set; }
public Variables RuntimeVariables { get; set; } public Variables RuntimeVariables { get; set; }
public IExecutionContext ExecutionContext { get; set; } public IExecutionContext ExecutionContext { get; set; }
@@ -49,13 +50,18 @@ namespace GitHub.Runner.Worker.Handlers
// Print out action details // Print out action details
PrintActionDetails(stage); PrintActionDetails(stage);
// Get telemetry for the action. // Get telemetry for the action
PopulateActionTelemetry(); PopulateActionTelemetry(stage);
} }
protected void PopulateActionTelemetry() protected void PopulateActionTelemetry(ActionRunStage stage)
{ {
if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry) if (!IsActionStep)
{
ExecutionContext.StepTelemetry.Type = "runner";
ExecutionContext.StepTelemetry.Action = $"{stage} Job Hook";
}
else if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry)
{ {
ExecutionContext.StepTelemetry.Type = "docker"; ExecutionContext.StepTelemetry.Type = "docker";
var registryAction = Action as Pipelines.ContainerRegistryReference; var registryAction = Action as Pipelines.ContainerRegistryReference;

View File

@@ -55,7 +55,23 @@ namespace GitHub.Runner.Worker.Handlers
else if (data.ExecutionType == ActionExecutionType.NodeJS) else if (data.ExecutionType == ActionExecutionType.NodeJS)
{ {
handler = HostContext.CreateService<INodeScriptActionHandler>(); handler = HostContext.CreateService<INodeScriptActionHandler>();
(handler as INodeScriptActionHandler).Data = data as NodeJSActionExecutionData; var nodeData = data as NodeJSActionExecutionData;
// With node12 EoL in 04/2022, we want to be able to uniformly upgrade all JS actions to node16 from the server
if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) &&
(executionContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode16") ?? false))
{
// The user can opt out of this behaviour by setting this variable to true, either setting 'env' in their workflow or as an environment variable on their machine
executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, out var workflowOptOut);
var isWorkflowOptOutSet = !string.IsNullOrEmpty(workflowOptOut);
var isLocalOptOut = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion));
bool isOptOut = isWorkflowOptOutSet ? StringUtil.ConvertToBoolean(workflowOptOut) : isLocalOptOut;
if (!isOptOut)
{
nodeData.NodeVersion = "node16";
}
}
(handler as INodeScriptActionHandler).Data = nodeData;
} }
else if (data.ExecutionType == ActionExecutionType.Script) else if (data.ExecutionType == ActionExecutionType.Script)
{ {

View File

@@ -3,10 +3,11 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker.Handlers namespace GitHub.Runner.Worker.Handlers
{ {
@@ -113,6 +114,17 @@ namespace GitHub.Runner.Worker.Handlers
// Remove environment variable that may cause conflicts with the node within the runner. // Remove environment variable that may cause conflicts with the node within the runner.
Environment.Remove("NODE_ICU_DATA"); // https://github.com/actions/runner/issues/795 Environment.Remove("NODE_ICU_DATA"); // https://github.com/actions/runner/issues/795
if (Data.NodeVersion == "node12" && (ExecutionContext.Global.Variables.GetBoolean(Constants.Runner.Features.Node12Warning) ?? false))
{
if (!ExecutionContext.JobContext.ContainsKey("Node12ActionsWarnings"))
{
ExecutionContext.JobContext["Node12ActionsWarnings"] = new ArrayContextData();
}
var repoAction = Action as RepositoryPathReference;
var actionDisplayName = new StringContextData(repoAction.Name ?? repoAction.Path); // local actions don't have a 'Name'
ExecutionContext.JobContext["Node12ActionsWarnings"].AssertArray("Node12ActionsWarnings").Add(actionDisplayName);
}
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager)) using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager)) using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager))
{ {

View File

@@ -24,16 +24,22 @@ namespace GitHub.Runner.Worker.Handlers
protected override void PrintActionDetails(ActionRunStage stage) protected override void PrintActionDetails(ActionRunStage stage)
{ {
// if we're executing a Job Extension, we won't have an 'Action'
if (stage == ActionRunStage.Post) if (!IsActionStep)
{ {
throw new NotSupportedException("Script action should not have 'Post' job action."); if (Inputs.TryGetValue("path", out var path))
{
ExecutionContext.Output($"##[group]Run '{path}'");
}
else
{
throw new InvalidOperationException("Inputs 'path' must be set for job extensions");
}
} }
else if (Action.Type == Pipelines.ActionSourceType.Script)
Inputs.TryGetValue("script", out string contents);
contents = contents ?? string.Empty;
if (Action.Type == Pipelines.ActionSourceType.Script)
{ {
Inputs.TryGetValue("script", out string contents);
contents = contents ?? string.Empty;
var firstLine = contents.TrimStart(' ', '\t', '\r', '\n'); var firstLine = contents.TrimStart(' ', '\t', '\r', '\n');
var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' }); var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' });
if (firstNewLine >= 0) if (firstNewLine >= 0)
@@ -42,17 +48,16 @@ namespace GitHub.Runner.Worker.Handlers
} }
ExecutionContext.Output($"##[group]Run {firstLine}"); ExecutionContext.Output($"##[group]Run {firstLine}");
var multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
foreach (var line in multiLines)
{
// Bright Cyan color
ExecutionContext.Output($"\x1b[36;1m{line}\x1b[0m");
}
} }
else else
{ {
throw new InvalidOperationException($"Invalid action type {Action.Type} for {nameof(ScriptHandler)}"); throw new InvalidOperationException($"Invalid action type {Action?.Type} for {nameof(ScriptHandler)}");
}
var multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
foreach (var line in multiLines)
{
// Bright Cyan color
ExecutionContext.Output($"\x1b[36;1m{line}\x1b[0m");
} }
string argFormat; string argFormat;
@@ -132,11 +137,6 @@ namespace GitHub.Runner.Worker.Handlers
public async Task RunAsync(ActionRunStage stage) public async Task RunAsync(ActionRunStage stage)
{ {
if (stage == ActionRunStage.Post)
{
throw new NotSupportedException("Script action should not have 'Post' job action.");
}
// Validate args // Validate args
Trace.Entering(); Trace.Entering();
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
@@ -153,7 +153,8 @@ namespace GitHub.Runner.Worker.Handlers
string workingDirectory = null; string workingDirectory = null;
if (!Inputs.TryGetValue("workingDirectory", out workingDirectory)) if (!Inputs.TryGetValue("workingDirectory", out workingDirectory))
{ {
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.Global.JobDefaults.TryGetValue("run", out var runDefaults)) // Don't use job level working directories for hooks
if (IsActionStep && string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.Global.JobDefaults.TryGetValue("run", out var runDefaults))
{ {
if (runDefaults.TryGetValue("working-directory", out workingDirectory)) if (runDefaults.TryGetValue("working-directory", out workingDirectory))
{ {
@@ -212,7 +213,8 @@ namespace GitHub.Runner.Worker.Handlers
} }
} }
if (!string.IsNullOrEmpty(shellCommand)) // Don't override runner telemetry here
if (!string.IsNullOrEmpty(shellCommand) && IsActionStep)
{ {
ExecutionContext.StepTelemetry.Action = shellCommand; ExecutionContext.StepTelemetry.Action = shellCommand;
} }
@@ -222,10 +224,24 @@ namespace GitHub.Runner.Worker.Handlers
{ {
throw new ArgumentException("Invalid shell option. Shell must be a valid built-in (bash, sh, cmd, powershell, pwsh) or a format string containing '{0}'"); throw new ArgumentException("Invalid shell option. Shell must be a valid built-in (bash, sh, cmd, powershell, pwsh) or a format string containing '{0}'");
} }
string scriptFilePath, resolvedScriptPath;
// We do not not the full path until we know what shell is being used, so that we can determine the file extension if (IsActionStep)
var scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}"); {
var resolvedScriptPath = $"{StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"")}"; // 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)}");
resolvedScriptPath = $"{StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"")}";
}
else
{
// JobExtensionRunners run a script file, we load that from the inputs here
if (!Inputs.ContainsKey("path"))
{
throw new ArgumentException("Expected 'path' input to be set");
}
scriptFilePath = Inputs["path"];
ArgUtil.NotNullOrEmpty(scriptFilePath, "path");
resolvedScriptPath = Inputs["path"].Replace("\"", "\\\"");
}
// Format arg string with script path // Format arg string with script path
var arguments = string.Format(argFormat, resolvedScriptPath); var arguments = string.Format(argFormat, resolvedScriptPath);
@@ -241,9 +257,12 @@ namespace GitHub.Runner.Worker.Handlers
#else #else
// Don't add a BOM. It causes the script to fail on some operating systems (e.g. on Ubuntu 14). // Don't add a BOM. It causes the script to fail on some operating systems (e.g. on Ubuntu 14).
var encoding = new UTF8Encoding(false); var encoding = new UTF8Encoding(false);
#endif #endif
// Script is written to local path (ie host) but executed relative to the StepHost, which may be a container if (IsActionStep)
File.WriteAllText(scriptFilePath, contents, encoding); {
// Script is written to local path (ie host) but executed relative to the StepHost, which may be a container
File.WriteAllText(scriptFilePath, contents, encoding);
}
// Prepend PATH // Prepend PATH
AddPrependPathToEnvironment(); AddPrependPathToEnvironment();
@@ -266,7 +285,7 @@ namespace GitHub.Runner.Worker.Handlers
if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS
{ {
// launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process // launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process
string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetNodeVersion(), "bin", $"node{IOUtil.ExeExtension}"); string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js"); string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js");
arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}"; arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}";
fileName = node; fileName = node;

View File

@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Worker.Handlers namespace GitHub.Runner.Worker.Handlers
{ {
@@ -79,5 +81,22 @@ namespace GitHub.Runner.Worker.Handlers
throw new ArgumentException($"Failed to parse COMMAND [..ARGS] from {shellOption}"); throw new ArgumentException($"Failed to parse COMMAND [..ARGS] from {shellOption}");
} }
} }
internal static string GetDefaultShellForScript(string path, Common.Tracing trace, string prependPath)
{
var format = "{0} {1}";
switch (Path.GetExtension(path))
{
case ".sh":
// use 'sh' args but prefer bash
var pathToShell = WhichUtil.Which("bash", false, trace, prependPath) ?? WhichUtil.Which("sh", true, trace, prependPath);
return string.Format(format, pathToShell, _defaultArguments["sh"]);
case ".ps1":
var pathToPowershell = WhichUtil.Which("pwsh", false, trace, prependPath) ?? WhichUtil.Which("powershell", true, trace, prependPath);
return string.Format(format, pathToPowershell, _defaultArguments["powershell"]);
default:
throw new ArgumentException($"{path} is not a valid path to a script. Make sure it ends in '.sh' or '.ps1'.");
}
}
} }
} }

View File

@@ -15,6 +15,7 @@ using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker namespace GitHub.Runner.Worker
@@ -248,6 +249,19 @@ namespace GitHub.Runner.Worker
Trace.Info("Downloading actions"); Trace.Info("Downloading actions");
var actionManager = HostContext.GetService<IActionManager>(); var actionManager = HostContext.GetService<IActionManager>();
var prepareResult = await actionManager.PrepareActionsAsync(context, message.Steps); var prepareResult = await actionManager.PrepareActionsAsync(context, message.Steps);
// add hook to preJobSteps
var startedHookPath = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_STARTED");
if (!string.IsNullOrEmpty(startedHookPath))
{
var hookProvider = HostContext.GetService<IJobHookProvider>();
var jobHookData = new JobHookData(ActionRunStage.Pre, startedHookPath);
preJobSteps.Add(new JobExtensionRunner(runAsync: hookProvider.RunHook,
condition: $"{PipelineTemplateConstants.Always}()",
displayName: Constants.Hooks.JobStartedStepName,
data: (object)jobHookData));
}
preJobSteps.AddRange(prepareResult.ContainerSetupSteps); preJobSteps.AddRange(prepareResult.ContainerSetupSteps);
// Add start-container steps, record and stop-container steps // Add start-container steps, record and stop-container steps
@@ -337,6 +351,18 @@ namespace GitHub.Runner.Worker
} }
} }
// Register Job Completed hook if the variable is set
var completedHookPath = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED");
if (!string.IsNullOrEmpty(completedHookPath))
{
var hookProvider = HostContext.GetService<IJobHookProvider>();
var jobHookData = new JobHookData(ActionRunStage.Post, completedHookPath);
jobContext.RegisterPostJobStep(new JobExtensionRunner(runAsync: hookProvider.RunHook,
condition: $"{PipelineTemplateConstants.Always}()",
displayName: Constants.Hooks.JobCompletedStepName,
data: (object)jobHookData));
}
List<IStep> steps = new List<IStep>(); List<IStep> steps = new List<IStep>();
steps.AddRange(preJobSteps); steps.AddRange(preJobSteps);
steps.AddRange(jobSteps); steps.AddRange(jobSteps);
@@ -406,7 +432,7 @@ namespace GitHub.Runner.Worker
// create a new timeline record node for 'Finalize job' // create a new timeline record node for 'Finalize job'
IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Complete job", $"{nameof(JobExtension)}_Final", null, null, ActionRunStage.Post); IExecutionContext context = jobContext.CreateChild(Guid.NewGuid(), "Complete job", $"{nameof(JobExtension)}_Final", null, null, ActionRunStage.Post);
context.StepTelemetry.Type = "runner"; context.StepTelemetry.Type = "runner";
context.StepTelemetry.Action = "complete_joh"; context.StepTelemetry.Action = "complete_job";
using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); })) using (var register = jobContext.CancellationToken.Register(() => { context.CancelToken(); }))
{ {
try try

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Linq;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Handlers;
namespace GitHub.Runner.Worker
{
[ServiceLocator(Default = typeof(JobHookProvider))]
public interface IJobHookProvider : IRunnerService
{
Task RunHook(IExecutionContext executionContext, object data);
}
public class JobHookData
{
public string Path {get; private set;}
public ActionRunStage Stage {get; private set;}
public JobHookData(ActionRunStage stage, string path)
{
Path = path;
Stage = stage;
}
}
public class JobHookProvider : RunnerService, IJobHookProvider
{
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
}
public async Task RunHook(IExecutionContext executionContext, object data)
{
// Get Inputs
var hookData = data as JobHookData;
ArgUtil.NotNull(hookData, nameof(JobHookData));
var displayName = hookData.Stage == ActionRunStage.Pre ? "job started hook" : "job completed hook";
// Log to users so that they know how this step was injected
executionContext.Output($"A {displayName} has been configured by the self-hosted runner administrator");
// Validate script file.
if (!File.Exists(hookData.Path))
{
throw new FileNotFoundException("File doesn't exist");
}
executionContext.WriteWebhookPayload();
// Create the handler data.
var scriptDirectory = Path.GetDirectoryName(hookData.Path);
var stepHost = HostContext.CreateService<IDefaultStepHost>();
var prependPath = string.Join(Path.PathSeparator.ToString(), executionContext.Global.PrependPath.Reverse<string>());
Dictionary<string, string> inputs = new()
{
["path"] = hookData.Path,
["shell"] = ScriptHandlerHelpers.GetDefaultShellForScript(hookData.Path, Trace, prependPath)
};
// Create the handler
var handlerFactory = HostContext.GetService<IHandlerFactory>();
var handler = handlerFactory.Create(
executionContext,
action: null,
stepHost,
new ScriptActionExecutionData(),
inputs,
environment: new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
executionContext.Global.Variables,
actionDirectory: scriptDirectory,
localActionContainerSetupSteps: null);
handler.PrepareExecution(hookData.Stage);
// Setup file commands
var fileCommandManager = HostContext.CreateService<IFileCommandManager>();
fileCommandManager.InitializeFiles(executionContext, null);
// Run the step and process the file commands
try
{
await handler.RunAsync(hookData.Stage);
}
finally
{
fileCommandManager.ProcessFiles(executionContext, executionContext.Global.Container);
}
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
@@ -131,9 +132,9 @@ namespace GitHub.Runner.Worker
} }
catch (OperationCanceledException ex) when (jobContext.CancellationToken.IsCancellationRequested) catch (OperationCanceledException ex) when (jobContext.CancellationToken.IsCancellationRequested)
{ {
// set the job to canceled // set the job to cancelled
// don't log error issue to job ExecutionContext, since server owns the job level issue // don't log error issue to job ExecutionContext, since server owns the job level issue
Trace.Error($"Job is canceled during initialize."); Trace.Error($"Job is cancelled during initialize.");
Trace.Error($"Caught exception: {ex}"); Trace.Error($"Caught exception: {ex}");
return await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled); return await CompleteJobAsync(jobServer, jobContext, message, TaskResult.Canceled);
} }
@@ -257,6 +258,12 @@ namespace GitHub.Runner.Worker
} }
} }
if (jobContext.JobContext.ContainsKey("Node12ActionsWarnings"))
{
var actions = string.Join(", ", jobContext.JobContext["Node12ActionsWarnings"].AssertArray("Node12ActionsWarnings").Select(action => action.ToString()));
jobContext.Warning(string.Format(Constants.Runner.Node12DetectedAfterEndOfLife, actions));
}
try try
{ {
await ShutdownQueue(throwOnFailure: true); await ShutdownQueue(throwOnFailure: true);

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -130,11 +131,13 @@ namespace GitHub.Runner.Worker
// Register job cancellation call back only if job cancellation token not been fire before each step run // Register job cancellation call back only if job cancellation token not been fire before each step run
if (!jobContext.CancellationToken.IsCancellationRequested) if (!jobContext.CancellationToken.IsCancellationRequested)
{ {
// Test the condition again. The job was canceled after the condition was originally evaluated. // Test the condition again. The job was cancelled after the condition was originally evaluated.
jobCancelRegister = jobContext.CancellationToken.Register(() => jobCancelRegister = jobContext.CancellationToken.Register(() =>
{ {
// Mark job as cancelled // Mark job as Cancelled or Failed depending on HostContext shutdown token's cancellation
jobContext.Result = TaskResult.Canceled; jobContext.Result = HostContext.RunnerShutdownToken.IsCancellationRequested
? TaskResult.Failed
: TaskResult.Canceled;
jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'."); step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
@@ -172,8 +175,10 @@ namespace GitHub.Runner.Worker
{ {
if (jobContext.Result != TaskResult.Canceled) if (jobContext.Result != TaskResult.Canceled)
{ {
// Mark job as cancelled // Mark job as Cancelled or Failed depending on HostContext shutdown token's cancellation
jobContext.Result = TaskResult.Canceled; jobContext.Result = HostContext.RunnerShutdownToken.IsCancellationRequested
? TaskResult.Failed
: TaskResult.Canceled;
jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
} }
} }
@@ -319,29 +324,8 @@ namespace GitHub.Runner.Worker
step.ExecutionContext.Result = TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value); step.ExecutionContext.Result = TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value);
} }
// Fixup the step result if ContinueOnError step.ExecutionContext.ApplyContinueOnError(step.ContinueOnError);
if (step.ExecutionContext.Result == TaskResult.Failed)
{
var continueOnError = false;
try
{
continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions);
}
catch (Exception ex)
{
Trace.Info("The step failed and an error occurred when attempting to determine whether to continue on error.");
Trace.Error(ex);
step.ExecutionContext.Error("The step failed and an error occurred when attempting to determine whether to continue on error.");
step.ExecutionContext.Error(ex);
}
if (continueOnError)
{
step.ExecutionContext.Outcome = step.ExecutionContext.Result;
step.ExecutionContext.Result = TaskResult.Succeeded;
Trace.Info($"Updated step result (continue on error)");
}
}
Trace.Info($"Step result: {step.ExecutionContext.Result}"); Trace.Info($"Step result: {step.ExecutionContext.Result}");
// Complete the step context // Complete the step context

View File

@@ -129,6 +129,7 @@
"required": true "required": true
}, },
"env": "step-env", "env": "step-env",
"continue-on-error": "boolean-steps-context",
"working-directory": "string-steps-context", "working-directory": "string-steps-context",
"shell": { "shell": {
"type": "non-empty-string", "type": "non-empty-string",
@@ -147,6 +148,7 @@
"type": "non-empty-string", "type": "non-empty-string",
"required": true "required": true
}, },
"continue-on-error": "boolean-steps-context",
"with": "step-with", "with": "step-with",
"env": "step-env" "env": "step-env"
} }
@@ -201,6 +203,20 @@
], ],
"string": {} "string": {}
}, },
"boolean-steps-context": {
"context": [
"github",
"inputs",
"strategy",
"matrix",
"steps",
"job",
"runner",
"env",
"hashFiles(1,255)"
],
"boolean": {}
},
"step-env": { "step-env": {
"context": [ "context": [
"github", "github",

View File

@@ -18,6 +18,7 @@ namespace GitHub.Services.Common
public override bool IsAuthenticationChallenge(IHttpResponse webResponse) public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{ {
// System.Console.WriteLine($"FederatedCredential.IsAuthenticationChallenge");
if (webResponse == null) if (webResponse == null)
{ {
return false; return false;

View File

@@ -88,6 +88,7 @@ namespace GitHub.Services.Common
IHttpResponse response, IHttpResponse response,
IssuedToken failedToken) IssuedToken failedToken)
{ {
// System.Console.WriteLine("IssuedTokenCredential.CreateTokenProvider");
if (response != null && !IsAuthenticationChallenge(response)) if (response != null && !IsAuthenticationChallenge(response))
{ {
throw new InvalidOperationException(); throw new InvalidOperationException();
@@ -99,12 +100,14 @@ namespace GitHub.Services.Common
{ {
throw new InvalidOperationException($"The {nameof(TokenStorageUrl)} property must have a value if the {nameof(Storage)} property is set on this instance of {GetType().Name}."); throw new InvalidOperationException($"The {nameof(TokenStorageUrl)} property must have a value if the {nameof(Storage)} property is set on this instance of {GetType().Name}.");
} }
// System.Console.WriteLine($"IssuedTokenCredential.CreateTokenProvider: TokenStorageUrl: {TokenStorageUrl}");
InitialToken = Storage.RetrieveToken(TokenStorageUrl, CredentialType); InitialToken = Storage.RetrieveToken(TokenStorageUrl, CredentialType);
} }
IssuedTokenProvider provider = OnCreateTokenProvider(serverUrl, response); IssuedTokenProvider provider = OnCreateTokenProvider(serverUrl, response);
if (provider != null) if (provider != null)
{ {
// System.Console.WriteLine($"IssuedTokenCredential.CreateTokenProvider: provider: {provider}");
provider.TokenStorageUrl = TokenStorageUrl; provider.TokenStorageUrl = TokenStorageUrl;
} }
@@ -123,6 +126,7 @@ namespace GitHub.Services.Common
internal virtual string GetAuthenticationChallenge(IHttpResponse webResponse) internal virtual string GetAuthenticationChallenge(IHttpResponse webResponse)
{ {
// System.Console.WriteLine($"IssuedTokenCredential.GetAuthenticationChallenge");
IEnumerable<String> values; IEnumerable<String> values;
if (!webResponse.Headers.TryGetValues(Internal.HttpHeaders.WwwAuthenticate, out values)) if (!webResponse.Headers.TryGetValues(Internal.HttpHeaders.WwwAuthenticate, out values))
{ {

View File

@@ -108,6 +108,7 @@ namespace GitHub.Services.Common
TaskScheduler scheduler, TaskScheduler scheduler,
IVssCredentialPrompt credentialPrompt) IVssCredentialPrompt credentialPrompt)
{ {
// System.Console.WriteLine($"VssCredentials.ctor");
this.PromptType = promptType; this.PromptType = promptType;
if (promptType == CredentialPromptType.PromptIfNeeded && scheduler == null) if (promptType == CredentialPromptType.PromptIfNeeded && scheduler == null)
@@ -150,6 +151,7 @@ namespace GitHub.Services.Common
{ {
get get
{ {
// System.Console.WriteLine($"VssCredentials.get_PromptType");
return m_promptType; return m_promptType;
} }
set set
@@ -170,6 +172,7 @@ namespace GitHub.Services.Common
{ {
get get
{ {
// System.Console.WriteLine($"VssCredentials.get_Federated");
return m_federatedCredential; return m_federatedCredential;
} }
} }
@@ -184,6 +187,7 @@ namespace GitHub.Services.Common
{ {
get get
{ {
// System.Console.WriteLine($"VssCredentials.get_Storage");
return m_credentialStorage; return m_credentialStorage;
} }
set set
@@ -203,6 +207,7 @@ namespace GitHub.Services.Common
/// </summary> /// </summary>
internal virtual bool TryGetValidAdalToken(IVssCredentialPrompt prompt) internal virtual bool TryGetValidAdalToken(IVssCredentialPrompt prompt)
{ {
// System.Console.WriteLine($"VssCredentials.TryGetValidAdalToken");
return false; return false;
} }
@@ -218,6 +223,7 @@ namespace GitHub.Services.Common
IHttpResponse webResponse, IHttpResponse webResponse,
IssuedToken failedToken) IssuedToken failedToken)
{ {
// System.Console.WriteLine("VssCredential.CreateTokenProvider");
ArgumentUtility.CheckForNull(serverUrl, "serverUrl"); ArgumentUtility.CheckForNull(serverUrl, "serverUrl");
IssuedTokenProvider tokenProvider = null; IssuedTokenProvider tokenProvider = null;
@@ -263,6 +269,7 @@ namespace GitHub.Services.Common
Uri serverUrl, Uri serverUrl,
out IssuedTokenProvider provider) out IssuedTokenProvider provider)
{ {
// System.Console.WriteLine($"VssCredentials.TryGetTokenProvider");
ArgumentUtility.CheckForNull(serverUrl, "serverUrl"); ArgumentUtility.CheckForNull(serverUrl, "serverUrl");
lock (m_thisLock) lock (m_thisLock)
@@ -272,11 +279,13 @@ namespace GitHub.Services.Common
{ {
if (m_federatedCredential != null) if (m_federatedCredential != null)
{ {
// System.Console.WriteLine($"VssCredentials.TryGetTokenProvider: Using federated credential");
m_currentProvider = m_federatedCredential.CreateTokenProvider(serverUrl, null, null); m_currentProvider = m_federatedCredential.CreateTokenProvider(serverUrl, null, null);
} }
if (m_currentProvider != null) if (m_currentProvider != null)
{ {
// System.Console.WriteLine($"VssCredentials.TryGetTokenProvider: Issued token provider created");
VssHttpEventSource.Log.IssuedTokenProviderCreated(VssTraceActivity.Current, m_currentProvider); VssHttpEventSource.Log.IssuedTokenProviderCreated(VssTraceActivity.Current, m_currentProvider);
} }
} }
@@ -294,6 +303,7 @@ namespace GitHub.Services.Common
/// <returns>True if this is an token authentication redirect, false otherwise</returns> /// <returns>True if this is an token authentication redirect, false otherwise</returns>
internal bool IsAuthenticationChallenge(IHttpResponse webResponse) internal bool IsAuthenticationChallenge(IHttpResponse webResponse)
{ {
// System.Console.WriteLine($"VssCredentials.IsAuthenticationChallenge");
if (webResponse == null) if (webResponse == null)
{ {
return false; return false;
@@ -313,6 +323,7 @@ namespace GitHub.Services.Common
Uri serviceLocation, Uri serviceLocation,
string identityProvider) string identityProvider)
{ {
// System.Console.WriteLine($"VssCredentials.SignOut");
// Remove the token in the storage and the current token provider. Note that we don't // Remove the token in the storage and the current token provider. Note that we don't
// call InvalidateToken here because we want to remove the whole token not just its value // call InvalidateToken here because we want to remove the whole token not just its value
if ((m_currentProvider != null) && (m_currentProvider.CurrentToken != null)) if ((m_currentProvider != null) && (m_currentProvider.CurrentToken != null))
@@ -349,6 +360,7 @@ namespace GitHub.Services.Common
string token, string token,
IDictionary<string, string> attributes) IDictionary<string, string> attributes)
{ {
// System.Console.WriteLine($"VssCredentials.WriteAuthorizationToken");
int i = 0; int i = 0;
for (int j = 0; j < token.Length; i++, j += 128) for (int j = 0; j < token.Length; i++, j += 128)
{ {
@@ -360,6 +372,7 @@ namespace GitHub.Services.Common
protected static string ReadAuthorizationToken(IDictionary<string, string> attributes) protected static string ReadAuthorizationToken(IDictionary<string, string> attributes)
{ {
// System.Console.WriteLine($"VssCredentials.ReadAuthorizationToken");
string authTokenCountValue; string authTokenCountValue;
if (attributes.TryGetValue("AuthTokenSegmentCount", out authTokenCountValue)) if (attributes.TryGetValue("AuthTokenSegmentCount", out authTokenCountValue))
{ {

View File

@@ -49,6 +49,7 @@ namespace GitHub.Services.Common
VssHttpRequestSettings settings, VssHttpRequestSettings settings,
HttpMessageHandler innerHandler) HttpMessageHandler innerHandler)
{ {
// System.Console.WriteLine("VssHttpMessageHandler.ctor");
this.Credentials = credentials; this.Credentials = credentials;
this.Settings = settings; this.Settings = settings;
this.ExpectContinue = settings.ExpectContinue; this.ExpectContinue = settings.ExpectContinue;
@@ -122,6 +123,7 @@ namespace GitHub.Services.Common
HttpRequestMessage request, HttpRequestMessage request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// System.Console.WriteLine("VssHttpMessageHandler.SendAsync");
VssTraceActivity traceActivity = VssTraceActivity.Current; VssTraceActivity traceActivity = VssTraceActivity.Current;
var traceInfo = VssHttpMessageHandlerTraceInfo.GetTraceInfo(request); var traceInfo = VssHttpMessageHandlerTraceInfo.GetTraceInfo(request);
@@ -130,6 +132,7 @@ namespace GitHub.Services.Common
if (!m_appliedClientCertificatesToTransportHandler && if (!m_appliedClientCertificatesToTransportHandler &&
request.RequestUri.Scheme == "https") request.RequestUri.Scheme == "https")
{ {
// System.Console.WriteLine("VssHttpMessageHandler.SendAsync: !appliedClientCertificatesToTransportHandler");
HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler; HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
if (httpClientHandler != null && if (httpClientHandler != null &&
this.Settings.ClientCertificateManager != null && this.Settings.ClientCertificateManager != null &&
@@ -144,6 +147,7 @@ namespace GitHub.Services.Common
if (!m_appliedServerCertificateValidationCallbackToTransportHandler && if (!m_appliedServerCertificateValidationCallbackToTransportHandler &&
request.RequestUri.Scheme == "https") request.RequestUri.Scheme == "https")
{ {
// System.Console.WriteLine("VssHttpMessageHandler.SendAsync: !appliedServerCertificateValidationCallbackToTransportHandler");
HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler; HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
if (httpClientHandler != null && if (httpClientHandler != null &&
this.Settings.ServerCertificateValidationCallback != null) this.Settings.ServerCertificateValidationCallback != null)
@@ -165,6 +169,7 @@ namespace GitHub.Services.Common
IssuedTokenProvider provider; IssuedTokenProvider provider;
if (this.Credentials.TryGetTokenProvider(request.RequestUri, out provider)) if (this.Credentials.TryGetTokenProvider(request.RequestUri, out provider))
{ {
// System.Console.WriteLine("VssHttpMessageHandler.SendAsync: Got token provider from credentials");
token = provider.CurrentToken; token = provider.CurrentToken;
} }
@@ -225,6 +230,7 @@ namespace GitHub.Services.Common
traceInfo?.TraceResponseContentTime(); traceInfo?.TraceResponseContentTime();
// System.Console.WriteLine($"VssHttpMessageHandler.SendAsync: Creating response wrapper");
responseWrapper = new HttpResponseMessageWrapper(response); responseWrapper = new HttpResponseMessageWrapper(response);
if (!this.Credentials.IsAuthenticationChallenge(responseWrapper)) if (!this.Credentials.IsAuthenticationChallenge(responseWrapper))
@@ -232,6 +238,7 @@ namespace GitHub.Services.Common
// Validate the token after it has been successfully authenticated with the server. // Validate the token after it has been successfully authenticated with the server.
if (provider != null) if (provider != null)
{ {
// System.Console.WriteLine("VssHttpMessageHandler.SendAsync: Validating token");
provider.ValidateToken(token, responseWrapper); provider.ValidateToken(token, responseWrapper);
} }
@@ -243,6 +250,7 @@ namespace GitHub.Services.Common
} }
else else
{ {
// System.Console.WriteLine($"VssHttpMessageHandler.SendAsync: Auth challenge. Response status code {response.StatusCode}; headers {response.Headers}");
// In the case of a Windows token, only apply it to the web proxy if it // In the case of a Windows token, only apply it to the web proxy if it
// returned a 407 Proxy Authentication Required. If we didn't get this // returned a 407 Proxy Authentication Required. If we didn't get this
// status code back, then the proxy (if there is one) is clearly working fine, // status code back, then the proxy (if there is one) is clearly working fine,
@@ -288,6 +296,7 @@ namespace GitHub.Services.Common
} }
// Now invoke the provider and await the result // Now invoke the provider and await the result
// System.Console.WriteLine($"VssHttpMessageHandler.SendAsync: Calling GetTokenAsync");
token = await provider.GetTokenAsync(token, tokenSource.Token).ConfigureAwait(false); token = await provider.GetTokenAsync(token, tokenSource.Token).ConfigureAwait(false);
// I always see 0 here, but the method above could take more time so keep for now // I always see 0 here, but the method above could take more time so keep for now
@@ -432,6 +441,7 @@ namespace GitHub.Services.Common
activity != VssTraceActivity.Empty && activity != VssTraceActivity.Empty &&
!request.Headers.Contains(HttpHeaders.TfsSessionHeader)) !request.Headers.Contains(HttpHeaders.TfsSessionHeader))
{ {
// System.Console.WriteLine($"VssHttpMessageHandler.ApplyHeaders: Activity ID {activity.Id}");
request.Headers.Add(HttpHeaders.TfsSessionHeader, activity.Id.ToString("D")); request.Headers.Add(HttpHeaders.TfsSessionHeader, activity.Id.ToString("D"));
} }
@@ -452,13 +462,16 @@ namespace GitHub.Services.Common
ICredentials credentialsToken = token as ICredentials; ICredentials credentialsToken = token as ICredentials;
if (credentialsToken != null) if (credentialsToken != null)
{ {
// System.Console.WriteLine("VssHttpMessageHandler.ApplyToken: Credentials token != null");
if (applyICredentialsToWebProxy) if (applyICredentialsToWebProxy)
{ {
// System.Console.WriteLine("VssHttpMessageHandler.ApplyToken: Apply credentials to web proxy");
HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler; HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
if (httpClientHandler != null && if (httpClientHandler != null &&
httpClientHandler.Proxy != null) httpClientHandler.Proxy != null)
{ {
// System.Console.WriteLine("VssHttpMessageHandler.ApplyToken: Setting proxy crednetials");
httpClientHandler.Proxy.Credentials = credentialsToken; httpClientHandler.Proxy.Credentials = credentialsToken;
} }
} }
@@ -467,6 +480,7 @@ namespace GitHub.Services.Common
} }
else else
{ {
// System.Console.WriteLine("VssHttpMessageHandler.ApplyToken: Applying credentials to request");
token.ApplyTo(new HttpRequestMessageWrapper(request)); token.ApplyTo(new HttpRequestMessageWrapper(request));
} }
} }
@@ -479,7 +493,8 @@ namespace GitHub.Services.Common
HttpClientHandler httpClientHandler = handler as HttpClientHandler; HttpClientHandler httpClientHandler = handler as HttpClientHandler;
if (httpClientHandler != null) if (httpClientHandler != null)
{ {
httpClientHandler.AllowAutoRedirect = settings.AllowAutoRedirect; // System.Console.WriteLine($"VssHttpMessageHandler.ApplySettings: Default credentials = {defaultCredentials} AllowAutoRedirect = {settings.AllowAutoRedirect}");
httpClientHandler.AllowAutoRedirect = true; //settings.AllowAutoRedirect;
httpClientHandler.ClientCertificateOptions = ClientCertificateOption.Manual; httpClientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
//Setting httpClientHandler.UseDefaultCredentials to false in .Net Core, clears httpClientHandler.Credentials if //Setting httpClientHandler.UseDefaultCredentials to false in .Net Core, clears httpClientHandler.Credentials if
//credentials is already set to defaultcredentials. Therefore httpClientHandler.Credentials must be //credentials is already set to defaultcredentials. Therefore httpClientHandler.Credentials must be
@@ -550,6 +565,7 @@ namespace GitHub.Services.Common
Uri uri, Uri uri,
String authType) String authType)
{ {
// System.Console.WriteLine($"CredentialWrapper.GetCredential: InnerCredentials = {InnerCredentials}");
return InnerCredentials != null ? InnerCredentials.GetCredential(uri, authType) : null; return InnerCredentials != null ? InnerCredentials.GetCredential(uri, authType) : null;
} }
} }

View File

@@ -27,6 +27,7 @@ using System.Net.Http.Headers;
using System.Net.Http.Formatting; using System.Net.Http.Formatting;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.Pipelines;
using GitHub.Services.Common; using GitHub.Services.Common;
using GitHub.Services.WebApi; using GitHub.Services.WebApi;
@@ -378,6 +379,7 @@ namespace GitHub.DistributedTask.WebApi
/// <param name="requestId"></param> /// <param name="requestId"></param>
/// <param name="lockToken"></param> /// <param name="lockToken"></param>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="targetHostId"></param>
/// <param name="userState"></param> /// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param> /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
@@ -386,6 +388,7 @@ namespace GitHub.DistributedTask.WebApi
long requestId, long requestId,
Guid lockToken, Guid lockToken,
TaskAgentJobRequest request, TaskAgentJobRequest request,
Guid targetHostId,
object userState = null, object userState = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
@@ -396,6 +399,7 @@ namespace GitHub.DistributedTask.WebApi
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>(); List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
queryParams.Add("lockToken", lockToken.ToString()); queryParams.Add("lockToken", lockToken.ToString());
queryParams.Add("targetHostId", targetHostId.ToString());
return SendAsync<TaskAgentJobRequest>( return SendAsync<TaskAgentJobRequest>(
httpMethod, httpMethod,
@@ -703,6 +707,47 @@ namespace GitHub.DistributedTask.WebApi
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
/// <summary>
/// [Preview API]
/// </summary>
/// <param name="scopeId"></param>
/// <param name="planType"></param>
/// <param name="planGroup"></param>
/// <param name="planId"></param>
/// <param name="instanceRefsJson"></param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual Task<Pipelines.AgentJobRequestMessage> GetJobMessageAsync(
Guid scopeId,
Guid hostId,
string planType,
string planGroup,
Guid planId,
string instanceRefsJson,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("GET");
Guid locationId = new Guid("25adab70-1379-4186-be8e-b643061ebe3a");
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
queryParams.Add("scopeId", scopeId.ToString());
queryParams.Add("hostId", hostId.ToString());
queryParams.Add("planType", planType);
queryParams.Add("planGroup", planGroup);
queryParams.Add("planId", planId.ToString());
queryParams.Add("instanceRefsJson", instanceRefsJson);
return SendAsync<Pipelines.AgentJobRequestMessage>(
httpMethod,
locationId,
version: new ApiResourceVersion(6.0, 1),
queryParameters: queryParams,
userState: userState,
cancellationToken: cancellationToken);
}
/// <summary> /// <summary>
/// [Preview API] /// [Preview API]
/// </summary> /// </summary>
@@ -717,6 +762,7 @@ namespace GitHub.DistributedTask.WebApi
object userState = null, object userState = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
// System.Console.WriteLine("TaskAgentHttpClientBase.CreateAgentSessionAsync");
HttpMethod httpMethod = new HttpMethod("POST"); HttpMethod httpMethod = new HttpMethod("POST");
Guid locationId = new Guid("134e239e-2df3-4794-a6f6-24f1f19ec8dc"); Guid locationId = new Guid("134e239e-2df3-4794-a6f6-24f1f19ec8dc");
object routeValues = new { poolId = poolId }; object routeValues = new { poolId = poolId };

View File

@@ -5,5 +5,6 @@ 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";
} }
} }

View File

@@ -64,6 +64,7 @@ namespace GitHub.DistributedTask.WebApi
Guid lockToken, Guid lockToken,
DateTime finishTime, DateTime finishTime,
TaskResult result, TaskResult result,
Guid targetHostId,
Object userState = null, Object userState = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
@@ -74,7 +75,7 @@ namespace GitHub.DistributedTask.WebApi
Result = result, Result = result,
}; };
return UpdateAgentRequestAsync(poolId, requestId, lockToken, request, userState, cancellationToken); return UpdateAgentRequestAsync(poolId, requestId, lockToken, request, targetHostId, userState, cancellationToken);
} }
public Task<List<TaskAgent>> GetAgentsAsync( public Task<List<TaskAgent>> GetAgentsAsync(
@@ -152,6 +153,7 @@ namespace GitHub.DistributedTask.WebApi
CancellationToken cancellationToken = default(CancellationToken), CancellationToken cancellationToken = default(CancellationToken),
Func<HttpResponseMessage, CancellationToken, Task<T>> processResponse = null) Func<HttpResponseMessage, CancellationToken, Task<T>> processResponse = null)
{ {
// System.Console.WriteLine("TaskAgentHttpClient.SendAsync 1");
return SendAsync<T>(method, null, locationId, routeValues, version, content, queryParameters, userState, cancellationToken, processResponse); return SendAsync<T>(method, null, locationId, routeValues, version, content, queryParameters, userState, cancellationToken, processResponse);
} }
@@ -170,6 +172,7 @@ namespace GitHub.DistributedTask.WebApi
using (VssTraceActivity.GetOrCreate().EnterCorrelationScope()) using (VssTraceActivity.GetOrCreate().EnterCorrelationScope())
using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, additionalHeaders, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false)) using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, additionalHeaders, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false))
{ {
// System.Console.WriteLine("TaskAgentHttpClient.SendAsync 2");
return await SendAsync<T>(requestMessage, userState, cancellationToken, processResponse).ConfigureAwait(false); return await SendAsync<T>(requestMessage, userState, cancellationToken, processResponse).ConfigureAwait(false);
} }
} }
@@ -180,6 +183,7 @@ namespace GitHub.DistributedTask.WebApi
CancellationToken cancellationToken = default(CancellationToken), CancellationToken cancellationToken = default(CancellationToken),
Func<HttpResponseMessage, CancellationToken, Task<T>> processResponse = null) Func<HttpResponseMessage, CancellationToken, Task<T>> processResponse = null)
{ {
// System.Console.WriteLine("TaskAgentHttpClient.SendAsync 3");
if (processResponse == null) if (processResponse == null)
{ {
processResponse = ReadContentAsAsync<T>; processResponse = ReadContentAsAsync<T>;

View File

@@ -801,6 +801,7 @@ namespace GitHub.Services.WebApi.Location
private async Task<ConnectionData> GetConnectionDataAsync(ConnectOptions connectOptions, int lastChangeId, CancellationToken cancellationToken) private async Task<ConnectionData> GetConnectionDataAsync(ConnectOptions connectOptions, int lastChangeId, CancellationToken cancellationToken)
{ {
// System.Console.WriteLine("ServerDataProvider.GetConnectionDataAsync");
int timeoutRetries = 1; int timeoutRetries = 1;
while (true) while (true)

View File

@@ -75,6 +75,7 @@ namespace GitHub.Services.OAuth
internal override void ApplyTo(IHttpRequest request) internal override void ApplyTo(IHttpRequest request)
{ {
// System.Console.WriteLine($"VssOAuthAccessToken.ApplyTo: Bearer {m_value}");
request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"Bearer {m_value}"); request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"Bearer {m_value}");
} }

View File

@@ -60,6 +60,7 @@ namespace GitHub.Services.OAuth
Uri serverUrl, Uri serverUrl,
IHttpResponse response) IHttpResponse response)
{ {
// System.Console.WriteLine($"VssOAuthAccessTokenCredential.OnCreateTokenProvider");
return new VssOAuthAccessTokenProvider(this, serverUrl, null); return new VssOAuthAccessTokenProvider(this, serverUrl, null);
} }
@@ -71,6 +72,7 @@ namespace GitHub.Services.OAuth
Uri signInUrl) Uri signInUrl)
: base(credential, serverUrl, signInUrl) : base(credential, serverUrl, signInUrl)
{ {
// System.Console.WriteLine($"VssOAuthAccessTokenProvider.ctor");
} }
public override Boolean GetTokenIsInteractive public override Boolean GetTokenIsInteractive

View File

@@ -103,17 +103,23 @@ namespace GitHub.Services.OAuth
/// <returns>True if the web response indicates an authorization challenge; otherwise, false</returns> /// <returns>True if the web response indicates an authorization challenge; otherwise, false</returns>
public override Boolean IsAuthenticationChallenge(IHttpResponse webResponse) public override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
{ {
// System.Console.WriteLine($"VssOAuthCredential.IsAuthenticationChallenge");
if (webResponse == null) if (webResponse == null)
{ {
// System.Console.WriteLine($"VssOAuthCredential.IsAuthenticationChallenge: webResponse is null");
return false; return false;
} }
if (webResponse.StatusCode == HttpStatusCode.Found || if (webResponse.StatusCode == HttpStatusCode.Found ||
webResponse.StatusCode == HttpStatusCode.Unauthorized) webResponse.StatusCode == HttpStatusCode.Unauthorized)
{ {
return webResponse.Headers.GetValues(Common.Internal.HttpHeaders.WwwAuthenticate).Any(x => x.IndexOf("Bearer", StringComparison.OrdinalIgnoreCase) >= 0); // System.Console.WriteLine($"VssOAuthCredential.IsAuthenticationChallenge: found or unauthorized");
var result = webResponse.Headers.GetValues(Common.Internal.HttpHeaders.WwwAuthenticate).Any(x => x.IndexOf("Bearer", StringComparison.OrdinalIgnoreCase) >= 0);
// System.Console.WriteLine($"VssOAuthCredential.IsAuthenticationChallenge: {result}");
return result;
} }
// System.Console.WriteLine($"VssOAuthCredential.IsAuthenticationChallenge: false");
return false; return false;
} }
@@ -121,6 +127,7 @@ namespace GitHub.Services.OAuth
Uri serverUrl, Uri serverUrl,
IHttpResponse response) IHttpResponse response)
{ {
// System.Console.WriteLine($"VssOAuthCredential.OnCreateTokenProvider");
return new VssOAuthTokenProvider(this, serverUrl); return new VssOAuthTokenProvider(this, serverUrl);
} }

View File

@@ -54,6 +54,7 @@ namespace GitHub.Services.OAuth
VssOAuthTokenParameters tokenParameters = null, VssOAuthTokenParameters tokenParameters = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
// todo: qqq
VssTraceActivity traceActivity = VssTraceActivity.Current; VssTraceActivity traceActivity = VssTraceActivity.Current;
using (HttpClient client = new HttpClient(CreateMessageHandler(this.AuthorizationUrl))) using (HttpClient client = new HttpClient(CreateMessageHandler(this.AuthorizationUrl)))
{ {

View File

@@ -47,6 +47,7 @@ namespace GitHub.Services.OAuth
VssOAuthTokenParameters tokenParameters) VssOAuthTokenParameters tokenParameters)
: base(credential, serverUrl, authorizationUrl) : base(credential, serverUrl, authorizationUrl)
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.ctor");
m_grant = grant; m_grant = grant;
m_tokenParameters = tokenParameters; m_tokenParameters = tokenParameters;
m_clientCredential = clientCrential; m_clientCredential = clientCrential;
@@ -59,6 +60,7 @@ namespace GitHub.Services.OAuth
{ {
get get
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.get_Grant");
return m_grant; return m_grant;
} }
} }
@@ -70,6 +72,7 @@ namespace GitHub.Services.OAuth
{ {
get get
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.get_ClientCredential");
return m_clientCredential; return m_clientCredential;
} }
} }
@@ -81,6 +84,7 @@ namespace GitHub.Services.OAuth
{ {
get get
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.get_TokenParameters");
return m_tokenParameters; return m_tokenParameters;
} }
} }
@@ -92,6 +96,7 @@ namespace GitHub.Services.OAuth
{ {
get get
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.get_GetTokenIsInteractive");
return false; return false;
} }
} }
@@ -100,6 +105,7 @@ namespace GitHub.Services.OAuth
{ {
get get
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.get_AuthenticationParameter");
if (this.ClientCredential == null) if (this.ClientCredential == null)
{ {
return null; return null;
@@ -115,12 +121,14 @@ namespace GitHub.Services.OAuth
{ {
get get
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.get_AuthenticationScheme");
return "Bearer"; return "Bearer";
} }
} }
public async Task<string> ValidateCredentialAsync(CancellationToken cancellationToken) public async Task<string> ValidateCredentialAsync(CancellationToken cancellationToken)
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.ValidateCredentialAsync: Calling VssOAuthTokenHttpClient.GetTokenAsync");
var tokenHttpClient = new VssOAuthTokenHttpClient(this.SignInUrl); var tokenHttpClient = new VssOAuthTokenHttpClient(this.SignInUrl);
var tokenResponse = await tokenHttpClient.GetTokenAsync(this.Grant, this.ClientCredential, this.TokenParameters, cancellationToken); var tokenResponse = await tokenHttpClient.GetTokenAsync(this.Grant, this.ClientCredential, this.TokenParameters, cancellationToken);
@@ -139,6 +147,7 @@ namespace GitHub.Services.OAuth
IssuedToken failedToken, IssuedToken failedToken,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.OnGetTokenAsync");
if (this.SignInUrl == null || if (this.SignInUrl == null ||
this.Grant == null || this.Grant == null ||
this.ClientCredential == null) this.ClientCredential == null)
@@ -151,6 +160,7 @@ namespace GitHub.Services.OAuth
try try
{ {
var tokenHttpClient = new VssOAuthTokenHttpClient(this.SignInUrl); var tokenHttpClient = new VssOAuthTokenHttpClient(this.SignInUrl);
// System.Console.WriteLine($"VssOAuthTokenProvider.OnGetTokenAsync: Calling VssOAuthTokenHttpClient.GetTokenAsync; sign-in url {this.SignInUrl.AbsoluteUri}");
var tokenResponse = await tokenHttpClient.GetTokenAsync(this.Grant, this.ClientCredential, this.TokenParameters, cancellationToken).ConfigureAwait(false); var tokenResponse = await tokenHttpClient.GetTokenAsync(this.Grant, this.ClientCredential, this.TokenParameters, cancellationToken).ConfigureAwait(false);
if (!String.IsNullOrEmpty(tokenResponse.AccessToken)) if (!String.IsNullOrEmpty(tokenResponse.AccessToken))
{ {
@@ -197,6 +207,7 @@ namespace GitHub.Services.OAuth
protected virtual IssuedToken CreateIssuedToken(VssOAuthTokenResponse tokenResponse) protected virtual IssuedToken CreateIssuedToken(VssOAuthTokenResponse tokenResponse)
{ {
// System.Console.WriteLine($"VssOAuthTokenProvider.CreateIssuedToken");
if (tokenResponse.ExpiresIn > 0) if (tokenResponse.ExpiresIn > 0)
{ {
return new VssOAuthAccessToken(tokenResponse.AccessToken, DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn)); return new VssOAuthAccessToken(tokenResponse.AccessToken, DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn));

View File

@@ -100,6 +100,7 @@ namespace GitHub.Services.WebApi
IDictionary<String, String> parameters, IDictionary<String, String> parameters,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("VssConnection.ConnectAsync");
CheckForDisposed(); CheckForDisposed();
// Set the connectMode on the credential's FederatedPrompt // Set the connectMode on the credential's FederatedPrompt
if (Credentials.Federated != null && Credentials.Federated.Prompt != null) if (Credentials.Federated != null && Credentials.Federated.Prompt != null)

View File

@@ -390,6 +390,7 @@ namespace GitHub.Services.WebApi
Object userState = null, Object userState = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("VssHttpClientBase.SendAsync 1");
return SendAsync<T>(method, null, locationId, routeValues, version, content, queryParameters, userState, cancellationToken); return SendAsync<T>(method, null, locationId, routeValues, version, content, queryParameters, userState, cancellationToken);
} }
@@ -404,6 +405,7 @@ namespace GitHub.Services.WebApi
Object userState = null, Object userState = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("VssHttpClientBase.SendAsync 2");
using (VssTraceActivity.GetOrCreate().EnterCorrelationScope()) using (VssTraceActivity.GetOrCreate().EnterCorrelationScope())
using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, additionalHeaders, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false)) using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, additionalHeaders, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false))
{ {
@@ -422,6 +424,7 @@ namespace GitHub.Services.WebApi
Object userState = null, Object userState = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("VssHttpClientBase.SendAsync 3");
using (VssTraceActivity.GetOrCreate().EnterCorrelationScope()) using (VssTraceActivity.GetOrCreate().EnterCorrelationScope())
using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, additionalHeaders, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false)) using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, additionalHeaders, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false))
{ {
@@ -455,6 +458,7 @@ namespace GitHub.Services.WebApi
Object userState = null, Object userState = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("VssHttpClientBase.SendAsync 4");
using (VssTraceActivity.GetOrCreate().EnterCorrelationScope()) using (VssTraceActivity.GetOrCreate().EnterCorrelationScope())
using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false)) using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false))
{ {
@@ -473,6 +477,7 @@ namespace GitHub.Services.WebApi
Object userState = null, Object userState = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("VssHttpClientBase.SendAsync 5");
using (VssTraceActivity.GetOrCreate().EnterCorrelationScope()) using (VssTraceActivity.GetOrCreate().EnterCorrelationScope())
using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false)) using (HttpRequestMessage requestMessage = await CreateRequestMessageAsync(method, locationId, routeValues, version, content, queryParameters, userState, cancellationToken).ConfigureAwait(false))
{ {
@@ -501,6 +506,7 @@ namespace GitHub.Services.WebApi
CancellationToken cancellationToken = default(CancellationToken), CancellationToken cancellationToken = default(CancellationToken),
String mediaType = c_jsonMediaType) String mediaType = c_jsonMediaType)
{ {
// System.Console.WriteLine("VssHttpClientBase.CreateRequestMessageAsync 1");
return CreateRequestMessageAsync(method, null, locationId, routeValues, version, content, queryParameters, userState, cancellationToken, mediaType); return CreateRequestMessageAsync(method, null, locationId, routeValues, version, content, queryParameters, userState, cancellationToken, mediaType);
} }
@@ -526,6 +532,7 @@ namespace GitHub.Services.WebApi
CancellationToken cancellationToken = default(CancellationToken), CancellationToken cancellationToken = default(CancellationToken),
String mediaType = c_jsonMediaType) String mediaType = c_jsonMediaType)
{ {
// System.Console.WriteLine("VssHttpClientBase.CreateRequestMessageAsync 2");
// Lookup the location // Lookup the location
ApiResourceLocation location = await GetResourceLocationAsync(locationId, userState, cancellationToken).ConfigureAwait(false); ApiResourceLocation location = await GetResourceLocationAsync(locationId, userState, cancellationToken).ConfigureAwait(false);
if (location == null) if (location == null)
@@ -555,6 +562,7 @@ namespace GitHub.Services.WebApi
IEnumerable<KeyValuePair<String, String>> queryParameters = null, IEnumerable<KeyValuePair<String, String>> queryParameters = null,
String mediaType = c_jsonMediaType) String mediaType = c_jsonMediaType)
{ {
// System.Console.WriteLine("VssHttpClientBase.CreateRequestMessageAsync 3");
return CreateRequestMessage(method, null, location, routeValues, version, content, queryParameters, mediaType); return CreateRequestMessage(method, null, location, routeValues, version, content, queryParameters, mediaType);
} }
@@ -578,6 +586,7 @@ namespace GitHub.Services.WebApi
IEnumerable<KeyValuePair<String, String>> queryParameters = null, IEnumerable<KeyValuePair<String, String>> queryParameters = null,
String mediaType = c_jsonMediaType) String mediaType = c_jsonMediaType)
{ {
// System.Console.WriteLine("VssHttpClientBase.CreateRequestMessageAsync 4");
CheckForDisposed(); CheckForDisposed();
// Negotiate the request version to send // Negotiate the request version to send
ApiResourceVersion requestVersion = NegotiateRequestVersion(location, version); ApiResourceVersion requestVersion = NegotiateRequestVersion(location, version);
@@ -749,12 +758,14 @@ namespace GitHub.Services.WebApi
//from deadlocking... //from deadlocking...
using (HttpResponseMessage response = await this.SendAsync(message, userState, cancellationToken).ConfigureAwait(false)) using (HttpResponseMessage response = await this.SendAsync(message, userState, cancellationToken).ConfigureAwait(false))
{ {
// System.Console.WriteLine("VssHttpClientBase.SendAsync 6");
return await ReadContentAsAsync<T>(response, cancellationToken).ConfigureAwait(false); return await ReadContentAsAsync<T>(response, cancellationToken).ConfigureAwait(false);
} }
} }
protected async Task<T> ReadContentAsAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken)) protected async Task<T> ReadContentAsAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine($"VssHttpClientBase.ReadContentAsAsync {response.Headers}");
CheckForDisposed(); CheckForDisposed();
Boolean isJson = IsJsonResponse(response); Boolean isJson = IsJsonResponse(response);
bool mismatchContentType = false; bool mismatchContentType = false;
@@ -766,17 +777,20 @@ namespace GitHub.Services.WebApi
!typeof(Byte[]).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) && !typeof(Byte[]).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()) &&
!typeof(JObject).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo())) !typeof(JObject).GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo()))
{ {
// System.Console.WriteLine("VssHttpClientBase.ReadContentAsAsync: isJson 1");
// expect it to come back wrapped, if it isn't it is a bug! // expect it to come back wrapped, if it isn't it is a bug!
var wrapper = await ReadJsonContentAsync<VssJsonCollectionWrapper<T>>(response, cancellationToken).ConfigureAwait(false); var wrapper = await ReadJsonContentAsync<VssJsonCollectionWrapper<T>>(response, cancellationToken).ConfigureAwait(false);
return wrapper.Value; return wrapper.Value;
} }
else if (isJson) else if (isJson)
{ {
// System.Console.WriteLine("VssHttpClientBase.ReadContentAsAsync: isJson 2");
return await ReadJsonContentAsync<T>(response, cancellationToken).ConfigureAwait(false); return await ReadJsonContentAsync<T>(response, cancellationToken).ConfigureAwait(false);
} }
} }
catch (JsonReaderException) catch (JsonReaderException)
{ {
// System.Console.WriteLine("VssHttpClientBase.ReadContentAsAsync: mismatchContentType");
// We thought the content was JSON but failed to parse. // We thought the content was JSON but failed to parse.
// In this case, do nothing and utilize the HandleUnknownContentType call below // In this case, do nothing and utilize the HandleUnknownContentType call below
mismatchContentType = true; mismatchContentType = true;
@@ -802,6 +816,7 @@ namespace GitHub.Services.WebApi
Object userState = null, Object userState = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("VssHttpClientBase.SendAsync 7");
// the default in httpClient for HttpCompletionOption is ResponseContentRead so that is what we do here // the default in httpClient for HttpCompletionOption is ResponseContentRead so that is what we do here
return this.SendAsync( return this.SendAsync(
message, message,
@@ -816,6 +831,7 @@ namespace GitHub.Services.WebApi
Object userState = null, Object userState = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
// System.Console.WriteLine("VssHttpClientBase.SendAsync 8");
CheckForDisposed(); CheckForDisposed();
if (message.Headers.UserAgent != null) if (message.Headers.UserAgent != null)
{ {
@@ -851,6 +867,7 @@ namespace GitHub.Services.WebApi
//ConfigureAwait(false) enables the continuation to be run outside //ConfigureAwait(false) enables the continuation to be run outside
//any captured SyncronizationContext (such as ASP.NET's) which keeps things //any captured SyncronizationContext (such as ASP.NET's) which keeps things
//from deadlocking... //from deadlocking...
// System.Console.WriteLine($"VssHttpClientBase.SendAsync 8: Calling Client.SendAsync {message}");
HttpResponseMessage response = await Client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false); HttpResponseMessage response = await Client.SendAsync(message, completionOption, cancellationToken).ConfigureAwait(false);
// Inject delay or failure for testing // Inject delay or failure for testing
@@ -868,6 +885,7 @@ namespace GitHub.Services.WebApi
[Obsolete("Use VssHttpClientBase.HandleResponseAsync instead")] [Obsolete("Use VssHttpClientBase.HandleResponseAsync instead")]
protected virtual void HandleResponse(HttpResponseMessage response) protected virtual void HandleResponse(HttpResponseMessage response)
{ {
// System.Console.WriteLine("VssHttpClientBase.HandleResponse 1");
} }
@@ -875,6 +893,7 @@ namespace GitHub.Services.WebApi
HttpResponseMessage response, HttpResponseMessage response,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// System.Console.WriteLine($"VssHttpClientBase.HandleResponse 2 status code {response.StatusCode} headers {response.Headers}");
response.Trace(); response.Trace();
VssHttpEventSource.Log.HttpRequestStop(VssTraceActivity.Current, response); VssHttpEventSource.Log.HttpRequestStop(VssTraceActivity.Current, response);
@@ -886,6 +905,7 @@ namespace GitHub.Services.WebApi
} }
else if (ShouldThrowError(response)) else if (ShouldThrowError(response))
{ {
// System.Console.WriteLine("VssHttpClientBase.HandleResponse: Should throw error");
Exception exToThrow = null; Exception exToThrow = null;
if (IsJsonResponse(response)) if (IsJsonResponse(response))
{ {
@@ -909,6 +929,7 @@ namespace GitHub.Services.WebApi
{ {
message = response.ReasonPhrase; message = response.ReasonPhrase;
} }
// System.Console.WriteLine($"VssHttpClientBase.HandleResponse: Exception message {message}");
exToThrow = new VssServiceResponseException(response.StatusCode, message, exToThrow); exToThrow = new VssServiceResponseException(response.StatusCode, message, exToThrow);
} }

View File

@@ -774,7 +774,14 @@ namespace GitHub.Runner.Common.Tests.Listener
var traceFile = Path.GetTempFileName(); var traceFile = Path.GetTempFileName();
File.Copy(hc.TraceFileName, traceFile, true); File.Copy(hc.TraceFileName, traceFile, true);
Assert.Contains("Something wrong with the trimmed runner package, failback to use the full package for runner updates", File.ReadAllText(traceFile)); if (File.ReadAllText(traceFile).Contains("Use trimmed (runtime+externals) package"))
{
Assert.Contains("Something wrong with the trimmed runner package, failback to use the full package for runner updates", File.ReadAllText(traceFile));
}
else
{
hc.GetTrace().Warning("Skipping the 'TestSelfUpdateAsync_FallbackToFullPackage' test, as the `externals` or `runtime` hashes have been updated");
}
} }
} }
finally finally

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Listener.Configuration; using GitHub.Runner.Listener.Configuration;
using Xunit; using Xunit;
@@ -15,7 +16,7 @@ namespace GitHub.Runner.Common.Tests
{ {
RunnerSettings settings = new RunnerSettings(); RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisiskindofalongrunnername1"; settings.AgentName = "thisiskindofalongrunnerabcde";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890"; settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample"; settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample";
@@ -43,7 +44,7 @@ namespace GitHub.Runner.Common.Tests
Assert.Equal("actions", serviceNameParts[0]); Assert.Equal("actions", serviceNameParts[0]);
Assert.Equal("runner", serviceNameParts[1]); Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-' Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-'
Assert.Equal("thisiskindofalongrunnername1", serviceNameParts[3]); Assert.Equal("thisiskindofalongrunnerabcde", serviceNameParts[3]);
} }
} }
@@ -54,7 +55,7 @@ namespace GitHub.Runner.Common.Tests
{ {
RunnerSettings settings = new RunnerSettings(); RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisiskindofalongrunnername12"; settings.AgentName = "thisiskindofalongrunnernabcde";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890"; settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample"; settings.GitHubUrl = "https://github.com/myorganizationexample/myrepoexample";
@@ -82,88 +83,129 @@ namespace GitHub.Runner.Common.Tests
Assert.Equal("actions", serviceNameParts[0]); Assert.Equal("actions", serviceNameParts[0]);
Assert.Equal("runner", serviceNameParts[1]); Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-' Assert.Equal("myorganizationexample-myrepoexample", serviceNameParts[2]); // '/' has been replaced with '-'
Assert.Equal("thisiskindofalongrunnername12", serviceNameParts[3]); Assert.Equal("thisiskindofalongrunnernabcde", serviceNameParts[3]); // should not have random numbers unless we exceed 80
} }
} }
[Fact] #if OS_WINDOWS
[Trait("Level", "L0")] [Fact]
[Trait("Category", "Service")] [Trait("Level", "L0")]
public void CalculateServiceNameLimitsServiceNameTo80Chars() [Trait("Category", "Service")]
{ public void CalculateServiceNameLimitsServiceNameTo80Chars()
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisisareallyreallylongbutstillvalidagentname";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myreallylongorganizationexample/myreallylongrepoexample";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{ {
ServiceControlManager scm = new ServiceControlManager(); RunnerSettings settings = new RunnerSettings();
scm.Initialize(hc); settings.AgentName = "thisisareallyreallylongbutstillvalidagentname";
scm.CalculateServiceName( settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings, settings.GitHubUrl = "https://github.com/myreallylongorganizationexample/myreallylongrepoexample";
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
// Verify name has been shortened to 80 characters string serviceNamePattern = "actions.runner.{0}.{1}";
Assert.Equal(80, serviceName.Length); string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
var serviceNameParts = serviceName.Split('.'); using (TestHostContext hc = CreateTestContext())
{
ServiceControlManager scm = new ServiceControlManager();
// Verify that each component has been shortened to a sensible length scm.Initialize(hc);
Assert.Equal("actions", serviceNameParts[0]); // Never shortened scm.CalculateServiceName(
Assert.Equal("runner", serviceNameParts[1]); // Never shortened settings,
Assert.Equal("myreallylongorganizationexample-myreallylongr", serviceNameParts[2]); // First 45 chars, '/' has been replaced with '-' serviceNamePattern,
Assert.Equal("thisisareallyreally", serviceNameParts[3]); // Remainder of unused chars serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
// Verify name has been shortened to 80 characters
Assert.Equal(80, serviceName.Length);
var serviceNameParts = serviceName.Split('.');
// Verify that each component has been shortened to a sensible length
Assert.Equal("actions", serviceNameParts[0]); // Never shortened
Assert.Equal("runner", serviceNameParts[1]); // Never shortened
Assert.Equal("myreallylongorganizationexample-myreallylongr", serviceNameParts[2]); // First 45 chars, '/' has been replaced with '-'
Assert.Matches(@"^(thisisareallyr-[0-9]{4})$", serviceNameParts[3]); // Remainder of unused chars, 4 random numbers added at the end
}
} }
}
// Special 'defensive' test that verifies we can gracefully handle creating service names // Special 'defensive' test that verifies we can gracefully handle creating service names
// in case GitHub.com changes its org/repo naming convention in the future, // in case GitHub.com changes its org/repo naming convention in the future,
// and some of these characters may be invalid for service names // and some of these characters may be invalid for service names
// Not meant to test character set exhaustively -- it's just here to exercise the sanitizing logic // Not meant to test character set exhaustively -- it's just here to exercise the sanitizing logic
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Service")] [Trait("Category", "Service")]
public void CalculateServiceNameSanitizeOutOfRangeChars() public void CalculateServiceNameSanitizeOutOfRangeChars()
{
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "name";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/org!@$*+[]()/repo!@$*+[]()";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{ {
ServiceControlManager scm = new ServiceControlManager(); RunnerSettings settings = new RunnerSettings();
scm.Initialize(hc); settings.AgentName = "name";
scm.CalculateServiceName( settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings, settings.GitHubUrl = "https://github.com/org!@$*+[]()/repo!@$*+[]()";
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
var serviceNameParts = serviceName.Split('.'); string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
// Verify service name parts are sanitized correctly using (TestHostContext hc = CreateTestContext())
Assert.Equal("actions", serviceNameParts[0]); {
Assert.Equal("runner", serviceNameParts[1]); ServiceControlManager scm = new ServiceControlManager();
Assert.Equal("org----------repo---------", serviceNameParts[2]); // Chars replaced with '-'
Assert.Equal("name", serviceNameParts[3]); scm.Initialize(hc);
scm.CalculateServiceName(
settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
var serviceNameParts = serviceName.Split('.');
// Verify service name parts are sanitized correctly
Assert.Equal("actions", serviceNameParts[0]);
Assert.Equal("runner", serviceNameParts[1]);
Assert.Equal("org----------repo---------", serviceNameParts[2]); // Chars replaced with '-'
Assert.Equal("name", serviceNameParts[3]);
}
} }
} #else
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Service")]
public void CalculateServiceNameLimitsServiceNameTo150Chars()
{
RunnerSettings settings = new RunnerSettings();
settings.AgentName = "thisisareallyreallylongbutstillvalidagentnameiamusingforthisexampletotestverylongnamelimits";
settings.ServerUrl = "https://example.githubusercontent.com/12345678901234567890123456789012345678901234567890";
settings.GitHubUrl = "https://github.com/myreallylongorganizationexampleonlinux/myreallylongrepoexampleonlinux1234";
string serviceNamePattern = "actions.runner.{0}.{1}";
string serviceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
using (TestHostContext hc = CreateTestContext())
{
ServiceControlManager scm = new ServiceControlManager();
scm.Initialize(hc);
scm.CalculateServiceName(
settings,
serviceNamePattern,
serviceDisplayNamePattern,
out string serviceName,
out string serviceDisplayName);
// Verify name has been shortened to 150
Assert.Equal(150, serviceName.Length);
var serviceNameParts = serviceName.Split('.');
// Verify that each component has been shortened to a sensible length
Assert.Equal("actions", serviceNameParts[0]); // Never shortened
Assert.Equal("runner", serviceNameParts[1]); // Never shortened
Assert.Equal("myreallylongorganizationexampleonlinux-myreallylongrepoexampleonlinux1", serviceNameParts[2]); // First 70 chars, '/' has been replaced with '-'
Assert.Matches(@"^(thisisareallyreallylongbutstillvalidagentnameiamusingforthi-[0-9]{4})$", serviceNameParts[3]);
}
}
#endif
private TestHostContext CreateTestContext([CallerMemberName] string testName = "") private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
{ {

View File

@@ -118,7 +118,7 @@ namespace GitHub.Runner.Common.Tests.Worker
await _actionRunner.RunAsync(); await _actionRunner.RunAsync();
//Assert //Assert
_ec.Verify(x => x.SetGitHubContext("event_path", Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "_github_workflow", "event.json")), Times.Once); _ec.Verify(x => x.WriteWebhookPayload(), Times.Once);
} }
[Fact] [Fact]

View File

@@ -8,6 +8,7 @@ using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker; using GitHub.Runner.Worker;
using Moq; using Moq;
using Xunit; using Xunit;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Common.Tests.Worker namespace GitHub.Runner.Common.Tests.Worker
@@ -90,6 +91,63 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void ApplyContinueOnError_CheckResultAndOutcome()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange: Create a job request message.
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{
Alias = Pipelines.PipelineConstants.SelfAlias,
Id = "github",
Version = "sha1"
});
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true";
// Arrange: Setup the paging logger.
var pagingLogger = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<long>())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); });
hc.EnqueueInstance(pagingLogger.Object);
hc.SetSingleton(jobServerQueue.Object);
var ec = new Runner.Worker.ExecutionContext();
ec.Initialize(hc);
// Act.
ec.InitializeJob(jobRequest, CancellationToken.None);
foreach (var tc in new List<(TemplateToken token, TaskResult result, TaskResult? expectedResult, TaskResult? expectedOutcome)> {
(token: new BooleanToken(null, null, null, true), result: TaskResult.Failed, expectedResult: TaskResult.Succeeded, expectedOutcome: TaskResult.Failed),
(token: new BooleanToken(null, null, null, true), result: TaskResult.Succeeded, expectedResult: TaskResult.Succeeded, expectedOutcome: null),
(token: new BooleanToken(null, null, null, true), result: TaskResult.Canceled, expectedResult: TaskResult.Canceled, expectedOutcome: null),
(token: new BooleanToken(null, null, null, false), result: TaskResult.Failed, expectedResult: TaskResult.Failed, expectedOutcome: null),
(token: new BooleanToken(null, null, null, false), result: TaskResult.Succeeded, expectedResult: TaskResult.Succeeded, expectedOutcome: null),
(token: new BooleanToken(null, null, null, false), result: TaskResult.Canceled, expectedResult: TaskResult.Canceled, expectedOutcome: null),
})
{
ec.Result = tc.result;
ec.Outcome = null;
ec.ApplyContinueOnError(tc.token);
Assert.Equal(ec.Result, tc.expectedResult);
Assert.Equal(ec.Outcome, tc.expectedOutcome);
}
}
}
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [Trait("Category", "Worker")]
@@ -144,6 +202,55 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void AddIssue_AddStepAndLineNumberInformation()
{
using (TestHostContext hc = CreateTestContext())
{
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{
Alias = Pipelines.PipelineConstants.SelfAlias,
Id = "github",
Version = "sha1"
});
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
// Arrange: Setup the paging logger.
var pagingLogger = new Mock<IPagingLogger>();
var pagingLogger2 = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
hc.EnqueueInstance(pagingLogger.Object);
hc.EnqueueInstance(pagingLogger2.Object);
hc.SetSingleton(jobServerQueue.Object);
var ec = new Runner.Worker.ExecutionContext();
ec.Initialize(hc);
ec.InitializeJob(jobRequest, CancellationToken.None);
ec.Start();
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
embeddedStep.Start();
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error annotation that should have step and line number information" });
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning annotation that should have step and line number information" });
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice annotation that should have step and line number information" });
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Error).Count() == 1)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Warning).Count() == 1)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Notice).Count() == 1)), Times.AtLeastOnce);
}
}
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [Trait("Category", "Worker")]

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Moq;
using Xunit;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Handlers;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.WebApi;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class HandlerFactoryL0
{
private Mock<IExecutionContext> _ec;
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
{
var hostContext = new TestHostContext(this, testName);
_ec = new Mock<IExecutionContext>();
_ec.SetupAllProperties();
_ec.Object.Initialize(hostContext);
var handler = new Mock<INodeScriptActionHandler>();
handler.SetupAllProperties();
hostContext.EnqueueInstance(handler.Object);
//hostContext.EnqueueInstance(new ActionCommandManager() as IActionCommandManager);
return hostContext;
}
[Theory]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
[InlineData("node12", "", "", "", "node12")]
[InlineData("node12", "true", "", "", "node16")]
[InlineData("node12", "true", "", "true", "node12")]
[InlineData("node12", "true", "true", "", "node12")]
[InlineData("node12", "true", "true", "true", "node12")]
[InlineData("node12", "true", "false", "true", "node16")] // workflow overrides env
[InlineData("node16", "", "", "", "node16")]
[InlineData("node16", "true", "", "", "node16")]
[InlineData("node16", "true", "", "true", "node16")]
[InlineData("node16", "true", "true", "", "node16")]
[InlineData("node16", "true", "true", "true", "node16")]
[InlineData("node16", "true", "false", "true", "node16")]
public void IsNodeVersionUpgraded(string inputVersion, string serverFeatureFlag, string workflowOptOut, string machineOptOut, string expectedVersion)
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var hf = new HandlerFactory();
hf.Initialize(hc);
// Server Feature Flag
var variables = new Dictionary<string, VariableValue>();
if (!string.IsNullOrEmpty(serverFeatureFlag))
{
variables["DistributedTask.ForceGithubJavascriptActionsToNode16"] = serverFeatureFlag;
}
Variables serverVariables = new Variables(hc, variables);
// Workflow opt-out
var workflowVariables = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(workflowOptOut))
{
workflowVariables[Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion] = workflowOptOut;
}
// Machine opt-out
if (!string.IsNullOrEmpty(machineOptOut))
{
Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, machineOptOut);
}
_ec.Setup(x => x.Global).Returns(new GlobalContext()
{
Variables = serverVariables,
EnvironmentVariables = workflowVariables
});
// Act.
var data = new NodeJSActionExecutionData();
data.NodeVersion = inputVersion;
var handler = hf.Create(
_ec.Object,
new ScriptReference(),
new Mock<IStepHost>().Object,
data,
new Dictionary<string, string>(),
new Dictionary<string, string>(),
new Variables(hc, new Dictionary<string, VariableValue>()), "", new List<JobExtensionRunner>()
) as INodeScriptActionHandler;
// Assert.
Assert.Equal(expectedVersion, handler.Data.NodeVersion);
Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, null);
}
}
}
}

View File

@@ -25,6 +25,7 @@ namespace GitHub.Runner.Common.Tests.Worker
private Mock<IPagingLogger> _logger; private Mock<IPagingLogger> _logger;
private Mock<IContainerOperationProvider> _containerProvider; private Mock<IContainerOperationProvider> _containerProvider;
private Mock<IDiagnosticLogManager> _diagnosticLogManager; private Mock<IDiagnosticLogManager> _diagnosticLogManager;
private Mock<IJobHookProvider> _jobHookProvider;
private CancellationTokenSource _tokenSource; private CancellationTokenSource _tokenSource;
private TestHostContext CreateTestContext([CallerMemberName] String testName = "") private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
@@ -40,6 +41,7 @@ namespace GitHub.Runner.Common.Tests.Worker
_directoryManager = new Mock<IPipelineDirectoryManager>(); _directoryManager = new Mock<IPipelineDirectoryManager>();
_directoryManager.Setup(x => x.PrepareDirectory(It.IsAny<IExecutionContext>(), It.IsAny<Pipelines.WorkspaceOptions>())) _directoryManager.Setup(x => x.PrepareDirectory(It.IsAny<IExecutionContext>(), It.IsAny<Pipelines.WorkspaceOptions>()))
.Returns(new TrackingConfig() { PipelineDirectory = "runner", WorkspaceDirectory = "runner/runner" }); .Returns(new TrackingConfig() { PipelineDirectory = "runner", WorkspaceDirectory = "runner/runner" });
_jobHookProvider = new Mock<IJobHookProvider>();
IActionRunner step1 = new ActionRunner(); IActionRunner step1 = new ActionRunner();
IActionRunner step2 = new ActionRunner(); IActionRunner step2 = new ActionRunner();
@@ -111,7 +113,9 @@ namespace GitHub.Runner.Common.Tests.Worker
hc.SetSingleton(_containerProvider.Object); hc.SetSingleton(_containerProvider.Object);
hc.SetSingleton(_directoryManager.Object); hc.SetSingleton(_directoryManager.Object);
hc.SetSingleton(_diagnosticLogManager.Object); hc.SetSingleton(_diagnosticLogManager.Object);
hc.SetSingleton(_jobHookProvider.Object);
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // JobExecutionContext hc.EnqueueInstance<IPagingLogger>(_logger.Object); // JobExecutionContext
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // job start hook
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // Initial Job hc.EnqueueInstance<IPagingLogger>(_logger.Object); // Initial Job
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step1 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step1
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step2 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step2
@@ -120,6 +124,7 @@ namespace GitHub.Runner.Common.Tests.Worker
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step5 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step5
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare1 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare1
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare2 hc.EnqueueInstance<IPagingLogger>(_logger.Object); // prepare2
hc.EnqueueInstance<IPagingLogger>(_logger.Object); // job complete hook
hc.EnqueueInstance<IActionRunner>(step1); hc.EnqueueInstance<IActionRunner>(step1);
hc.EnqueueInstance<IActionRunner>(step2); hc.EnqueueInstance<IActionRunner>(step2);
@@ -348,5 +353,62 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal(TaskResult.Succeeded, _jobEc.Result); Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async Task EnsurePreAndPostHookStepsIfEnvExists()
{
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_STARTED", "/foo/bar");
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", "/bar/foo");
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 Dictionary<Guid, IActionRunner>())));
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
var trace = hc.GetTrace();
var hookStart = result.First() as JobExtensionRunner;
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
Assert.Equal(Constants.Hooks.JobStartedStepName, hookStart.DisplayName);
Assert.Equal(Constants.Hooks.JobCompletedStepName, (_jobEc.PostJobSteps.Last() as JobExtensionRunner).DisplayName);
}
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_STARTED", null);
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", null);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EnsureNoPreAndPostHookSteps()
{
using (TestHostContext hc = CreateTestContext())
{
var jobExtension = new JobExtension();
jobExtension.Initialize(hc);
_message.ActionsEnvironment = null;
_jobEc = new Runner.Worker.ExecutionContext {Result = TaskResult.Succeeded};
_jobEc.Initialize(hc);
_jobEc.InitializeJob(_message, _tokenSource.Token);
var x = _jobEc.JobSteps;
jobExtension.FinalizeJob(_jobEc, _message, DateTime.UtcNow);
Assert.Equal(TaskResult.Succeeded, _jobEc.Result);
Assert.Equal(0, _jobEc.PostJobSteps.Count);
}
}
} }
} }

View File

@@ -302,6 +302,50 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void SetEnvFileCommand_Heredoc_MissingNewLine()
{
using (var hostContext = Setup())
{
var envFile = Path.Combine(_rootDirectory, "heredoc");
var content = new List<string>
{
"MY_ENV<<EOF",
"line one",
"line two",
"line three",
"EOF",
};
WriteContent(envFile, content, " ");
var ex = Assert.Throws<Exception>(() => _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null));
Assert.Contains("Matching delimiter not found", ex.Message);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void SetEnvFileCommand_Heredoc_MissingNewLineMultipleLinesEnv()
{
using (var hostContext = Setup())
{
var envFile = Path.Combine(_rootDirectory, "heredoc");
var content = new List<string>
{
"MY_ENV<<EOF",
@"line one
line two
line three",
"EOF",
};
WriteContent(envFile, content, " ");
var ex = Assert.Throws<Exception>(() => _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null));
Assert.Contains("EOF marker missing new line", ex.Message);
}
}
#if OS_WINDOWS #if OS_WINDOWS
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]

View File

@@ -622,6 +622,40 @@ namespace GitHub.Runner.Common.Tests.Worker
_stepContext.SetOutcome("", stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult()); _stepContext.SetOutcome("", stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
_stepContext.SetConclusion("", stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult()); _stepContext.SetConclusion("", stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
}); });
stepContext.Setup(x => x.UpdateGlobalStepsContext()).Callback(() =>
{
if (!string.IsNullOrEmpty(stepContext.Object.ContextName) && !stepContext.Object.ContextName.StartsWith("__", StringComparison.Ordinal))
{
stepContext.Object.Global.StepsContext.SetOutcome(stepContext.Object.ScopeName, stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
stepContext.Object.Global.StepsContext.SetConclusion(stepContext.Object.ScopeName, stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
}
});
stepContext.Setup(x => x.ApplyContinueOnError(It.IsAny<TemplateToken>())).Callback((TemplateToken token) =>
{
if (stepContext.Object.Result != TaskResult.Failed)
{
return;
}
var continueOnError = false;
try
{
var templateEvaluator = stepContext.Object.ToPipelineTemplateEvaluator();
continueOnError = templateEvaluator.EvaluateStepContinueOnError(token, stepContext.Object.ExpressionValues, stepContext.Object.ExpressionFunctions);
}
catch (Exception ex)
{
stepContext.Object.Error("The step failed and an error occurred when attempting to determine whether to continue on error.");
stepContext.Object.Error(ex);
}
if (continueOnError)
{
stepContext.Object.Outcome = stepContext.Object.Result;
stepContext.Object.Result = TaskResult.Succeeded;
}
stepContext.Object.UpdateGlobalStepsContext();
});
var trace = hc.GetTrace(); var trace = hc.GetTrace();
stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); }); stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); });
stepContext.Object.Result = result; stepContext.Object.Result = result;

View File

@@ -2,7 +2,7 @@
############################################################################### ###############################################################################
# #
# ./dev.sh build/layout/test/package [Debug/Release] # ./dev.sh build/layout/test/package [Debug/Release] [linux-x64|linux-x86|linux-arm64|linux-arm|osx-x64|win-x64|win-x86] [use-broker]
# #
############################################################################### ###############################################################################
@@ -11,6 +11,7 @@ set -e
DEV_CMD=$1 DEV_CMD=$1
DEV_CONFIG=$2 DEV_CONFIG=$2
DEV_TARGET_RUNTIME=$3 DEV_TARGET_RUNTIME=$3
DEV_USE_BROKER=$4
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LAYOUT_DIR="$SCRIPT_DIR/../_layout" LAYOUT_DIR="$SCRIPT_DIR/../_layout"
@@ -81,6 +82,13 @@ elif [[ "$CURRENT_PLATFORM" == 'darwin' ]]; then
fi fi
fi fi
if [ -n "$DEV_USE_BROKER" ]; then
USE_BROKER='-p:USE_BROKER="true"'
else
USE_BROKER=''
fi
function failed() function failed()
{ {
local error=${1:-Undefined error} local error=${1:-Undefined error}
@@ -114,13 +122,13 @@ function heading()
function build () function build ()
{ {
heading "Building ..." heading "Building ..."
dotnet msbuild -t:Build -p:PackageRuntime="${RUNTIME_ID}" -p:BUILDCONFIG="${BUILD_CONFIG}" -p:RunnerVersion="${RUNNER_VERSION}" ./dir.proj || failed build dotnet msbuild -t:Build -p:PackageRuntime="${RUNTIME_ID}" -p:BUILDCONFIG="${BUILD_CONFIG}" -p:RunnerVersion="${RUNNER_VERSION}" $USE_BROKER ./dir.proj || failed build
} }
function layout () function layout ()
{ {
heading "Create layout ..." heading "Create layout ..."
dotnet msbuild -t:layout -p:PackageRuntime="${RUNTIME_ID}" -p:BUILDCONFIG="${BUILD_CONFIG}" -p:RunnerVersion="${RUNNER_VERSION}" ./dir.proj || failed build dotnet msbuild -t:layout -p:PackageRuntime="${RUNTIME_ID}" -p:BUILDCONFIG="${BUILD_CONFIG}" -p:RunnerVersion="${RUNNER_VERSION}" $USE_BROKER ./dir.proj || failed build
#change execution flag to allow running with sudo #change execution flag to allow running with sudo
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
@@ -128,6 +136,7 @@ function layout ()
chmod +x "${LAYOUT_DIR}/bin/Runner.Worker" chmod +x "${LAYOUT_DIR}/bin/Runner.Worker"
chmod +x "${LAYOUT_DIR}/bin/Runner.PluginHost" chmod +x "${LAYOUT_DIR}/bin/Runner.PluginHost"
chmod +x "${LAYOUT_DIR}/bin/installdependencies.sh" chmod +x "${LAYOUT_DIR}/bin/installdependencies.sh"
chmod +x "${LAYOUT_DIR}/safe_sleep.sh"
fi fi
heading "Setup externals folder for $RUNTIME_ID runner's layout" heading "Setup externals folder for $RUNTIME_ID runner's layout"

View File

@@ -1 +1 @@
2.287.1 2.289.2