Compare commits

...

239 Commits

Author SHA1 Message Date
Salman Muin Kayser Chishti
163adda80b Simplify and just focus on fedora to mimimze scope of changes for this PR 2025-09-02 18:02:45 +01:00
Salman Muin Kayser Chishti
f80ce89387 Refactor dependency installation logic to use a fallback mechanism for package installation across different OS types, lttng-ust1 & ttng-ust 2025-08-30 05:35:51 +01:00
dependabot[bot]
65dfa460ba Bump eslint-plugin-github in /src/Misc/expressionFunc/hashFiles (#3180)
Bumps [eslint-plugin-github](https://github.com/github/eslint-plugin-github) from 4.10.0 to 4.10.2.
- [Release notes](https://github.com/github/eslint-plugin-github/releases)
- [Commits](https://github.com/github/eslint-plugin-github/compare/v4.10.0...v4.10.2)

---
updated-dependencies:
- dependency-name: eslint-plugin-github
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Patrick Ellis <319655+pje@users.noreply.github.com>
2025-08-30 04:03:46 +00:00
dependabot[bot]
80ee51f164 Bump @vercel/ncc from 0.38.0 to 0.38.3 in /src/Misc/expressionFunc/hashFiles (#3841)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Salman Chishti <salmanmkc@GitHub.com>
2025-08-30 03:24:53 +00:00
dependabot[bot]
c95883f28e Bump husky from 8.0.3 to 9.1.7 in /src/Misc/expressionFunc/hashFiles (#3842)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Salman Chishti <salmanmkc@GitHub.com>
2025-08-30 03:19:49 +00:00
dependabot[bot]
6e940643a9 Bump @typescript-eslint/eslint-plugin from 6.7.2 to 8.35.0 in /src/Misc/expressionFunc/hashFiles (#3920)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Salman Chishti <salmanmkc@GitHub.com>
2025-08-29 20:08:31 +00:00
dependabot[bot]
629f2384a4 Bump actions/attest-build-provenance from 2 to 3 (#4002)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 21:01:36 +01:00
github-actions[bot]
c3bf70becb Update dotnet sdk to latest version @8.0.413 (#4000)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-25 02:09:47 +00:00
github-actions[bot]
8b65f5f9df Update Docker to v28.3.3 and Buildx to v0.27.0 (#3999)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-25 00:31:52 +00:00
eric sciple
5f1efec208 Acknowledge runner request (#3996) 2025-08-22 13:52:32 -05:00
Doug Horner
20d82ad357 Update safe_sleep.sh for bug when scheduler is paused for more than 1 second (#3157) 2025-08-20 19:04:48 +00:00
Salman Chishti
0ebdf9e83d Prepare runner release v2.328.0 (#3984) 2025-08-13 17:38:32 +01:00
dependabot[bot]
6543bf206b Bump actions/checkout from 4 to 5 (#3982)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-13 12:44:39 +01:00
dependabot[bot]
a942627965 Bump actions/download-artifact from 4 to 5 (#3973)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 19:32:29 -04:00
dependabot[bot]
83539166c9 Bump Azure.Storage.Blobs from 12.24.0 to 12.25.0 (#3974)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 23:23:54 +00:00
dependabot[bot]
1c1e8bfd18 Bump Microsoft.NET.Test.Sdk from 17.13.0 to 17.14.1 (#3975)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 19:17:20 -04:00
Cory Calahan
59177fa379 Redirect supported OS doc section to current public Docs location (#3979) 2025-08-07 18:49:02 -04:00
djs-intel
2d7635a7f0 Update Node20 and Node24 to latest (#3972) 2025-08-07 22:41:18 +00:00
Salman Chishti
0203cf24d3 Node 20 -> Node 24 migration feature flagging, opt-in and opt-out environment variables (#3948) 2025-08-07 16:30:03 +00:00
Joshua Brooks
5e74a4d8e4 Add V2 flow for runner deletion (#3954) 2025-08-07 10:52:46 -04:00
Salman Chishti
6ca97eeb88 Fix if statement structure in update script and variable reference (#3956) 2025-07-25 16:42:28 +01:00
github-actions[bot]
8a9b96806d Update Docker to v28.3.2 and Buildx to v0.26.1 (#3953)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-23 22:41:02 -04:00
Salman Chishti
dc9cf684c9 Prepare runner release 2.327.0 (#3951) 2025-07-22 18:59:15 +01:00
github-actions[bot]
c765c990b9 Update dotnet sdk to latest version @8.0.412 (#3941)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-22 11:32:03 -04:00
Salman Chishti
ed48ddd08c Runner Support for executing Node24 Actions (#3940) 2025-07-17 01:00:17 +00:00
Salman Chishti
a1e6ad8d2e Fix null reference exception in user agent handling (#3946) 2025-07-17 01:12:03 +01:00
Tingluo Huang
14856e63bc Try add orchestrationid into user-agent using token claim. (#3945) 2025-07-16 14:11:09 -04:00
Tingluo Huang
0d24afa114 Prepare 2.326.0 runner release. (#3936) 2025-07-07 15:19:21 -04:00
Tingluo Huang
20912234a5 Upgrade node.js to latest version. (#3935) 2025-07-07 11:47:48 -04:00
Tingluo Huang
5969cbe208 Bump windows service app to dotnet 4.7 (#3926) 2025-07-01 15:49:30 -04:00
github-actions[bot]
9f57d37642 Update Docker to v28.2.2 and Buildx to v0.25.0 (#3918)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-30 12:43:59 -04:00
github-actions[bot]
60563d82d1 Update dotnet sdk to latest version @8.0.411 (#3911)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-30 11:42:02 -04:00
Ben De St Paer-Gotch
097ada9374 Update README.md (#3898) 2025-06-09 18:16:35 +01:00
Ryan Ghadimi
9b457781d6 runner timestamps invariant (#3888) 2025-06-02 21:47:51 +00:00
Aiqiao Yan
9709b69571 prep for version 2.325.0 (#3889) 2025-06-02 14:40:01 -04:00
Tingluo Huang
acf3f2ba12 Allow NO_SSL_VERIFY in RawHttpMessageHandler. (#3883) 2025-05-30 22:48:16 -04:00
github-actions[bot]
f03fcc8a01 Update Docker to v28.2.1 and Buildx to v0.24.0 (#3881)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-29 18:43:10 -04:00
github-actions[bot]
e4e103c5ed Update dotnet sdk to latest version @8.0.410 (#3871)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-29 17:56:04 -04:00
Aiqiao Yan
a906ec302b show helpful error message when resolving actions directly with launch (#3874) 2025-05-28 22:46:25 -04:00
Tingluo Huang
d9e714496d Allow runner to use authv2 during config. (#3866) 2025-05-21 16:49:35 -04:00
github-actions[bot]
df189ba6e3 Update dotnet sdk to latest version @8.0.409 (#3860)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-19 01:43:01 +00:00
Tingluo Huang
4c1de69e1c Create schedule workflow to upgrade docker and buildx version. (#3859) 2025-05-14 10:04:01 -04:00
Tingluo Huang
26185d43d0 Prepare 2.324.0 runner release. (#3856) 2025-05-12 19:29:05 -04:00
Tingluo Huang
e911d2908d Update docker and buildx (#3854) 2025-05-12 17:54:44 -04:00
Lokesh Gopu
ce4b7f4dd6 Prefer _migrated config on startup (#3853) 2025-05-12 16:54:43 -04:00
Tingluo Huang
505fa60905 Make sure the token's claims are match as expected. (#3846)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-07 17:35:38 -04:00
dependabot[bot]
57459ad274 Bump xunit.runner.visualstudio from 2.5.8 to 2.8.2 in /src (#3845)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-07 10:38:40 -04:00
dependabot[bot]
890e43f6c5 Bump System.ServiceProcess.ServiceController from 8.0.0 to 8.0.1 in /src (#3844)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-07 10:27:05 -04:00
Patrick Ellis
3a27ca292a Feature-flagged support for JobContext.CheckRunId (#3811) 2025-05-06 11:45:51 -04:00
Tingluo Huang
282f7cd2b2 Bump nodejs version. (#3840) 2025-05-06 10:42:55 -04:00
dependabot[bot]
f060fe5c85 Bump Azure.Storage.Blobs from 12.23.0 to 12.24.0 in /src (#3837)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-06 08:14:07 -04:00
David Sanders
1a092a24a3 feat: default fromPath for problem matchers (#3802) 2025-05-05 20:45:23 +00:00
Tingluo Huang
26eff8e55a Ignore exception during auth migration. (#3835) 2025-05-05 14:23:24 -04:00
dependabot[bot]
d7cfd2e341 Bump actions/upload-release-asset from 1.0.1 to 1.0.2 (#3553)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 04:24:49 +00:00
Patrick Ellis
a3a7b6a77e Add copilot-instructions.md (#3810) 2025-05-05 00:17:20 -04:00
dependabot[bot]
db6005b0a7 Bump Microsoft.NET.Test.Sdk from 17.12.0 to 17.13.0 in /src (#3719)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-05 04:15:50 +00:00
eric sciple
9155c42c09 Do not retry /renewjob on 404 (#3828) 2025-04-30 14:44:40 -05:00
Tingluo Huang
1c319b4d42 Allow enable auth migration by default. (#3804) 2025-04-23 16:57:54 -04:00
Nikola Jokic
fe10d4ae82 Bump hook to 0.7.0 (#3813) 2025-04-17 09:32:34 -04:00
github-actions[bot]
27d9c886ab Update dotnet sdk to latest version @8.0.408 (#3808)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-14 10:32:45 -04:00
eric sciple
5106d6578e Cleanup feature flag actions_skip_retry_complete_job_upon_known_errors (#3806) 2025-04-11 08:34:17 -05:00
Tingluo Huang
d5ccbd10d1 Support auth migration using authUrlV2 in Runner/MessageListener. (#3787) 2025-04-10 12:58:33 -04:00
Tingluo Huang
f1b5b5bd5c Enable FIPS by default. (#3793) 2025-04-07 15:53:53 +00:00
Tingluo Huang
aaf1b92847 Set JWT.alg to PS256 with PssPadding. (#3789) 2025-04-07 11:49:14 -04:00
Tingluo Huang
c1095ae2d1 Enable auth migration based on config refresh. (#3786) 2025-04-02 23:24:57 -04:00
Tingluo Huang
a0a0a76378 Remove create session with broker in MessageListener. (#3782) 2025-04-01 12:24:01 -04:00
Tingluo Huang
d47013928b Add option in OAuthCred to load authUrlV2. (#3777) 2025-03-31 17:05:41 -04:00
Tingluo Huang
cdeec012aa Enable hostcontext to track auth migration. (#3776) 2025-03-31 15:26:56 -04:00
Tingluo Huang
2cb1f9431a Small runner code cleanup. (#3773) 2025-03-28 16:25:12 -04:00
Tingluo Huang
e86c9487ab Fix release.yml break by upgrading actions/github-script (#3772) 2025-03-28 12:20:15 -04:00
eric sciple
dc9695f123 Increase error body max length before truncation (#3762) 2025-03-20 20:09:00 -05:00
Tingluo Huang
6654f6b3de Prepare runner release 2.323.0 (#3759) 2025-03-19 12:48:41 -04:00
Tingluo Huang
f5e4e7e47c Support refresh runner configs with pipelines service. (#3706) 2025-03-19 12:37:08 -04:00
Tingluo Huang
68ca457917 Allow server enforce runner settings. (#3758) 2025-03-19 09:12:17 -04:00
Tingluo Huang
77700abf81 Send annotation title to run-service. (#3757) 2025-03-18 15:33:47 -04:00
Tingluo Huang
a0ba8fd399 Exit hosted runner cleanly during deprovisioning. (#3755) 2025-03-18 10:33:40 -04:00
github-actions[bot]
6b08f23b6c Update dotnet sdk to latest version @8.0.407 (#3753)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-16 21:54:16 -04:00
Timotej Ecimovic
8131246933 Improve the out-of-date warning message. (#3595) 2025-03-14 21:03:13 +00:00
Thomas Boop
7211a53c9e Housekeeping: Update npm packages and node version (#3752) 2025-03-14 14:51:10 -04:00
Tingluo Huang
07310cabc0 Create vssconnection to actions service when URL provided. (#3751) 2025-03-14 13:55:57 -04:00
Ryan Ghadimi
0195d7ca77 Fix typo, add invariant culture to timestamp for workflow log reporting (#3749) 2025-03-14 15:02:55 +00:00
Eric
259af3eda2 Update Bocker and Buildx version to mitigate images scanners alerts (#3750) 2025-03-14 10:48:46 -04:00
Tingluo Huang
0ce29d09c6 Add request-id to http eventsource trace. (#3740) 2025-03-10 21:49:29 -04:00
Pavel Iakovenko
a84e1c2b15 Docker container provenance (#3736) 2025-03-10 20:45:37 +00:00
dependabot[bot]
de51cd0ed6 Bump actions/github-script from 0.3.0 to 7.0.1 (#3557)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-17 21:34:08 +00:00
Sion Kang
3333de3a36 fix: actions feedback link is incorrect (#3165) 2025-02-17 21:26:42 +00:00
finaltrip
b065e5abbe chore: remove redundant words (#3705)
Signed-off-by: finaltrip <finaltrip@qq.com>
2025-02-17 15:24:15 +00:00
Thomas Boop
bae52e28f9 Update Dockerfile (#3680)
Update the dependencies in the dockerfil
2025-02-17 14:30:02 +00:00
github-actions[bot]
c2c91438e8 Upgrade dotnet sdk to v8.0.406 (#3712)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-16 22:51:49 -05:00
eric sciple
3486c54ccb Do not retry CompleteJobAsync upon job-not-found (#3696) 2025-02-04 10:07:42 -06:00
Luke Tomlinson
a61328a7e7 Pass BillingOwnerId through Acquire/Complete calls (#3689)
* Pass BillingOwnerId through Acquire/Complete calls

* add param to test
2025-02-03 20:15:54 +00:00
Aiqiao Yan
52dc98b10f update node version (#3682) 2025-01-29 09:29:31 -05:00
dependabot[bot]
a7b319530e Bump docker/build-push-action from 3 to 6 (#3674)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 11:11:40 -05:00
dependabot[bot]
54f082722f Bump actions/stale from 8 to 9 (#3554)
Bumps [actions/stale](https://github.com/actions/stale) from 8 to 9.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 10:56:29 -05:00
dependabot[bot]
ed9d8fc9f7 Bump docker/login-action from 2 to 3 (#3673)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 09:53:28 -05:00
Tingluo Huang
fccbe8fb0b Prepare runner release 2.322.0 (#3676) 2025-01-24 09:06:26 -05:00
dependabot[bot]
e3bc10a931 Bump Moq from 4.20.70 to 4.20.72 in /src (#3672)
Bumps [Moq](https://github.com/moq/moq) from 4.20.70 to 4.20.72.
- [Release notes](https://github.com/moq/moq/releases)
- [Changelog](https://github.com/devlooped/moq/blob/main/changelog.md)
- [Commits](https://github.com/moq/moq/compare/v4.20.70...v4.20.72)

---
updated-dependencies:
- dependency-name: Moq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 04:41:59 +00:00
dependabot[bot]
ba50bf6482 Bump github/codeql-action from 2 to 3 (#3555)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 04:35:10 +00:00
dependabot[bot]
8eef71d93d Bump docker/setup-buildx-action from 2 to 3 (#3564)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 23:30:45 -05:00
dependabot[bot]
7ae9fc03a2 Bump Microsoft.NET.Test.Sdk from 17.8.0 to 17.12.0 in /src (#3584)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.8.0 to 17.12.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.8.0...v17.12.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 23:18:52 -05:00
Victor Sollerhed
8e97ad4d86 Upgrade docker from 27.3.1 to 27.4.1 (#3648)
Includes:
- https://github.com/moby/moby/releases/tag/v27.4.0
- https://github.com/moby/moby/releases/tag/v27.4.1

See also:
- https://docs.docker.com/engine/release-notes/27/#2741

Co-authored-by: Tingluo Huang <tingluohuang@github.com>
2025-01-22 01:00:53 -05:00
Victor Sollerhed
aa76aa476f Upgrade buildx from 0.18.0 to 0.19.3 (#3647)
Includes:
- https://github.com/docker/buildx/releases/tag/v0.19.0
- https://github.com/docker/buildx/releases/tag/v0.19.1
- https://github.com/docker/buildx/releases/tag/v0.19.2
- https://github.com/docker/buildx/releases/tag/v0.19.3
2025-01-22 02:51:48 +00:00
github-actions[bot]
0738df9702 Upgrade dotnet sdk to v8.0.405 (#3666)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-20 00:13:57 -05:00
Dylan
8bf52ffe7d Print immutable action package details in set up job logs (#3645)
* Print immutable action package details in set up job logs

* "Source commit SHA" instead of "Commit SHA" for immutable actions logs

---------

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
2025-01-15 17:25:12 +00:00
Tingluo Huang
9df3fc825d Update dotnet install script. (#3659) 2025-01-15 11:57:06 -05:00
Tingluo Huang
fde5227fbf Enable nuget audit. (#3615) 2024-12-09 13:49:18 -05:00
Tingluo Huang
27f6ca8177 Send stepNumber for annotation to run-service (#3614) 2024-12-09 17:40:58 +00:00
Tingluo Huang
078eb3b381 Fix null ref in 'OnEventWritten()' (#3593) 2024-11-25 15:44:03 -05:00
Tingluo Huang
c46dac6736 Ignore error when fail to report worker crash. (#3588) 2024-11-21 16:10:12 -05:00
Satadru Pramanik, DO, MPH, MEng
e640a9fef3 Fix generation of artifact builds from GitHub workflow. (#3568)
Signed-off-by: Satadru Pramanik <satadru@gmail.com>
2024-11-13 18:08:32 +00:00
Tingluo Huang
6d266a7c44 Prepare runner release 2.321.0 (#3566) 2024-11-13 12:20:10 -05:00
dependabot[bot]
4700649bb5 Bump actions/checkout from 3 to 4 (#3556)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-12 23:01:57 -05:00
Zongle Wang
27580ef8de Configure dependabot to check github-actions updates (#3333)
* Configure dependabot to check github-actions updates

Some actions based on Node 16 are deprecated.

See https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20.

* Under /.github

https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot#example-dependabotyml-file-for-github-actions

* Try /.github/workflows

* Update .github/dependabot.yml

Co-authored-by: Zongle Wang <wangzongler@gmail.com>

---------

Co-authored-by: Tingluo Huang <tingluohuang@github.com>
2024-11-12 22:35:19 -05:00
github-actions[bot]
6c94f78f37 Upgrade dotnet sdk to v8.0.404 (#3552)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-12 17:06:51 -05:00
Tingluo Huang
074d9c0922 fix dotnet-upgrade.yml to print right version (#3550) 2024-11-12 16:55:57 -05:00
dependabot[bot]
59f2be2158 Bump Azure.Storage.Blobs from 12.19.1 to 12.23.0 in /src (#3549)
Bumps [Azure.Storage.Blobs](https://github.com/Azure/azure-sdk-for-net) from 12.19.1 to 12.23.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-net/releases)
- [Commits](https://github.com/Azure/azure-sdk-for-net/compare/Azure.Storage.Blobs_12.19.1...Azure.Storage.Blobs_12.23.0)

---
updated-dependencies:
- dependency-name: Azure.Storage.Blobs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-12 16:46:16 -05:00
Tingluo Huang
1e1f7845fa Update runner docker image. (#3511)
* Update docker and buildx version.

* .
2024-11-12 16:37:15 -05:00
Tingluo Huang
694ae12b23 Expose ENV for cache service v2. (#3548) 2024-11-12 14:56:24 -05:00
Tingluo Huang
d16fb2c593 Allow runner to check service connection in background. (#3542)
* Allow runner to check service connection in background.

* .

* .
2024-11-12 13:30:30 -05:00
Luca Cavallin
d37a7ae14d Fetch repo-level runner groups from API in v2 flow (#3546)
* fetch repo-level runner groups from api in v2 flow

* stricter isRepoRunner
2024-11-12 10:32:04 -05:00
Tingluo Huang
6ef5803f24 Publish job telemetry to run-service. (#3545)
* Publish job telemetry to run-service.

* .
2024-11-07 21:00:03 -05:00
eric sciple
2c03d74f11 Handle runner not found (#3536) 2024-11-04 20:11:58 -06:00
Yashwanth Anantharaju
3d34a3c6d6 send action name for run service (#3520)
* send action

* format

* comment

* Delete .github/workflows/lint.yml
2024-10-21 15:00:59 +00:00
Tingluo Huang
59ec9b4139 Remove node16 from the runner. (#3503) 2024-10-16 22:42:43 -04:00
Tingluo Huang
4a99838fa2 Remove dotnet8 compatibility test. (#3502) 2024-10-16 12:41:41 -04:00
Tingluo Huang
af8dee51e1 Bump dotnet SDK to dotnet 8. (#3500) 2024-10-16 12:32:51 -04:00
Luke Tomlinson
9b3b554758 Remove Broker Migration Message logging (#3493) 2024-10-09 11:07:48 -04:00
Yashwanth Anantharaju
4d8402c260 add ref and type to job completion in run service (#3492)
* add ref and type to job completion in run service

* lint
2024-10-08 15:52:48 -04:00
github-actions[bot]
aa0ee2bf64 Upgrade dotnet sdk to v6.0.425 (#3433)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-07 10:12:31 -04:00
eric sciple
dcc64fead2 Fix release workflow to use distinct artifact names (#3485) 2024-10-03 14:43:10 -05:00
eric sciple
149123c232 Prepare v2.320.0 (#3484) 2024-10-03 13:38:35 -05:00
Raj R
e292ec220e Adding Snapshot additional mapping tokens (#3468)
* Adding Snapshot additional mapping tokens

* Lint failure fixes

* Lint failure fixes - 2

* Lint failure fixes - 3

* Fixed a few nits

* Lint fixes

* Removed unncessary white space
2024-10-01 14:04:48 -04:00
Tingluo Huang
3696b7d89f Create launch httpclient using the right handler and setting. (#3476) 2024-09-30 10:57:08 -04:00
Tingluo Huang
6d7446a45e fix missing default user-agent for jitconfig runner. (#3473) 2024-09-25 09:01:53 -04:00
eric sciple
ddf41af767 Cleanup back-compat code for interpreting Run Service status codes (#3456) 2024-09-06 17:04:17 -05:00
Tingluo Huang
0b0cb5520d Add runner or worker to the useragent. (#3457) 2024-09-06 17:16:17 -04:00
Luke Tomlinson
4c0a43f0e4 Handle Error Body in Responses from Broker (#3454) 2024-09-05 17:08:57 -04:00
Tingluo Huang
65764d9ddc Capature actions_type after resolving alpine container. (#3455) 2024-09-05 16:12:29 -04:00
eric sciple
36c66c8083 Fix issues for composite actions (Run Service flow) (#3446) 2024-09-03 17:06:35 -05:00
Tingluo Huang
99b464e102 Trace GitHub RequestId to log. (#3442) 2024-08-27 12:05:26 -04:00
Devin Buhl
e1fa1fcbc3 fix: add jq, git, unzip and curl to default packages installed (#3056)
* fix: add `git` and `curl` to default packages installed

Hi 👋🏼 

These packages are used in a ton of actions on the marketplace. It would be nice if they were installed and ready for use instead of having to install them with `apt-get` on every single Github workflow.

* Update Dockerfile

* Update images/Dockerfile

Co-authored-by: Guillermo Caracuel <633810+gcaracuel@users.noreply.github.com>

* Update images/Dockerfile

Co-authored-by: Tingluo Huang <tingluohuang@github.com>

---------

Co-authored-by: Guillermo Caracuel <633810+gcaracuel@users.noreply.github.com>
Co-authored-by: Tingluo Huang <tingluohuang@github.com>
2024-08-20 09:55:30 -04:00
Tingluo Huang
2979fbad94 Add pid to user-agent and session owner. (#3432) 2024-08-16 15:17:13 -04:00
eric sciple
a77fe8a53f .NET 8 compat test adjustments: 1) do not trim SDK, 2) support pattern to match output, 3) modify output truncation length (#3427) 2024-08-13 09:02:26 -05:00
eric sciple
7e84ae0b30 Prepare release 2.319.0 (#3424) 2024-08-08 08:57:44 -05:00
eric sciple
fb6d1adb43 .NET 8 OS compatibility test (#3422)
* Revert "Warn for soon to be deprecated OS versions (#3413)"

This reverts commit ae04147f96.

* Add .NET 8 OS compatibility test

* feedback
2024-08-07 16:53:00 -05:00
Tingluo Huang
7303cb5673 Ignore ssl cert on websocket client. (#3423) 2024-08-06 18:20:54 -04:00
Tingluo Huang
43d67e46db Revert "Bump runner to dotnet 8" (#3412)
* Revert "Upgrade dotnet sdk to v8.0.303 (#3388)"

This reverts commit dbcaa7cf3d.

* Revert "Bump System.Security.Cryptography.Pkcs from 5.0.0 to 8.0.0 in /src (#3347)"

This reverts commit 3dab1f1fb0.

* Revert "Upgrade dotnet sdk to v8.0.302 (#3346)"

This reverts commit 8f1c723ba0.

* Revert "Bump runner to dotnet 8 (#3345)"

This reverts commit 1e74a8137b.
2024-08-05 10:03:18 -05:00
eric sciple
ae04147f96 Warn for soon to be deprecated OS versions (#3413) 2024-08-02 14:37:46 -05:00
eric sciple
12506842c0 Prepare release 2.318.0 (#3404) 2024-07-26 10:03:59 -05:00
Tingluo Huang
2190396357 Update Docker to v27.1.1 (#3401)
* Update Docker to v27.1.1

* Update Dockerfile
2024-07-26 10:36:05 -04:00
Kynan Ware
41bc0da6fe Redirect supported OS doc section to the public docs (#3396)
* redirect supported OS doc to public docs

* Anchor to appropriate OS heading

Co-authored-by: Patrick Ellis <319655+pje@users.noreply.github.com>

---------

Co-authored-by: Patrick Ellis <319655+pje@users.noreply.github.com>
2024-07-22 17:07:09 -04:00
Kynan Ware
2a7f327d93 Update supported distros to match new docs (#3226)
Co-authored-by: Patrick Ellis <319655+pje@users.noreply.github.com>
2024-07-17 10:35:03 -04:00
github-actions[bot]
dbcaa7cf3d Upgrade dotnet sdk to v8.0.303 (#3388)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-07-15 01:06:31 -04:00
Francesco Renzi
8df87a82b0 Rephrase node20 warning (#3376) 2024-07-08 12:41:06 +01:00
Nikola Jokic
70746ff593 Bump hook version to 0.6.1 (#3350) 2024-06-26 14:56:33 +02:00
eric sciple
054fc2e046 Backoff to avoid excessive retries to Run Service in a duration (#3354) 2024-06-24 16:33:22 -05:00
eric sciple
ecb732eaf4 Receive error body from Run Service (#3342) 2024-06-19 16:38:32 +00:00
dependabot[bot]
3dab1f1fb0 Bump System.Security.Cryptography.Pkcs from 5.0.0 to 8.0.0 in /src (#3347)
Bumps [System.Security.Cryptography.Pkcs](https://github.com/dotnet/runtime) from 5.0.0 to 8.0.0.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v5.0.0...v8.0.0)

---
updated-dependencies:
- dependency-name: System.Security.Cryptography.Pkcs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-18 12:12:10 -04:00
github-actions[bot]
8f1c723ba0 Upgrade dotnet sdk to v8.0.302 (#3346)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-06-18 12:03:44 -04:00
Tingluo Huang
1e74a8137b Bump runner to dotnet 8 (#3345)
* Bump runner to dotnet 8

* .
2024-06-18 11:28:53 -04:00
Josh Gross
3f28dd845f Pass runner version as environment variable in workflow (#3318) 2024-06-10 18:13:17 -04:00
Tingluo Huang
edfdbb9661 Make sure we mask secrets when reporting telemetry. (#3315) 2024-06-04 09:57:15 -04:00
Hidetake Iwata
00888c10f9 Bump docker version and docker buildx version (#3277) 2024-05-31 16:22:54 +00:00
Francesco Renzi
84b1bea43e Prepare relese 2.317.0 (#3311) 2024-05-30 13:36:44 +01:00
dependabot[bot]
ce4d7be00f Bump xunit from 2.4.1 to 2.7.1 in /src (#3242)
* Bump xunit from 2.4.1 to 2.7.1 in /src

Bumps [xunit](https://github.com/xunit/xunit) from 2.4.1 to 2.7.1.
- [Commits](https://github.com/xunit/xunit/compare/2.4.1...2.7.1)

---
updated-dependencies:
- dependency-name: xunit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

* Appease xunit warnings after upgrading to v2.7.1

* Appease the whitespace linter

* Appease the whitespace linter

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Patrick Ellis <319655+pje@users.noreply.github.com>
2024-05-21 10:47:43 -04:00
John Wesley Walker III
bd7235ef62 Install gpg-agent during actions/runner container image build (#3294)
`add-apt-repository` depends on `gpg-agent`
2024-05-17 09:55:44 -04:00
John Wesley Walker III
0f15173045 Make it easy to install git on an Action Runner Image (#3273)
(We don't actually install `git`.  We simply get the prerequisites out of the way.)
2024-05-15 18:01:06 +00:00
Patrick Ellis
76dc3a28c0 Upgrade node20: 20.8.1 → 20.13.1 (#3284)
* Upgrade node20: 20.8.1 → 20.13.1

* Call out the release process for `alpine_nodejs` in a comment

* move the comment to the end of the line so it's more obvious which variable it's talking about
2024-05-14 13:50:33 -04:00
Patrick Ellis
c67e7f2813 Delete all the contentHash files (#3285)
Nothing uses them anymore after #3074.
2024-05-13 17:40:23 -04:00
Yang Cao
54052b94fb Also do not give up when uploading steps metadata (#3280) 2024-05-10 11:02:29 -04:00
Luke Tomlinson
f2c05de91c Prep 2.316.1 Release (#3272) 2024-05-02 13:44:48 -04:00
eric sciple
18803bdff6 Preserve dates when deserializing job message from Run Service (#3269)
* Preserve dates when deserializing job message from Run Service

* Preserve dates when deserializing job message from "Actions Run Service"
2024-05-02 10:44:57 -04:00
Francesco Renzi
04b07b6675 Prepare v2.316.0 release (#3252) 2024-04-23 16:46:17 +01:00
eric sciple
dd9fcfc5b2 Replace invalid file name chars in diag log name (#3249) 2024-04-20 11:37:25 -05:00
Tingluo Huang
5107c5efb2 Cleanup enabled feature flags. (#3248) 2024-04-19 15:31:44 -04:00
Yang Cao
1b61d78c07 Relax the condition to stop uploading to Results (#3230) 2024-04-17 13:55:03 +00:00
Tingluo Huang
2e0eb2c11f Cleanup enabled feature flags. (#3246)
* Cleanup FF 'DistributedTask.UseWhich2'.

* more ff.
2024-04-16 16:52:10 -04:00
github-actions[bot]
2d83e1d88f Upgrade dotnet sdk to v6.0.421 (#3244)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-14 22:47:26 -04:00
Aiqiao Yan
4a1e38095b backoff if we retried polling for more than 50 times in less than 30m… (#3232)
* backoff if we retried polling for more than 50 times in less than 30minutes

* run dotnet format

* move delay to after no message trace

* run dotnet format
2024-04-09 14:13:07 -04:00
eeSquared
f467e9e125 Add new SessionConflict return code (#3215)
* Add new SessionConflict return code

* formatting

* Change return type of CreateSessionAsync to new enum

* Update entry scripts to handle new exit code

* Move enum
2024-03-27 18:49:58 +00:00
Tingluo Huang
77e0bfbb8a Load '_runnerSettings' in the early point of JobRunner.cs (#3218) 2024-03-23 23:12:39 -04:00
Luke Tomlinson
a52c53955c Prepare v2.315.0 release (#3216) 2024-03-22 11:38:53 -04:00
Luke Tomlinson
8ebf298bcd Always Delete Actions Service Session (#3214)
* Delete Actions Service session always

* update tes
2024-03-21 16:30:34 -04:00
Jacob Wallraff
4b85145661 Handle new non-retryable exception type (#3191)
* Handle new non-retryable exception type

* Update ActionManager.cs
2024-03-21 18:50:45 +00:00
Nikola Jokic
bc8b6e0152 Bump docker version and docker buildx version (#3208) 2024-03-20 11:16:41 -04:00
github-actions[bot]
82e01c6173 Upgrade dotnet sdk to v6.0.420 (#3211)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-03-17 22:42:44 -04:00
Nikola Jokic
93bc1cd918 Bump hook version to 0.6.0 (#3203) 2024-03-15 11:13:29 -04:00
Tatyana Kostromskaya
692d910868 Add ability to enforce actions to run on node20 (#3192)
Add options to enforce actions execute on node20
2024-03-14 14:12:08 +01:00
Patrick Carnahan
2c8c941622 consume new pipelines service url in handlers (#3185)
* consume pipelines service url if present

updates how the `ACTIONS_RUNTIME_URL` variable is set to utilize a new value, `PipelinesServiceUrl` if present in the endpoint. if this value is not present then the existing system connection endpoint is used to retain backward compatibility.

* consume pipelines url

updates how the `ACTIONS_RUNTIME_URL` variable is set to utilize a new value, `PipelinesServiceUrl` if present in the endpoint. if this value is not present then the existing system connection endpoint is used to retain backward compatibility.
2024-03-05 11:13:16 -05:00
Nikola Jokic
86d6211c75 Remove -f flag in wait when manually trap signal (#3182)
* Remove -f flag in wait when manually trap signal

* Remove extra empty line
2024-03-04 11:32:21 +01:00
Yashwanth Anantharaju
aa90563cae don't crash listener on getting job exceptions (#3177) 2024-02-29 15:39:29 +00:00
Tingluo Huang
4cb3cb2962 Bump runner version to match the latest patch release (#3175) 2024-02-28 20:08:31 +00:00
Ryan Troost
d7777fd632 fix summaries for actions results (#3174)
* fix summaries for actions results

* remove negative
2024-02-27 15:22:26 -05:00
Luke Tomlinson
d8bce88c4f Prepare v2.314.0 release (#3172)
* Prepare v2.314.0 release

* update releaseNoteMd
2024-02-26 13:19:04 -05:00
Tingluo Huang
601d3de3f3 Better step timeout message. (#3166) 2024-02-26 15:37:59 +00:00
Luke Tomlinson
034c51cd0b Refresh Token for BrokerServer (#3167) 2024-02-26 10:05:41 -05:00
Luke Tomlinson
d296014f99 Remove USE_BROKER_FLOW (#3162) 2024-02-21 22:39:26 +00:00
Luke Tomlinson
3449d5fa52 Broker fixes for token refreshes and AccessDeniedException (#3161) 2024-02-21 16:43:01 -05:00
Enes Çakır
6603bfb74c Add a retry logic to docker login operation (#3089)
While there's an existing retry mechanism for the `docker pull` command
[^1], it's missing for `docker login`.

Similar to the `docker pull` scenario, the container registry could
potentially be briefly unavailable or inaccessible, leading to failed
`docker login` attempt and subsequent workflow run failures.

Since it's container based workflow, there is not way to retry on
customer side. The runner should retry itself.

It also aligns with community feedback [^2].

[^1]: 8e0cd36cd8/src/Runner.Worker/ContainerOperationProvider.cs (L201)
[^2]: https://github.com/orgs/community/discussions/73069

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
2024-02-21 17:32:37 +00:00
Yashwanth Anantharaju
b19b9462d8 handle broker run service exception handling (#3163)
* handle run service exception handling

* force fail always

* format

* format
2024-02-21 17:04:13 +00:00
github-actions[bot]
3db5c90cc4 Upgrade dotnet sdk to v6.0.419 (#3158)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-19 00:34:02 -05:00
David Omid
927b26a364 Process snapshot tokens (#3135)
* Added Snapshot TemplateToken to AgentJobRequestMessage

* WIP for processing the snapshot token

* Changed snapshot post job step condition to Success, added comments

* Refactored snapshot post-job step

* Added evaluation of snapshot token to retrieve image name

* Added snapshot to workflow schema

* Fixed linter error

* Migrated snapshot logic to new SnapshotOperationProvider

* Fixed linter error

* Fixed linter errors

* Fixed linter error

* Fixed linter errors

* Updated L0 tests

* Fixed linter errors

* Added new JobExtensionL0 tests for snapshot post-job step

* Added JobExtensionL0 test case for snapshot mappings

* Added SnapshotOperationProviderL0 tests

* Enabled nullable types for SnapshotOperationProvider and its tests

* Added more assertions to SnapshotOperationProviderL0 tests

* Fixed linter errors

* Made sure TestHostContexts are disposed of properlyh in SnapshotOperationProviderL0 tests

* Resolved PR comments

* Fixed formatting

* Removed redundant reference

* Addressed PR comments
2024-02-14 21:34:52 -05:00
Tingluo Huang
72559572f6 Pass RunnerOS during job acquire. (#3140) 2024-02-09 15:19:52 -05:00
Luke Tomlinson
31318d81ba Prepare v2.313.0 Release (#3137)
* update runnerversion

* update releaseNote.md

* update-releasenote
2024-02-07 15:15:32 -05:00
Nikola Jokic
1d47bfa6c7 Bump hook version to 0.5.1 (#3129)
Co-authored-by: Tingluo Huang <tingluohuang@github.com>
2024-02-07 19:55:33 +00:00
Luke Tomlinson
651ea42e00 Handle ForceTokenRefresh message (#3133)
* Handle ForceTokenRefresh message

* move to constants

* format
2024-02-07 11:24:40 -05:00
Lukas Hauser
bcc665a7a1 Fix CVE-2024-21626 (#3123)
Co-authored-by: Ferenc Hammerl <31069338+fhammerl@users.noreply.github.com>
2024-02-07 17:06:57 +01:00
Patrick Ellis
cd812f0395 Add sshd to .devcontainer.json (#3079)
This is required in order to ssh into a codespace for the repo: 


> error getting ssh server details: failed to start SSH server:
Please check if an SSH server is installed in the container.
If the docker image being used for the codespace does not have an SSH server,
install it in your Dockerfile or, for codespaces that use Debian-based images,
you can add the following to your devcontainer.json:
> 
> "features": {
>     "ghcr.io/devcontainers/features/sshd:1": {
>         "version": "latest"
>     }
> }
2024-02-06 15:06:07 +00:00
Josh Soref
fa874cf314 Improve error report for invalid action.yml (#3106) 2024-02-06 13:40:53 +00:00
Bethany
bf0e76631b Specify Content-Type for BlockBlob upload (#3119)
* add content-type to block blob upload

* Add content-type for sdk path

* fix spacing

* merge headers and only when file extension is .txt

* add conditions

* tweak conditions and path matching

* pass in headers

* add content-type for appendblob
2024-02-05 18:19:02 +00:00
Yang Cao
1d82031a2c Make sure to drain the upload queue before clean temp directory (#3125) 2024-02-02 20:37:45 +00:00
Victor Sollerhed
d1a619ff09 Upgrade docker from 24.0.8 to 24.0.9 (#3126)
Release notes:
- https://docs.docker.com/engine/release-notes/24.0/#2409
2024-02-02 09:46:12 -05:00
Jonathan Tamsut
11680fc78f Upload the diagnostic logs to the Results Service (#3114)
* upload diagnostic logs to results

* correct casing

* correct typo

* update contract

* update constant and define private method

* fix access

* fix reference to logs url

* update

* use results func

* fix method naming

* rename to match rpc

* change API contract

* fix lint issue

* refactor

* remove unused method

* create new log type
2024-02-01 14:27:06 -05:00
Victor Sollerhed
3e5433ec86 Upgrade docker from 24.0.7 to 24.0.8 (#3124)
Which (among other things) fixes this High `CVE-2024-21626`:
- https://github.com/advisories/GHSA-xr7r-f8xq-vfvv

Release notes:
- https://docs.docker.com/engine/release-notes/24.0/#2408
2024-02-01 11:10:46 -05:00
Tingluo Huang
b647b890c5 Only keep 1 older version runner around after self-upgrade. (#3122) 2024-01-31 10:03:49 -05:00
Luke Tomlinson
894c50073a Implement Broker Redirects for Session and Messages (#3103) 2024-01-30 20:57:49 +00:00
Tingluo Huang
5268d74ade Fix JobDispatcher crash during force cancellation. (#3118) 2024-01-30 15:24:22 -05:00
Tingluo Huang
7414e08fbd Add user-agent to all http clients using RawClientHttpRequestSettings. (#3115) 2024-01-30 13:39:41 -05:00
Tingluo Huang
dcb790f780 Fix release workflow. (#3102) 2024-01-30 13:25:58 -05:00
Tingluo Huang
b7ab810945 Make embedded timeline record has same order as its parent record. (#3113) 2024-01-26 17:26:30 -05:00
Tingluo Huang
7310ba0a08 Revert "Bump container hook version to 0.5.0 in runner image (#3003)" (#3101)
This reverts commit c7d65c42d6.
2024-01-18 11:50:36 -05:00
Diogo Torres
e842959e3e Bump docker and buildx to the latest version (#3100) 2024-01-18 10:42:44 -05:00
Tingluo Huang
9f19310b5b Prepare 2.312.0 runner release. (#3092) 2024-01-16 16:44:30 -05:00
Thomas Boop
84220a21d1 Patch Curl to no longer use -k (#3091)
* Update externals.sh

* Update externals.sh
2024-01-16 13:39:21 -05:00
github-actions[bot]
8e0cd36cd8 Upgrade dotnet sdk to v6.0.418 (#3085)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-10 12:48:40 -05:00
Tingluo Huang
f1f18f67e1 Remove runner trimmed packages. (#3074) 2024-01-10 12:32:17 -05:00
Yang Cao
ac39c4bd0a Use Azure SDK to upload files to Azure Blob (#3033) 2024-01-08 16:13:06 -05:00
Tingluo Huang
3f3d9b0d99 Extend --check to check Results-Receiver service. (#3078)
* Extend --check to check Results-Receiver service.

* Update docs/checks/actions.md

Co-authored-by: Christopher Schleiden <cschleiden@live.de>

---------

Co-authored-by: Christopher Schleiden <cschleiden@live.de>
2024-01-05 14:18:59 -05:00
adjn
af485fb660 Update envlinux.md (#3040)
Matching supported distros to https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners
2024-01-02 11:09:10 -05:00
Stefan Ruvceski
9e3e57ff90 close reason update (#3027)
* Update close-bugs-bot.yml

* Update close-features-bot.yml
2023-12-06 17:12:55 +01:00
Tingluo Huang
ac89b31d2f Add codeload to the list of service we check during '--check'. (#3011) 2023-11-28 13:39:39 -05:00
Tingluo Huang
65201ff6be Include whether http proxy configured as part of UserAgent. (#3009) 2023-11-28 09:43:13 -05:00
Tingluo Huang
661b261959 Mark job as failed on worker crash. (#3006) 2023-11-27 16:43:36 -05:00
Hidetake Iwata
8a25302ba3 Set ImageOS environment variable in runner images (#2878)
Co-authored-by: Nikola Jokic <jokicnikola07@gmail.com>
2023-11-23 15:04:54 +01:00
Nikola Jokic
c7d65c42d6 Bump container hook version to 0.5.0 in runner image (#3003) 2023-11-22 14:52:08 +01:00
Luke Tomlinson
a9bae6f37a Handle SelfUpdate Flow when Package is provided in Message (#2926) 2023-11-13 16:44:07 -05:00
Luke Tomlinson
3136ce3a71 Send disableUpdate as query parameter (#2970) 2023-11-13 10:19:42 -05:00
Stefan Ruvceski
a4c57f2747 Create close-features and close-bugs bot for runner issues (#2909)
* Create close-bugs-bot.yml

close bugs bot

* Update close-bugs-bot.yml

* Update close-bugs-bot.yml

* Update close-bugs-bot.yml

* Create close-features-bot.yml

* Update close-bugs-bot.yml

* Update close-features-bot.yml

* Update close-bugs-bot.yml

---------

Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
2023-10-27 10:06:10 +00:00
AJ Schmidt
ce4e62c849 fix buildx installation (#2952) 2023-10-25 13:59:39 -04:00
218 changed files with 13077 additions and 5356 deletions

View File

@@ -4,10 +4,13 @@
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
"ghcr.io/devcontainers/features/dotnet": {
"version": "6.0.415"
"version": "8.0.413"
},
"ghcr.io/devcontainers/features/node:1": {
"version": "16"
"version": "20"
},
"ghcr.io/devcontainers/features/sshd:1": {
"version": "latest"
}
},
"customizations": {

View File

@@ -7,7 +7,7 @@ contact_links:
url: https://github.community/c/code-to-cloud/52
about: If you have questions about GitHub Actions or need support writing workflows, please ask in the GitHub Community Support forum.
- name: ✅ Feedback and suggestions for GitHub Actions
url: https://github.com/github/feedback/discussions/categories/actions-and-packages-feedback
url: https://github.com/github/feedback/discussions/categories/actions
about: If you have feedback or suggestions about GitHub Actions, please open a discussion (or add to an existing one) in the GitHub Actions Feedback. GitHub Actions Product Managers and Engineers monitor the feedback forum.
- name: ‼️ GitHub Security Bug Bounty
url: https://bounty.github.com/

25
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,25 @@
## Making changes
### Tests
Whenever possible, changes should be accompanied by non-trivial tests that meaningfully exercise the core functionality of the new code being introduced.
All tests are in the `Test/` directory at the repo root. Fast unit tests are in the `Test/L0` directory and by convention have the suffix `L0.cs`. For example: unit tests for a hypothetical `src/Runner.Worker/Foo.cs` would go in `src/Test/L0/Worker/FooL0.cs`.
Run tests using this command:
```sh
cd src && ./dev.sh test
```
### Formatting
After editing .cs files, always format the code using this command:
```sh
cd src && ./dev.sh format
```
### Feature Flags
Wherever possible, all changes should be safeguarded by a feature flag; `Features` are declared in [Constants.cs](src/Runner.Common/Constants.cs).

View File

@@ -5,6 +5,11 @@ updates:
schedule:
interval: "daily"
target-branch: "main"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
target-branch: "main"
- package-ecosystem: "nuget"
directory: "/src"
schedule:

View File

@@ -41,7 +41,7 @@ jobs:
devScript: ./dev.sh
- runtime: win-x64
os: windows-2019
os: windows-latest
devScript: ./dev
- runtime: win-arm64
@@ -50,7 +50,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v5
# Build runner layout
- name: Build & Layout Release
@@ -58,29 +58,6 @@ jobs:
${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
working-directory: src
# Check runtime/externals hash
- name: Compute/Compare runtime and externals Hash
shell: bash
run: |
echo "Current dotnet runtime hash result: $DOTNET_RUNTIME_HASH"
echo "Current Externals hash result: $EXTERNALS_HASH"
NeedUpdate=0
if [ "$EXTERNALS_HASH" != "$(cat ./src/Misc/contentHash/externals/${{ matrix.runtime }})" ] ;then
echo Hash mismatch, Update ./src/Misc/contentHash/externals/${{ matrix.runtime }} to $EXTERNALS_HASH
NeedUpdate=1
fi
if [ "$DOTNET_RUNTIME_HASH" != "$(cat ./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }})" ] ;then
echo Hash mismatch, Update ./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }} to $DOTNET_RUNTIME_HASH
NeedUpdate=1
fi
exit $NeedUpdate
env:
DOTNET_RUNTIME_HASH: ${{hashFiles('**/_layout_trims/runtime/**/*')}}
EXTERNALS_HASH: ${{hashFiles('**/_layout_trims/externals/**/*')}}
# Run tests
- name: L0
run: |
@@ -92,17 +69,14 @@ jobs:
- name: Package Release
if: github.event_name != 'pull_request'
run: |
${{ matrix.devScript }} package Release
${{ matrix.devScript }} package Release ${{ matrix.runtime }}
working-directory: src
# Upload runner package tar.gz/zip as artifact
- name: Publish Artifact
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: runner-package-${{ matrix.runtime }}
path: |
_package
_package_trims/trim_externals
_package_trims/trim_runtime
_package_trims/trim_runtime_externals

17
.github/workflows/close-bugs-bot.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Close Bugs Bot
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # every day at midnight
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
close-issue-message: "This issue does not seem to be a problem with the runner application, it concerns the GitHub actions platform more generally. Could you please post your feedback on the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions) which is actively monitored. Using the forum ensures that we route your problem to the correct team. 😃"
exempt-issue-labels: "keep"
stale-issue-label: "actions-bug"
only-labels: "actions-bug"
days-before-stale: 0
days-before-close: 1

View File

@@ -0,0 +1,17 @@
name: Close Features Bot
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # every day at midnight
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
close-issue-message: "Thank you for your interest in the runner application and taking the time to provide your valuable feedback. We kindly ask you to redirect this feedback to the [GitHub Community Support Forum](https://github.com/orgs/community/discussions/categories/actions-and-packages) which our team actively monitors and would be a better place to start a discussion for new feature requests in GitHub Actions. For more information on this policy please [read our contribution guidelines](https://github.com/actions/runner#contribute). 😃"
exempt-issue-labels: "keep"
stale-issue-label: "actions-feature"
only-labels: "actions-feature"
days-before-stale: 0
days-before-close: 1

View File

@@ -23,11 +23,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v5
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@@ -38,4 +38,4 @@ jobs:
working-directory: src
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@@ -0,0 +1,144 @@
name: "Docker/Buildx Version Upgrade"
on:
schedule:
- cron: '0 0 * * 1' # Run every Monday at midnight
workflow_dispatch: # Allow manual triggering
jobs:
check-versions:
runs-on: ubuntu-latest
outputs:
DOCKER_SHOULD_UPDATE: ${{ steps.check_docker_version.outputs.SHOULD_UPDATE }}
DOCKER_LATEST_VERSION: ${{ steps.check_docker_version.outputs.LATEST_VERSION }}
DOCKER_CURRENT_VERSION: ${{ steps.check_docker_version.outputs.CURRENT_VERSION }}
BUILDX_SHOULD_UPDATE: ${{ steps.check_buildx_version.outputs.SHOULD_UPDATE }}
BUILDX_LATEST_VERSION: ${{ steps.check_buildx_version.outputs.LATEST_VERSION }}
BUILDX_CURRENT_VERSION: ${{ steps.check_buildx_version.outputs.CURRENT_VERSION }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Check Docker version
id: check_docker_version
shell: bash
run: |
# Extract current Docker version from Dockerfile
current_version=$(grep "ARG DOCKER_VERSION=" ./images/Dockerfile | cut -d'=' -f2)
# Fetch latest Docker Engine version from Docker's download site
# This gets the latest Linux static binary version which matches what's used in the Dockerfile
latest_version=$(curl -s https://download.docker.com/linux/static/stable/x86_64/ | grep -o 'docker-[0-9]*\.[0-9]*\.[0-9]*\.tgz' | sort -V | tail -n 1 | sed 's/docker-\(.*\)\.tgz/\1/')
# Extra check to ensure we got a valid version
if [[ ! $latest_version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Failed to retrieve a valid Docker version"
exit 1
fi
should_update=0
[ "$current_version" != "$latest_version" ] && should_update=1
echo "CURRENT_VERSION=${current_version}" >> $GITHUB_OUTPUT
echo "LATEST_VERSION=${latest_version}" >> $GITHUB_OUTPUT
echo "SHOULD_UPDATE=${should_update}" >> $GITHUB_OUTPUT
- name: Check Buildx version
id: check_buildx_version
shell: bash
run: |
# Extract current Buildx version from Dockerfile
current_version=$(grep "ARG BUILDX_VERSION=" ./images/Dockerfile | cut -d'=' -f2)
# Fetch latest Buildx version
latest_version=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | jq -r '.tag_name' | sed 's/^v//')
should_update=0
[ "$current_version" != "$latest_version" ] && should_update=1
echo "CURRENT_VERSION=${current_version}" >> $GITHUB_OUTPUT
echo "LATEST_VERSION=${latest_version}" >> $GITHUB_OUTPUT
echo "SHOULD_UPDATE=${should_update}" >> $GITHUB_OUTPUT
- name: Create annotations for versions
run: |
docker_should_update="${{ steps.check_docker_version.outputs.SHOULD_UPDATE }}"
buildx_should_update="${{ steps.check_buildx_version.outputs.SHOULD_UPDATE }}"
# Show annotation if only Docker needs update
if [[ "$docker_should_update" == "1" && "$buildx_should_update" == "0" ]]; then
echo "::warning ::Docker version (${{ steps.check_docker_version.outputs.LATEST_VERSION }}) needs update but Buildx is current. Only updating when both need updates."
fi
# Show annotation if only Buildx needs update
if [[ "$docker_should_update" == "0" && "$buildx_should_update" == "1" ]]; then
echo "::warning ::Buildx version (${{ steps.check_buildx_version.outputs.LATEST_VERSION }}) needs update but Docker is current. Only updating when both need updates."
fi
# Show annotation when both are current
if [[ "$docker_should_update" == "0" && "$buildx_should_update" == "0" ]]; then
echo "::warning ::Latest Docker version is ${{ steps.check_docker_version.outputs.LATEST_VERSION }} and Buildx version is ${{ steps.check_buildx_version.outputs.LATEST_VERSION }}. No updates needed."
fi
update-versions:
permissions:
pull-requests: write
contents: write
needs: [check-versions]
if: ${{ needs.check-versions.outputs.DOCKER_SHOULD_UPDATE == 1 && needs.check-versions.outputs.BUILDX_SHOULD_UPDATE == 1 }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Update Docker version
shell: bash
run: |
latest_version="${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }}"
current_version="${{ needs.check-versions.outputs.DOCKER_CURRENT_VERSION }}"
# Update version in Dockerfile
sed -i "s/ARG DOCKER_VERSION=$current_version/ARG DOCKER_VERSION=$latest_version/g" ./images/Dockerfile
- name: Update Buildx version
shell: bash
run: |
latest_version="${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}"
current_version="${{ needs.check-versions.outputs.BUILDX_CURRENT_VERSION }}"
# Update version in Dockerfile
sed -i "s/ARG BUILDX_VERSION=$current_version/ARG BUILDX_VERSION=$latest_version/g" ./images/Dockerfile
- name: Commit changes and create Pull Request
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Setup branch and commit information
branch_name="feature/docker-buildx-upgrade"
commit_message="Upgrade Docker to v${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }} and Buildx to v${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}"
pr_title="Update Docker to v${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }} and Buildx to v${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}"
# Configure git
git config --global user.name "github-actions[bot]"
git config --global user.email "<41898282+github-actions[bot]@users.noreply.github.com>"
# Create branch or switch to it if it exists
if git show-ref --quiet refs/remotes/origin/$branch_name; then
git fetch origin
git checkout -B "$branch_name" origin/$branch_name
else
git checkout -b "$branch_name"
fi
# Commit and push changes
git commit -a -m "$commit_message"
git push --force origin "$branch_name"
# Create PR
pr_body="Upgrades Docker version from ${{ needs.check-versions.outputs.DOCKER_CURRENT_VERSION }} to ${{ needs.check-versions.outputs.DOCKER_LATEST_VERSION }} and Docker Buildx version from ${{ needs.check-versions.outputs.BUILDX_CURRENT_VERSION }} to ${{ needs.check-versions.outputs.BUILDX_LATEST_VERSION }}.\n\n"
pr_body+="Release notes: https://docs.docker.com/engine/release-notes/\n\n"
pr_body+="---\n\nAutogenerated by [Docker/Buildx Version Upgrade Workflow](https://github.com/actions/runner/blob/main/.github/workflows/docker-buildx-upgrade.yml)"
gh pr create -B main -H "$branch_name" \
--title "$pr_title" \
--body "$pr_body"

View File

@@ -15,7 +15,7 @@ jobs:
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v5
- name: Get current major minor version
id: fetch_current_version
shell: bash
@@ -51,7 +51,7 @@ jobs:
run: echo "::error links::feature/dotnet-sdk-upgrade${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }} https://github.com/actions/runner/tree/feature/dotnet-sdk-upgrade${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}::Branch feature/dotnetsdk-upgrade/${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }} already exists. Please take a look and delete that branch if you wish to recreate"
- name: Create a warning annotation if no need to update
if: ${{ steps.fetch_latest_version.outputs.SHOULD_UPDATE == 0 && steps.fetch_latest_version.outputs.BRANCH_EXISTS == 0 }}
run: echo "::warning ::Latest DotNet SDK patch is ${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}, and we are on ${{ steps.fetch_latest_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_PATCH_VERSION }}. No need to update"
run: echo "::warning ::Latest DotNet SDK patch is ${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}, and we are on ${{ steps.fetch_current_version.outputs.DOTNET_CURRENT_MAJOR_MINOR_PATCH_VERSION }}. No need to update"
- name: Update patch version
if: ${{ steps.fetch_latest_version.outputs.SHOULD_UPDATE == 1 && steps.fetch_latest_version.outputs.BRANCH_EXISTS == 0 }}
shell: bash
@@ -84,221 +84,20 @@ jobs:
git commit -a -m "Upgrade dotnet sdk to v${{ steps.fetch_latest_version.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}"
git push --set-upstream origin $branch_name
build-hashes:
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
create-pr:
needs: [dotnet-update]
outputs:
# pass outputs from this job to create-pr for use
DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION: ${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ needs.dotnet-update.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
NEEDS_HASH_UPDATE: ${{ steps.compute-hash.outputs.NEED_UPDATE }}
strategy:
fail-fast: false
matrix:
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, win-arm64, osx-x64, osx-arm64 ]
include:
- runtime: linux-x64
os: ubuntu-latest
devScript: ./dev.sh
- runtime: linux-arm64
os: ubuntu-latest
devScript: ./dev.sh
- runtime: linux-arm
os: ubuntu-latest
devScript: ./dev.sh
- runtime: osx-x64
os: macOS-latest
devScript: ./dev.sh
- runtime: osx-arm64
os: macOS-latest
devScript: ./dev.sh
- runtime: win-x64
os: windows-2019
devScript: ./dev
- runtime: win-arm64
os: windows-latest
devScript: ./dev
runs-on: ${{ matrix.os }}
if: ${{ needs.dotnet-update.outputs.SHOULD_UPDATE == 1 && needs.dotnet-update.outputs.BRANCH_EXISTS == 0 }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v5
with:
ref: feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
# Build runner layout
- name: Build & Layout Release
run: |
${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
working-directory: src
# Check runtime/externals hash
- name: Compute/Compare runtime and externals Hash
id: compute-hash
continue-on-error: true
shell: bash
run: |
echo "Current dotnet runtime hash result: $DOTNET_RUNTIME_HASH"
echo "Current Externals hash result: $EXTERNALS_HASH"
NeedUpdate=0
if [ "$EXTERNALS_HASH" != "$(cat ./src/Misc/contentHash/externals/${{ matrix.runtime }})" ] ;then
echo Hash mismatch, Update ./src/Misc/contentHash/externals/${{ matrix.runtime }} to $EXTERNALS_HASH
echo "EXTERNAL_HASH=$EXTERNALS_HASH" >> $GITHUB_OUTPUT
NeedUpdate=1
fi
if [ "$DOTNET_RUNTIME_HASH" != "$(cat ./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }})" ] ;then
echo Hash mismatch, Update ./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }} to $DOTNET_RUNTIME_HASH
echo "DOTNET_RUNTIME_HASH=$DOTNET_RUNTIME_HASH" >> $GITHUB_OUTPUT
NeedUpdate=1
fi
echo "NEED_UPDATE=$NeedUpdate" >> $GITHUB_OUTPUT
env:
DOTNET_RUNTIME_HASH: ${{hashFiles('**/_layout_trims/runtime/**/*')}}
EXTERNALS_HASH: ${{hashFiles('**/_layout_trims/externals/**/*')}}
- name: update hash
if: ${{ steps.compute-hash.outputs.NEED_UPDATE == 1 }}
shell: bash
run: |
ExternalHash=${{ steps.compute-hash.outputs.EXTERNAL_HASH }}
DotNetRuntimeHash=${{ steps.compute-hash.outputs.DOTNET_RUNTIME_HASH }}
if [ -n "$ExternalHash" ]; then
echo "$ExternalHash" > ./src/Misc/contentHash/externals/${{ matrix.runtime }}
fi
if [ -n "$DotNetRuntimeHash" ]; then
echo "$DotNetRuntimeHash" > ./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }}
fi
- name: cache updated hashes
if: ${{ steps.compute-hash.outputs.NEED_UPDATE == 1 }}
uses: actions/cache/save@v3
with:
enableCrossOsArchive: true
path: |
./src/Misc/contentHash/externals/${{ matrix.runtime }}
./src/Misc/contentHash/dotnetRuntime/${{ matrix.runtime }}
key: compute-hashes-${{ matrix.runtime }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
hash-update:
needs: [build-hashes]
if: ${{ needs.build-hashes.outputs.NEEDS_HASH_UPDATE == 1 }}
outputs:
# pass outputs from this job to create-pr for use
DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION: ${{ needs.build-hashes.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ needs.build-hashes.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: feature/dotnetsdk-upgrade/${{ needs.build-hashes.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
- name: Restore cached hashes - linux-x64
id: cache-restore-linux-x64
uses: actions/cache/restore@v3
with:
enableCrossOsArchive: true
path: |
./src/Misc/contentHash/externals/linux-x64
./src/Misc/contentHash/dotnetRuntime/linux-x64
key: compute-hashes-linux-x64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
- name: Restore cached hashes - linux-arm64
id: cache-restore-linux-arm64
uses: actions/cache/restore@v3
with:
enableCrossOsArchive: true
path: |
./src/Misc/contentHash/externals/linux-arm64
./src/Misc/contentHash/dotnetRuntime/linux-arm64
key: compute-hashes-linux-arm64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
- name: Restore cached hashes - linux-arm
id: cache-restore-linux-arm
uses: actions/cache/restore@v3
with:
enableCrossOsArchive: true
path: |
./src/Misc/contentHash/externals/linux-arm
./src/Misc/contentHash/dotnetRuntime/linux-arm
key: compute-hashes-linux-arm-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
- name: Restore cached hashes - osx-x64
id: cache-restore-osx-x64
uses: actions/cache/restore@v3
with:
enableCrossOsArchive: true
path: |
./src/Misc/contentHash/externals/osx-x64
./src/Misc/contentHash/dotnetRuntime/osx-x64
key: compute-hashes-osx-x64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
- name: Restore cached hashes - osx-arm64
id: cache-restore-osx-arm64
uses: actions/cache/restore@v3
with:
enableCrossOsArchive: true
path: |
./src/Misc/contentHash/externals/osx-arm64
./src/Misc/contentHash/dotnetRuntime/osx-arm64
key: compute-hashes-osx-arm64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
- name: Restore cached hashes - win-x64
id: cache-restore-win-x64
uses: actions/cache/restore@v3
with:
enableCrossOsArchive: true
path: |
./src/Misc/contentHash/externals/win-x64
./src/Misc/contentHash/dotnetRuntime/win-x64
key: compute-hashes-win-x64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
- name: Restore cached hashes - win-arm64
id: cache-restore-win-arm64
uses: actions/cache/restore@v3
with:
enableCrossOsArchive: true
path: |
./src/Misc/contentHash/externals/win-arm64
./src/Misc/contentHash/dotnetRuntime/win-arm64
key: compute-hashes-win-arm64-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}
- name: Fetch cached computed hashes
if: steps.cache-restore-linux-x64.outputs.cache-hit == 'true' ||
steps.cache-restore-linux-arm64.outputs.cache-hit == 'true' ||
steps.cache-restore-linux-arm.outputs.cache-hit == 'true' ||
steps.cache-restore-win-x64.outputs.cache-hit == 'true' ||
steps.cache-restore-win-arm64.outputs.cache-hit == 'true' ||
steps.cache-restore-osx-x64.outputs.cache-hit == 'true' ||
steps.cache-restore-osx-arm64.outputs.cache-hit == 'true'
shell: bash
run: |
Environments=( "linux-x64" "linux-arm64" "linux-arm" "win-x64" "win-arm64" "osx-x64" "osx-arm64" )
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -a -m "Update computed hashes"
git push --set-upstream origin feature/dotnetsdk-upgrade/${{ needs.build-hashes.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
create-pr:
needs: [hash-update]
outputs:
# pass outputs from this job to run-tests for use
DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION: ${{ needs.hash-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
DOTNET_CURRENT_MAJOR_MINOR_VERSION: ${{ needs.hash-update.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: feature/dotnetsdk-upgrade/${{ needs.hash-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}
- name: Create Pull Request
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr create -B main -H feature/dotnetsdk-upgrade/${{ needs.hash-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }} --title "Update dotnet sdk to latest version @${{ needs.hash-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}" --body "
https://dotnetcli.blob.core.windows.net/dotnet/Sdk/${{ needs.hash-update.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}/latest.version
gh pr create -B main -H feature/dotnetsdk-upgrade/${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }} --title "Update dotnet sdk to latest version @${{ needs.dotnet-update.outputs.DOTNET_LATEST_MAJOR_MINOR_PATCH_VERSION }}" --body "
https://dotnetcli.blob.core.windows.net/dotnet/Sdk/${{ needs.dotnet-update.outputs.DOTNET_CURRENT_MAJOR_MINOR_VERSION }}/latest.version
---

View File

@@ -1,24 +0,0 @@
name: Lint
on:
pull_request:
branches: [ main ]
jobs:
build:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
# Ensure full list of changed files within `super-linter`
fetch-depth: 0
- name: Run linters
uses: github/super-linter@v4
env:
DEFAULT_BRANCH: ${{ github.base_ref }}
EDITORCONFIG_FILE_NAME: .editorconfig
LINTER_RULES_PATH: /src/
VALIDATE_ALL_CODEBASE: false
VALIDATE_CSHARP: true

View File

@@ -1,68 +0,0 @@
name: Publish Runner Image
on:
workflow_dispatch:
inputs:
runnerVersion:
type: string
description: Version of the runner being installed
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Compute image version
id: image
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const inputRunnerVersion = "${{ github.event.inputs.runnerVersion }}"
if (inputRunnerVersion) {
console.log(`Using input runner version ${inputRunnerVersion}`)
core.setOutput('version', inputRunnerVersion);
return
}
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
console.log(`Using runner version ${runnerVersion}`)
core.setOutput('version', runnerVersion);
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v3
with:
context: ./images
platforms: |
linux/amd64
linux/arm64
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.version }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
build-args: |
RUNNER_VERSION=${{ steps.image.outputs.version }}
push: true
labels: |
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
org.opencontainers.image.licenses=MIT

View File

@@ -11,16 +11,15 @@ jobs:
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v5
# Make sure ./releaseVersion match ./src/runnerversion
# Query GitHub release ensure version is not used
- name: Check version
uses: actions/github-script@0.3.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const core = require('@actions/core')
const fs = require('fs');
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
const releaseVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
@@ -30,7 +29,7 @@ jobs:
return
}
try {
const release = await github.repos.getReleaseByTag({
const release = await github.rest.repos.getReleaseByTag({
owner: '${{ github.event.repository.owner.name }}',
repo: '${{ github.event.repository.name }}',
tag: 'v' + runnerVersion
@@ -53,27 +52,6 @@ jobs:
win-arm64-sha: ${{ steps.sha.outputs.win-arm64-sha256 }}
osx-x64-sha: ${{ steps.sha.outputs.osx-x64-sha256 }}
osx-arm64-sha: ${{ steps.sha.outputs.osx-arm64-sha256 }}
linux-x64-sha-noexternals: ${{ steps.sha_noexternals.outputs.linux-x64-sha256 }}
linux-arm64-sha-noexternals: ${{ steps.sha_noexternals.outputs.linux-arm64-sha256 }}
linux-arm-sha-noexternals: ${{ steps.sha_noexternals.outputs.linux-arm-sha256 }}
win-x64-sha-noexternals: ${{ steps.sha_noexternals.outputs.win-x64-sha256 }}
win-arm64-sha-noexternals: ${{ steps.sha_noexternals.outputs.win-arm64-sha256 }}
osx-x64-sha-noexternals: ${{ steps.sha_noexternals.outputs.osx-x64-sha256 }}
osx-arm64-sha-noexternals: ${{ steps.sha_noexternals.outputs.osx-arm64-sha256 }}
linux-x64-sha-noruntime: ${{ steps.sha_noruntime.outputs.linux-x64-sha256 }}
linux-arm64-sha-noruntime: ${{ steps.sha_noruntime.outputs.linux-arm64-sha256 }}
linux-arm-sha-noruntime: ${{ steps.sha_noruntime.outputs.linux-arm-sha256 }}
win-x64-sha-noruntime: ${{ steps.sha_noruntime.outputs.win-x64-sha256 }}
win-arm64-sha-noruntime: ${{ steps.sha_noruntime.outputs.win-arm64-sha256 }}
osx-x64-sha-noruntime: ${{ steps.sha_noruntime.outputs.osx-x64-sha256 }}
osx-arm64-sha-noruntime: ${{ steps.sha_noruntime.outputs.osx-arm64-sha256 }}
linux-x64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.linux-x64-sha256 }}
linux-arm64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.linux-arm64-sha256 }}
linux-arm-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.linux-arm-sha256 }}
win-x64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.win-x64-sha256 }}
win-arm64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.win-arm64-sha256 }}
osx-x64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.osx-x64-sha256 }}
osx-arm64-sha-noruntime-noexternals: ${{ steps.sha_noruntime_noexternals.outputs.osx-arm64-sha256 }}
strategy:
matrix:
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64, osx-arm64, win-arm64 ]
@@ -99,7 +77,7 @@ jobs:
devScript: ./dev.sh
- runtime: win-x64
os: windows-2019
os: windows-latest
devScript: ./dev
- runtime: win-arm64
@@ -108,7 +86,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v5
# Build runner layout
- name: Build & Layout Release
@@ -136,113 +114,67 @@ jobs:
id: sha
name: Compute SHA256
working-directory: _package
- run: |
file=$(ls)
sha=$(sha256sum $file | awk '{ print $1 }')
echo "Computed sha256: $sha for $file"
echo "${{matrix.runtime}}-sha256=$sha" >> $GITHUB_OUTPUT
echo "sha256=$sha" >> $GITHUB_OUTPUT
shell: bash
id: sha_noexternals
name: Compute SHA256
working-directory: _package_trims/trim_externals
- run: |
file=$(ls)
sha=$(sha256sum $file | awk '{ print $1 }')
echo "Computed sha256: $sha for $file"
echo "${{matrix.runtime}}-sha256=$sha" >> $GITHUB_OUTPUT
echo "sha256=$sha" >> $GITHUB_OUTPUT
shell: bash
id: sha_noruntime
name: Compute SHA256
working-directory: _package_trims/trim_runtime
- run: |
file=$(ls)
sha=$(sha256sum $file | awk '{ print $1 }')
echo "Computed sha256: $sha for $file"
echo "${{matrix.runtime}}-sha256=$sha" >> $GITHUB_OUTPUT
echo "sha256=$sha" >> $GITHUB_OUTPUT
shell: bash
id: sha_noruntime_noexternals
name: Compute SHA256
working-directory: _package_trims/trim_runtime_externals
- name: Create trimmedpackages.json for ${{ matrix.runtime }}
if: matrix.runtime == 'win-x64' || matrix.runtime == 'win-arm64'
uses: actions/github-script@0.3.0
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const core = require('@actions/core')
const fs = require('fs');
const runnerVersion = fs.readFileSync('src/runnerversion', 'utf8').replace(/\n$/g, '')
var trimmedPackages = fs.readFileSync('src/Misc/trimmedpackages_zip.json', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion).replace(/<RUNNER_PLATFORM>/g, '${{ matrix.runtime }}')
trimmedPackages = trimmedPackages.replace(/<RUNTIME_HASH>/g, '${{hashFiles('**/_layout_trims/runtime/**/*')}}')
trimmedPackages = trimmedPackages.replace(/<EXTERNALS_HASH>/g, '${{hashFiles('**/_layout_trims/externals/**/*')}}')
trimmedPackages = trimmedPackages.replace(/<NO_RUNTIME_EXTERNALS_HASH>/g, '${{steps.sha_noruntime_noexternals.outputs.sha256}}')
trimmedPackages = trimmedPackages.replace(/<NO_RUNTIME_HASH>/g, '${{steps.sha_noruntime.outputs.sha256}}')
trimmedPackages = trimmedPackages.replace(/<NO_EXTERNALS_HASH>/g, '${{steps.sha_noexternals.outputs.sha256}}')
console.log(trimmedPackages)
fs.writeFileSync('${{ matrix.runtime }}-trimmedpackages.json', trimmedPackages)
- name: Create trimmedpackages.json for ${{ matrix.runtime }}
if: matrix.runtime != 'win-x64' && matrix.runtime != 'win-arm64'
uses: actions/github-script@0.3.0
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const core = require('@actions/core')
const fs = require('fs');
const runnerVersion = fs.readFileSync('src/runnerversion', 'utf8').replace(/\n$/g, '')
var trimmedPackages = fs.readFileSync('src/Misc/trimmedpackages_targz.json', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion).replace(/<RUNNER_PLATFORM>/g, '${{ matrix.runtime }}')
trimmedPackages = trimmedPackages.replace(/<RUNTIME_HASH>/g, '${{hashFiles('**/_layout_trims/runtime/**/*')}}')
trimmedPackages = trimmedPackages.replace(/<EXTERNALS_HASH>/g, '${{hashFiles('**/_layout_trims/externals/**/*')}}')
trimmedPackages = trimmedPackages.replace(/<NO_RUNTIME_EXTERNALS_HASH>/g, '${{steps.sha_noruntime_noexternals.outputs.sha256}}')
trimmedPackages = trimmedPackages.replace(/<NO_RUNTIME_HASH>/g, '${{steps.sha_noruntime.outputs.sha256}}')
trimmedPackages = trimmedPackages.replace(/<NO_EXTERNALS_HASH>/g, '${{steps.sha_noexternals.outputs.sha256}}')
console.log(trimmedPackages)
fs.writeFileSync('${{ matrix.runtime }}-trimmedpackages.json', trimmedPackages)
# Upload runner package tar.gz/zip as artifact.
# Since each package name is unique, so we don't need to put ${{matrix}} info into artifact name
- name: Publish Artifact
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: runner-packages
name: runner-packages-${{ matrix.runtime }}
path: |
_package
_package_trims/trim_externals
_package_trims/trim_runtime
_package_trims/trim_runtime_externals
${{ matrix.runtime }}-trimmedpackages.json
release:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v5
# Download runner package tar.gz/zip produced by 'build' job
- name: Download Artifact
uses: actions/download-artifact@v1
- name: Download Artifact (win-x64)
uses: actions/download-artifact@v5
with:
name: runner-packages
name: runner-packages-win-x64
path: ./
- name: Download Artifact (win-arm64)
uses: actions/download-artifact@v5
with:
name: runner-packages-win-arm64
path: ./
- name: Download Artifact (osx-x64)
uses: actions/download-artifact@v5
with:
name: runner-packages-osx-x64
path: ./
- name: Download Artifact (osx-arm64)
uses: actions/download-artifact@v5
with:
name: runner-packages-osx-arm64
path: ./
- name: Download Artifact (linux-x64)
uses: actions/download-artifact@v5
with:
name: runner-packages-linux-x64
path: ./
- name: Download Artifact (linux-arm)
uses: actions/download-artifact@v5
with:
name: runner-packages-linux-arm
path: ./
- name: Download Artifact (linux-arm64)
uses: actions/download-artifact@v5
with:
name: runner-packages-linux-arm64
path: ./
# Create ReleaseNote file
- name: Create ReleaseNote
id: releaseNote
uses: actions/github-script@0.3.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const core = require('@actions/core')
const fs = require('fs');
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
var releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
@@ -253,33 +185,11 @@ jobs:
releaseNote = releaseNote.replace(/<LINUX_X64_SHA>/g, '${{needs.build.outputs.linux-x64-sha}}')
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA>/g, '${{needs.build.outputs.linux-arm-sha}}')
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA>/g, '${{needs.build.outputs.linux-arm64-sha}}')
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.win-x64-sha-noexternals}}')
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noexternals}}')
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.osx-x64-sha-noexternals}}')
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.osx-arm64-sha-noexternals}}')
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-x64-sha-noexternals}}')
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm-sha-noexternals}}')
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm64-sha-noexternals}}')
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.win-x64-sha-noruntime}}')
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.win-arm64-sha-noruntime}}')
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.osx-x64-sha-noruntime}}')
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.osx-arm64-sha-noruntime}}')
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-x64-sha-noruntime}}')
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-arm-sha-noruntime}}')
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NORUNTIME>/g, '${{needs.build.outputs.linux-arm64-sha-noruntime}}')
releaseNote = releaseNote.replace(/<WIN_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.win-x64-sha-noruntime-noexternals}}')
releaseNote = releaseNote.replace(/<WIN_ARM64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.win-arm64-sha-noruntime-noexternals}}')
releaseNote = releaseNote.replace(/<OSX_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.osx-x64-sha-noruntime-noexternals}}')
releaseNote = releaseNote.replace(/<OSX_ARM64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.osx-arm64-sha-noruntime-noexternals}}')
releaseNote = releaseNote.replace(/<LINUX_X64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.linux-x64-sha-noruntime-noexternals}}')
releaseNote = releaseNote.replace(/<LINUX_ARM_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm-sha-noruntime-noexternals}}')
releaseNote = releaseNote.replace(/<LINUX_ARM64_SHA_NORUNTIME_NOEXTERNALS>/g, '${{needs.build.outputs.linux-arm64-sha-noruntime-noexternals}}')
console.log(releaseNote)
core.setOutput('version', runnerVersion);
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
@@ -304,379 +214,93 @@ jobs:
# Upload release assets (full runner packages)
- name: Upload Release Asset (win-x64)
uses: actions/upload-release-asset@v1.0.1
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package/actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_path: ${{ github.workspace }}/actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_name: actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_content_type: application/octet-stream
- name: Upload Release Asset (win-arm64)
uses: actions/upload-release-asset@v1.0.1
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package/actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}.zip
asset_path: ${{ github.workspace }}/actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}.zip
asset_name: actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}.zip
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-x64)
uses: actions/upload-release-asset@v1.0.1
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package/actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_path: ${{ github.workspace }}/actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_name: actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-x64)
uses: actions/upload-release-asset@v1.0.1
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package/actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_path: ${{ github.workspace }}/actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-arm64)
uses: actions/upload-release-asset@v1.0.1
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package/actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_path: ${{ github.workspace }}/actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm)
uses: actions/upload-release-asset@v1.0.1
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package/actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_path: ${{ github.workspace }}/actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_name: actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm64)
uses: actions/upload-release-asset@v1.0.1
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package/actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_path: ${{ github.workspace }}/actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_name: actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_content_type: application/octet-stream
# Upload release assets (trim externals)
- name: Upload Release Asset (win-x64-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_externals/actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}-noexternals.zip
asset_name: actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}-noexternals.zip
asset_content_type: application/octet-stream
# Upload release assets (trim externals)
- name: Upload Release Asset (win-arm64-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_externals/actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}-noexternals.zip
asset_name: actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}-noexternals.zip
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-x64-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_externals/actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_name: actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-x64-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_externals/actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-arm64-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_externals/actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_externals/actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_name: actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm64-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_externals/actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_name: actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}-noexternals.tar.gz
asset_content_type: application/octet-stream
# Upload release assets (trim runtime)
- name: Upload Release Asset (win-x64-noruntime)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime/actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}-noruntime.zip
asset_name: actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}-noruntime.zip
asset_content_type: application/octet-stream
# Upload release assets (trim runtime)
- name: Upload Release Asset (win-arm64-noruntime)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime/actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}-noruntime.zip
asset_name: actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}-noruntime.zip
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-x64-noruntime)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime/actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_name: actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-x64-noruntime)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime/actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-arm64-noruntime)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime/actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm-noruntime)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime/actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_name: actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm64-noruntime)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime/actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_name: actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}-noruntime.tar.gz
asset_content_type: application/octet-stream
# Upload release assets (trim runtime and externals)
- name: Upload Release Asset (win-x64-noruntime-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime_externals/actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.zip
asset_name: actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.zip
asset_content_type: application/octet-stream
# Upload release assets (trim runtime and externals)
- name: Upload Release Asset (win-arm64-noruntime-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime_externals/actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.zip
asset_name: actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.zip
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-x64-noruntime-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime_externals/actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_name: actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-x64-noruntime-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime_externals/actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-arm64-noruntime-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime_externals/actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm-noruntime-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime_externals/actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_name: actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm64-noruntime-noexternals)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/_package_trims/trim_runtime_externals/actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_name: actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}-noruntime-noexternals.tar.gz
asset_content_type: application/octet-stream
# Upload release assets (trimmedpackages.json)
- name: Upload Release Asset (win-x64-trimmedpackages.json)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/win-x64-trimmedpackages.json
asset_name: actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
asset_content_type: application/octet-stream
# Upload release assets (trimmedpackages.json)
- name: Upload Release Asset (win-arm64-trimmedpackages.json)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/win-arm64-trimmedpackages.json
asset_name: actions-runner-win-arm64-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-x64-trimmedpackages.json)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/linux-x64-trimmedpackages.json
asset_name: actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-x64-trimmedpackages.json)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/osx-x64-trimmedpackages.json
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
asset_content_type: application/octet-stream
- name: Upload Release Asset (osx-arm64-trimmedpackages.json)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/osx-arm64-trimmedpackages.json
asset_name: actions-runner-osx-arm64-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm-trimmedpackages.json)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/linux-arm-trimmedpackages.json
asset_name: actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm64-trimmedpackages.json)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ${{ github.workspace }}/linux-arm64-trimmedpackages.json
asset_name: actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}-trimmedpackages.json
asset_content_type: application/octet-stream
publish-image:
needs: release
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/actions-runner
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v5
- name: Compute image version
id: image
uses: actions/github-script@v6
uses: actions/github-script@v7.0.1
with:
script: |
const fs = require('fs');
@@ -685,10 +309,10 @@ jobs:
core.setOutput('version', runnerVersion);
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -696,7 +320,7 @@ jobs:
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v6
with:
context: ./images
platforms: |
@@ -712,3 +336,10 @@ jobs:
org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}
org.opencontainers.image.description=https://github.com/actions/runner/releases/tag/v${{ steps.image.outputs.version }}
org.opencontainers.image.licenses=MIT
- name: Generate attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build-and-push.outputs.digest }}
push-to-registry: true

View File

@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
stale-issue-message: "This issue is stale because it has been open 365 days with no activity. Remove stale label or comment or this will be closed in 15 days."
close-issue-message: "This issue was closed because it has been stalled for 15 days with no activity."

1
.gitignore vendored
View File

@@ -26,4 +26,5 @@ _dotnetsdk
TestResults
TestLogs
.DS_Store
.mono
**/*.DotSettings.user

View File

@@ -20,6 +20,20 @@ Runner releases:
![linux](docs/res/linux_sm.png) [Pre-reqs](docs/start/envlinux.md) | [Download](https://github.com/actions/runner/releases)
## Contribute
### Note
We accept contributions in the form of issues and pull requests. The runner typically requires changes across the entire system and we aim for issues in the runner to be entirely self contained and fixable here. Therefore, we will primarily handle bug issues opened in this repo and we kindly request you to create all feature and enhancement requests on the [GitHub Feedback](https://github.com/community/community/discussions/categories/actions-and-packages) page. [Read more about our guidelines here](docs/contribute.md) before contributing.
Thank you for your interest in this GitHub repo, however, right now we are not taking contributions.
We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features were working on and what stage theyre in.
We are taking the following steps to better direct requests related to GitHub Actions, including:
1. We will be directing questions and support requests to our [Community Discussions area](https://github.com/orgs/community/discussions/categories/actions)
2. High Priority bugs can be reported through Community Discussions or you can report these to our support team https://support.github.com/contact/bug-report.
3. Security Issues should be handled as per our [security.md](security.md)
We will still provide security updates for this project and fix major breaking changes during this time.
You are welcome to still raise bugs in this repo.

View File

@@ -250,6 +250,42 @@ Two problem matchers can be used:
}
```
#### Default from path
The problem matcher can specify a `fromPath` property at the top level, which applies when a specific pattern doesn't provide a value for `fromPath`. This is useful for tools that don't include project file information in their output.
For example, given the following compiler output that doesn't include project file information:
```
ClassLibrary.cs(16,24): warning CS0612: 'ClassLibrary.Helpers.MyHelper.Name' is obsolete
```
A problem matcher with a default from path can be used:
```json
{
"problemMatcher": [
{
"owner": "csc-minimal",
"fromPath": "ClassLibrary/ClassLibrary.csproj",
"pattern": [
{
"regexp": "^(.+)\\((\\d+),(\\d+)\\): (error|warning) (.+): (.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"code": 5,
"message": 6
}
]
}
]
}
```
This ensures that the file is rooted to the correct path when there's not enough information in the error messages to extract a `fromPath`.
#### Mitigate regular expression denial of service (ReDos)
If a matcher exceeds a 1 second timeout when processing a line, retry up to two three times total.

View File

@@ -23,7 +23,7 @@ This feature is mainly intended for self hosted runner administrators.
- `ACTIONS_RUNNER_HOOK_JOB_STARTED`
- `ACTIONS_RUNNER_HOOK_JOB_COMPLETED`
You can set these variables to the **absolute** path of a a `.sh` or `.ps1` file.
You can set these variables to the **absolute** path of a `.sh` or `.ps1` file.
We will execute `pwsh` (fallback to `powershell`) or `bash` (fallback to `sh`) as appropriate.
- `.sh` files will execute with the args `-e {pathtofile}`

View File

@@ -7,8 +7,10 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
- For GitHub.com
- The runner needs to access `https://api.github.com` for downloading actions.
- The runner needs to access `https://codeload.github.com` for downloading actions tar.gz/zip.
- The runner needs to access `https://vstoken.actions.githubusercontent.com/_apis/.../` for requesting an access token.
- The runner needs to access `https://pipelines.actions.githubusercontent.com/_apis/.../` for receiving workflow jobs.
- The runner needs to access `https://results-receiver.actions.githubusercontent.com/.../` for reporting progress and uploading logs during a workflow job execution.
---
**NOTE:** for the full list of domains that are required to be in the firewall allow list refer to the [GitHub self-hosted runners requirements documentation](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#communication-between-self-hosted-runners-and-github).
@@ -16,12 +18,15 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
```
curl -v https://api.github.com/zen
curl -v https://codeload.github.com/_ping
curl -v https://vstoken.actions.githubusercontent.com/_apis/health
curl -v https://pipelines.actions.githubusercontent.com/_apis/health
curl -v https://results-receiver.actions.githubusercontent.com/health
```
- For GitHub Enterprise Server
- The runner needs to access `https://[hostname]/api/v3` for downloading actions.
- The runner needs to access `https://codeload.[hostname]/_ping` for downloading actions tar.gz/zip.
- The runner needs to access `https://[hostname]/_services/vstoken/_apis/.../` for requesting an access token.
- The runner needs to access `https://[hostname]/_services/pipelines/_apis/.../` for receiving workflow jobs.
@@ -29,6 +34,7 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
```
curl -v https://[hostname]/api/v3/zen
curl -v https://codeload.[hostname]/_ping
curl -v https://[hostname]/_services/vstoken/_apis/health
curl -v https://[hostname]/_services/pipelines/_apis/health
```
@@ -44,6 +50,10 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
- Ping api.github.com or myGHES.com using dotnet
- Make HTTP GET to https://api.github.com or https://myGHES.com/api/v3 using dotnet, check response headers contains `X-GitHub-Request-Id`
---
- DNS lookup for codeload.github.com or codeload.myGHES.com using dotnet
- Ping codeload.github.com or codeload.myGHES.com using dotnet
- Make HTTP GET to https://codeload.github.com/_ping or https://codeload.myGHES.com/_ping using dotnet, check response headers contains `X-GitHub-Request-Id`
---
- DNS lookup for vstoken.actions.githubusercontent.com using dotnet
- Ping vstoken.actions.githubusercontent.com using dotnet
- Make HTTP GET to https://vstoken.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/vstoken/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
@@ -52,6 +62,10 @@ Make sure the runner has access to actions service for GitHub.com or GitHub Ente
- Ping pipelines.actions.githubusercontent.com using dotnet
- Make HTTP GET to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
- Make HTTP POST to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
---
- DNS lookup for results-receiver.actions.githubusercontent.com using dotnet
- Ping results-receiver.actions.githubusercontent.com using dotnet
- Make HTTP GET to https://results-receiver.actions.githubusercontent.com/health using dotnet, check response headers contains `X-GitHub-Request-Id`
## How to fix the issue?

View File

@@ -42,6 +42,7 @@ If you are having trouble connecting, try these steps:
- https://api.github.com/
- https://vstoken.actions.githubusercontent.com/_apis/health
- https://pipelines.actions.githubusercontent.com/_apis/health
- https://results-receiver.actions.githubusercontent.com/health
- For GHES/GHAE
- https://myGHES.com/_services/vstoken/_apis/health
- https://myGHES.com/_services/pipelines/_apis/health

View File

@@ -4,9 +4,9 @@
Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server.
The runner carries its own copy of node.js executable under `<runner_root>/externals/node16/`.
The runner carries its own copies of node.js executables under `<runner_root>/externals/node20/` and `<runner_root>/externals/node24/`.
All javascript base Actions will get executed by the built-in `node` at `<runner_root>/externals/node16/`.
All javascript base Actions will get executed by the built-in `node` at either `<runner_root>/externals/node20/` or `<runner_root>/externals/node24/` depending on the version specified in the action's metadata.
> Not the `node` from `$PATH`

View File

@@ -1,6 +1,6 @@
# Contributions
We welcome contributions in the form of issues and pull requests. We view the contributions and the process as the same for github and external contributors.Please note the runner typically requires changes across the entire system and we aim for issues in the runner to be entirely self contained and fixable here. Therefore, we will primarily handle bug issues opened in this repo and we kindly request you to create all feature and enhancement requests on the [GitHub Feedback](https://github.com/community/community/discussions/categories/actions-and-packages) page.
We welcome contributions in the form of issues and pull requests. We view the contributions and the process as the same for github and external contributors. Please note the runner typically requires changes across the entire system and we aim for issues in the runner to be entirely self contained and fixable here. Therefore, we will primarily handle bug issues opened in this repo and we kindly request you to create all feature and enhancement requests on the [GitHub Feedback](https://github.com/community/community/discussions/categories/actions-and-packages) page.
> IMPORTANT: Building your own runner is critical for the dev inner loop process when contributing changes. However, only runners built and distributed by GitHub (releases) are supported in production. Be aware that workflows and orchestrations run service side with the runner being a remote process to run steps. For that reason, the service can pull the runner forward so customizations can be lost.

View File

@@ -4,16 +4,7 @@
## Supported Distributions and Versions
x64
- Red Hat Enterprise Linux 7
- CentOS 7
- Oracle Linux 7
- Fedora 29+
- Debian 9+
- Ubuntu 16.04+
- Linux Mint 18+
- openSUSE 15+
- SUSE Enterprise Linux (SLES) 12 SP2+
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#linux)."
## Install .Net Core 3.x Linux Dependencies

View File

@@ -4,7 +4,6 @@
## Supported Versions
- macOS High Sierra (10.13) and later versions
- x64 and arm64 (Apple Silicon)
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#macos)."
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)

View File

@@ -2,11 +2,6 @@
## Supported Versions
- Windows 7 64-bit
- Windows 8.1 64-bit
- Windows 10 64-bit
- Windows Server 2012 R2 64-bit
- Windows Server 2016 64-bit
- Windows Server 2019 64-bit
Please see "[Supported architectures and operating systems for self-hosted runners](https://docs.github.com/en/actions/reference/runners/self-hosted-runners#windows)."
## [More .NET Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore30)

View File

@@ -1,12 +1,12 @@
# Source: https://github.com/dotnet/dotnet-docker
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy as build
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy AS build
ARG TARGETOS
ARG TARGETARCH
ARG RUNNER_VERSION
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.4.0
ARG DOCKER_VERSION=24.0.6
ARG BUILDX_VERSION=0.11.2
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
ARG DOCKER_VERSION=28.3.3
ARG BUILDX_VERSION=0.27.0
RUN apt update -y && apt install curl unzip -y
@@ -32,16 +32,22 @@ RUN export RUNNER_ARCH=${TARGETARCH} \
"https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-${TARGETARCH}" \
&& chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0-jammy
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-jammy
ENV DEBIAN_FRONTEND=noninteractive
ENV RUNNER_MANUALLY_TRAP_SIG=1
ENV ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT=1
ENV ImageOS=ubuntu22
RUN apt-get update -y \
&& apt-get install -y --no-install-recommends \
sudo \
lsb-release \
# 'gpg-agent' and 'software-properties-common' are needed for the 'add-apt-repository' command that follows
RUN apt update -y \
&& apt install -y --no-install-recommends sudo lsb-release gpg-agent software-properties-common curl jq unzip \
&& rm -rf /var/lib/apt/lists/*
# Configure git-core/ppa based on guidance here: https://git-scm.com/download/linux
RUN add-apt-repository ppa:git-core/ppa \
&& apt update -y \
&& apt install -y git \
&& rm -rf /var/lib/apt/lists/*
RUN adduser --disabled-password --gecos "" --uid 1001 runner \
@@ -54,6 +60,7 @@ RUN adduser --disabled-password --gecos "" --uid 1001 runner \
WORKDIR /home/runner
COPY --chown=runner:docker --from=build /actions-runner .
COPY --from=build /usr/local/lib/docker/cli-plugins/docker-buildx /usr/local/lib/docker/cli-plugins/docker-buildx
RUN install -o root -g root -m 755 docker/* /usr/bin/ && rm -rf docker

View File

@@ -1,30 +1,32 @@
## What's Changed
* Trim whitespace in `./Misc/contentHash/dotnetRuntime/*` by @TingluoHuang in https://github.com/actions/runner/pull/2915
* Send os and arch during long poll by @luketomlinson in https://github.com/actions/runner/pull/2913
* Revert "Update default version to node20 (#2844)" by @takost in https://github.com/actions/runner/pull/2918
* Fix telemetry publish from JobServerQueue. by @TingluoHuang in https://github.com/actions/runner/pull/2919
* Use block blob instead of append blob by @yacaovsnc in https://github.com/actions/runner/pull/2924
* Provide detail info on untar failures. by @TingluoHuang in https://github.com/actions/runner/pull/2939
* Bump node.js to 20.8.1 by @TingluoHuang in https://github.com/actions/runner/pull/2945
* Update dotnet sdk to latest version @6.0.415 by @github-actions in https://github.com/actions/runner/pull/2929
* Fix typo in log strings by @rajbos in https://github.com/actions/runner/pull/2695
* feat: add support of arm64 arch runners in service creation script by @tuxity in https://github.com/actions/runner/pull/2606
* Add `buildx` to images by @ajschmidt8 in https://github.com/actions/runner/pull/2901
* Update Docker to v28.3.2 and Buildx to v0.26.1 by @github-actions[bot] in https://github.com/actions/runner/pull/3953
* Fix if statement structure in update script and variable reference by @salmanmkc in https://github.com/actions/runner/pull/3956
* Add V2 flow for runner deletion by @Samirat in https://github.com/actions/runner/pull/3954
* Node 20 -> Node 24 migration feature flagging, opt-in and opt-out environment variables by @salmanmkc in https://github.com/actions/runner/pull/3948
* Update Node20 and Node24 to latest by @djs-intel in https://github.com/actions/runner/pull/3972
* Redirect supported OS doc section to current public Docs location by @corycalahan in https://github.com/actions/runner/pull/3979
* Bump Microsoft.NET.Test.Sdk from 17.13.0 to 17.14.1 by @dependabot[bot] in https://github.com/actions/runner/pull/3975
* Bump Azure.Storage.Blobs from 12.24.0 to 12.25.0 by @dependabot[bot] in https://github.com/actions/runner/pull/3974
* Bump actions/download-artifact from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3973
* Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3982
## New Contributors
* @tuxity made their first contribution in https://github.com/actions/runner/pull/2606
* @Samirat made their first contribution in https://github.com/actions/runner/pull/3954
* @djs-intel made their first contribution in https://github.com/actions/runner/pull/3972
**Full Changelog**: https://github.com/actions/runner/compare/v2.310.2...v2.311.0
**Full Changelog**: https://github.com/actions/runner/compare/v2.327.1...v2.328.0
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
_Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet.
To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository.
See https://docs.github.com/en/enterprise-cloud@latest/actions/hosting-your-own-runners/adding-self-hosted-runners_
## Windows x64
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
The following snipped needs to be run on `powershell`:
``` powershell
```powershell
# Create a folder under the drive root
mkdir \actions-runner ; cd \actions-runner
# Download the latest runner package
@@ -34,13 +36,13 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem ;
[System.IO.Compression.ZipFile]::ExtractToDirectory("$PWD\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
```
## [Pre-release] Windows arm64
**Warning:** Windows arm64 runners are currently in preview status and use [unofficial versions of nodejs](https://unofficial-builds.nodejs.org/). They are not intended for production workflows.
## Windows arm64
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
The following snipped needs to be run on `powershell`:
``` powershell
```powershell
# Create a folder under the drive root
mkdir \actions-runner ; cd \actions-runner
# Download the latest runner package
@@ -52,7 +54,7 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem ;
## OSX x64
``` bash
```bash
# Create a folder
mkdir actions-runner && cd actions-runner
# Download the latest runner package
@@ -63,7 +65,7 @@ tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
## OSX arm64 (Apple silicon)
``` bash
```bash
# Create a folder
mkdir actions-runner && cd actions-runner
# Download the latest runner package
@@ -74,7 +76,7 @@ tar xzf ./actions-runner-osx-arm64-<RUNNER_VERSION>.tar.gz
## Linux x64
``` bash
```bash
# Create a folder
mkdir actions-runner && cd actions-runner
# Download the latest runner package
@@ -85,7 +87,7 @@ tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
## Linux arm64
``` bash
```bash
# Create a folder
mkdir actions-runner && cd actions-runner
# Download the latest runner package
@@ -96,7 +98,7 @@ tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
## Linux arm
``` bash
```bash
# Create a folder
mkdir actions-runner && cd actions-runner
# Download the latest runner package
@@ -106,6 +108,7 @@ tar xzf ./actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
```
## Using your self hosted runner
For additional details about configuring, running, or shutting down the runner please check out our [product docs.](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/adding-self-hosted-runners)
## SHA-256 Checksums
@@ -119,27 +122,3 @@ The SHA-256 checksums for the packages included in this build are shown below:
- actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz <!-- BEGIN SHA linux-x64 --><LINUX_X64_SHA><!-- END SHA linux-x64 -->
- actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz <!-- BEGIN SHA linux-arm64 --><LINUX_ARM64_SHA><!-- END SHA linux-arm64 -->
- actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz <!-- BEGIN SHA linux-arm --><LINUX_ARM_SHA><!-- END SHA linux-arm -->
- actions-runner-win-x64-<RUNNER_VERSION>-noexternals.zip <!-- BEGIN SHA win-x64_noexternals --><WIN_X64_SHA_NOEXTERNALS><!-- END SHA win-x64_noexternals -->
- actions-runner-win-arm64-<RUNNER_VERSION>-noexternals.zip <!-- BEGIN SHA win-arm64_noexternals --><WIN_ARM64_SHA_NOEXTERNALS><!-- END SHA win-arm64_noexternals -->
- actions-runner-osx-x64-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA osx-x64_noexternals --><OSX_X64_SHA_NOEXTERNALS><!-- END SHA osx-x64_noexternals -->
- actions-runner-osx-arm64-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA osx-arm64_noexternals --><OSX_ARM64_SHA_NOEXTERNALS><!-- END SHA osx-arm64_noexternals -->
- actions-runner-linux-x64-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA linux-x64_noexternals --><LINUX_X64_SHA_NOEXTERNALS><!-- END SHA linux-x64_noexternals -->
- actions-runner-linux-arm64-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA linux-arm64_noexternals --><LINUX_ARM64_SHA_NOEXTERNALS><!-- END SHA linux-arm64_noexternals -->
- actions-runner-linux-arm-<RUNNER_VERSION>-noexternals.tar.gz <!-- BEGIN SHA linux-arm_noexternals --><LINUX_ARM_SHA_NOEXTERNALS><!-- END SHA linux-arm_noexternals -->
- actions-runner-win-x64-<RUNNER_VERSION>-noruntime.zip <!-- BEGIN SHA win-x64_noruntime --><WIN_X64_SHA_NORUNTIME><!-- END SHA win-x64_noruntime -->
- actions-runner-win-arm64-<RUNNER_VERSION>-noruntime.zip <!-- BEGIN SHA win-arm64_noruntime --><WIN_ARM64_SHA_NORUNTIME><!-- END SHA win-arm64_noruntime -->
- actions-runner-osx-x64-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA osx-x64_noruntime --><OSX_X64_SHA_NORUNTIME><!-- END SHA osx-x64_noruntime -->
- actions-runner-osx-arm64-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA osx-arm64_noruntime --><OSX_ARM64_SHA_NORUNTIME><!-- END SHA osx-arm64_noruntime -->
- actions-runner-linux-x64-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA linux-x64_noruntime --><LINUX_X64_SHA_NORUNTIME><!-- END SHA linux-x64_noruntime -->
- actions-runner-linux-arm64-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA linux-arm64_noruntime --><LINUX_ARM64_SHA_NORUNTIME><!-- END SHA linux-arm64_noruntime -->
- actions-runner-linux-arm-<RUNNER_VERSION>-noruntime.tar.gz <!-- BEGIN SHA linux-arm_noruntime --><LINUX_ARM_SHA_NORUNTIME><!-- END SHA linux-arm_noruntime -->
- actions-runner-win-x64-<RUNNER_VERSION>-noruntime-noexternals.zip <!-- BEGIN SHA win-x64_noruntime_noexternals --><WIN_X64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA win-x64_noruntime_noexternals -->
- actions-runner-win-arm64-<RUNNER_VERSION>-noruntime-noexternals.zip <!-- BEGIN SHA win-arm64_noruntime_noexternals --><WIN_ARM64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA win-arm64_noruntime_noexternals -->
- actions-runner-osx-x64-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA osx-x64_noruntime_noexternals --><OSX_X64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA osx-x64_noruntime_noexternals -->
- actions-runner-osx-arm64-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA osx-arm64_noruntime_noexternals --><OSX_ARM64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA osx-arm64_noruntime_noexternals -->
- actions-runner-linux-x64-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA linux-x64_noruntime_noexternals --><LINUX_X64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA linux-x64_noruntime_noexternals -->
- actions-runner-linux-arm64-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA linux-arm64_noruntime_noexternals --><LINUX_ARM64_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA linux-arm64_noruntime_noexternals -->
- actions-runner-linux-arm-<RUNNER_VERSION>-noruntime-noexternals.tar.gz <!-- BEGIN SHA linux-arm_noruntime_noexternals --><LINUX_ARM_SHA_NORUNTIME_NOEXTERNALS><!-- END SHA linux-arm_noruntime_noexternals -->

View File

@@ -57,4 +57,13 @@
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup>
<!-- Enable NuGet package auditing -->
<NuGetAudit>true</NuGetAudit>
<!-- Audit direct and transitive packages -->
<NuGetAuditMode>all</NuGetAuditMode>
<!-- Report low, moderate, high and critical advisories -->
<NuGetAuditLevel>moderate</NuGetAuditLevel>
</PropertyGroup>
</Project>

View File

@@ -1 +0,0 @@
531b31914e525ecb12cc5526415bc70a112ebc818f877347af1a231011f539c5

View File

@@ -1 +0,0 @@
722dd5fa5ecc207fcccf67f6e502d689f2119d8117beff2041618fba17dc66a4

View File

@@ -1 +0,0 @@
8ca75c76e15ab9dc7fe49a66c5c74e171e7fabd5d26546fda8931bd11bff30f9

View File

@@ -1 +0,0 @@
70496eb1c99b39b3373b5088c95a35ebbaac1098e6c47c8aab94771f3ffbf501

View File

@@ -1 +0,0 @@
4f8d48727d535daabcaec814e0dafb271c10625366c78e7e022ca7477a73023f

View File

@@ -1 +0,0 @@
d54d7428f2b9200a0030365a6a4e174e30a1b29b922f8254dffb2924bd09549d

View File

@@ -1 +0,0 @@
eaa939c45307f46b7003902255b3a2a09287215d710984107667e03ac493eb26

View File

@@ -1 +0,0 @@
4bf3e1af0d482af1b2eaf9f08250248a8c1aea8ec20a3c5be116d58cdd930009

View File

@@ -1 +0,0 @@
ec1719a8cb4d8687328aa64f4aa7c4e3498a715d8939117874782e3e6e63a14b

View File

@@ -1 +0,0 @@
50538de29f173bb73f708c4ed2c8328a62b8795829b97b2a6cb57197e2305287

View File

@@ -1 +0,0 @@
a0a96cbb7593643b69e669bf14d7b29b7f27800b3a00bb3305aebe041456c701

View File

@@ -1 +0,0 @@
6255b22692779467047ecebd60ad46984866d75cdfe10421d593a7b51d620b09

View File

@@ -1 +0,0 @@
6ff1abd055dc35bfbf06f75c2f08908f660346f66ad1d8f81c910068e9ba029d

View File

@@ -1 +0,0 @@
433a6d748742d12abd20dc2a79b62ac3d9718ae47ef26f8e84dc8c180eea3659

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,6 @@
"pack": "ncc build -o ../../layoutbin/hashFiles",
"all": "npm run format && npm run lint && npm run build && npm run pack",
"prepare": "cd ../../../../ && husky install"
},
"repository": {
"type": "git",
@@ -37,15 +36,15 @@
},
"devDependencies": {
"@types/node": "^20.6.2",
"@typescript-eslint/eslint-plugin": "^6.7.2",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.7.2",
"@vercel/ncc": "^0.38.0",
"@vercel/ncc": "^0.38.3",
"eslint": "^8.47.0",
"eslint-plugin-github": "^4.10.0",
"eslint-plugin-github": "^4.10.2",
"eslint-plugin-prettier": "^5.0.0",
"husky": "^9.1.7",
"lint-staged": "^15.5.0",
"prettier": "^3.0.3",
"typescript": "^5.2.2",
"husky": "^8.0.3",
"lint-staged": "^14.0.0"
"typescript": "^5.2.2"
}
}
}

View File

@@ -3,12 +3,11 @@ PACKAGERUNTIME=$1
PRECACHE=$2
NODE_URL=https://nodejs.org/dist
UNOFFICIAL_NODE_URL=https://unofficial-builds.nodejs.org/download/release
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
NODE16_VERSION="16.20.2"
NODE20_VERSION="20.8.1"
# used only for win-arm64, remove node16 unofficial version when official version is available
NODE16_UNOFFICIAL_VERSION="16.20.0"
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
NODE20_VERSION="20.19.4"
NODE24_VERSION="24.5.0"
get_abs_path() {
# exploits the fact that pwd will print abs path when no args
@@ -63,17 +62,16 @@ function acquireExternalTool() {
echo "Curl version: $CURL_VERSION"
# curl -f Fail silently (no output at all) on HTTP errors (H)
# -k Allow connections to SSL sites without certs (H)
# -S Show error. With -s, make curl show errors when they occur
# -L Follow redirects (H)
# -o FILE Write to FILE instead of stdout
# --retry 3 Retries transient errors 3 times (timeouts, 5xx)
if [[ "$(printf '%s\n' "7.71.0" "$CURL_VERSION" | sort -V | head -n1)" != "7.71.0" ]]; then
# Curl version is less than or equal to 7.71.0, skipping retry-all-errors flag
curl -fkSL --retry 3 -o "$partial_target" "$download_source" 2>"${download_target}_download.log" || checkRC 'curl'
curl -fSL --retry 3 -o "$partial_target" "$download_source" 2>"${download_target}_download.log" || checkRC 'curl'
else
# Curl version is greater than 7.71.0, running curl with --retry-all-errors flag
curl -fkSL --retry 3 --retry-all-errors -o "$partial_target" "$download_source" 2>"${download_target}_download.log" || checkRC 'curl'
curl -fSL --retry 3 --retry-all-errors -o "$partial_target" "$download_source" 2>"${download_target}_download.log" || checkRC 'curl'
fi
# Move the partial file to the download target.
@@ -140,10 +138,10 @@ function acquireExternalTool() {
# Download the external tools only for Windows.
if [[ "$PACKAGERUNTIME" == "win-x64" || "$PACKAGERUNTIME" == "win-x86" ]]; then
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.exe" node24/bin
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.lib" node24/bin
if [[ "$PRECACHE" != "" ]]; then
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
fi
@@ -152,10 +150,10 @@ fi
# Download the external tools only for Windows.
if [[ "$PACKAGERUNTIME" == "win-arm64" ]]; then
# todo: replace these with official release when available
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_UNOFFICIAL_VERSION}/$PACKAGERUNTIME/node.exe" node16/bin
acquireExternalTool "$UNOFFICIAL_NODE_URL/v${NODE16_UNOFFICIAL_VERSION}/$PACKAGERUNTIME/node.lib" node16/bin
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.exe" node20/bin
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/$PACKAGERUNTIME/node.lib" node20/bin
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.exe" node24/bin
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/$PACKAGERUNTIME/node.lib" node24/bin
if [[ "$PRECACHE" != "" ]]; then
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
fi
@@ -163,30 +161,29 @@ fi
# Download the external tools only for OSX.
if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-x64.tar.gz" node16 fix_nested_dir
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-x64.tar.gz" node20 fix_nested_dir
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-darwin-x64.tar.gz" node24 fix_nested_dir
fi
if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then
# node.js v12 doesn't support macOS on arm64.
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-arm64.tar.gz" node16 fix_nested_dir
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-arm64.tar.gz" node20 fix_nested_dir
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-darwin-arm64.tar.gz" node24 fix_nested_dir
fi
# Download the external tools for Linux PACKAGERUNTIMEs.
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-x64.tar.gz" node16 fix_nested_dir
acquireExternalTool "$NODE_ALPINE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-alpine-x64.tar.gz" node16_alpine
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-x64.tar.gz" node20 fix_nested_dir
acquireExternalTool "$NODE_ALPINE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-alpine-x64.tar.gz" node20_alpine
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-linux-x64.tar.gz" node24 fix_nested_dir
acquireExternalTool "$NODE_ALPINE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-alpine-x64.tar.gz" node24_alpine
fi
if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-arm64.tar.gz" node16 fix_nested_dir
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-arm64.tar.gz" node20 fix_nested_dir
acquireExternalTool "$NODE_URL/v${NODE24_VERSION}/node-v${NODE24_VERSION}-linux-arm64.tar.gz" node24 fix_nested_dir
fi
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then
acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-armv7l.tar.gz" node16 fix_nested_dir
acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-armv7l.tar.gz" node20 fix_nested_dir
fi

View File

@@ -114,6 +114,11 @@ var runService = function () {
);
stopping = true;
}
} else if (code === 5) {
console.log(
"Runner listener exit with Session Conflict error, stop the service, no retry needed."
);
stopping = true;
} else {
var messagePrefix = "Runner listener exit with undefined return code";
unknownFailureRetryCount++;

View File

@@ -3299,7 +3299,7 @@ function expand(str, isTop) {
var isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) {
// {a},b}
if (m.post.match(/,.*\}/)) {
if (m.post.match(/,(?!,).*\}/)) {
str = m.pre + '{' + m.body + escClose + m.post;
return expand(str);
}

View File

@@ -131,7 +131,32 @@ then
command -v dnf
if [ $? -eq 0 ]
then
dnf install -y lttng-ust openssl-libs krb5-libs zlib libicu
dnf_with_fallbacks() {
dnf install -y $1
fail=$?
if [ $fail -eq 0 ]
then
return 0
fi
if [ $fail -ne 0 ]
then
shift
if [ -n "$1" ]
then
dnf_with_fallbacks "$@"
fi
fi
}
dnf_with_fallbacks lttng-ust1 lttng-ust0
if [ $? -ne 0 ]
then
echo "'dnf' failed with exit code '$?'"
print_errormessage
exit 1
fi
dnf install -y openssl-libs krb5-libs zlib libicu
if [ $? -ne 0 ]
then
echo "'dnf' failed with exit code '$?'"

View File

@@ -10,7 +10,7 @@ if [ -f ".path" ]; then
echo ".path=${PATH}"
fi
nodever=${GITHUB_ACTIONS_RUNNER_FORCED_NODE_VERSION:-node16}
nodever="node20"
# insert anything to setup env when running as a service
# run the host process which keep the listener alive

View File

@@ -123,7 +123,7 @@ fi
# fix upgrade issue with macOS when running as a service
attemptedtargetedfix=0
currentplatform=$(uname | awk '{print tolower($0)}')
if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
if [[ "$currentplatform" == 'darwin' && $restartinteractiverunner -eq 0 ]]; then
# We needed a fix for https://github.com/actions/runner/issues/743
# We will recreate the ./externals/nodeXY/bin/node of the past runner version that launched the runnerlistener service
# Otherwise mac gatekeeper kills the processes we spawn on creation as we are running a process with no backing file
@@ -135,12 +135,23 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
then
# inspect the open file handles to find the node process
# we can't actually inspect the process using ps because it uses relative paths and doesn't follow symlinks
nodever="node16"
# Try finding node24 first, then fallback to earlier versions if needed
nodever="node24"
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node20
then
nodever="node12"
nodever="node20"
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node16
then
nodever="node16"
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
if [[ $? -ne 0 || -z "$path" ]] # Fallback if RunnerService.js was started with node12
then
nodever="node12"
path=$(lsof -a -g "$procgroup" -F n | grep $nodever/bin/node | grep externals | tail -1 | cut -c2-)
fi
fi
fi
if [[ $? -eq 0 && -n "$path" ]]
then
@@ -178,6 +189,19 @@ if [[ "$currentplatform" == 'darwin' && restartinteractiverunner -eq 0 ]]; then
fi
fi
# update runsvc.sh
if [ -f "$rootfolder/runsvc.sh" ]
then
date "+[%F %T-%4N] Update runsvc.sh" >> "$logfile" 2>&1
cat "$rootfolder/bin/runsvc.sh" > "$rootfolder/runsvc.sh"
if [ $? -ne 0 ]
then
date "+[%F %T-%4N] Can't update $rootfolder/runsvc.sh using $rootfolder/bin/runsvc.sh" >> "$logfile" 2>&1
mv -fv "$logfile" "$logfile.failed"
exit 1
fi
fi
date "+[%F %T-%4N] Update succeed" >> "$logfile"
touch update.finished

View File

@@ -49,5 +49,10 @@ if %ERRORLEVEL% EQU 4 (
exit /b 1
)
if %ERRORLEVEL% EQU 5 (
echo "Runner listener exit with Session Conflict error, stop the service, no retry needed."
exit /b 0
)
echo "Exiting after unknown error code: %ERRORLEVEL%"
exit /b 0

View File

@@ -70,6 +70,9 @@ elif [[ $returnCode == 4 ]]; then
"$DIR"/safe_sleep.sh 1
done
exit 2
elif [[ $returnCode == 5 ]]; then
echo "Runner listener exit with Session Conflict error, stop the service, no retry needed."
exit 0
else
echo "Exiting with unknown error code: ${returnCode}"
exit 0

View File

@@ -38,7 +38,7 @@ runWithManualTrap() {
cp -f "$DIR"/run-helper.sh.template "$DIR"/run-helper.sh
"$DIR"/run-helper.sh $* &
PID=$!
wait -f $PID
wait $PID
returnCode=$?
if [[ $returnCode -eq 2 ]]; then
echo "Restarting runner..."
@@ -84,4 +84,4 @@ if [[ -z "$RUNNER_MANUALLY_TRAP_SIG" ]]; then
run $*
else
runWithManualTrap $*
fi
fi

View File

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

View File

@@ -1,57 +0,0 @@
actions.runner.plist.template
actions.runner.service.template
checkScripts/downloadCert.js
checkScripts/makeWebRequest.js
darwin.svc.sh.template
hashFiles/index.js
installdependencies.sh
macos-run-invoker.js
Microsoft.IdentityModel.Logging.dll
Microsoft.IdentityModel.Tokens.dll
Minimatch.dll
Newtonsoft.Json.Bson.dll
Newtonsoft.Json.dll
Runner.Common.deps.json
Runner.Common.dll
Runner.Common.pdb
Runner.Listener
Runner.Listener.deps.json
Runner.Listener.dll
Runner.Listener.exe
Runner.Listener.pdb
Runner.Listener.runtimeconfig.json
Runner.PluginHost
Runner.PluginHost.deps.json
Runner.PluginHost.dll
Runner.PluginHost.exe
Runner.PluginHost.pdb
Runner.PluginHost.runtimeconfig.json
Runner.Plugins.deps.json
Runner.Plugins.dll
Runner.Plugins.pdb
Runner.Sdk.deps.json
Runner.Sdk.dll
Runner.Sdk.pdb
Runner.Worker
Runner.Worker.deps.json
Runner.Worker.dll
Runner.Worker.exe
Runner.Worker.pdb
Runner.Worker.runtimeconfig.json
RunnerService.exe
RunnerService.exe.config
RunnerService.js
RunnerService.pdb
runsvc.sh
Sdk.deps.json
Sdk.dll
Sdk.pdb
System.IdentityModel.Tokens.Jwt.dll
System.Net.Http.Formatting.dll
System.Security.Cryptography.Pkcs.dll
System.Security.Cryptography.ProtectedData.dll
System.ServiceProcess.ServiceController.dll
systemd.svc.sh.template
update.cmd.template
update.sh.template
YamlDotNet.dll

View File

@@ -1,270 +0,0 @@
api-ms-win-core-console-l1-1-0.dll
api-ms-win-core-console-l1-2-0.dll
api-ms-win-core-datetime-l1-1-0.dll
api-ms-win-core-debug-l1-1-0.dll
api-ms-win-core-errorhandling-l1-1-0.dll
api-ms-win-core-fibers-l1-1-0.dll
api-ms-win-core-file-l1-1-0.dll
api-ms-win-core-file-l1-2-0.dll
api-ms-win-core-file-l2-1-0.dll
api-ms-win-core-handle-l1-1-0.dll
api-ms-win-core-heap-l1-1-0.dll
api-ms-win-core-interlocked-l1-1-0.dll
api-ms-win-core-libraryloader-l1-1-0.dll
api-ms-win-core-localization-l1-2-0.dll
api-ms-win-core-memory-l1-1-0.dll
api-ms-win-core-namedpipe-l1-1-0.dll
api-ms-win-core-processenvironment-l1-1-0.dll
api-ms-win-core-processthreads-l1-1-0.dll
api-ms-win-core-processthreads-l1-1-1.dll
api-ms-win-core-profile-l1-1-0.dll
api-ms-win-core-rtlsupport-l1-1-0.dll
api-ms-win-core-string-l1-1-0.dll
api-ms-win-core-synch-l1-1-0.dll
api-ms-win-core-synch-l1-2-0.dll
api-ms-win-core-sysinfo-l1-1-0.dll
api-ms-win-core-timezone-l1-1-0.dll
api-ms-win-core-util-l1-1-0.dll
api-ms-win-crt-conio-l1-1-0.dll
api-ms-win-crt-convert-l1-1-0.dll
api-ms-win-crt-environment-l1-1-0.dll
api-ms-win-crt-filesystem-l1-1-0.dll
api-ms-win-crt-heap-l1-1-0.dll
api-ms-win-crt-locale-l1-1-0.dll
api-ms-win-crt-math-l1-1-0.dll
api-ms-win-crt-multibyte-l1-1-0.dll
api-ms-win-crt-private-l1-1-0.dll
api-ms-win-crt-process-l1-1-0.dll
api-ms-win-crt-runtime-l1-1-0.dll
api-ms-win-crt-stdio-l1-1-0.dll
api-ms-win-crt-string-l1-1-0.dll
api-ms-win-crt-time-l1-1-0.dll
api-ms-win-crt-utility-l1-1-0.dll
clrcompression.dll
clretwrc.dll
clrjit.dll
coreclr.dll
createdump
createdump.exe
dbgshim.dll
hostfxr.dll
hostpolicy.dll
libclrjit.dylib
libclrjit.so
libcoreclr.dylib
libcoreclr.so
libcoreclrtraceptprovider.so
libdbgshim.dylib
libdbgshim.so
libhostfxr.dylib
libhostfxr.so
libhostpolicy.dylib
libhostpolicy.so
libmscordaccore.dylib
libmscordaccore.so
libmscordbi.dylib
libmscordbi.so
Microsoft.CSharp.dll
Microsoft.DiaSymReader.Native.amd64.dll
Microsoft.DiaSymReader.Native.arm64.dll
Microsoft.VisualBasic.Core.dll
Microsoft.VisualBasic.dll
Microsoft.Win32.Primitives.dll
Microsoft.Win32.Registry.dll
mscordaccore.dll
mscordaccore_amd64_amd64_6.0.522.21309.dll
mscordaccore_arm64_arm64_6.0.522.21309.dll
mscordaccore_amd64_amd64_6.0.1322.58009.dll
mscordaccore_amd64_amd64_6.0.2023.32017.dll
mscordaccore_amd64_amd64_6.0.2223.42425.dll
mscordaccore_amd64_amd64_6.0.2323.48002.dll
mscordbi.dll
mscorlib.dll
mscorrc.debug.dll
mscorrc.dll
msquic.dll
netstandard.dll
SOS_README.md
System.AppContext.dll
System.Buffers.dll
System.Collections.Concurrent.dll
System.Collections.dll
System.Collections.Immutable.dll
System.Collections.NonGeneric.dll
System.Collections.Specialized.dll
System.ComponentModel.Annotations.dll
System.ComponentModel.DataAnnotations.dll
System.ComponentModel.dll
System.ComponentModel.EventBasedAsync.dll
System.ComponentModel.Primitives.dll
System.ComponentModel.TypeConverter.dll
System.Configuration.dll
System.Console.dll
System.Core.dll
System.Data.Common.dll
System.Data.DataSetExtensions.dll
System.Data.dll
System.Diagnostics.Contracts.dll
System.Diagnostics.Debug.dll
System.Diagnostics.DiagnosticSource.dll
System.Diagnostics.FileVersionInfo.dll
System.Diagnostics.Process.dll
System.Diagnostics.StackTrace.dll
System.Diagnostics.TextWriterTraceListener.dll
System.Diagnostics.Tools.dll
System.Diagnostics.TraceSource.dll
System.Diagnostics.Tracing.dll
System.dll
System.Drawing.dll
System.Drawing.Primitives.dll
System.Dynamic.Runtime.dll
System.Formats.Asn1.dll
System.Globalization.Calendars.dll
System.Globalization.dll
System.Globalization.Extensions.dll
System.Globalization.Native.dylib
System.Globalization.Native.so
System.IO.Compression.Brotli.dll
System.IO.Compression.dll
System.IO.Compression.FileSystem.dll
System.IO.Compression.Native.a
System.IO.Compression.Native.dll
System.IO.Compression.Native.dylib
System.IO.Compression.Native.so
System.IO.Compression.ZipFile.dll
System.IO.dll
System.IO.FileSystem.AccessControl.dll
System.IO.FileSystem.dll
System.IO.FileSystem.DriveInfo.dll
System.IO.FileSystem.Primitives.dll
System.IO.FileSystem.Watcher.dll
System.IO.IsolatedStorage.dll
System.IO.MemoryMappedFiles.dll
System.IO.Pipes.AccessControl.dll
System.IO.Pipes.dll
System.IO.UnmanagedMemoryStream.dll
System.Linq.dll
System.Linq.Expressions.dll
System.Linq.Parallel.dll
System.Linq.Queryable.dll
System.Memory.dll
System.Native.a
System.Native.dylib
System.Native.so
System.Net.dll
System.Net.Http.dll
System.Net.Http.Json.dll
System.Net.Http.Native.a
System.Net.Http.Native.dylib
System.Net.Http.Native.so
System.Net.HttpListener.dll
System.Net.Mail.dll
System.Net.NameResolution.dll
System.Net.NetworkInformation.dll
System.Net.Ping.dll
System.Net.Primitives.dll
System.Net.Quic.dll
System.Net.Requests.dll
System.Net.Security.dll
System.Net.Security.Native.a
System.Net.Security.Native.dylib
System.Net.Security.Native.so
System.Net.ServicePoint.dll
System.Net.Sockets.dll
System.Net.WebClient.dll
System.Net.WebHeaderCollection.dll
System.Net.WebProxy.dll
System.Net.WebSockets.Client.dll
System.Net.WebSockets.dll
System.Numerics.dll
System.Numerics.Vectors.dll
System.ObjectModel.dll
System.Private.CoreLib.dll
System.Private.DataContractSerialization.dll
System.Private.Uri.dll
System.Private.Xml.dll
System.Private.Xml.Linq.dll
System.Reflection.DispatchProxy.dll
System.Reflection.dll
System.Reflection.Emit.dll
System.Reflection.Emit.ILGeneration.dll
System.Reflection.Emit.Lightweight.dll
System.Reflection.Extensions.dll
System.Reflection.Metadata.dll
System.Reflection.Primitives.dll
System.Reflection.TypeExtensions.dll
System.Resources.Reader.dll
System.Resources.ResourceManager.dll
System.Resources.Writer.dll
System.Runtime.CompilerServices.Unsafe.dll
System.Runtime.CompilerServices.VisualC.dll
System.Runtime.dll
System.Runtime.Extensions.dll
System.Runtime.Handles.dll
System.Runtime.InteropServices.dll
System.Runtime.InteropServices.RuntimeInformation.dll
System.Runtime.InteropServices.WindowsRuntime.dll
System.Runtime.Intrinsics.dll
System.Runtime.Loader.dll
System.Runtime.Numerics.dll
System.Runtime.Serialization.dll
System.Runtime.Serialization.Formatters.dll
System.Runtime.Serialization.Json.dll
System.Runtime.Serialization.Primitives.dll
System.Runtime.Serialization.Xml.dll
System.Runtime.WindowsRuntime.dll
System.Runtime.WindowsRuntime.UI.Xaml.dll
System.Security.AccessControl.dll
System.Security.Claims.dll
System.Security.Cryptography.Algorithms.dll
System.Security.Cryptography.Cng.dll
System.Security.Cryptography.Csp.dll
System.Security.Cryptography.Encoding.dll
System.Security.Cryptography.Native.Apple.a
System.Security.Cryptography.Native.Apple.dylib
System.Security.Cryptography.Native.OpenSsl.a
System.Security.Cryptography.Native.OpenSsl.dylib
System.Security.Cryptography.Native.OpenSsl.so
System.Security.Cryptography.OpenSsl.dll
System.Security.Cryptography.Primitives.dll
System.Security.Cryptography.X509Certificates.dll
System.Security.Cryptography.XCertificates.dll
System.Security.dll
System.Security.Principal.dll
System.Security.Principal.Windows.dll
System.Security.SecureString.dll
System.ServiceModel.Web.dll
System.ServiceProcess.dll
System.Text.Encoding.CodePages.dll
System.Text.Encoding.dll
System.Text.Encoding.Extensions.dll
System.Text.Encodings.Web.dll
System.Text.Json.dll
System.Text.RegularExpressions.dll
System.Threading.Channels.dll
System.Threading.dll
System.Threading.Overlapped.dll
System.Threading.Tasks.Dataflow.dll
System.Threading.Tasks.dll
System.Threading.Tasks.Extensions.dll
System.Threading.Tasks.Parallel.dll
System.Threading.Thread.dll
System.Threading.ThreadPool.dll
System.Threading.Timer.dll
System.Transactions.dll
System.Transactions.Local.dll
System.ValueTuple.dll
System.Web.dll
System.Web.HttpUtility.dll
System.Windows.dll
System.Xml.dll
System.Xml.Linq.dll
System.Xml.ReaderWriter.dll
System.Xml.Serialization.dll
System.Xml.XDocument.dll
System.Xml.XmlDocument.dll
System.Xml.XmlSerializer.dll
System.Xml.XPath.dll
System.Xml.XPath.XDocument.dll
ucrtbase.dll
WindowsBase.dll

View File

@@ -1,24 +0,0 @@
[
{
"HashValue": "<NO_RUNTIME_EXTERNALS_HASH>",
"DownloadUrl": "https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-<RUNNER_PLATFORM>-<RUNNER_VERSION>-noruntime-noexternals.tar.gz",
"TrimmedContents": {
"dotnetRuntime": "<RUNTIME_HASH>",
"externals": "<EXTERNALS_HASH>"
}
},
{
"HashValue": "<NO_RUNTIME_HASH>",
"DownloadUrl": "https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-<RUNNER_PLATFORM>-<RUNNER_VERSION>-noruntime.tar.gz",
"TrimmedContents": {
"dotnetRuntime": "<RUNTIME_HASH>"
}
},
{
"HashValue": "<NO_EXTERNALS_HASH>",
"DownloadUrl": "https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-<RUNNER_PLATFORM>-<RUNNER_VERSION>-noexternals.tar.gz",
"TrimmedContents": {
"externals": "<EXTERNALS_HASH>"
}
}
]

View File

@@ -1,24 +0,0 @@
[
{
"HashValue": "<NO_RUNTIME_EXTERNALS_HASH>",
"DownloadUrl": "https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-<RUNNER_PLATFORM>-<RUNNER_VERSION>-noruntime-noexternals.zip",
"TrimmedContents": {
"dotnetRuntime": "<RUNTIME_HASH>",
"externals": "<EXTERNALS_HASH>"
}
},
{
"HashValue": "<NO_RUNTIME_HASH>",
"DownloadUrl": "https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-<RUNNER_PLATFORM>-<RUNNER_VERSION>-noruntime.zip",
"TrimmedContents": {
"dotnetRuntime": "<RUNTIME_HASH>"
}
},
{
"HashValue": "<NO_EXTERNALS_HASH>",
"DownloadUrl": "https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-<RUNNER_PLATFORM>-<RUNNER_VERSION>-noexternals.zip",
"TrimmedContents": {
"externals": "<EXTERNALS_HASH>"
}
}
]

View File

@@ -20,12 +20,12 @@ namespace GitHub.Runner.Common
{
private bool _hasConnection;
private VssConnection _connection;
private TaskAgentHttpClient _taskAgentClient;
private ActionsRunServerHttpClient _actionsRunServerClient;
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
{
_connection = await EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
_taskAgentClient = _connection.GetClient<TaskAgentHttpClient>();
_actionsRunServerClient = _connection.GetClient<ActionsRunServerHttpClient>();
_hasConnection = true;
}
@@ -42,7 +42,7 @@ namespace GitHub.Runner.Common
CheckConnection();
var jobMessage = RetryRequest<AgentJobRequestMessage>(async () =>
{
return await _taskAgentClient.GetJobMessageAsync(id, cancellationToken);
return await _actionsRunServerClient.GetJobMessageAsync(id, cancellationToken);
}, cancellationToken);
return jobMessage;

View File

@@ -0,0 +1,13 @@
using System;
namespace GitHub.Runner.Common
{
public class AuthMigrationEventArgs : EventArgs
{
public AuthMigrationEventArgs(string trace)
{
Trace = trace;
}
public string Trace { get; private set; }
}
}

View File

@@ -7,6 +7,7 @@ using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
using Sdk.RSWebApi.Contracts;
using Sdk.WebApi.WebApi.RawClient;
@@ -17,7 +18,16 @@ namespace GitHub.Runner.Common
{
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken token, TaskAgentStatus status, string version, string os, string architecture);
Task<TaskAgentSession> CreateSessionAsync(TaskAgentSession session, CancellationToken cancellationToken);
Task DeleteSessionAsync(CancellationToken cancellationToken);
Task<TaskAgentMessage> GetRunnerMessageAsync(Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate, CancellationToken token);
Task AcknowledgeRunnerRequestAsync(string runnerRequestId, Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, CancellationToken token);
Task UpdateConnectionIfNeeded(Uri serverUri, VssCredentials credentials);
Task ForceRefreshConnection(VssCredentials credentials);
}
public sealed class BrokerServer : RunnerService, IBrokerServer
@@ -29,6 +39,7 @@ namespace GitHub.Runner.Common
public async Task ConnectAsync(Uri serverUri, VssCredentials credentials)
{
Trace.Entering();
_brokerUri = serverUri;
_connection = VssUtil.CreateRawConnection(serverUri, credentials);
@@ -44,13 +55,65 @@ namespace GitHub.Runner.Common
}
}
public Task<TaskAgentMessage> GetRunnerMessageAsync(CancellationToken cancellationToken, TaskAgentStatus status, string version, string os, string architecture)
public async Task<TaskAgentSession> CreateSessionAsync(TaskAgentSession session, CancellationToken cancellationToken)
{
CheckConnection();
var jobMessage = RetryRequest<TaskAgentMessage>(
async () => await _brokerHttpClient.GetRunnerMessageAsync(version, status, os, architecture, cancellationToken), cancellationToken);
var jobMessage = await _brokerHttpClient.CreateSessionAsync(session, cancellationToken);
return jobMessage;
}
public Task<TaskAgentMessage> GetRunnerMessageAsync(Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken)
{
CheckConnection();
var brokerSession = RetryRequest<TaskAgentMessage>(
async () => await _brokerHttpClient.GetRunnerMessageAsync(sessionId, version, status, os, architecture, disableUpdate, cancellationToken), cancellationToken, shouldRetry: ShouldRetryException);
return brokerSession;
}
public async Task AcknowledgeRunnerRequestAsync(string runnerRequestId, Guid? sessionId, TaskAgentStatus status, string version, string os, string architecture, CancellationToken cancellationToken)
{
CheckConnection();
// No retries
await _brokerHttpClient.AcknowledgeRunnerRequestAsync(runnerRequestId, sessionId, version, status, os, architecture, cancellationToken);
}
public async Task DeleteSessionAsync(CancellationToken cancellationToken)
{
CheckConnection();
await _brokerHttpClient.DeleteSessionAsync(cancellationToken);
}
public Task UpdateConnectionIfNeeded(Uri serverUri, VssCredentials credentials)
{
if (_brokerUri != serverUri || !_hasConnection)
{
return ConnectAsync(serverUri, credentials);
}
return Task.CompletedTask;
}
public Task ForceRefreshConnection(VssCredentials credentials)
{
if (!string.IsNullOrEmpty(_brokerUri?.AbsoluteUri))
{
return ConnectAsync(_brokerUri, credentials);
}
return Task.CompletedTask;
}
public bool ShouldRetryException(Exception ex)
{
if (ex is AccessDeniedException || ex is RunnerNotFoundException || ex is HostedRunnerDeprovisionedException)
{
return false;
}
return true;
}
}
}

View File

@@ -116,11 +116,15 @@ namespace GitHub.Runner.Common
bool IsConfigured();
bool IsServiceConfigured();
bool HasCredentials();
bool IsMigratedConfigured();
CredentialData GetCredentials();
CredentialData GetMigratedCredentials();
RunnerSettings GetSettings();
RunnerSettings GetMigratedSettings();
void SaveCredential(CredentialData credential);
void SaveMigratedCredential(CredentialData credential);
void SaveSettings(RunnerSettings settings);
void SaveMigratedSettings(RunnerSettings settings);
void DeleteCredential();
void DeleteMigratedCredential();
void DeleteSettings();
@@ -130,6 +134,7 @@ namespace GitHub.Runner.Common
{
private string _binPath;
private string _configFilePath;
private string _migratedConfigFilePath;
private string _credFilePath;
private string _migratedCredFilePath;
private string _serviceConfigFilePath;
@@ -137,6 +142,7 @@ namespace GitHub.Runner.Common
private CredentialData _creds;
private CredentialData _migratedCreds;
private RunnerSettings _settings;
private RunnerSettings _migratedSettings;
public override void Initialize(IHostContext hostContext)
{
@@ -154,6 +160,9 @@ namespace GitHub.Runner.Common
_configFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Runner);
Trace.Info("ConfigFilePath: {0}", _configFilePath);
_migratedConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.MigratedRunner);
Trace.Info("MigratedConfigFilePath: {0}", _migratedConfigFilePath);
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
Trace.Info("CredFilePath: {0}", _credFilePath);
@@ -169,7 +178,7 @@ namespace GitHub.Runner.Common
public bool HasCredentials()
{
Trace.Info("HasCredentials()");
bool credsStored = (new FileInfo(_credFilePath)).Exists || (new FileInfo(_migratedCredFilePath)).Exists;
bool credsStored = new FileInfo(_credFilePath).Exists || new FileInfo(_migratedCredFilePath).Exists;
Trace.Info("stored {0}", credsStored);
return credsStored;
}
@@ -177,7 +186,7 @@ namespace GitHub.Runner.Common
public bool IsConfigured()
{
Trace.Info("IsConfigured()");
bool configured = new FileInfo(_configFilePath).Exists;
bool configured = new FileInfo(_configFilePath).Exists || new FileInfo(_migratedConfigFilePath).Exists;
Trace.Info("IsConfigured: {0}", configured);
return configured;
}
@@ -185,11 +194,19 @@ namespace GitHub.Runner.Common
public bool IsServiceConfigured()
{
Trace.Info("IsServiceConfigured()");
bool serviceConfigured = (new FileInfo(_serviceConfigFilePath)).Exists;
bool serviceConfigured = new FileInfo(_serviceConfigFilePath).Exists;
Trace.Info($"IsServiceConfigured: {serviceConfigured}");
return serviceConfigured;
}
public bool IsMigratedConfigured()
{
Trace.Info("IsMigratedConfigured()");
bool configured = new FileInfo(_migratedConfigFilePath).Exists;
Trace.Info("IsMigratedConfigured: {0}", configured);
return configured;
}
public CredentialData GetCredentials()
{
if (_creds == null)
@@ -229,6 +246,25 @@ namespace GitHub.Runner.Common
return _settings;
}
public RunnerSettings GetMigratedSettings()
{
if (_migratedSettings == null)
{
RunnerSettings configuredSettings = null;
if (File.Exists(_migratedConfigFilePath))
{
string json = File.ReadAllText(_migratedConfigFilePath, Encoding.UTF8);
Trace.Info($"Read migrated setting file: {json.Length} chars");
configuredSettings = StringUtil.ConvertFromJson<RunnerSettings>(json);
}
ArgUtil.NotNull(configuredSettings, nameof(configuredSettings));
_migratedSettings = configuredSettings;
}
return _migratedSettings;
}
public void SaveCredential(CredentialData credential)
{
Trace.Info("Saving {0} credential @ {1}", credential.Scheme, _credFilePath);
@@ -244,6 +280,21 @@ namespace GitHub.Runner.Common
File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden);
}
public void SaveMigratedCredential(CredentialData credential)
{
Trace.Info("Saving {0} migrated credential @ {1}", credential.Scheme, _migratedCredFilePath);
if (File.Exists(_migratedCredFilePath))
{
// Delete existing credential file first, since the file is hidden and not able to overwrite.
Trace.Info("Delete exist runner migrated credential file.");
IOUtil.DeleteFile(_migratedCredFilePath);
}
IOUtil.SaveObject(credential, _migratedCredFilePath);
Trace.Info("Migrated Credentials Saved.");
File.SetAttributes(_migratedCredFilePath, File.GetAttributes(_migratedCredFilePath) | FileAttributes.Hidden);
}
public void SaveSettings(RunnerSettings settings)
{
Trace.Info("Saving runner settings.");
@@ -259,6 +310,21 @@ namespace GitHub.Runner.Common
File.SetAttributes(_configFilePath, File.GetAttributes(_configFilePath) | FileAttributes.Hidden);
}
public void SaveMigratedSettings(RunnerSettings settings)
{
Trace.Info("Saving runner migrated settings");
if (File.Exists(_migratedConfigFilePath))
{
// Delete existing settings file first, since the file is hidden and not able to overwrite.
Trace.Info("Delete exist runner migrated settings file.");
IOUtil.DeleteFile(_migratedConfigFilePath);
}
IOUtil.SaveObject(settings, _migratedConfigFilePath);
Trace.Info("Migrated Settings Saved.");
File.SetAttributes(_migratedConfigFilePath, File.GetAttributes(_migratedConfigFilePath) | FileAttributes.Hidden);
}
public void DeleteCredential()
{
IOUtil.Delete(_credFilePath, default(CancellationToken));
@@ -273,6 +339,12 @@ namespace GitHub.Runner.Common
public void DeleteSettings()
{
IOUtil.Delete(_configFilePath, default(CancellationToken));
IOUtil.Delete(_migratedConfigFilePath, default(CancellationToken));
}
public void DeleteMigratedSettings()
{
IOUtil.Delete(_migratedConfigFilePath, default(CancellationToken));
}
}
}

View File

@@ -18,6 +18,7 @@ namespace GitHub.Runner.Common
public enum WellKnownConfigFile
{
Runner,
MigratedRunner,
Credentials,
MigratedCredentials,
RSACredentials,
@@ -153,15 +154,37 @@ namespace GitHub.Runner.Common
public const int RetryableError = 2;
public const int RunnerUpdating = 3;
public const int RunOnceRunnerUpdating = 4;
public const int SessionConflict = 5;
// Temporary error code to indicate that the runner configuration has been refreshed
// and the runner should be restarted. This is a temporary code and will be removed in the future after
// the runner is migrated to runner admin.
public const int RunnerConfigurationRefreshed = 6;
}
public static class Features
{
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
public static readonly string Node16Warning = "DistributedTask.AddWarningToNode16Action";
public static readonly string LogTemplateErrorsAsDebugMessages = "DistributedTask.LogTemplateErrorsAsDebugMessages";
public static readonly string UseContainerPathForTemplate = "DistributedTask.UseContainerPathForTemplate";
public static readonly string AllowRunnerContainerHooks = "DistributedTask.AllowRunnerContainerHooks";
public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context";
public static readonly string DisplayHelpfulActionsDownloadErrors = "actions_display_helpful_actions_download_errors";
}
// Node version migration related constants
public static class NodeMigration
{
// Node versions
public static readonly string Node20 = "node20";
public static readonly string Node24 = "node24";
// Environment variables for controlling node version selection
public static readonly string ForceNode24Variable = "FORCE_JAVASCRIPT_ACTIONS_TO_NODE24";
public static readonly string AllowUnsecureNodeVersionVariable = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";
// Feature flags for controlling the migration phases
public static readonly string UseNode24ByDefaultFlag = "actions.runner.usenode24bydefault";
public static readonly string RequireNode24Flag = "actions.runner.requirenode24";
}
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
@@ -175,11 +198,6 @@ namespace GitHub.Runner.Common
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 SummaryUploadError = "$GITHUB_STEP_SUMMARY upload aborted, an error occurred when uploading the summary. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary";
public static readonly string DetectedNodeAfterEndOfLifeMessage = "Node.js 16 actions are deprecated. Please update the following actions to use Node.js 20: {0}. For more information see: https://github.blog/changelog/2023-09-22-github-actions-transitioning-from-node-16-to-node-20/.";
public static readonly string DeprecatedNodeDetectedAfterEndOfLifeActions = "DeprecatedNodeActionsMessageWarnings";
public static readonly string DeprecatedNodeVersion = "node16";
public static readonly string EnforcedNode12DetectedAfterEndOfLife = "The following actions uses node12 which is deprecated and will be forced to run on node16: {0}. For more info: https://github.blog/changelog/2023-06-13-github-actions-all-actions-will-run-on-node16-instead-of-node12-by-default/";
public static readonly string EnforcedNode12DetectedAfterEndOfLifeEnvVariable = "Node16ForceActionsWarnings";
}
public static class RunnerEvent
@@ -250,14 +268,13 @@ namespace GitHub.Runner.Common
public static readonly string RequireJobContainer = "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER";
public static readonly string RunnerDebug = "ACTIONS_RUNNER_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 readonly string ToolsDirectory = "agent.ToolsDirectory";
// 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.
// Set this env var to "nodeXY" to downgrade the node version for internal functions (e.g hashfiles). This does NOT affect the version of node actions.
public static readonly string ForcedInternalNodeVersion = "ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION";
public static readonly string ForcedActionsNodeVersion = "ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION";
public static readonly string PrintLogToStdout = "ACTIONS_RUNNER_PRINT_LOG_TO_STDOUT";

View File

@@ -15,6 +15,7 @@ using System.Threading.Tasks;
using GitHub.DistributedTask.Logging;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Services.WebApi.Jwt;
namespace GitHub.Runner.Common
{
@@ -36,6 +37,12 @@ namespace GitHub.Runner.Common
event EventHandler Unloading;
void ShutdownRunner(ShutdownReason reason);
void WritePerfCounter(string counter);
void LoadDefaultUserAgents();
bool AllowAuthMigration { get; }
void EnableAuthMigration(string trace);
void DeferAuthMigration(TimeSpan deferred, string trace);
event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
}
public enum StartupType
@@ -67,17 +74,28 @@ namespace GitHub.Runner.Common
private StartupType _startupType;
private string _perfFile;
private RunnerWebProxy _webProxy = new();
private string _hostType = string.Empty;
// disable auth migration by default
private readonly ManualResetEventSlim _allowAuthMigration = new ManualResetEventSlim(false);
private DateTime _deferredAuthMigrationTime = DateTime.MaxValue;
private readonly object _authMigrationLock = new object();
private CancellationTokenSource _authMigrationAutoReenableTaskCancellationTokenSource = new();
private Task _authMigrationAutoReenableTask;
public event EventHandler Unloading;
public event EventHandler<AuthMigrationEventArgs> AuthMigrationChanged;
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
public ShutdownReason RunnerShutdownReason { get; private set; }
public ISecretMasker SecretMasker => _secretMasker;
public List<ProductInfoHeaderValue> UserAgents => _userAgents;
public RunnerWebProxy WebProxy => _webProxy;
public bool AllowAuthMigration => _allowAuthMigration.IsSet;
public HostContext(string hostType, string logFile = null)
{
// Validate args.
ArgUtil.NotNullOrEmpty(hostType, nameof(hostType));
_hostType = hostType;
_loadContext = AssemblyLoadContext.GetLoadContext(typeof(HostContext).GetTypeInfo().Assembly);
_loadContext.Unloading += LoadContext_Unloading;
@@ -196,14 +214,88 @@ namespace GitHub.Runner.Common
}
}
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
{
_trace.Warning($"Runner is running under insecure mode: HTTPS server certificate validation has been turned off by GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY environment variable.");
}
LoadDefaultUserAgents();
}
// marked as internal for testing
internal async Task AuthMigrationAuthReenableAsync(TimeSpan refreshInterval, CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
_trace.Verbose($"Auth migration defer timer is set to expire at {_deferredAuthMigrationTime.ToString("O")}. AllowAuthMigration: {_allowAuthMigration.IsSet}.");
await Task.Delay(refreshInterval, token);
if (!_allowAuthMigration.IsSet && DateTime.UtcNow > _deferredAuthMigrationTime)
{
_trace.Info($"Auth migration defer timer expired. Allowing auth migration.");
EnableAuthMigration("Auth migration defer timer expired.");
}
}
}
catch (TaskCanceledException)
{
// Task was cancelled, exit the loop.
}
catch (Exception ex)
{
_trace.Info("Error in auth migration reenable task.");
_trace.Error(ex);
}
}
public void EnableAuthMigration(string trace)
{
_allowAuthMigration.Set();
lock (_authMigrationLock)
{
if (_authMigrationAutoReenableTask == null)
{
var refreshIntervalInMS = 60 * 1000;
#if DEBUG
// For L0, we will refresh faster
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL")))
{
refreshIntervalInMS = int.Parse(Environment.GetEnvironmentVariable("_GITHUB_ACTION_AUTH_MIGRATION_REFRESH_INTERVAL"));
}
#endif
_authMigrationAutoReenableTask = AuthMigrationAuthReenableAsync(TimeSpan.FromMilliseconds(refreshIntervalInMS), _authMigrationAutoReenableTaskCancellationTokenSource.Token);
}
}
_trace.Info($"Enable auth migration at {DateTime.UtcNow.ToString("O")}.");
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
}
public void DeferAuthMigration(TimeSpan deferred, string trace)
{
_allowAuthMigration.Reset();
// defer migration for a while
lock (_authMigrationLock)
{
_deferredAuthMigrationTime = DateTime.UtcNow.Add(deferred);
}
_trace.Info($"Disabled auth migration until {_deferredAuthMigrationTime.ToString("O")}.");
AuthMigrationChanged?.Invoke(this, new AuthMigrationEventArgs(trace));
}
public void LoadDefaultUserAgents()
{
if (string.IsNullOrEmpty(WebProxy.HttpProxyAddress) && string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
{
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
}
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
else
{
_trace.Warning($"Runner is running under insecure mode: HTTPS server certificate validation has been turned off by GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY environment variable.");
_userAgents.Add(new ProductInfoHeaderValue("HttpProxyConfigured", bool.TrueString));
}
var credFile = GetConfigFile(WellKnownConfigFile.Credentials);
@@ -215,6 +307,36 @@ namespace GitHub.Runner.Common
{
_userAgents.Add(new ProductInfoHeaderValue("ClientId", clientId));
}
// for Hosted runner, we can pull orchestrationId from JWT claims of the runner listening token.
if (credData != null &&
credData.Scheme == Constants.Configuration.OAuthAccessToken &&
credData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Token, out var accessToken) &&
!string.IsNullOrEmpty(accessToken))
{
try
{
var jwt = JsonWebToken.Create(accessToken);
var claims = jwt.ExtractClaims();
var orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orch_id", StringComparison.OrdinalIgnoreCase))?.Value;
if (string.IsNullOrEmpty(orchestrationId))
{
// fallback to orchid for C# actions-service
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
}
if (!string.IsNullOrEmpty(orchestrationId))
{
_trace.Info($"Pull OrchestrationId {orchestrationId} from runner JWT claims");
_userAgents.Insert(0, new ProductInfoHeaderValue("OrchestrationId", orchestrationId));
}
}
catch (Exception ex)
{
_trace.Error("Fail to extract OrchestrationId from runner JWT claims");
_trace.Error(ex);
}
}
}
var runnerFile = GetConfigFile(WellKnownConfigFile.Runner);
@@ -240,6 +362,11 @@ namespace GitHub.Runner.Common
_trace.Info($"Adding extra user agent '{extraUserAgentHeader}' to all HTTP requests.");
_userAgents.Add(extraUserAgentHeader);
}
var currentProcess = Process.GetCurrentProcess();
_userAgents.Add(new ProductInfoHeaderValue("Pid", currentProcess.Id.ToString()));
_userAgents.Add(new ProductInfoHeaderValue("CreationTime", Uri.EscapeDataString(DateTime.UtcNow.ToString("O"))));
_userAgents.Add(new ProductInfoHeaderValue($"({_hostType})"));
}
public string GetDirectory(WellKnownDirectory directory)
@@ -326,6 +453,12 @@ namespace GitHub.Runner.Common
".runner");
break;
case WellKnownConfigFile.MigratedRunner:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".runner_migrated");
break;
case WellKnownConfigFile.Credentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
@@ -526,6 +659,18 @@ namespace GitHub.Runner.Common
_loadContext.Unloading -= LoadContext_Unloading;
_loadContext = null;
}
if (_authMigrationAutoReenableTask != null)
{
_authMigrationAutoReenableTaskCancellationTokenSource?.Cancel();
}
if (_authMigrationAutoReenableTaskCancellationTokenSource != null)
{
_authMigrationAutoReenableTaskCancellationTokenSource?.Dispose();
_authMigrationAutoReenableTaskCancellationTokenSource = null;
}
_httpTraceSubscription?.Dispose();
_diagListenerSubscription?.Dispose();
_traceManager?.Dispose();
@@ -612,7 +757,7 @@ namespace GitHub.Runner.Common
payload[0] = Enum.Parse(typeof(GitHub.Services.Common.VssCredentialsType), ((int)payload[0]).ToString());
}
if (payload.Length > 0)
if (payload.Length > 0 && !string.IsNullOrEmpty(eventData.Message))
{
message = String.Format(eventData.Message.Replace("%n", Environment.NewLine), payload);
}

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
@@ -179,6 +180,10 @@ namespace GitHub.Runner.Common
userAgentValues.AddRange(UserAgentUtility.GetDefaultRestUserAgent());
userAgentValues.AddRange(HostContext.UserAgents);
this._websocketClient.Options.SetRequestHeader("User-Agent", string.Join(" ", userAgentValues.Select(x => x.ToString())));
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
{
this._websocketClient.Options.RemoteCertificateValidationCallback = (_, _, _, _) => true;
}
this._websocketConnectTask = ConnectWebSocketClient(feedStreamUrl, delay);
}

View File

@@ -19,7 +19,7 @@ namespace GitHub.Runner.Common
TaskCompletionSource<int> JobRecordUpdated { get; }
event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
Task ShutdownAsync();
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false);
void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false);
void QueueWebConsoleLine(Guid stepRecordId, string line, long? lineNumber = null);
void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource);
void QueueResultsUpload(Guid timelineRecordId, string name, string path, string type, bool deleteSource, bool finalize, bool firstBlock, long totalLines);
@@ -74,6 +74,7 @@ namespace GitHub.Runner.Common
private readonly List<JobTelemetry> _jobTelemetries = new();
private bool _queueInProcess = false;
private bool _resultsServiceOnly = false;
private int _resultsServiceExceptionsCount = 0;
private Stopwatch _resultsUploadTimer = new();
private Stopwatch _actionsUploadTimer = new();
@@ -104,11 +105,10 @@ namespace GitHub.Runner.Common
_resultsServer = hostContext.GetService<IResultsServer>();
}
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false, bool enableTelemetry = false)
public void Start(Pipelines.AgentJobRequestMessage jobRequest, bool resultsServiceOnly = false)
{
Trace.Entering();
_resultsServiceOnly = resultsServiceOnly;
_enableTelemetry = enableTelemetry;
var serviceEndPoint = jobRequest.Resources.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
@@ -134,11 +134,17 @@ namespace GitHub.Runner.Common
{
liveConsoleFeedUrl = feedStreamUrl;
}
_resultsServer.InitializeResultsClient(new Uri(resultsReceiverEndpoint), liveConsoleFeedUrl, accessToken);
jobRequest.Variables.TryGetValue("system.github.results_upload_with_sdk", out VariableValue resultsUseSdkVariable);
_resultsServer.InitializeResultsClient(new Uri(resultsReceiverEndpoint), liveConsoleFeedUrl, accessToken, StringUtil.ConvertToBoolean(resultsUseSdkVariable?.Value));
_resultsClientInitiated = true;
}
// Enable telemetry if we have both results service and actions service
if (_resultsClientInitiated && !_resultsServiceOnly)
{
_enableTelemetry = true;
}
if (_queueInProcess)
{
Trace.Info("No-opt, all queue process tasks are running.");
@@ -551,6 +557,10 @@ namespace GitHub.Runner.Common
{
await UploadSummaryFile(file);
}
if (string.Equals(file.Type, CoreAttachmentType.ResultsDiagnosticLog, StringComparison.OrdinalIgnoreCase))
{
await UploadResultsDiagnosticLogsFile(file);
}
else if (String.Equals(file.Type, CoreAttachmentType.ResultsLog, StringComparison.OrdinalIgnoreCase))
{
if (file.RecordId != _jobTimelineRecordId)
@@ -570,9 +580,9 @@ namespace GitHub.Runner.Common
Trace.Info("Catch exception during file upload to results, keep going since the process is best effort.");
Trace.Error(ex);
errorCount++;
_resultsServiceExceptionsCount++;
// If we hit any exceptions uploading to Results, let's skip any additional uploads to Results unless Results is serving logs
if (!_resultsServiceOnly)
if (!_resultsServiceOnly && _resultsServiceExceptionsCount > 3)
{
_resultsClientInitiated = false;
SendResultsTelemetry(ex);
@@ -603,7 +613,7 @@ namespace GitHub.Runner.Common
private void SendResultsTelemetry(Exception ex)
{
var issue = new Issue() { Type = IssueType.Warning, Message = $"Caught exception with results. {ex.Message}" };
var issue = new Issue() { Type = IssueType.Warning, Message = $"Caught exception with results. {HostContext.SecretMasker.MaskSecrets(ex.Message)}" };
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.ResultsUploadFailure;
var telemetryRecord = new TimelineRecord()
@@ -699,7 +709,9 @@ namespace GitHub.Runner.Common
{
Trace.Info("Catch exception during update steps, skip update Results.");
Trace.Error(e);
if (!_resultsServiceOnly)
_resultsServiceExceptionsCount++;
// If we hit any exceptions uploading to Results, let's skip any additional uploads to Results unless Results is serving logs
if (!_resultsServiceOnly && _resultsServiceExceptionsCount > 3)
{
_resultsClientInitiated = false;
SendResultsTelemetry(e);
@@ -922,6 +934,17 @@ namespace GitHub.Runner.Common
await UploadResultsFile(file, summaryHandler);
}
private async Task UploadResultsDiagnosticLogsFile(ResultsUploadFileInfo file)
{
Trace.Info($"Starting to upload diagnostic logs file to results service {file.Name}, {file.Path}");
ResultsFileUploadHandler diagnosticLogsHandler = async (file) =>
{
await _resultsServer.CreateResultsDiagnosticLogsAsync(file.PlanId, file.JobId, file.Path, CancellationToken.None);
};
await UploadResultsFile(file, diagnosticLogsHandler);
}
private async Task UploadResultsStepLogFile(ResultsUploadFileInfo file)
{
Trace.Info($"Starting upload of step log file to results service {file.Name}, {file.Path}");

View File

@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.Launch.Client;
using GitHub.Services.WebApi;
namespace GitHub.Runner.Common
{
@@ -14,7 +15,7 @@ namespace GitHub.Runner.Common
{
void InitializeLaunchClient(Uri uri, string token);
Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken);
Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors);
}
public sealed class LaunchServer : RunnerService, ILaunchServer
@@ -23,17 +24,34 @@ namespace GitHub.Runner.Common
public void InitializeLaunchClient(Uri uri, string token)
{
var httpMessageHandler = HostContext.CreateHttpClientHandler();
this._launchClient = new LaunchHttpClient(uri, httpMessageHandler, token, disposeHandler: true);
// Using default 100 timeout
RawClientHttpRequestSettings settings = VssUtil.GetHttpRequestSettings(null);
// Create retry handler
IEnumerable<DelegatingHandler> delegatingHandlers = new List<DelegatingHandler>();
if (settings.MaxRetryRequest > 0)
{
delegatingHandlers = new DelegatingHandler[] { new VssHttpRetryMessageHandler(settings.MaxRetryRequest) };
}
// Setup RawHttpMessageHandler without credentials
var httpMessageHandler = new RawHttpMessageHandler(new NoOpCredentials(null), settings);
var pipeline = HttpClientFactory.CreatePipeline(httpMessageHandler, delegatingHandlers);
this._launchClient = new LaunchHttpClient(uri, pipeline, token, disposeHandler: true);
}
public Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList,
CancellationToken cancellationToken)
CancellationToken cancellationToken, bool displayHelpfulActionsDownloadErrors)
{
if (_launchClient != null)
{
return _launchClient.GetResolveActionsDownloadInfoAsync(planId, jobId, actionReferenceList,
cancellationToken: cancellationToken);
if (!displayHelpfulActionsDownloadErrors)
{
return _launchClient.GetResolveActionsDownloadInfoAsync(planId, jobId, actionReferenceList,
cancellationToken: cancellationToken);
}
return _launchClient.GetResolveActionsDownloadInfoAsyncV2(planId, jobId, actionReferenceList, cancellationToken);
}
throw new InvalidOperationException("Launch client is not initialized.");

View File

@@ -19,7 +19,7 @@ namespace GitHub.Runner.Common
[ServiceLocator(Default = typeof(ResultServer))]
public interface IResultsServer : IRunnerService, IAsyncDisposable
{
void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token);
void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token, bool useSdk);
Task<bool> AppendLiveConsoleFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, long? startLine, CancellationToken cancellationToken);
@@ -35,6 +35,8 @@ namespace GitHub.Runner.Common
Task UpdateResultsWorkflowStepsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId,
IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
Task CreateResultsDiagnosticLogsAsync(string planId, string jobId, string file, CancellationToken cancellationToken);
}
public sealed class ResultServer : RunnerService, IResultsServer
@@ -51,9 +53,9 @@ namespace GitHub.Runner.Common
private String _liveConsoleFeedUrl;
private string _token;
public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token)
public void InitializeResultsClient(Uri uri, string liveConsoleFeedUrl, string token, bool useSdk)
{
this._resultsClient = CreateHttpClient(uri, token);
this._resultsClient = CreateHttpClient(uri, token, useSdk);
_token = token;
if (!string.IsNullOrEmpty(liveConsoleFeedUrl))
@@ -63,7 +65,7 @@ namespace GitHub.Runner.Common
}
}
public ResultsHttpClient CreateHttpClient(Uri uri, string token)
public ResultsHttpClient CreateHttpClient(Uri uri, string token, bool useSdk)
{
// Using default 100 timeout
RawClientHttpRequestSettings settings = VssUtil.GetHttpRequestSettings(null);
@@ -80,7 +82,7 @@ namespace GitHub.Runner.Common
var pipeline = HttpClientFactory.CreatePipeline(httpMessageHandler, delegatingHandlers);
return new ResultsHttpClient(uri, pipeline, token, disposeHandler: true);
return new ResultsHttpClient(uri, pipeline, token, disposeHandler: true, useSdk: useSdk);
}
public Task CreateResultsStepSummaryAsync(string planId, string jobId, Guid stepId, string file,
@@ -141,6 +143,18 @@ namespace GitHub.Runner.Common
throw new InvalidOperationException("Results client is not initialized.");
}
public Task CreateResultsDiagnosticLogsAsync(string planId, string jobId, string file,
CancellationToken cancellationToken)
{
if (_resultsClient != null)
{
return _resultsClient.UploadResultsDiagnosticLogsAsync(planId, jobId, file,
cancellationToken: cancellationToken);
}
throw new InvalidOperationException("Results client is not initialized.");
}
public ValueTask DisposeAsync()
{
CloseWebSocket(WebSocketCloseStatus.NormalClosure, CancellationToken.None);

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using GitHub.Actions.RunService.WebApi;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using Sdk.RSWebApi.Contracts;
@@ -17,7 +18,7 @@ namespace GitHub.Runner.Common
{
Task ConnectAsync(Uri serverUrl, VssCredentials credentials);
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken token);
Task<AgentJobRequestMessage> GetJobMessageAsync(string id, string billingOwnerId, CancellationToken token);
Task CompleteJobAsync(
Guid planId,
@@ -27,6 +28,8 @@ namespace GitHub.Runner.Common
IList<StepResult> stepResults,
IList<Annotation> jobAnnotations,
string environmentUrl,
IList<Telemetry> telemetry,
string billingOwnerId,
CancellationToken token);
Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken token);
@@ -56,12 +59,15 @@ namespace GitHub.Runner.Common
}
}
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, CancellationToken cancellationToken)
public Task<AgentJobRequestMessage> GetJobMessageAsync(string id, string billingOwnerId, CancellationToken cancellationToken)
{
CheckConnection();
return RetryRequest<AgentJobRequestMessage>(
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, cancellationToken), cancellationToken,
shouldRetry: ex => ex is not TaskOrchestrationJobAlreadyAcquiredException);
async () => await _runServiceHttpClient.GetJobMessageAsync(requestUri, id, VarUtil.OS, billingOwnerId, cancellationToken), cancellationToken,
shouldRetry: ex =>
ex is not TaskOrchestrationJobNotFoundException && // HTTP status 404
ex is not TaskOrchestrationJobAlreadyAcquiredException && // HTTP status 409
ex is not TaskOrchestrationJobUnprocessableException); // HTTP status 422
}
public Task CompleteJobAsync(
@@ -72,18 +78,25 @@ namespace GitHub.Runner.Common
IList<StepResult> stepResults,
IList<Annotation> jobAnnotations,
string environmentUrl,
IList<Telemetry> telemetry,
string billingOwnerId,
CancellationToken cancellationToken)
{
CheckConnection();
return RetryRequest(
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, cancellationToken), cancellationToken);
async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, telemetry, billingOwnerId, cancellationToken), cancellationToken,
shouldRetry: ex =>
ex is not VssUnauthorizedException && // HTTP status 401
ex is not TaskOrchestrationJobNotFoundException); // HTTP status 404
}
public Task<RenewJobResponse> RenewJobAsync(Guid planId, Guid jobId, CancellationToken cancellationToken)
{
CheckConnection();
return RetryRequest<RenewJobResponse>(
async () => await _runServiceHttpClient.RenewJobAsync(requestUri, planId, jobId, cancellationToken), cancellationToken);
async () => await _runServiceHttpClient.RenewJobAsync(requestUri, planId, jobId, cancellationToken), cancellationToken,
shouldRetry: ex =>
ex is not TaskOrchestrationJobNotFoundException); // HTTP status 404
}
}
}

View File

@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<NoWarn>NU1701;NU1603</NoWarn>
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
<Version>$(Version)</Version>
</PropertyGroup>
@@ -15,11 +15,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
<PackageReference Include="System.Threading.Channels" Version="4.4.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

View File

@@ -19,6 +19,7 @@ namespace GitHub.Runner.Common
Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
Task<DistributedTask.WebApi.Runner> ReplaceRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
Task DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId);
Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken);
}
@@ -43,89 +44,15 @@ namespace GitHub.Runner.Common
public async Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName)
{
var githubApiUrl = "";
var gitHubUrlBuilder = new UriBuilder(githubUrl);
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
if (path.Length == 1)
{
// org runner
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
}
else
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
}
}
else if (path.Length == 2)
{
// repo or enterprise runner.
if (!string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
{
return null;
}
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
}
else
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}";
}
}
else
{
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
}
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners?name={Uri.EscapeDataString(agentName)}";
var runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
return runnersList.ToTaskAgents();
}
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
{
var githubApiUrl = "";
var gitHubUrlBuilder = new UriBuilder(githubUrl);
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
if (path.Length == 1)
{
// org runner
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runner-groups";
}
else
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runner-groups";
}
}
else if (path.Length == 2)
{
// repo or enterprise runner.
if (!string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
{
return null;
}
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runner-groups";
}
else
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runner-groups";
}
}
else
{
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
}
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runner-groups";
var agentPools = await RetryRequest<RunnerGroupList>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
return agentPools?.ToAgentPoolList();
}
@@ -176,6 +103,12 @@ namespace GitHub.Runner.Common
return await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body);
}
public async Task DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId)
{
var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners/{runnerId}";
await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Delete, 3, "Failed to delete agent");
}
private async Task<T> RetryRequest<T>(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null)
{
int retry = 0;
@@ -192,13 +125,22 @@ namespace GitHub.Runner.Common
try
{
HttpResponseMessage response = null;
if (requestType == RequestType.Get)
switch (requestType)
{
response = await httpClient.GetAsync(githubApiUrl);
}
else
{
response = await httpClient.PostAsync(githubApiUrl, body);
case RequestType.Get:
response = await httpClient.GetAsync(githubApiUrl);
break;
case RequestType.Post:
response = await httpClient.PostAsync(githubApiUrl, body);
break;
case RequestType.Patch:
response = await httpClient.PatchAsync(githubApiUrl, body);
break;
case RequestType.Delete:
response = await httpClient.DeleteAsync(githubApiUrl);
break;
default:
throw new ArgumentOutOfRangeException(nameof(requestType), requestType, null);
}
if (response != null)
@@ -233,5 +175,61 @@ namespace GitHub.Runner.Common
await Task.Delay(backOff);
}
}
private string GetEntityUrl(string githubUrl)
{
var githubApiUrl = "";
var gitHubUrlBuilder = new UriBuilder(githubUrl);
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
var isOrgRunner = path.Length == 1;
var isRepoOrEnterpriseRunner = path.Length == 2;
var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase);
if (isOrgRunner)
{
// org runner
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions";
}
else
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions";
}
}
else if (isRepoOrEnterpriseRunner)
{
// Repository Runner
if (isRepoRunner)
{
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions";
}
else
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions";
}
}
else
{
// Enterprise Runner
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions";
}
else
{
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions";
}
}
}
else
{
throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise.");
}
return githubApiUrl;
}
}
}

View File

@@ -1,11 +1,11 @@
using GitHub.DistributedTask.WebApi;
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Services.WebApi;
using GitHub.Services.Common;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.Runner.Common
{
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Common
Task<TaskAgentSession> CreateAgentSessionAsync(Int32 poolId, TaskAgentSession session, CancellationToken cancellationToken);
Task DeleteAgentMessageAsync(Int32 poolId, Int64 messageId, Guid sessionId, CancellationToken cancellationToken);
Task DeleteAgentSessionAsync(Int32 poolId, Guid sessionId, CancellationToken cancellationToken);
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, CancellationToken cancellationToken);
Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken);
// job request
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
@@ -50,7 +50,10 @@ namespace GitHub.Runner.Common
Task<PackageMetadata> GetPackageAsync(string packageType, string platform, string version, bool includeToken, CancellationToken cancellationToken);
// agent update
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace);
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace, CancellationToken cancellationToken = default);
// runner config refresh
Task<string> RefreshRunnerConfigAsync(int agentId, string configType, string encodedRunnerConfig, CancellationToken cancellationToken);
}
public sealed class RunnerServer : RunnerService, IRunnerServer
@@ -272,10 +275,10 @@ namespace GitHub.Runner.Common
return _messageTaskAgentClient.DeleteAgentSessionAsync(poolId, sessionId, cancellationToken: cancellationToken);
}
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, CancellationToken cancellationToken)
public Task<TaskAgentMessage> GetAgentMessageAsync(Int32 poolId, Guid sessionId, Int64? lastMessageId, TaskAgentStatus status, string runnerVersion, string os, string architecture, bool disableUpdate, CancellationToken cancellationToken)
{
CheckConnection(RunnerConnectionType.MessageQueue);
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, status, runnerVersion, os, architecture, cancellationToken: cancellationToken);
return _messageTaskAgentClient.GetMessageAsync(poolId, sessionId, lastMessageId, status, runnerVersion, os, architecture, disableUpdate, cancellationToken: cancellationToken);
}
//-----------------------------------------------------------------
@@ -315,10 +318,17 @@ namespace GitHub.Runner.Common
return _genericTaskAgentClient.GetPackageAsync(packageType, platform, version, includeToken, cancellationToken: cancellationToken);
}
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace)
public Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, ulong agentId, string currentState, string trace, CancellationToken cancellationToken = default)
{
CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace);
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState, trace, cancellationToken: cancellationToken);
}
// runner config refresh
public Task<string> RefreshRunnerConfigAsync(int agentId, string configType, string encodedRunnerConfig, CancellationToken cancellationToken)
{
CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.RefreshRunnerConfigAsync(agentId, configType, encodedRunnerConfig, cancellationToken: cancellationToken);
}
}
}

View File

@@ -70,7 +70,8 @@ namespace GitHub.Runner.Common
protected async Task RetryRequest(Func<Task> func,
CancellationToken cancellationToken,
int maxRetryAttemptsCount = 5
int maxAttempts = 5,
Func<Exception, bool> shouldRetry = null
)
{
async Task<Unit> wrappedFunc()
@@ -78,31 +79,31 @@ namespace GitHub.Runner.Common
await func();
return Unit.Value;
}
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxRetryAttemptsCount);
await RetryRequest<Unit>(wrappedFunc, cancellationToken, maxAttempts, shouldRetry);
}
protected async Task<T> RetryRequest<T>(Func<Task<T>> func,
CancellationToken cancellationToken,
int maxRetryAttemptsCount = 5,
int maxAttempts = 5,
Func<Exception, bool> shouldRetry = null
)
{
var retryCount = 0;
var attempt = 0;
while (true)
{
retryCount++;
attempt++;
cancellationToken.ThrowIfCancellationRequested();
try
{
return await func();
}
// TODO: Add handling of non-retriable exceptions: https://github.com/github/actions-broker/issues/122
catch (Exception ex) when (retryCount < maxRetryAttemptsCount && (shouldRetry == null || shouldRetry(ex)))
catch (Exception ex) when (attempt < maxAttempts && (shouldRetry == null || shouldRetry(ex)))
{
Trace.Error("Catch exception during request");
Trace.Error(ex);
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15));
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxRetryAttemptsCount - retryCount} attempt left.");
Trace.Warning($"Back off {backOff.TotalSeconds} seconds before next retry. {maxAttempts - attempt} attempt left.");
await Task.Delay(backOff, cancellationToken);
}
}

View File

@@ -1,12 +1,35 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Util
{
public static class NodeUtil
{
private const string _defaultNodeVersion = "node16";
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node16", "node20" });
/// <summary>
/// Represents details about an environment variable, including its value and source
/// </summary>
private class EnvironmentVariableInfo
{
/// <summary>
/// Gets or sets whether the value evaluates to true
/// </summary>
public bool IsTrue { get; set; }
/// <summary>
/// Gets or sets whether the value came from the workflow environment
/// </summary>
public bool FromWorkflow { get; set; }
/// <summary>
/// Gets or sets whether the value came from the system environment
/// </summary>
public bool FromSystem { get; set; }
}
private const string _defaultNodeVersion = "node20";
public static readonly ReadOnlyCollection<string> BuiltInNodeVersions = new(new[] { "node20" });
public static string GetInternalNodeVersion()
{
var forcedInternalNodeVersion = Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedInternalNodeVersion);
@@ -18,5 +41,122 @@ namespace GitHub.Runner.Common.Util
}
return _defaultNodeVersion;
}
/// <summary>
/// Determines the appropriate Node version for Actions to use
/// </summary>
/// <param name="workflowEnvironment">Optional dictionary containing workflow-level environment variables</param>
/// <param name="useNode24ByDefault">Feature flag indicating if Node 24 should be the default</param>
/// <param name="requireNode24">Feature flag indicating if Node 24 is required</param>
/// <returns>The Node version to use (node20 or node24) and warning message if both env vars are set</returns>
public static (string nodeVersion, string warningMessage) DetermineActionsNodeVersion(
IDictionary<string, string> workflowEnvironment = null,
bool useNode24ByDefault = false,
bool requireNode24 = false)
{
// Phase 3: Always use Node 24 regardless of environment variables
if (requireNode24)
{
return (Constants.Runner.NodeMigration.Node24, null);
}
// Get environment variable details with source information
var forceNode24Details = GetEnvironmentVariableDetails(
Constants.Runner.NodeMigration.ForceNode24Variable, workflowEnvironment);
var allowUnsecureNodeDetails = GetEnvironmentVariableDetails(
Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable, workflowEnvironment);
bool forceNode24 = forceNode24Details.IsTrue;
bool allowUnsecureNode = allowUnsecureNodeDetails.IsTrue;
string warningMessage = null;
// Check if both flags are set from the same source
bool bothFromWorkflow = forceNode24Details.IsTrue && allowUnsecureNodeDetails.IsTrue &&
forceNode24Details.FromWorkflow && allowUnsecureNodeDetails.FromWorkflow;
bool bothFromSystem = forceNode24Details.IsTrue && allowUnsecureNodeDetails.IsTrue &&
forceNode24Details.FromSystem && allowUnsecureNodeDetails.FromSystem;
// Handle the case when both are set in the same source
if (bothFromWorkflow || bothFromSystem)
{
string source = bothFromWorkflow ? "workflow" : "system";
string defaultVersion = useNode24ByDefault ? Constants.Runner.NodeMigration.Node24 : Constants.Runner.NodeMigration.Node20;
warningMessage = $"Both {Constants.Runner.NodeMigration.ForceNode24Variable} and {Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable} environment variables are set to true in the {source} environment. This is likely a configuration error. Using the default Node version: {defaultVersion}.";
return (defaultVersion, warningMessage);
}
// Phase 2: Node 24 is the default
if (useNode24ByDefault)
{
if (allowUnsecureNode)
{
return (Constants.Runner.NodeMigration.Node20, null);
}
return (Constants.Runner.NodeMigration.Node24, null);
}
// Phase 1: Node 20 is the default
if (forceNode24)
{
return (Constants.Runner.NodeMigration.Node24, null);
}
return (Constants.Runner.NodeMigration.Node20, null);
}
/// <summary>
/// Checks if Node24 is requested but running on ARM32 Linux, and determines if fallback is needed.
/// </summary>
/// <param name="preferredVersion">The preferred Node version</param>
/// <returns>A tuple containing the adjusted node version and an optional warning message</returns>
public static (string nodeVersion, string warningMessage) CheckNodeVersionForLinuxArm32(string preferredVersion)
{
if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase) &&
Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm) &&
Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
return (Constants.Runner.NodeMigration.Node20, "Node 24 is not supported on Linux ARM32 platforms. Falling back to Node 20.");
}
return (preferredVersion, null);
}
/// <summary>
/// Gets detailed information about an environment variable from both workflow and system environments
/// </summary>
/// <param name="variableName">The name of the environment variable</param>
/// <param name="workflowEnvironment">Optional dictionary containing workflow-level environment variables</param>
/// <returns>An EnvironmentVariableInfo object containing details about the variable from both sources</returns>
private static EnvironmentVariableInfo GetEnvironmentVariableDetails(string variableName, IDictionary<string, string> workflowEnvironment)
{
var info = new EnvironmentVariableInfo();
// Check workflow environment
bool foundInWorkflow = false;
string workflowValue = null;
if (workflowEnvironment != null && workflowEnvironment.TryGetValue(variableName, out workflowValue))
{
foundInWorkflow = true;
info.FromWorkflow = true;
info.IsTrue = StringUtil.ConvertToBoolean(workflowValue); // Workflow value takes precedence for the boolean value
}
// Also check system environment
string systemValue = Environment.GetEnvironmentVariable(variableName);
bool foundInSystem = !string.IsNullOrEmpty(systemValue);
info.FromSystem = foundInSystem;
// If not found in workflow, use system values
if (!foundInWorkflow)
{
info.IsTrue = StringUtil.ConvertToBoolean(systemValue);
}
return info;
}
}
}

View File

@@ -9,11 +9,12 @@ using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Listener.Configuration;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Runner.Common.Util;
using GitHub.Services.OAuth;
using GitHub.Services.WebApi;
namespace GitHub.Runner.Listener
{
@@ -22,33 +23,242 @@ namespace GitHub.Runner.Listener
private RunnerSettings _settings;
private ITerminal _term;
private TimeSpan _getNextMessageRetryInterval;
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
private TaskAgentStatus _runnerStatus = TaskAgentStatus.Online;
private CancellationTokenSource _getMessagesTokenSource;
private VssCredentials _creds;
private VssCredentials _credsV2;
private TaskAgentSession _session;
private IRunnerServer _runnerServer;
private IBrokerServer _brokerServer;
private ICredentialManager _credMgr;
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
private bool _accessTokenRevoked = false;
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
private bool _needRefreshCredsV2 = false;
private bool _handlerInitialized = false;
private bool _isMigratedSettings = false;
private const int _maxMigratedSettingsRetries = 3;
private int _migratedSettingsRetryCount = 0;
public BrokerMessageListener()
{
}
public BrokerMessageListener(RunnerSettings settings, bool isMigratedSettings = false)
{
_settings = settings;
_isMigratedSettings = isMigratedSettings;
}
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_term = HostContext.GetService<ITerminal>();
_runnerServer = HostContext.GetService<IRunnerServer>();
_brokerServer = HostContext.GetService<IBrokerServer>();
_credMgr = HostContext.GetService<ICredentialManager>();
}
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
{
await RefreshBrokerConnection();
return await Task.FromResult(true);
Trace.Entering();
// Load settings if not provided through constructor
if (_settings == null)
{
var configManager = HostContext.GetService<IConfigurationManager>();
_settings = configManager.LoadSettings();
Trace.Info("Settings loaded from config manager");
}
else
{
Trace.Info("Using provided settings");
if (_isMigratedSettings)
{
Trace.Info("Using migrated settings from .runner_migrated");
}
}
var serverUrlV2 = _settings.ServerUrlV2;
var serverUrl = _settings.ServerUrl;
Trace.Info(_settings);
if (string.IsNullOrEmpty(_settings.ServerUrlV2))
{
throw new InvalidOperationException("ServerUrlV2 is not set");
}
// Create connection.
Trace.Info("Loading Credentials");
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false);
var agent = new TaskAgentReference
{
Id = _settings.AgentId,
Name = _settings.AgentName,
Version = BuildConstants.RunnerPackage.Version,
OSDescription = RuntimeInformation.OSDescription,
};
var currentProcess = Process.GetCurrentProcess();
string sessionName = $"{Environment.MachineName ?? "RUNNER"} (PID: {currentProcess.Id})";
var taskAgentSession = new TaskAgentSession(sessionName, agent);
string errorMessage = string.Empty;
bool encounteringError = false;
while (true)
{
token.ThrowIfCancellationRequested();
Trace.Info($"Attempt to create session.");
try
{
Trace.Info("Connecting to the Broker Server...");
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
await _brokerServer.ConnectAsync(new Uri(serverUrlV2), _credsV2);
Trace.Info("VssConnection created");
if (!string.IsNullOrEmpty(serverUrl) &&
!string.Equals(serverUrl, serverUrlV2, StringComparison.OrdinalIgnoreCase))
{
Trace.Info("Connecting to the Runner server...");
await _runnerServer.ConnectAsync(new Uri(serverUrl), _creds);
Trace.Info("VssConnection created");
}
_term.WriteLine();
_term.WriteSuccessMessage("Connected to GitHub");
_term.WriteLine();
_session = await _brokerServer.CreateSessionAsync(taskAgentSession, token);
Trace.Info($"Session created.");
if (encounteringError)
{
_term.WriteLine($"{DateTime.UtcNow:u}: Runner reconnected.");
_sessionCreationExceptionTracker.Clear();
encounteringError = false;
}
if (!_handlerInitialized)
{
// Register event handler for auth migration state change
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
_handlerInitialized = true;
}
return CreateSessionResult.Success;
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
Trace.Info("Session creation has been cancelled.");
throw;
}
catch (TaskAgentAccessTokenExpiredException)
{
Trace.Info("Runner OAuth token has been revoked. Session creation failed.");
_accessTokenRevoked = true;
throw;
}
catch (Exception ex)
{
Trace.Error("Catch exception during create session.");
Trace.Error(ex);
// If using migrated settings, limit the number of retries before returning failure
if (_isMigratedSettings)
{
_migratedSettingsRetryCount++;
Trace.Warning($"Migrated settings retry {_migratedSettingsRetryCount} of {_maxMigratedSettingsRetries}");
if (_migratedSettingsRetryCount >= _maxMigratedSettingsRetries)
{
Trace.Warning("Reached maximum retry attempts for migrated settings. Returning failure to try default settings.");
return CreateSessionResult.Failure;
}
}
if (!HostContext.AllowAuthMigration &&
ex is VssOAuthTokenRequestException vssOAuthEx &&
_credsV2.Federated is VssOAuthCredential vssOAuthCred)
{
// "invalid_client" means the runner registration has been deleted from the server.
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
{
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
return CreateSessionResult.Failure;
}
// Check whether we get 401 because the runner registration already removed by the service.
// If the runner registration get deleted, we can't exchange oauth token.
Trace.Error("Test oauth app registration.");
var oauthTokenProvider = new VssOAuthTokenProvider(vssOAuthCred, new Uri(serverUrlV2));
var authError = await oauthTokenProvider.ValidateCredentialAsync(token);
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
{
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
return CreateSessionResult.Failure;
}
}
if (!HostContext.AllowAuthMigration &&
!IsSessionCreationExceptionRetriable(ex))
{
_term.WriteError($"Failed to create session. {ex.Message}");
if (ex is TaskAgentSessionConflictException)
{
return CreateSessionResult.SessionConflict;
}
return CreateSessionResult.Failure;
}
if (HostContext.AllowAuthMigration)
{
Trace.Info("Disable migration mode for 60 minutes.");
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Session creation failed with exception: {ex}");
}
if (!encounteringError) //print the message only on the first error
{
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
encounteringError = true;
}
Trace.Info("Sleeping for {0} seconds before retrying.", _sessionCreationRetryInterval.TotalSeconds);
await HostContext.Delay(_sessionCreationRetryInterval, token);
}
}
}
public async Task DeleteSessionAsync()
{
await Task.CompletedTask;
if (_session != null && _session.SessionId != Guid.Empty)
{
if (_handlerInitialized)
{
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
}
if (!_accessTokenRevoked)
{
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
await _brokerServer.DeleteSessionAsync(ts.Token);
}
}
else
{
Trace.Warning("Runner OAuth token has been revoked. Skip deleting session.");
}
}
}
public void OnJobStatus(object sender, JobStatusEventArgs e)
{
Trace.Info("Received job status event. JobState: {0}", e.Status);
runnerStatus = e.Status;
_runnerStatus = e.Status;
try
{
_getMessagesTokenSource?.Cancel();
@@ -73,7 +283,20 @@ namespace GitHub.Runner.Listener
_getMessagesTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
try
{
message = await _brokerServer.GetRunnerMessageAsync(_getMessagesTokenSource.Token, runnerStatus, BuildConstants.RunnerPackage.Version, VarUtil.OS, VarUtil.OSArchitecture);
if (_needRefreshCredsV2)
{
Trace.Info("Refreshing broker connection.");
await RefreshBrokerConnectionAsync();
_needRefreshCredsV2 = false;
}
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
_runnerStatus,
BuildConstants.RunnerPackage.Version,
VarUtil.OS,
VarUtil.OSArchitecture,
_settings.DisableUpdate,
_getMessagesTokenSource.Token);
if (message == null)
{
@@ -97,7 +320,16 @@ namespace GitHub.Runner.Listener
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
throw;
}
catch (AccessDeniedException e) when (e.ErrorCode == 1)
catch (HostedRunnerDeprovisionedException)
{
Trace.Info("Hosted runner has been deprovisioned.");
throw;
}
catch (AccessDeniedException e) when (e.ErrorCode == 1 && !HostContext.AllowAuthMigration)
{
throw;
}
catch (RunnerNotFoundException) when (!HostContext.AllowAuthMigration)
{
throw;
}
@@ -106,7 +338,8 @@ namespace GitHub.Runner.Listener
Trace.Error("Catch exception during get next message.");
Trace.Error(ex);
if (!IsGetNextMessageExceptionRetriable(ex))
if (!HostContext.AllowAuthMigration &&
!IsGetNextMessageExceptionRetriable(ex))
{
throw new NonRetryableException("Get next message failed with non-retryable error.", ex);
}
@@ -137,8 +370,14 @@ namespace GitHub.Runner.Listener
encounteringError = true;
}
if (HostContext.AllowAuthMigration)
{
Trace.Info("Disable migration mode for 60 minutes.");
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Get next message failed with exception: {ex}");
}
// re-create VssConnection before next retry
await RefreshBrokerConnection();
await RefreshBrokerConnectionAsync();
Trace.Info("Sleeping for {0} seconds before retrying.", _getNextMessageRetryInterval.TotalSeconds);
await HostContext.Delay(_getNextMessageRetryInterval, token);
@@ -168,17 +407,38 @@ namespace GitHub.Runner.Listener
}
}
public async Task RefreshListenerTokenAsync()
{
await RefreshBrokerConnectionAsync();
}
public async Task DeleteMessageAsync(TaskAgentMessage message)
{
await Task.CompletedTask;
}
public async Task AcknowledgeMessageAsync(string runnerRequestId, CancellationToken cancellationToken)
{
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // Short timeout
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
Trace.Info($"Acknowledging runner request '{runnerRequestId}'.");
await _brokerServer.AcknowledgeRunnerRequestAsync(
runnerRequestId,
_session.SessionId,
_runnerStatus,
BuildConstants.RunnerPackage.Version,
VarUtil.OS,
VarUtil.OSArchitecture,
linkedCts.Token);
}
private bool IsGetNextMessageExceptionRetriable(Exception ex)
{
if (ex is TaskAgentNotFoundException ||
ex is TaskAgentPoolNotFoundException ||
ex is TaskAgentSessionExpiredException ||
ex is AccessDeniedException ||
ex is RunnerNotFoundException ||
ex is VssUnauthorizedException)
{
Trace.Info($"Non-retriable exception: {ex.Message}");
@@ -191,19 +451,90 @@ namespace GitHub.Runner.Listener
}
}
private async Task RefreshBrokerConnection()
private bool IsSessionCreationExceptionRetriable(Exception ex)
{
var configManager = HostContext.GetService<IConfigurationManager>();
_settings = configManager.LoadSettings();
if (_settings.ServerUrlV2 == null)
if (ex is TaskAgentNotFoundException)
{
throw new InvalidOperationException("ServerUrlV2 is not set");
Trace.Info("The runner no longer exists on the server. Stopping the runner.");
_term.WriteError("The runner no longer exists on the server. Please reconfigure the runner.");
return false;
}
else if (ex is TaskAgentSessionConflictException)
{
Trace.Info("The session for this runner already exists.");
_term.WriteError("A session for this runner already exists.");
if (_sessionCreationExceptionTracker.ContainsKey(nameof(TaskAgentSessionConflictException)))
{
_sessionCreationExceptionTracker[nameof(TaskAgentSessionConflictException)]++;
if (_sessionCreationExceptionTracker[nameof(TaskAgentSessionConflictException)] * _sessionCreationRetryInterval.TotalSeconds >= _sessionConflictRetryLimit.TotalSeconds)
{
Trace.Info("The session conflict exception have reached retry limit.");
_term.WriteError($"Stop retry on SessionConflictException after retried for {_sessionConflictRetryLimit.TotalSeconds} seconds.");
return false;
}
}
else
{
_sessionCreationExceptionTracker[nameof(TaskAgentSessionConflictException)] = 1;
}
Trace.Info("The session conflict exception haven't reached retry limit.");
return true;
}
else if (ex is VssOAuthTokenRequestException && ex.Message.Contains("Current server time is"))
{
Trace.Info("Local clock might be skewed.");
_term.WriteError("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again.");
if (_sessionCreationExceptionTracker.ContainsKey(nameof(VssOAuthTokenRequestException)))
{
_sessionCreationExceptionTracker[nameof(VssOAuthTokenRequestException)]++;
if (_sessionCreationExceptionTracker[nameof(VssOAuthTokenRequestException)] * _sessionCreationRetryInterval.TotalSeconds >= _clockSkewRetryLimit.TotalSeconds)
{
Trace.Info("The OAuth token request exception have reached retry limit.");
_term.WriteError($"Stopped retrying OAuth token request exception after {_clockSkewRetryLimit.TotalSeconds} seconds.");
return false;
}
}
else
{
_sessionCreationExceptionTracker[nameof(VssOAuthTokenRequestException)] = 1;
}
Trace.Info("The OAuth token request exception haven't reached retry limit.");
return true;
}
else if (ex is TaskAgentPoolNotFoundException ||
ex is AccessDeniedException ||
ex is VssUnauthorizedException)
{
Trace.Info($"Non-retriable exception: {ex.Message}");
return false;
}
var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials creds = credMgr.LoadCredentials();
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), creds);
else if (ex is InvalidOperationException)
{
Trace.Info($"Non-retriable exception: {ex.Message}");
return false;
}
else
{
Trace.Info($"Retriable exception: {ex.Message}");
return true;
}
}
private async Task RefreshBrokerConnectionAsync()
{
Trace.Info("Reload credentials.");
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), _credsV2);
Trace.Info("Connection to Broker Server recreated.");
}
private void HandleAuthMigrationChanged(object sender, EventArgs e)
{
Trace.Info($"Auth migration changed. Current allow auth migration state: {HostContext.AllowAuthMigration}");
_needRefreshCredsV2 = true;
}
}
}

View File

@@ -39,6 +39,7 @@ namespace GitHub.Runner.Listener.Check
string githubApiUrl = null;
string actionsTokenServiceUrl = null;
string actionsPipelinesServiceUrl = null;
string resultsReceiverServiceUrl = null;
var urlBuilder = new UriBuilder(url);
if (UrlUtil.IsHostedServer(urlBuilder))
{
@@ -47,6 +48,7 @@ namespace GitHub.Runner.Listener.Check
githubApiUrl = urlBuilder.Uri.AbsoluteUri;
actionsTokenServiceUrl = "https://vstoken.actions.githubusercontent.com/_apis/health";
actionsPipelinesServiceUrl = "https://pipelines.actions.githubusercontent.com/_apis/health";
resultsReceiverServiceUrl = "https://results-receiver.actions.githubusercontent.com/health";
}
else
{
@@ -56,13 +58,31 @@ namespace GitHub.Runner.Listener.Check
actionsTokenServiceUrl = urlBuilder.Uri.AbsoluteUri;
urlBuilder.Path = "_services/pipelines/_apis/health";
actionsPipelinesServiceUrl = urlBuilder.Uri.AbsoluteUri;
resultsReceiverServiceUrl = string.Empty; // we don't have Results service in GHES yet.
}
var codeLoadUrlBuilder = new UriBuilder(url);
codeLoadUrlBuilder.Host = $"codeload.{codeLoadUrlBuilder.Host}";
codeLoadUrlBuilder.Path = "_ping";
// check github api
checkTasks.Add(CheckUtil.CheckDns(githubApiUrl));
checkTasks.Add(CheckUtil.CheckPing(githubApiUrl));
checkTasks.Add(HostContext.CheckHttpsGetRequests(githubApiUrl, pat, expectedHeader: "X-GitHub-Request-Id"));
// check github codeload
checkTasks.Add(CheckUtil.CheckDns(codeLoadUrlBuilder.Uri.AbsoluteUri));
checkTasks.Add(CheckUtil.CheckPing(codeLoadUrlBuilder.Uri.AbsoluteUri));
checkTasks.Add(HostContext.CheckHttpsGetRequests(codeLoadUrlBuilder.Uri.AbsoluteUri, pat, expectedHeader: "X-GitHub-Request-Id"));
// check results-receiver service
if (!string.IsNullOrEmpty(resultsReceiverServiceUrl))
{
checkTasks.Add(CheckUtil.CheckDns(resultsReceiverServiceUrl));
checkTasks.Add(CheckUtil.CheckPing(resultsReceiverServiceUrl));
checkTasks.Add(HostContext.CheckHttpsGetRequests(resultsReceiverServiceUrl, pat, expectedHeader: "X-GitHub-Request-Id"));
}
// check actions token service
checkTasks.Add(CheckUtil.CheckDns(actionsTokenServiceUrl));
checkTasks.Add(CheckUtil.CheckPing(actionsTokenServiceUrl));

View File

@@ -25,6 +25,7 @@ namespace GitHub.Runner.Listener.Configuration
Task UnconfigureAsync(CommandSettings command);
void DeleteLocalRunnerConfig();
RunnerSettings LoadSettings();
RunnerSettings LoadMigratedSettings();
}
public sealed class ConfigurationManager : RunnerService, IConfigurationManager
@@ -66,6 +67,22 @@ namespace GitHub.Runner.Listener.Configuration
return settings;
}
public RunnerSettings LoadMigratedSettings()
{
Trace.Info(nameof(LoadMigratedSettings));
// Check if migrated settings file exists
if (!_store.IsMigratedConfigured())
{
throw new NonRetryableException("No migrated configuration found.");
}
RunnerSettings settings = _store.GetMigratedSettings();
Trace.Info("Migrated Settings Loaded");
return settings;
}
public async Task ConfigureAsync(CommandSettings command)
{
_term.WriteLine();
@@ -127,7 +144,7 @@ namespace GitHub.Runner.Listener.Configuration
runnerSettings.ServerUrl = inputUrl;
// Get the credentials
credProvider = GetCredentialProvider(command, runnerSettings.ServerUrl);
creds = credProvider.GetVssCredentials(HostContext);
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
Trace.Info("legacy vss cred retrieved");
}
else
@@ -366,10 +383,18 @@ namespace GitHub.Runner.Listener.Configuration
{
{ "clientId", agent.Authorization.ClientId.ToString("D") },
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", false).ToString() }
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", true).ToString() }
},
};
if (agent.Properties.GetValue("EnableAuthMigrationByDefault", false) &&
agent.Properties.TryGetValue<string>("AuthorizationUrlV2", out var authUrlV2) &&
!string.IsNullOrEmpty(authUrlV2))
{
credentialData.Data["enableAuthMigrationByDefault"] = "true";
credentialData.Data["authorizationUrlV2"] = authUrlV2;
}
// Save the negotiated OAuth credential data
_store.SaveCredential(credentialData);
}
@@ -384,7 +409,7 @@ namespace GitHub.Runner.Listener.Configuration
if (!runnerSettings.UseV2Flow)
{
var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials credential = credMgr.LoadCredentials();
VssCredentials credential = credMgr.LoadCredentials(allowAuthUrlV2: false);
try
{
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
@@ -404,6 +429,20 @@ namespace GitHub.Runner.Listener.Configuration
}
}
// allow the server to override the serverUrlV2 and useV2Flow
if (agent.Properties.TryGetValue("ServerUrlV2", out string serverUrlV2) &&
!string.IsNullOrEmpty(serverUrlV2))
{
Trace.Info($"Service enforced serverUrlV2: {serverUrlV2}");
runnerSettings.ServerUrlV2 = serverUrlV2;
}
if (agent.Properties.TryGetValue("UseV2Flow", out bool useV2Flow) && useV2Flow)
{
Trace.Info($"Service enforced useV2Flow: {useV2Flow}");
runnerSettings.UseV2Flow = useV2Flow;
}
_term.WriteSection("Runner settings");
// We will Combine() what's stored with root. Defaults to string a relative path
@@ -498,41 +537,50 @@ namespace GitHub.Runner.Listener.Configuration
if (isConfigured && hasCredentials)
{
RunnerSettings settings = _store.GetSettings();
var credentialManager = HostContext.GetService<ICredentialManager>();
// Get the credentials
VssCredentials creds = null;
if (string.IsNullOrEmpty(settings.GitHubUrl))
{
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
creds = credProvider.GetVssCredentials(HostContext);
Trace.Info("legacy vss cred retrieved");
}
else
if (settings.UseV2Flow)
{
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove);
creds = authResult.ToVssCredentials();
Trace.Info("cred retrieved via GitHub auth");
}
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
var agents = await _runnerServer.GetAgentsAsync(settings.AgentName);
Trace.Verbose("Returns {0} agents", agents.Count);
TaskAgent agent = agents.FirstOrDefault();
if (agent == null)
{
_term.WriteLine("Does not exist. Skipping " + currentAction);
await _dotcomServer.DeleteRunnerAsync(settings.GitHubUrl, deletionToken, settings.AgentId);
}
else
{
await _runnerServer.DeleteAgentAsync(settings.AgentId);
var credentialManager = HostContext.GetService<ICredentialManager>();
_term.WriteLine();
_term.WriteSuccessMessage("Runner removed successfully");
// Get the credentials
VssCredentials creds = null;
if (string.IsNullOrEmpty(settings.GitHubUrl))
{
var credProvider = GetCredentialProvider(command, settings.ServerUrl);
creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false);
Trace.Info("legacy vss cred retrieved");
}
else
{
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove);
creds = authResult.ToVssCredentials();
Trace.Info("cred retrieved via GitHub auth");
}
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
var agents = await _runnerServer.GetAgentsAsync(settings.AgentName);
Trace.Verbose("Returns {0} agents", agents.Count);
TaskAgent agent = agents.FirstOrDefault();
if (agent == null)
{
_term.WriteLine("Does not exist. Skipping " + currentAction);
}
else
{
await _runnerServer.DeleteAgentAsync(settings.AgentId);
}
}
_term.WriteLine();
_term.WriteSuccessMessage("Runner removed successfully");
}
else
{

View File

@@ -13,7 +13,7 @@ namespace GitHub.Runner.Listener.Configuration
public interface ICredentialManager : IRunnerService
{
ICredentialProvider GetCredentialProvider(string credType);
VssCredentials LoadCredentials();
VssCredentials LoadCredentials(bool allowAuthUrlV2);
}
public class CredentialManager : RunnerService, ICredentialManager
@@ -40,7 +40,7 @@ namespace GitHub.Runner.Listener.Configuration
return creds;
}
public VssCredentials LoadCredentials()
public VssCredentials LoadCredentials(bool allowAuthUrlV2)
{
IConfigurationStore store = HostContext.GetService<IConfigurationStore>();
@@ -51,21 +51,16 @@ namespace GitHub.Runner.Listener.Configuration
CredentialData credData = store.GetCredentials();
var migratedCred = store.GetMigratedCredentials();
if (migratedCred != null)
if (migratedCred != null &&
migratedCred.Scheme == Constants.Configuration.OAuth)
{
credData = migratedCred;
// Re-write .credentials with Token URL
store.SaveCredential(credData);
// Delete .credentials_migrated
store.DeleteMigratedCredential();
}
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
credProv.CredentialData = credData;
VssCredentials creds = credProv.GetVssCredentials(HostContext);
VssCredentials creds = credProv.GetVssCredentials(HostContext, allowAuthUrlV2);
return creds;
}

View File

@@ -1,7 +1,7 @@
using System;
using GitHub.Services.Common;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.OAuth;
namespace GitHub.Runner.Listener.Configuration
@@ -10,7 +10,7 @@ namespace GitHub.Runner.Listener.Configuration
{
Boolean RequireInteractive { get; }
CredentialData CredentialData { get; set; }
VssCredentials GetVssCredentials(IHostContext context);
VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2);
void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
}
@@ -25,7 +25,7 @@ namespace GitHub.Runner.Listener.Configuration
public virtual Boolean RequireInteractive => false;
public CredentialData CredentialData { get; set; }
public abstract VssCredentials GetVssCredentials(IHostContext context);
public abstract VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2);
public abstract void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
}
@@ -33,7 +33,7 @@ namespace GitHub.Runner.Listener.Configuration
{
public OAuthAccessTokenCredential() : base(Constants.Configuration.OAuthAccessToken) { }
public override VssCredentials GetVssCredentials(IHostContext context)
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2)
{
ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(OAuthAccessTokenCredential));

View File

@@ -22,10 +22,18 @@ namespace GitHub.Runner.Listener.Configuration
// Nothing to verify here
}
public override VssCredentials GetVssCredentials(IHostContext context)
public override VssCredentials GetVssCredentials(IHostContext context, bool allowAuthUrlV2)
{
var clientId = this.CredentialData.Data.GetValueOrDefault("clientId", null);
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null);
var authorizationUrlV2 = this.CredentialData.Data.GetValueOrDefault("authorizationUrlV2", null);
if (allowAuthUrlV2 &&
!string.IsNullOrEmpty(authorizationUrlV2) &&
context.AllowAuthMigration)
{
authorizationUrl = authorizationUrlV2;
}
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
var oauthEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);

View File

@@ -1,4 +1,5 @@
#if OS_WINDOWS
#pragma warning disable CA1416
using System.IO;
using System.Security.Cryptography;
using System.Text;
@@ -84,4 +85,5 @@ namespace GitHub.Runner.Listener.Configuration
}
}
}
#pragma warning restore CA1416
#endif

View File

@@ -0,0 +1,44 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Runner.Common;
using GitHub.Services.Common;
namespace GitHub.Runner.Listener
{
[ServiceLocator(Default = typeof(ErrorThrottler))]
public interface IErrorThrottler : IRunnerService
{
void Reset();
Task IncrementAndWaitAsync(CancellationToken token);
}
public sealed class ErrorThrottler : RunnerService, IErrorThrottler
{
internal static readonly TimeSpan MinBackoff = TimeSpan.FromSeconds(1);
internal static readonly TimeSpan MaxBackoff = TimeSpan.FromMinutes(1);
internal static readonly TimeSpan BackoffCoefficient = TimeSpan.FromSeconds(1);
private int _count = 0;
public void Reset()
{
_count = 0;
}
public async Task IncrementAndWaitAsync(CancellationToken token)
{
if (++_count <= 1)
{
return;
}
TimeSpan backoff = BackoffTimerHelper.GetExponentialBackoff(
attempt: _count - 2, // 0-based attempt
minBackoff: MinBackoff,
maxBackoff: MaxBackoff,
deltaBackoff: BackoffCoefficient);
Trace.Warning($"Back off {backoff.TotalSeconds} seconds before next attempt. Current consecutive error count: {_count}");
await HostContext.Delay(backoff, token);
}
}
}

View File

@@ -35,7 +35,7 @@ namespace GitHub.Runner.Listener
// This implementation of IJobDispatcher is not thread safe.
// It is based on the fact that the current design of the runner is a dequeue
// and processes one message from the message queue at a time.
// In addition, it only executes one job every time,
// In addition, it only executes one job every time,
// and the server will not send another job while this one is still running.
public sealed class JobDispatcher : RunnerService, IJobDispatcher
{
@@ -110,7 +110,12 @@ namespace GitHub.Runner.Listener
{
var jwt = JsonWebToken.Create(accessToken);
var claims = jwt.ExtractClaims();
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orch_id", StringComparison.OrdinalIgnoreCase))?.Value;
if (string.IsNullOrEmpty(orchestrationId))
{
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
}
if (!string.IsNullOrEmpty(orchestrationId))
{
Trace.Info($"Pull OrchestrationId {orchestrationId} from JWT claims");
@@ -545,14 +550,36 @@ namespace GitHub.Runner.Listener
detailInfo = string.Join(Environment.NewLine, workerOutput);
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
var jobServer = await InitializeJobServerAsync(systemConnection);
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
try
{
Trace.Info($"Finish job with result 'Failed' due to IOException.");
await ForceFailJob(jobServer, message, detailInfo);
var jobServer = await InitializeJobServerAsync(systemConnection);
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
switch (jobServer)
{
case IJobServer js:
{
await LogWorkerProcessUnhandledException(js, message, unhandledExceptionIssue);
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
{
Trace.Info($"Finish job with result 'Failed' due to IOException.");
await ForceFailJob(js, message);
}
break;
}
case IRunServer rs:
await ForceFailJob(rs, message, unhandledExceptionIssue);
break;
default:
throw new NotSupportedException($"JobServer type '{jobServer.GetType().Name}' is not supported.");
}
}
catch (Exception ex)
{
Trace.Error($"Catch exception during log worker process unhandled exception.");
Trace.Error(ex);
}
}
@@ -629,8 +656,22 @@ namespace GitHub.Runner.Listener
Trace.Info("worker process has been killed.");
}
}
catch (Exception ex)
{
// message send failed, this might indicate worker process is already exited or stuck.
Trace.Info($"Job cancel message sending for job {message.JobId} failed, kill running worker. {ex}");
workerProcessCancelTokenSource.Cancel();
try
{
await workerProcessTask;
}
catch (OperationCanceledException)
{
Trace.Info("worker process has been killed.");
}
}
// wait worker to exit
// wait worker to exit
// if worker doesn't exit within timeout, then kill worker.
completedTask = await Task.WhenAny(workerProcessTask, Task.Delay(-1, workerCancelTimeoutKillToken));
@@ -1117,77 +1158,65 @@ namespace GitHub.Runner.Listener
}
// log an error issue to job level timeline record
private async Task LogWorkerProcessUnhandledException(IRunnerService server, Pipelines.AgentJobRequestMessage message, string detailInfo)
private async Task LogWorkerProcessUnhandledException(IJobServer jobServer, Pipelines.AgentJobRequestMessage message, Issue issue)
{
if (server is IJobServer jobServer)
try
{
try
{
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
ArgUtil.NotNull(timeline, nameof(timeline));
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
ArgUtil.NotNull(timeline, nameof(timeline));
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
jobRecord.ErrorCount++;
jobRecord.Issues.Add(unhandledExceptionIssue);
jobRecord.ErrorCount++;
jobRecord.Issues.Add(issue);
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
}
catch (Exception ex)
{
Trace.Error("Fail to report unhandled exception from Runner.Worker process");
Trace.Error(ex);
}
Trace.Info("Mark the job as failed since the worker crashed");
jobRecord.Result = TaskResult.Failed;
// mark the job as completed so service will pickup the result
jobRecord.State = TimelineRecordState.Completed;
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
}
else
catch (Exception ex)
{
Trace.Info("Job server does not support handling unhandled exception yet, error message: {0}", detailInfo);
return;
Trace.Error("Fail to report unhandled exception from Runner.Worker process");
Trace.Error(ex);
}
}
// raise job completed event to fail the job.
private async Task ForceFailJob(IRunnerService server, Pipelines.AgentJobRequestMessage message, string detailInfo)
private async Task ForceFailJob(IJobServer jobServer, Pipelines.AgentJobRequestMessage message)
{
if (server is IJobServer jobServer)
try
{
try
{
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
}
catch (Exception ex)
{
Trace.Error("Fail to raise JobCompletedEvent back to service.");
Trace.Error(ex);
}
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
}
else if (server is IRunServer runServer)
catch (Exception ex)
{
try
{
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = detailInfo };
var unhandledAnnotation = unhandledExceptionIssue.ToAnnotation();
var jobAnnotations = new List<Annotation>();
if (unhandledAnnotation.HasValue)
{
jobAnnotations.Add(unhandledAnnotation.Value);
}
Trace.Error("Fail to raise JobCompletedEvent back to service.");
Trace.Error(ex);
}
}
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, CancellationToken.None);
}
catch (Exception ex)
{
Trace.Error("Fail to raise job completion back to service.");
Trace.Error(ex);
}
}
else
private async Task ForceFailJob(IRunServer runServer, Pipelines.AgentJobRequestMessage message, Issue issue)
{
try
{
throw new NotSupportedException($"Server type {server.GetType().FullName} is not supported.");
var annotation = issue.ToAnnotation();
var jobAnnotations = new List<Annotation>();
if (annotation.HasValue)
{
jobAnnotations.Add(annotation.Value);
}
await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, telemetry: null, billingOwnerId: message.BillingOwnerId, CancellationToken.None);
}
catch (Exception ex)
{
Trace.Error("Fail to raise job completion back to service.");
Trace.Error(ex);
}
}

View File

@@ -14,16 +14,27 @@ using GitHub.Runner.Listener.Configuration;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.OAuth;
using GitHub.Services.WebApi;
namespace GitHub.Runner.Listener
{
public enum CreateSessionResult
{
Success,
Failure,
SessionConflict
}
[ServiceLocator(Default = typeof(MessageListener))]
public interface IMessageListener : IRunnerService
{
Task<Boolean> CreateSessionAsync(CancellationToken token);
Task<CreateSessionResult> CreateSessionAsync(CancellationToken token);
Task DeleteSessionAsync();
Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token);
Task DeleteMessageAsync(TaskAgentMessage message);
Task AcknowledgeMessageAsync(string runnerRequestId, CancellationToken cancellationToken);
Task RefreshListenerTokenAsync();
void OnJobStatus(object sender, JobStatusEventArgs e);
}
@@ -33,6 +44,8 @@ namespace GitHub.Runner.Listener
private RunnerSettings _settings;
private ITerminal _term;
private IRunnerServer _runnerServer;
private IBrokerServer _brokerServer;
private ICredentialManager _credMgr;
private TaskAgentSession _session;
private TimeSpan _getNextMessageRetryInterval;
private bool _accessTokenRevoked = false;
@@ -40,8 +53,12 @@ namespace GitHub.Runner.Listener
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new();
private TaskAgentStatus runnerStatus = TaskAgentStatus.Online;
private TaskAgentStatus _runnerStatus = TaskAgentStatus.Online;
private CancellationTokenSource _getMessagesTokenSource;
private VssCredentials _creds;
private VssCredentials _credsV2;
private bool _needRefreshCredsV2 = false;
private bool _handlerInitialized = false;
public override void Initialize(IHostContext hostContext)
{
@@ -49,9 +66,11 @@ namespace GitHub.Runner.Listener
_term = HostContext.GetService<ITerminal>();
_runnerServer = HostContext.GetService<IRunnerServer>();
_brokerServer = hostContext.GetService<IBrokerServer>();
_credMgr = hostContext.GetService<ICredentialManager>();
}
public async Task<Boolean> CreateSessionAsync(CancellationToken token)
public async Task<CreateSessionResult> CreateSessionAsync(CancellationToken token)
{
Trace.Entering();
@@ -63,8 +82,7 @@ namespace GitHub.Runner.Listener
// Create connection.
Trace.Info("Loading Credentials");
var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials creds = credMgr.LoadCredentials();
_creds = _credMgr.LoadCredentials(allowAuthUrlV2: false);
var agent = new TaskAgentReference
{
@@ -73,7 +91,8 @@ namespace GitHub.Runner.Listener
Version = BuildConstants.RunnerPackage.Version,
OSDescription = RuntimeInformation.OSDescription,
};
string sessionName = $"{Environment.MachineName ?? "RUNNER"}";
var currentProcess = Process.GetCurrentProcess();
string sessionName = $"{Environment.MachineName ?? "RUNNER"} (PID: {currentProcess.Id})";
var taskAgentSession = new TaskAgentSession(sessionName, agent);
string errorMessage = string.Empty;
@@ -86,7 +105,7 @@ namespace GitHub.Runner.Listener
try
{
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");
_term.WriteLine();
@@ -97,7 +116,6 @@ namespace GitHub.Runner.Listener
_settings.PoolId,
taskAgentSession,
token);
Trace.Info($"Session created.");
if (encounteringError)
{
@@ -106,7 +124,14 @@ namespace GitHub.Runner.Listener
encounteringError = false;
}
return true;
if (!_handlerInitialized)
{
Trace.Info("Registering AuthMigrationChanged event handler.");
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
_handlerInitialized = true;
}
return CreateSessionResult.Success;
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
@@ -124,13 +149,13 @@ namespace GitHub.Runner.Listener
Trace.Error("Catch exception during create session.");
Trace.Error(ex);
if (ex is VssOAuthTokenRequestException vssOAuthEx && creds.Federated is VssOAuthCredential vssOAuthCred)
if (ex is VssOAuthTokenRequestException vssOAuthEx && _creds.Federated is VssOAuthCredential vssOAuthCred)
{
// "invalid_client" means the runner registration has been deleted from the server.
if (string.Equals(vssOAuthEx.Error, "invalid_client", StringComparison.OrdinalIgnoreCase))
{
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
return false;
return CreateSessionResult.Failure;
}
// Check whether we get 401 because the runner registration already removed by the service.
@@ -141,14 +166,18 @@ namespace GitHub.Runner.Listener
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
{
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure. Runner registrations are automatically deleted for runners that have not connected to the service recently.");
return false;
return CreateSessionResult.Failure;
}
}
if (!IsSessionCreationExceptionRetriable(ex))
{
_term.WriteError($"Failed to create session. {ex.Message}");
return false;
if (ex is TaskAgentSessionConflictException)
{
return CreateSessionResult.SessionConflict;
}
return CreateSessionResult.Failure;
}
if (!encounteringError) //print the message only on the first error
@@ -167,6 +196,11 @@ namespace GitHub.Runner.Listener
{
if (_session != null && _session.SessionId != Guid.Empty)
{
if (_handlerInitialized)
{
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
}
if (!_accessTokenRevoked)
{
using (var ts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
@@ -183,19 +217,17 @@ namespace GitHub.Runner.Listener
public void OnJobStatus(object sender, JobStatusEventArgs e)
{
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("USE_BROKER_FLOW")))
Trace.Info("Received job status event. JobState: {0}", e.Status);
_runnerStatus = e.Status;
try
{
Trace.Info("Received job status event. JobState: {0}", e.Status);
runnerStatus = e.Status;
try
{
_getMessagesTokenSource?.Cancel();
}
catch (ObjectDisposedException)
{
Trace.Info("_getMessagesTokenSource is already disposed.");
}
_getMessagesTokenSource?.Cancel();
}
catch (ObjectDisposedException)
{
Trace.Info("_getMessagesTokenSource is already disposed.");
}
}
public async Task<TaskAgentMessage> GetNextMessageAsync(CancellationToken token)
@@ -205,6 +237,7 @@ namespace GitHub.Runner.Listener
ArgUtil.NotNull(_settings, nameof(_settings));
bool encounteringError = false;
int continuousError = 0;
int continuousEmptyMessage = 0;
string errorMessage = string.Empty;
Stopwatch heartbeat = new();
heartbeat.Restart();
@@ -218,15 +251,38 @@ namespace GitHub.Runner.Listener
message = await _runnerServer.GetAgentMessageAsync(_settings.PoolId,
_session.SessionId,
_lastMessageId,
runnerStatus,
_runnerStatus,
BuildConstants.RunnerPackage.Version,
VarUtil.OS,
VarUtil.OSArchitecture,
_settings.DisableUpdate,
_getMessagesTokenSource.Token);
// Decrypt the message body if the session is using encryption
message = DecryptMessage(message);
if (message != null && message.MessageType == BrokerMigrationMessage.MessageType)
{
var migrationMessage = JsonUtility.FromString<BrokerMigrationMessage>(message.Body);
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
await _brokerServer.UpdateConnectionIfNeeded(migrationMessage.BrokerBaseUrl, _credsV2);
if (_needRefreshCredsV2)
{
Trace.Info("Refreshing credentials for V2.");
await _brokerServer.ForceRefreshConnection(_credsV2);
_needRefreshCredsV2 = false;
}
message = await _brokerServer.GetRunnerMessageAsync(_session.SessionId,
_runnerStatus,
BuildConstants.RunnerPackage.Version,
VarUtil.OS,
VarUtil.OSArchitecture,
_settings.DisableUpdate,
token);
}
if (message != null)
{
_lastMessageId = message.MessageId;
@@ -255,7 +311,16 @@ namespace GitHub.Runner.Listener
_accessTokenRevoked = true;
throw;
}
catch (AccessDeniedException e) when (e.ErrorCode == 1)
catch (HostedRunnerDeprovisionedException)
{
Trace.Info("Hosted runner has been deprovisioned.");
throw;
}
catch (AccessDeniedException e) when (e.ErrorCode == 1 && !HostContext.AllowAuthMigration)
{
throw;
}
catch (RunnerNotFoundException) when (!HostContext.AllowAuthMigration)
{
throw;
}
@@ -264,12 +329,19 @@ namespace GitHub.Runner.Listener
Trace.Error("Catch exception during get next message.");
Trace.Error(ex);
// clear out potential message for broker migration,
// in case the exception is thrown from get message from broker-listener.
message = null;
// 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))
if (!HostContext.AllowAuthMigration &&
ex is TaskAgentSessionExpiredException &&
!_settings.SkipSessionRecover && (await CreateSessionAsync(token) == CreateSessionResult.Success))
{
Trace.Info($"{nameof(TaskAgentSessionExpiredException)} received, recovered by recreate session.");
}
else if (!IsGetNextMessageExceptionRetriable(ex))
else if (!HostContext.AllowAuthMigration &&
!IsGetNextMessageExceptionRetriable(ex))
{
throw;
}
@@ -296,6 +368,12 @@ namespace GitHub.Runner.Listener
encounteringError = true;
}
if (HostContext.AllowAuthMigration)
{
Trace.Info("Disable migration mode for 60 minutes.");
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Get next message failed with exception: {ex}");
}
// re-create VssConnection before next retry
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
@@ -310,16 +388,27 @@ namespace GitHub.Runner.Listener
if (message == null)
{
continuousEmptyMessage++;
if (heartbeat.Elapsed > TimeSpan.FromMinutes(30))
{
Trace.Info($"No message retrieved from session '{_session.SessionId}' within last 30 minutes.");
heartbeat.Restart();
continuousEmptyMessage = 0;
}
else
{
Trace.Verbose($"No message retrieved from session '{_session.SessionId}'.");
}
if (continuousEmptyMessage > 50)
{
// retried more than 50 times in less than 30mins and still getting empty message
// something is not right on the service side, backoff for 15-30s before retry
_getNextMessageRetryInterval = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(30), _getNextMessageRetryInterval);
Trace.Info("Sleeping for {0} seconds before retrying.", _getNextMessageRetryInterval.TotalSeconds);
await HostContext.Delay(_getNextMessageRetryInterval, token);
}
continue;
}
@@ -342,6 +431,28 @@ namespace GitHub.Runner.Listener
}
}
public async Task RefreshListenerTokenAsync()
{
await _runnerServer.RefreshConnectionAsync(RunnerConnectionType.MessageQueue, TimeSpan.FromSeconds(60));
_credsV2 = _credMgr.LoadCredentials(allowAuthUrlV2: true);
await _brokerServer.ForceRefreshConnection(_credsV2);
}
public async Task AcknowledgeMessageAsync(string runnerRequestId, CancellationToken cancellationToken)
{
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // Short timeout
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
Trace.Info($"Acknowledging runner request '{runnerRequestId}'.");
await _brokerServer.AcknowledgeRunnerRequestAsync(
runnerRequestId,
_session.SessionId,
_runnerStatus,
BuildConstants.RunnerPackage.Version,
VarUtil.OS,
VarUtil.OSArchitecture,
linkedCts.Token);
}
private TaskAgentMessage DecryptMessage(TaskAgentMessage message)
{
if (_session.EncryptionKey == null ||
@@ -391,6 +502,7 @@ namespace GitHub.Runner.Listener
ex is TaskAgentPoolNotFoundException ||
ex is TaskAgentSessionExpiredException ||
ex is AccessDeniedException ||
ex is RunnerNotFoundException ||
ex is VssUnauthorizedException)
{
Trace.Info($"Non-retriable exception: {ex.Message}");
@@ -457,7 +569,8 @@ namespace GitHub.Runner.Listener
}
else if (ex is TaskAgentPoolNotFoundException ||
ex is AccessDeniedException ||
ex is VssUnauthorizedException)
ex is VssUnauthorizedException ||
(ex is VssOAuthTokenRequestException oauthEx && oauthEx.Error != "server_error"))
{
Trace.Info($"Non-retriable exception: {ex.Message}");
return false;
@@ -468,5 +581,11 @@ namespace GitHub.Runner.Listener
return true;
}
}
private void HandleAuthMigrationChanged(object sender, EventArgs e)
{
Trace.Info($"Auth migration changed. Current allow auth migration state: {HostContext.AllowAuthMigration}");
_needRefreshCredsV2 = true;
}
}
}

View File

@@ -7,6 +7,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Services.WebApi;
namespace GitHub.Runner.Listener
{
@@ -144,6 +145,12 @@ namespace GitHub.Runner.Listener
trace.Error(e);
return Constants.Runner.ReturnCode.TerminatedError;
}
catch (RunnerNotFoundException e)
{
terminal.WriteError($"An error occurred: {e.Message}");
trace.Error(e);
return Constants.Runner.ReturnCode.TerminatedError;
}
catch (Exception e)
{
terminal.WriteError($"An error occurred: {e.Message}");

View File

@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
<SelfContained>true</SelfContained>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<NoWarn>NU1701;NU1603</NoWarn>
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
<Version>$(Version)</Version>
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
@@ -18,17 +19,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\Misc\runnercoreassets">
<LogicalName>GitHub.Runner.Listener.runnercoreassets</LogicalName>
</EmbeddedResource>
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Security.Claims;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -14,7 +16,9 @@ using GitHub.Runner.Common.Util;
using GitHub.Runner.Listener.Check;
using GitHub.Runner.Listener.Configuration;
using GitHub.Runner.Sdk;
using GitHub.Services.OAuth;
using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Jwt;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Listener
@@ -31,11 +35,35 @@ namespace GitHub.Runner.Listener
private ITerminal _term;
private bool _inConfigStage;
private ManualResetEvent _completedCommand = new(false);
private readonly ConcurrentQueue<string> _authMigrationTelemetries = new();
private Task _authMigrationTelemetryTask;
private readonly object _authMigrationTelemetryLock = new();
private Task _authMigrationClaimsCheckTask;
private readonly object _authMigrationClaimsCheckLock = new();
private IRunnerServer _runnerServer;
private CancellationTokenSource _authMigrationTelemetryTokenSource = new();
private CancellationTokenSource _authMigrationClaimsCheckTokenSource = new();
// <summary>
// Helps avoid excessive calls to Run Service when encountering non-retriable errors from /acquirejob.
// Normally we rely on the HTTP clients to back off between retry attempts. However, acquiring a job
// involves calls to both Run Serivce and Broker. And Run Service and Broker communicate with each other
// in an async fashion.
//
// When Run Service encounters a non-retriable error, it sends an async message to Broker. The runner will,
// however, immediately call Broker to get the next message. If the async event from Run Service to Broker
// has not yet been processed, the next message from Broker may be the same job message.
//
// The error throttler helps us back off when encountering successive, non-retriable errors from /acquirejob.
// </summary>
private IErrorThrottler _acquireJobThrottler;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_term = HostContext.GetService<ITerminal>();
_acquireJobThrottler = HostContext.CreateService<IErrorThrottler>();
_runnerServer = HostContext.GetService<IRunnerServer>();
}
public async Task<int> ExecuteCommand(CommandSettings command)
@@ -51,6 +79,8 @@ namespace GitHub.Runner.Listener
//register a SIGTERM handler
HostContext.Unloading += Runner_Unloading;
HostContext.AuthMigrationChanged += HandleAuthMigrationChanged;
// TODO Unit test to cover this logic
Trace.Info(nameof(ExecuteCommand));
var configManager = HostContext.GetService<IConfigurationManager>();
@@ -213,15 +243,21 @@ namespace GitHub.Runner.Listener
var configFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), config.Key);
var configContent = Convert.FromBase64String(config.Value);
#if OS_WINDOWS
#pragma warning disable CA1416
if (configFile == HostContext.GetConfigFile(WellKnownConfigFile.RSACredentials))
{
configContent = ProtectedData.Protect(configContent, null, DataProtectionScope.LocalMachine);
}
#pragma warning restore CA1416
#endif
File.WriteAllBytes(configFile, configContent);
File.SetAttributes(configFile, File.GetAttributes(configFile) | FileAttributes.Hidden);
Trace.Info($"Saved {configContent.Length} bytes to '{configFile}'.");
}
// make sure we have the right user agent data added from the jitconfig
HostContext.LoadDefaultUserAgents();
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
}
catch (Exception ex)
{
@@ -279,8 +315,17 @@ namespace GitHub.Runner.Listener
_term.WriteLine("https://docs.github.com/en/actions/hosting-your-own-runners/autoscaling-with-self-hosted-runners#using-ephemeral-runners-for-autoscaling", ConsoleColor.Yellow);
}
var cred = store.GetCredentials();
if (cred != null &&
cred.Scheme == Constants.Configuration.OAuth &&
cred.Data.ContainsKey("EnableAuthMigrationByDefault"))
{
Trace.Info("Enable auth migration by default.");
HostContext.EnableAuthMigration("EnableAuthMigrationByDefault");
}
// Run the runner interactively or as service
return await RunAsync(settings, command.RunOnce || settings.Ephemeral);
return await ExecuteRunnerAsync(settings, command.RunOnce || settings.Ephemeral);
}
else
{
@@ -290,6 +335,9 @@ namespace GitHub.Runner.Listener
}
finally
{
_authMigrationClaimsCheckTokenSource?.Cancel();
_authMigrationTelemetryTokenSource?.Cancel();
HostContext.AuthMigrationChanged -= HandleAuthMigrationChanged;
_term.CancelKeyPress -= CtrlCHandler;
HostContext.Unloading -= Runner_Unloading;
_completedCommand.Set();
@@ -339,12 +387,12 @@ namespace GitHub.Runner.Listener
}
}
private IMessageListener GetMesageListener(RunnerSettings settings)
private IMessageListener GetMessageListener(RunnerSettings settings, bool isMigratedSettings = false)
{
if (settings.UseV2Flow)
{
Trace.Info($"Using BrokerMessageListener");
var brokerListener = new BrokerMessageListener();
var brokerListener = new BrokerMessageListener(settings, isMigratedSettings);
brokerListener.Initialize(HostContext);
return brokerListener;
}
@@ -358,10 +406,65 @@ namespace GitHub.Runner.Listener
try
{
Trace.Info(nameof(RunAsync));
_listener = GetMesageListener(settings);
if (!await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken))
// First try using migrated settings if available
var configManager = HostContext.GetService<IConfigurationManager>();
RunnerSettings migratedSettings = null;
try
{
return Constants.Runner.ReturnCode.TerminatedError;
migratedSettings = configManager.LoadMigratedSettings();
Trace.Info("Loaded migrated settings from .runner_migrated file");
Trace.Info(migratedSettings);
}
catch (Exception ex)
{
// If migrated settings file doesn't exist or can't be loaded, we'll use the provided settings
Trace.Info($"Failed to load migrated settings: {ex.Message}");
}
bool usedMigratedSettings = false;
if (migratedSettings != null)
{
// Try to create session with migrated settings first
Trace.Info("Attempting to create session using migrated settings");
_listener = GetMessageListener(migratedSettings, isMigratedSettings: true);
try
{
CreateSessionResult createSessionResult = await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken);
if (createSessionResult == CreateSessionResult.Success)
{
Trace.Info("Successfully created session with migrated settings");
settings = migratedSettings; // Use migrated settings for the rest of the process
usedMigratedSettings = true;
}
else
{
Trace.Warning($"Failed to create session with migrated settings: {createSessionResult}");
}
}
catch (Exception ex)
{
Trace.Error($"Exception when creating session with migrated settings: {ex}");
}
}
// If migrated settings weren't used or session creation failed, use original settings
if (!usedMigratedSettings)
{
Trace.Info("Falling back to original .runner settings");
_listener = GetMessageListener(settings);
CreateSessionResult createSessionResult = await _listener.CreateSessionAsync(HostContext.RunnerShutdownToken);
if (createSessionResult == CreateSessionResult.SessionConflict)
{
return Constants.Runner.ReturnCode.SessionConflict;
}
else if (createSessionResult == CreateSessionResult.Failure)
{
return Constants.Runner.ReturnCode.TerminatedError;
}
}
HostContext.WritePerfCounter("SessionCreated");
@@ -375,6 +478,8 @@ namespace GitHub.Runner.Listener
// Should we try to cleanup ephemeral runners
bool runOnceJobCompleted = false;
bool skipSessionDeletion = false;
bool restartSession = false; // Flag to indicate session restart
bool restartSessionPending = false;
try
{
var notification = HostContext.GetService<IJobNotification>();
@@ -390,6 +495,15 @@ namespace GitHub.Runner.Listener
while (!HostContext.RunnerShutdownToken.IsCancellationRequested)
{
// Check if we need to restart the session and can do so (job dispatcher not busy)
if (restartSessionPending && !jobDispatcher.Busy)
{
Trace.Info("Pending session restart detected and job dispatcher is not busy. Restarting session now.");
messageQueueLoopTokenSource.Cancel();
restartSession = true;
break;
}
TaskAgentMessage message = null;
bool skipMessageDeletion = false;
try
@@ -457,22 +571,13 @@ namespace GitHub.Runner.Listener
message = await getNextMessage; //get next message
HostContext.WritePerfCounter($"MessageReceived_{message.MessageType}");
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase) ||
string.Equals(message.MessageType, RunnerRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
{
if (autoUpdateInProgress == false)
{
autoUpdateInProgress = true;
AgentRefreshMessage runnerUpdateMessage = null;
if (string.Equals(message.MessageType, AgentRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
{
runnerUpdateMessage = JsonUtility.FromString<AgentRefreshMessage>(message.Body);
}
else
{
var brokerRunnerUpdateMessage = JsonUtility.FromString<RunnerRefreshMessage>(message.Body);
runnerUpdateMessage = new AgentRefreshMessage(brokerRunnerUpdateMessage.RunnerId, brokerRunnerUpdateMessage.TargetVersion, TimeSpan.FromSeconds(brokerRunnerUpdateMessage.TimeoutInSeconds));
}
AgentRefreshMessage runnerUpdateMessage = JsonUtility.FromString<AgentRefreshMessage>(message.Body);
#if DEBUG
// Can mock the update for testing
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE")))
@@ -503,6 +608,22 @@ namespace GitHub.Runner.Listener
Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running.");
}
}
else if (string.Equals(message.MessageType, RunnerRefreshMessage.MessageType, StringComparison.OrdinalIgnoreCase))
{
if (autoUpdateInProgress == false)
{
autoUpdateInProgress = true;
RunnerRefreshMessage brokerRunnerUpdateMessage = JsonUtility.FromString<RunnerRefreshMessage>(message.Body);
var selfUpdater = HostContext.GetService<ISelfUpdaterV2>();
selfUpdateTask = selfUpdater.SelfUpdate(brokerRunnerUpdateMessage, jobDispatcher, false, HostContext.RunnerShutdownToken);
Trace.Info("Refresh message received, kick-off selfupdate background process.");
}
else
{
Trace.Info("Refresh message received, skip autoupdate since a previous autoupdate is already running.");
}
}
else if (string.Equals(message.MessageType, JobRequestMessageTypes.PipelineAgentJobRequest, StringComparison.OrdinalIgnoreCase))
{
if (autoUpdateInProgress || runOnceJobReceived)
@@ -533,36 +654,74 @@ namespace GitHub.Runner.Listener
else
{
var messageRef = StringUtil.ConvertFromJson<RunnerJobRequestRef>(message.Body);
// Acknowledge (best-effort)
if (messageRef.ShouldAcknowledge) // Temporary feature flag
{
try
{
await _listener.AcknowledgeMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
}
catch (Exception ex)
{
Trace.Error($"Best-effort acknowledge failed for request '{messageRef.RunnerRequestId}'");
Trace.Error(ex);
}
}
Pipelines.AgentJobRequestMessage jobRequestMessage = null;
// Create connection
var credMgr = HostContext.GetService<ICredentialManager>();
var creds = credMgr.LoadCredentials();
if (string.IsNullOrEmpty(messageRef.RunServiceUrl))
{
// Connect
var credMgr = HostContext.GetService<ICredentialManager>();
var creds = credMgr.LoadCredentials(allowAuthUrlV2: false);
var actionsRunServer = HostContext.CreateService<IActionsRunServer>();
await actionsRunServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
// Get job message
jobRequestMessage = await actionsRunServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageQueueLoopTokenSource.Token);
}
else
{
// Connect
var credMgr = HostContext.GetService<ICredentialManager>();
var credsV2 = credMgr.LoadCredentials(allowAuthUrlV2: true);
var runServer = HostContext.CreateService<IRunServer>();
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), creds);
await runServer.ConnectAsync(new Uri(messageRef.RunServiceUrl), credsV2);
// Get job message
try
{
jobRequestMessage =
await runServer.GetJobMessageAsync(messageRef.RunnerRequestId,
messageQueueLoopTokenSource.Token);
jobRequestMessage = await runServer.GetJobMessageAsync(messageRef.RunnerRequestId, messageRef.BillingOwnerId, messageQueueLoopTokenSource.Token);
_acquireJobThrottler.Reset();
}
catch (TaskOrchestrationJobAlreadyAcquiredException)
catch (Exception ex) when (
ex is TaskOrchestrationJobNotFoundException || // HTTP status 404
ex is TaskOrchestrationJobAlreadyAcquiredException || // HTTP status 409
ex is TaskOrchestrationJobUnprocessableException) // HTTP status 422
{
Trace.Info("Job is already acquired, skip this message.");
Trace.Info($"Skipping message Job. {ex.Message}");
await _acquireJobThrottler.IncrementAndWaitAsync(messageQueueLoopTokenSource.Token);
continue;
}
catch (Exception ex)
{
Trace.Error($"Caught exception from acquiring job message: {ex}");
if (HostContext.AllowAuthMigration)
{
Trace.Info("Disable migration mode for 60 minutes.");
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Acquire job failed with exception: {ex}");
}
continue;
}
}
// Dispatch
jobDispatcher.Run(jobRequestMessage, runOnce);
// Run once?
if (runOnce)
{
Trace.Info("One time used runner received job message.");
@@ -589,6 +748,33 @@ namespace GitHub.Runner.Listener
Trace.Info($"Service requests the hosted runner to shutdown. Reason: '{HostedRunnerShutdownMessage.Reason}'.");
return Constants.Runner.ReturnCode.Success;
}
else if (string.Equals(message.MessageType, TaskAgentMessageTypes.ForceTokenRefresh))
{
Trace.Info("Received ForceTokenRefreshMessage");
await _listener.RefreshListenerTokenAsync();
}
else if (string.Equals(message.MessageType, RunnerRefreshConfigMessage.MessageType))
{
var runnerRefreshConfigMessage = JsonUtility.FromString<RunnerRefreshConfigMessage>(message.Body);
Trace.Info($"Received RunnerRefreshConfigMessage for '{runnerRefreshConfigMessage.ConfigType}' config file");
var configUpdater = HostContext.GetService<IRunnerConfigUpdater>();
await configUpdater.UpdateRunnerConfigAsync(
runnerQualifiedId: runnerRefreshConfigMessage.RunnerQualifiedId,
configType: runnerRefreshConfigMessage.ConfigType,
serviceType: runnerRefreshConfigMessage.ServiceType,
configRefreshUrl: runnerRefreshConfigMessage.ConfigRefreshUrl);
// Set flag to schedule session restart if ConfigType is "runner"
if (string.Equals(runnerRefreshConfigMessage.ConfigType, "runner", StringComparison.OrdinalIgnoreCase))
{
Trace.Info("Runner configuration was updated. Session restart has been scheduled");
restartSessionPending = true;
}
else
{
Trace.Info($"No session restart needed for config type: {runnerRefreshConfigMessage.ConfigType}");
}
}
else
{
Trace.Error($"Received message {message.MessageId} with unsupported message type {message.MessageType}.");
@@ -627,6 +813,7 @@ namespace GitHub.Runner.Listener
{
try
{
Trace.Info("Deleting Runner Session...");
await _listener.DeleteSessionAsync();
}
catch (Exception ex) when (runOnce)
@@ -641,19 +828,243 @@ namespace GitHub.Runner.Listener
if (settings.Ephemeral && runOnceJobCompleted)
{
var configManager = HostContext.GetService<IConfigurationManager>();
configManager.DeleteLocalRunnerConfig();
}
}
// After cleanup, check if we need to restart the session
if (restartSession)
{
Trace.Info("Restarting runner session after config update...");
return Constants.Runner.ReturnCode.RunnerConfigurationRefreshed;
}
}
catch (TaskAgentAccessTokenExpiredException)
{
Trace.Info("Runner OAuth token has been revoked. Shutting down.");
}
catch (HostedRunnerDeprovisionedException)
{
Trace.Info("Hosted runner has been deprovisioned. Shutting down.");
}
return Constants.Runner.ReturnCode.Success;
}
private async Task<int> ExecuteRunnerAsync(RunnerSettings settings, bool runOnce)
{
int returnCode = Constants.Runner.ReturnCode.Success;
bool restart = false;
do
{
restart = false;
returnCode = await RunAsync(settings, runOnce);
if (returnCode == Constants.Runner.ReturnCode.RunnerConfigurationRefreshed)
{
Trace.Info("Runner configuration was refreshed, restarting session...");
// Reload settings in case they changed
var configManager = HostContext.GetService<IConfigurationManager>();
settings = configManager.LoadSettings();
restart = true;
}
} while (restart);
return returnCode;
}
private void HandleAuthMigrationChanged(object sender, AuthMigrationEventArgs e)
{
Trace.Verbose("Handle AuthMigrationChanged in Runner");
_authMigrationTelemetries.Enqueue($"{DateTime.UtcNow.ToString("O")}: {e.Trace}");
// only start the telemetry reporting task once auth migration is changed (enabled or disabled)
lock (_authMigrationTelemetryLock)
{
if (_authMigrationTelemetryTask == null)
{
_authMigrationTelemetryTask = ReportAuthMigrationTelemetryAsync(_authMigrationTelemetryTokenSource.Token);
}
}
// only start the claims check task once auth migration is changed (enabled or disabled)
lock (_authMigrationClaimsCheckLock)
{
if (_authMigrationClaimsCheckTask == null)
{
_authMigrationClaimsCheckTask = CheckOAuthTokenClaimsAsync(_authMigrationClaimsCheckTokenSource.Token);
}
}
}
private async Task CheckOAuthTokenClaimsAsync(CancellationToken token)
{
string[] expectedClaims =
[
"owner_id",
"runner_id",
"runner_group_id",
"scale_set_id",
"is_ephemeral",
"labels"
];
try
{
var credMgr = HostContext.GetService<ICredentialManager>();
while (!token.IsCancellationRequested)
{
try
{
await HostContext.Delay(TimeSpan.FromMinutes(100), token);
}
catch (TaskCanceledException)
{
// Ignore cancellation
}
if (token.IsCancellationRequested)
{
break;
}
if (!HostContext.AllowAuthMigration)
{
Trace.Info("Skip checking oauth token claims since auth migration is disabled.");
continue;
}
var baselineCred = credMgr.LoadCredentials(allowAuthUrlV2: false);
var authV2Cred = credMgr.LoadCredentials(allowAuthUrlV2: true);
if (!(baselineCred.Federated is VssOAuthCredential baselineVssOAuthCred) ||
!(authV2Cred.Federated is VssOAuthCredential vssOAuthCredV2) ||
baselineVssOAuthCred == null ||
vssOAuthCredV2 == null)
{
Trace.Info("Skip checking oauth token claims for non-oauth credentials");
continue;
}
if (string.Equals(baselineVssOAuthCred.AuthorizationUrl.AbsoluteUri, vssOAuthCredV2.AuthorizationUrl.AbsoluteUri, StringComparison.OrdinalIgnoreCase))
{
Trace.Info("Skip checking oauth token claims for same authorization url");
continue;
}
var baselineProvider = baselineVssOAuthCred.GetTokenProvider(baselineVssOAuthCred.AuthorizationUrl);
var v2Provider = vssOAuthCredV2.GetTokenProvider(vssOAuthCredV2.AuthorizationUrl);
try
{
using (var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
using (var requestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutTokenSource.Token))
{
var baselineToken = await baselineProvider.GetTokenAsync(null, requestTokenSource.Token);
var v2Token = await v2Provider.GetTokenAsync(null, requestTokenSource.Token);
if (baselineToken is VssOAuthAccessToken baselineAccessToken &&
v2Token is VssOAuthAccessToken v2AccessToken &&
!string.IsNullOrEmpty(baselineAccessToken.Value) &&
!string.IsNullOrEmpty(v2AccessToken.Value))
{
var baselineJwt = JsonWebToken.Create(baselineAccessToken.Value);
var baselineClaims = baselineJwt.ExtractClaims();
var v2Jwt = JsonWebToken.Create(v2AccessToken.Value);
var v2Claims = v2Jwt.ExtractClaims();
// Log extracted claims for debugging
Trace.Verbose($"Baseline token expected claims: {string.Join(", ", baselineClaims
.Where(c => expectedClaims.Contains(c.Type.ToLowerInvariant()))
.Select(c => $"{c.Type}:{c.Value}"))}");
Trace.Verbose($"V2 token expected claims: {string.Join(", ", v2Claims
.Where(c => expectedClaims.Contains(c.Type.ToLowerInvariant()))
.Select(c => $"{c.Type}:{c.Value}"))}");
foreach (var claim in expectedClaims)
{
// if baseline has the claim, v2 should have it too with exactly same value.
if (baselineClaims.FirstOrDefault(c => c.Type.ToLowerInvariant() == claim) is Claim baselineClaim &&
!string.IsNullOrEmpty(baselineClaim?.Value))
{
var v2Claim = v2Claims.FirstOrDefault(c => c.Type.ToLowerInvariant() == claim);
if (v2Claim?.Value != baselineClaim.Value)
{
Trace.Info($"Token Claim mismatch between two issuers. Expected: {baselineClaim.Type}:{baselineClaim.Value}. Actual: {v2Claim?.Type ?? "Empty"}:{v2Claim?.Value ?? "Empty"}");
HostContext.DeferAuthMigration(TimeSpan.FromMinutes(60), $"Expected claim {baselineClaim.Type}:{baselineClaim.Value} does not match {v2Claim?.Type ?? "Empty"}:{v2Claim?.Value ?? "Empty"}");
break;
}
}
}
Trace.Info("OAuth token claims check passed.");
}
}
}
catch (Exception ex)
{
Trace.Error("Failed to fetch and check OAuth token claims.");
Trace.Error(ex);
}
}
}
catch (Exception ex)
{
Trace.Error("Failed to check OAuth token claims in background.");
Trace.Error(ex);
}
}
private async Task ReportAuthMigrationTelemetryAsync(CancellationToken token)
{
var configManager = HostContext.GetService<IConfigurationManager>();
var runnerSettings = configManager.LoadSettings();
while (!token.IsCancellationRequested)
{
try
{
await HostContext.Delay(TimeSpan.FromSeconds(60), token);
}
catch (TaskCanceledException)
{
// Ignore cancellation
}
Trace.Verbose("Checking for auth migration telemetry to report");
while (_authMigrationTelemetries.TryDequeue(out var telemetry))
{
Trace.Verbose($"Reporting auth migration telemetry: {telemetry}");
if (runnerSettings != null)
{
try
{
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
await _runnerServer.UpdateAgentUpdateStateAsync(runnerSettings.PoolId, runnerSettings.AgentId, "RefreshConfig", telemetry, tokenSource.Token);
}
}
catch (Exception ex)
{
Trace.Error("Failed to report auth migration telemetry.");
Trace.Error(ex);
_authMigrationTelemetries.Enqueue(telemetry);
}
}
if (!token.IsCancellationRequested)
{
try
{
await HostContext.Delay(TimeSpan.FromSeconds(10), token);
}
catch (TaskCanceledException)
{
// Ignore cancellation
}
}
}
}
}
private void PrintUsage(CommandSettings command)
{
string separator;

View File

@@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
namespace GitHub.Runner.Listener
{
[ServiceLocator(Default = typeof(RunnerConfigUpdater))]
public interface IRunnerConfigUpdater : IRunnerService
{
Task UpdateRunnerConfigAsync(string runnerQualifiedId, string configType, string serviceType, string configRefreshUrl);
}
public sealed class RunnerConfigUpdater : RunnerService, IRunnerConfigUpdater
{
private RunnerSettings _settings;
private CredentialData _credData;
private IRunnerServer _runnerServer;
private IConfigurationStore _store;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_store = hostContext.GetService<IConfigurationStore>();
_settings = _store.GetSettings();
_credData = _store.GetCredentials();
_runnerServer = HostContext.GetService<IRunnerServer>();
}
public async Task UpdateRunnerConfigAsync(string runnerQualifiedId, string configType, string serviceType, string configRefreshUrl)
{
Trace.Entering();
try
{
ArgUtil.NotNullOrEmpty(runnerQualifiedId, nameof(runnerQualifiedId));
ArgUtil.NotNullOrEmpty(configType, nameof(configType));
ArgUtil.NotNullOrEmpty(serviceType, nameof(serviceType));
ArgUtil.NotNullOrEmpty(configRefreshUrl, nameof(configRefreshUrl));
// make sure the runner qualified id matches the current runner
if (!await VerifyRunnerQualifiedId(runnerQualifiedId))
{
return;
}
// keep the timeout short to avoid blocking the main thread
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
switch (configType.ToLowerInvariant())
{
case "runner":
await UpdateRunnerSettingsAsync(serviceType, configRefreshUrl, tokenSource.Token);
break;
case "credentials":
await UpdateRunnerCredentialsAsync(serviceType, configRefreshUrl, tokenSource.Token);
break;
default:
Trace.Error($"Invalid config type '{configType}'.");
await ReportTelemetryAsync($"Invalid config type '{configType}'.");
return;
}
}
}
catch (Exception ex)
{
Trace.Error($"Failed to update runner '{configType}' config.");
Trace.Error(ex);
await ReportTelemetryAsync($"Failed to update runner '{configType}' config: {ex}");
}
}
private async Task UpdateRunnerSettingsAsync(string serviceType, string configRefreshUrl, CancellationToken token)
{
Trace.Entering();
// read the current runner settings and encode with base64
var runnerConfig = HostContext.GetConfigFile(WellKnownConfigFile.Runner);
string runnerConfigContent = File.ReadAllText(runnerConfig, Encoding.UTF8);
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(runnerConfigContent));
if (string.IsNullOrEmpty(encodedConfig))
{
await ReportTelemetryAsync("Failed to get encoded runner settings.");
return;
}
// exchange the encoded runner settings with the service
string refreshedEncodedConfig = await RefreshRunnerConfigAsync(encodedConfig, serviceType, "runner", configRefreshUrl, token);
if (string.IsNullOrEmpty(refreshedEncodedConfig))
{
// service will return empty string if there is no change in the config
return;
}
var decodedConfig = Encoding.UTF8.GetString(Convert.FromBase64String(refreshedEncodedConfig));
RunnerSettings refreshedRunnerConfig;
try
{
refreshedRunnerConfig = StringUtil.ConvertFromJson<RunnerSettings>(decodedConfig);
}
catch (Exception ex)
{
Trace.Error($"Failed to convert runner config from json '{decodedConfig}'.");
Trace.Error(ex);
await ReportTelemetryAsync($"Failed to convert runner config '{decodedConfig}' from json: {ex}");
return;
}
// make sure the runner id and name in the refreshed config match the current runner
if (refreshedRunnerConfig?.AgentId != _settings.AgentId)
{
Trace.Error($"Runner id in refreshed config '{refreshedRunnerConfig?.AgentId.ToString() ?? "Empty"}' does not match the current runner '{_settings.AgentId}'.");
await ReportTelemetryAsync($"Runner id in refreshed config '{refreshedRunnerConfig?.AgentId.ToString() ?? "Empty"}' does not match the current runner '{_settings.AgentId}'.");
return;
}
if (refreshedRunnerConfig?.AgentName != _settings.AgentName)
{
Trace.Error($"Runner name in refreshed config '{refreshedRunnerConfig?.AgentName ?? "Empty"}' does not match the current runner '{_settings.AgentName}'.");
await ReportTelemetryAsync($"Runner name in refreshed config '{refreshedRunnerConfig?.AgentName ?? "Empty"}' does not match the current runner '{_settings.AgentName}'.");
return;
}
// save the refreshed runner settings as a separate file
_store.SaveMigratedSettings(refreshedRunnerConfig);
await ReportTelemetryAsync("Runner settings updated successfully.");
}
private async Task UpdateRunnerCredentialsAsync(string serviceType, string configRefreshUrl, CancellationToken token)
{
Trace.Entering();
// read the current runner credentials and encode with base64
var credConfig = HostContext.GetConfigFile(WellKnownConfigFile.Credentials);
string credConfigContent = File.ReadAllText(credConfig, Encoding.UTF8);
var encodedConfig = Convert.ToBase64String(Encoding.UTF8.GetBytes(credConfigContent));
if (string.IsNullOrEmpty(encodedConfig))
{
await ReportTelemetryAsync("Failed to get encoded credentials.");
return;
}
CredentialData currentCred = _store.GetCredentials();
if (currentCred == null)
{
await ReportTelemetryAsync("Failed to get current credentials.");
return;
}
// we only support refreshing OAuth credentials which is used by self-hosted runners.
if (currentCred.Scheme != Constants.Configuration.OAuth)
{
await ReportTelemetryAsync($"Not supported credential scheme '{currentCred.Scheme}'.");
return;
}
// exchange the encoded runner credentials with the service
string refreshedEncodedConfig = await RefreshRunnerConfigAsync(encodedConfig, serviceType, "credentials", configRefreshUrl, token);
if (string.IsNullOrEmpty(refreshedEncodedConfig))
{
// service will return empty string if there is no change in the config
return;
}
var decodedConfig = Encoding.UTF8.GetString(Convert.FromBase64String(refreshedEncodedConfig));
CredentialData refreshedCredConfig;
try
{
refreshedCredConfig = StringUtil.ConvertFromJson<CredentialData>(decodedConfig);
}
catch (Exception ex)
{
Trace.Error($"Failed to convert credentials config from json '{decodedConfig}'.");
Trace.Error(ex);
await ReportTelemetryAsync($"Failed to convert credentials config '{decodedConfig}' from json: {ex}");
return;
}
// make sure the credential scheme in the refreshed config match the current credential scheme
if (refreshedCredConfig?.Scheme != _credData.Scheme)
{
Trace.Error($"Credential scheme in refreshed config '{refreshedCredConfig?.Scheme ?? "Empty"}' does not match the current credential scheme '{_credData.Scheme}'.");
await ReportTelemetryAsync($"Credential scheme in refreshed config '{refreshedCredConfig?.Scheme ?? "Empty"}' does not match the current credential scheme '{_credData.Scheme}'.");
return;
}
if (_credData.Scheme == Constants.Configuration.OAuth)
{
// make sure the credential clientId in the refreshed config match the current credential clientId for OAuth auth scheme
var clientId = _credData.Data.GetValueOrDefault("clientId", null);
var refreshedClientId = refreshedCredConfig.Data.GetValueOrDefault("clientId", null);
if (clientId != refreshedClientId)
{
Trace.Error($"Credential clientId in refreshed config '{refreshedClientId ?? "Empty"}' does not match the current credential clientId '{clientId}'.");
await ReportTelemetryAsync($"Credential clientId in refreshed config '{refreshedClientId ?? "Empty"}' does not match the current credential clientId '{clientId}'.");
return;
}
// make sure the credential authorizationUrl in the refreshed config match the current credential authorizationUrl for OAuth auth scheme
var authorizationUrl = _credData.Data.GetValueOrDefault("authorizationUrl", null);
var refreshedAuthorizationUrl = refreshedCredConfig.Data.GetValueOrDefault("authorizationUrl", null);
if (authorizationUrl != refreshedAuthorizationUrl)
{
Trace.Error($"Credential authorizationUrl in refreshed config '{refreshedAuthorizationUrl ?? "Empty"}' does not match the current credential authorizationUrl '{authorizationUrl}'.");
await ReportTelemetryAsync($"Credential authorizationUrl in refreshed config '{refreshedAuthorizationUrl ?? "Empty"}' does not match the current credential authorizationUrl '{authorizationUrl}'.");
return;
}
}
// save the refreshed runner credentials as a separate file
_store.SaveMigratedCredential(refreshedCredConfig);
if (refreshedCredConfig.Data.ContainsKey("authorizationUrlV2"))
{
HostContext.EnableAuthMigration("Credential file updated");
await ReportTelemetryAsync("Runner credentials updated successfully. Auth migration is enabled.");
}
else
{
HostContext.DeferAuthMigration(TimeSpan.FromDays(365), "Credential file does not contain authorizationUrlV2");
await ReportTelemetryAsync("Runner credentials updated successfully. Auth migration is disabled.");
}
}
private async Task<bool> VerifyRunnerQualifiedId(string runnerQualifiedId)
{
Trace.Entering();
Trace.Info($"Verifying runner qualified id: {runnerQualifiedId}");
var idParts = runnerQualifiedId.Split("/", StringSplitOptions.RemoveEmptyEntries);
if (idParts.Length != 4 || idParts[3] != _settings.AgentId.ToString())
{
Trace.Error($"Runner qualified id '{runnerQualifiedId}' does not match the current runner '{_settings.AgentId}'.");
await ReportTelemetryAsync($"Runner qualified id '{runnerQualifiedId}' does not match the current runner '{_settings.AgentId}'.");
return false;
}
return true;
}
private async Task<string> RefreshRunnerConfigAsync(string encodedConfig, string serviceType, string configType, string configRefreshUrl, CancellationToken token)
{
string refreshedEncodedConfig;
switch (serviceType.ToLowerInvariant())
{
case "pipelines":
try
{
refreshedEncodedConfig = await _runnerServer.RefreshRunnerConfigAsync((int)_settings.AgentId, configType, encodedConfig, token);
}
catch (Exception ex)
{
Trace.Error($"Failed to refresh runner {configType} config with service.");
Trace.Error(ex);
await ReportTelemetryAsync($"Failed to refresh {configType} config: {ex}");
return null;
}
break;
case "runner-admin":
throw new NotSupportedException("Runner admin service is not supported.");
default:
Trace.Error($"Invalid service type '{serviceType}'.");
await ReportTelemetryAsync($"Invalid service type '{serviceType}'.");
return null;
}
return refreshedEncodedConfig;
}
private async Task ReportTelemetryAsync(string telemetry)
{
Trace.Entering();
try
{
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
await _runnerServer.UpdateAgentUpdateStateAsync(_settings.PoolId, _settings.AgentId, "RefreshConfig", telemetry, tokenSource.Token);
}
}
catch (Exception ex)
{
Trace.Error("Failed to report telemetry.");
Trace.Error(ex);
}
}
}
}

View File

@@ -7,9 +7,17 @@ namespace GitHub.Runner.Listener
{
[DataMember(Name = "id")]
public string Id { get; set; }
[DataMember(Name = "runner_request_id")]
public string RunnerRequestId { get; set; }
[DataMember(Name = "should_acknowledge")]
public bool ShouldAcknowledge { get; set; }
[DataMember(Name = "run_service_url")]
public string RunServiceUrl { get; set; }
[DataMember(Name = "billing_owner_id")]
public string BillingOwnerId { get; set; }
}
}

View File

@@ -6,13 +6,11 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
@@ -30,20 +28,14 @@ namespace GitHub.Runner.Listener
{
private static string _packageType = "agent";
private static string _platform = BuildConstants.RunnerPackage.PackageName;
private static string _dotnetRuntime = "dotnetRuntime";
private static string _externals = "externals";
private readonly Dictionary<string, string> _contentHashes = new();
private PackageMetadata _targetPackage;
private ITerminal _terminal;
private IRunnerServer _runnerServer;
private int _poolId;
private ulong _agentId;
private const int _numberOfOldVersionsToKeep = 1;
private readonly ConcurrentQueue<string> _updateTrace = new();
private Task _cloneAndCalculateContentHashTask;
private string _dotnetRuntimeCloneDirectory;
private string _externalsCloneDirectory;
public bool Busy { get; private set; }
public override void Initialize(IHostContext hostContext)
@@ -56,8 +48,6 @@ namespace GitHub.Runner.Listener
var settings = configStore.GetSettings();
_poolId = settings.PoolId;
_agentId = settings.AgentId;
_dotnetRuntimeCloneDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), "__dotnet_runtime__");
_externalsCloneDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), "__externals__");
}
public async Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token)
@@ -67,13 +57,6 @@ namespace GitHub.Runner.Listener
{
var totalUpdateTime = Stopwatch.StartNew();
// Copy dotnet runtime and externals of current runner to a temp folder
// So we can re-use them with trimmed runner package, if possible.
// This process is best effort, if we can't use trimmed runner package,
// we will just go with the full package.
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
_cloneAndCalculateContentHashTask = CloneAndCalculateAssetsHash(_dotnetRuntimeCloneDirectory, _externalsCloneDirectory, linkedTokenSource.Token);
if (!await UpdateNeeded(updateMessage.TargetVersion, token))
{
Trace.Info($"Can't find available update package.");
@@ -87,24 +70,6 @@ namespace GitHub.Runner.Listener
await UpdateRunnerUpdateStateAsync("Runner update in progress, do not shutdown runner.");
await UpdateRunnerUpdateStateAsync($"Downloading {_targetPackage.Version} runner");
if (_targetPackage.TrimmedPackages?.Count > 0)
{
// wait for cloning assets task to finish only if we have trimmed packages
await _cloneAndCalculateContentHashTask;
}
else
{
linkedTokenSource.Cancel();
try
{
await _cloneAndCalculateContentHashTask;
}
catch (Exception ex)
{
Trace.Info($"Ingore errors after cancelling cloning assets task: {ex}");
}
}
await DownloadLatestRunner(token, updateMessage.TargetVersion);
Trace.Info($"Download latest runner and unzip into runner root.");
@@ -218,54 +183,8 @@ namespace GitHub.Runner.Listener
string archiveFile = null;
var packageDownloadUrl = _targetPackage.DownloadUrl;
var packageHashValue = _targetPackage.HashValue;
var runtimeTrimmed = false;
var externalsTrimmed = false;
var fallbackToFullPackage = false;
// Only try trimmed package if sever sends them and we have calculated hash value of the current runtime/externals.
if (_contentHashes.Count == 2 &&
_contentHashes.ContainsKey(_dotnetRuntime) &&
_contentHashes.ContainsKey(_externals) &&
_targetPackage.TrimmedPackages?.Count > 0)
{
Trace.Info($"Current runner content hash: {StringUtil.ConvertToJson(_contentHashes)}");
Trace.Info($"Trimmed packages info from service: {StringUtil.ConvertToJson(_targetPackage.TrimmedPackages)}");
// Try to see whether we can use any size trimmed down package to speed up runner updates.
foreach (var trimmedPackage in _targetPackage.TrimmedPackages)
{
if (trimmedPackage.TrimmedContents.Count == 2 &&
trimmedPackage.TrimmedContents.TryGetValue(_dotnetRuntime, out var trimmedRuntimeHash) &&
trimmedRuntimeHash == _contentHashes[_dotnetRuntime] &&
trimmedPackage.TrimmedContents.TryGetValue(_externals, out var trimmedExternalsHash) &&
trimmedExternalsHash == _contentHashes[_externals])
{
Trace.Info($"Use trimmed (runtime+externals) package '{trimmedPackage.DownloadUrl}' to update runner.");
packageDownloadUrl = trimmedPackage.DownloadUrl;
packageHashValue = trimmedPackage.HashValue;
runtimeTrimmed = true;
externalsTrimmed = true;
break;
}
else if (trimmedPackage.TrimmedContents.Count == 1 &&
trimmedPackage.TrimmedContents.TryGetValue(_externals, out trimmedExternalsHash) &&
trimmedExternalsHash == _contentHashes[_externals])
{
Trace.Info($"Use trimmed (externals) package '{trimmedPackage.DownloadUrl}' to update runner.");
packageDownloadUrl = trimmedPackage.DownloadUrl;
packageHashValue = trimmedPackage.HashValue;
externalsTrimmed = true;
break;
}
else
{
Trace.Info($"Can't use trimmed package from '{trimmedPackage.DownloadUrl}' since the current runner does not carry those trimmed content (Hash mismatch).");
}
}
}
_updateTrace.Enqueue($"DownloadUrl: {packageDownloadUrl}");
_updateTrace.Enqueue($"RuntimeTrimmed: {runtimeTrimmed}");
_updateTrace.Enqueue($"ExternalsTrimmed: {externalsTrimmed}");
try
{
@@ -323,12 +242,6 @@ namespace GitHub.Runner.Listener
await ExtractRunnerPackage(archiveFile, latestRunnerDirectory, token);
}
catch (Exception ex) when (runtimeTrimmed || externalsTrimmed)
{
// if anything failed when we use trimmed package (download/validatehase/extract), try again with the full runner package.
Trace.Error($"Fail to download latest runner using trimmed package: {ex}");
fallbackToFullPackage = true;
}
finally
{
try
@@ -347,74 +260,6 @@ namespace GitHub.Runner.Listener
}
}
var trimmedPackageRestoreTasks = new List<Task<bool>>();
if (!fallbackToFullPackage)
{
// Skip restoring externals and runtime if we are going to fullback to the full package.
if (externalsTrimmed)
{
trimmedPackageRestoreTasks.Add(RestoreTrimmedExternals(latestRunnerDirectory, token));
}
if (runtimeTrimmed)
{
trimmedPackageRestoreTasks.Add(RestoreTrimmedDotnetRuntime(latestRunnerDirectory, token));
}
}
if (trimmedPackageRestoreTasks.Count > 0)
{
var restoreResults = await Task.WhenAll(trimmedPackageRestoreTasks);
if (restoreResults.Any(x => x == false))
{
// if any of the restore failed, fallback to full package.
fallbackToFullPackage = true;
}
}
if (fallbackToFullPackage)
{
Trace.Error("Something wrong with the trimmed runner package, failback to use the full package for runner updates.");
_updateTrace.Enqueue($"FallbackToFullPackage: {fallbackToFullPackage}");
IOUtil.DeleteDirectory(latestRunnerDirectory, token);
Directory.CreateDirectory(latestRunnerDirectory);
packageDownloadUrl = _targetPackage.DownloadUrl;
packageHashValue = _targetPackage.HashValue;
_updateTrace.Enqueue($"DownloadUrl: {packageDownloadUrl}");
try
{
archiveFile = await DownLoadRunner(latestRunnerDirectory, packageDownloadUrl, packageHashValue, token);
if (string.IsNullOrEmpty(archiveFile))
{
throw new TaskCanceledException($"Runner package '{packageDownloadUrl}' failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts");
}
await ValidateRunnerHash(archiveFile, packageHashValue);
await ExtractRunnerPackage(archiveFile, latestRunnerDirectory, token);
}
finally
{
try
{
// delete .zip file
if (!string.IsNullOrEmpty(archiveFile) && File.Exists(archiveFile))
{
Trace.Verbose("Deleting latest runner package zip: {0}", archiveFile);
IOUtil.DeleteFile(archiveFile);
}
}
catch (Exception ex)
{
//it is not critical if we fail to delete the .zip file
Trace.Warning("Failed to delete runner package zip '{0}'. Exception: {1}", archiveFile, ex);
}
}
}
await CopyLatestRunnerToRoot(latestRunnerDirectory, token);
}
@@ -665,9 +510,9 @@ namespace GitHub.Runner.Listener
// delete old bin.2.99.0 folder, only leave the current version and the latest download version
var allBinDirs = Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "bin.*");
if (allBinDirs.Length > 2)
if (allBinDirs.Length > _numberOfOldVersionsToKeep)
{
// there are more than 2 bin.version folder.
// there are more than one bin.version folder.
// delete older bin.version folders.
foreach (var oldBinDir in allBinDirs)
{
@@ -694,9 +539,9 @@ namespace GitHub.Runner.Listener
// delete old externals.2.99.0 folder, only leave the current version and the latest download version
var allExternalsDirs = Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "externals.*");
if (allExternalsDirs.Length > 2)
if (allExternalsDirs.Length > _numberOfOldVersionsToKeep)
{
// there are more than 2 externals.version folder.
// there are more than one externals.version folder.
// delete older externals.version folders.
foreach (var oldExternalDir in allExternalsDirs)
{
@@ -795,330 +640,5 @@ namespace GitHub.Runner.Listener
Trace.Info($"Catch exception during report update state, ignore this error and continue auto-update.");
}
}
private async Task<bool> RestoreTrimmedExternals(string downloadDirectory, CancellationToken token)
{
// Copy the current runner's externals if we are using a externals trimmed package
// Execute the node.js to make sure the copied externals is working.
var stopWatch = Stopwatch.StartNew();
try
{
Trace.Info($"Copy {_externalsCloneDirectory} to {Path.Combine(downloadDirectory, Constants.Path.ExternalsDirectory)}.");
IOUtil.CopyDirectory(_externalsCloneDirectory, Path.Combine(downloadDirectory, Constants.Path.ExternalsDirectory), token);
// try run node.js to see if current node.js works fine after copy over to new location.
var nodeVersions = NodeUtil.BuiltInNodeVersions;
foreach (var nodeVersion in nodeVersions)
{
var newNodeBinary = Path.Combine(downloadDirectory, Constants.Path.ExternalsDirectory, nodeVersion, "bin", $"node{IOUtil.ExeExtension}");
if (File.Exists(newNodeBinary))
{
using (var p = HostContext.CreateService<IProcessInvoker>())
{
var outputs = "";
p.ErrorDataReceived += (_, data) =>
{
if (!string.IsNullOrEmpty(data.Data))
{
Trace.Error(data.Data);
}
};
p.OutputDataReceived += (_, data) =>
{
if (!string.IsNullOrEmpty(data.Data))
{
Trace.Info(data.Data);
outputs = data.Data;
}
};
var exitCode = await p.ExecuteAsync(HostContext.GetDirectory(WellKnownDirectory.Root), newNodeBinary, $"-e \"console.log('{nameof(RestoreTrimmedExternals)}')\"", null, token);
if (exitCode != 0)
{
Trace.Error($"{newNodeBinary} -e \"console.log()\" failed with exit code {exitCode}");
return false;
}
if (!string.Equals(outputs, nameof(RestoreTrimmedExternals), StringComparison.OrdinalIgnoreCase))
{
Trace.Error($"{newNodeBinary} -e \"console.log()\" did not output expected content.");
return false;
}
}
}
}
return true;
}
catch (Exception ex)
{
Trace.Error($"Fail to restore externals for trimmed package: {ex}");
return false;
}
finally
{
stopWatch.Stop();
_updateTrace.Enqueue($"{nameof(RestoreTrimmedExternals)}Time: {stopWatch.ElapsedMilliseconds}ms");
}
}
private async Task<bool> RestoreTrimmedDotnetRuntime(string downloadDirectory, CancellationToken token)
{
// Copy the current runner's dotnet runtime if we are using a dotnet runtime trimmed package
// Execute the runner.listener to make sure the copied runtime is working.
var stopWatch = Stopwatch.StartNew();
try
{
Trace.Info($"Copy {_dotnetRuntimeCloneDirectory} to {Path.Combine(downloadDirectory, Constants.Path.BinDirectory)}.");
IOUtil.CopyDirectory(_dotnetRuntimeCloneDirectory, Path.Combine(downloadDirectory, Constants.Path.BinDirectory), token);
// try run the runner executable to see if current dotnet runtime + future runner binary works fine.
var newRunnerBinary = Path.Combine(downloadDirectory, Constants.Path.BinDirectory, "Runner.Listener");
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.ErrorDataReceived += (_, data) =>
{
if (!string.IsNullOrEmpty(data.Data))
{
Trace.Error(data.Data);
}
};
p.OutputDataReceived += (_, data) =>
{
if (!string.IsNullOrEmpty(data.Data))
{
Trace.Info(data.Data);
}
};
var exitCode = await p.ExecuteAsync(HostContext.GetDirectory(WellKnownDirectory.Root), newRunnerBinary, "--version", null, token);
if (exitCode != 0)
{
Trace.Error($"{newRunnerBinary} --version failed with exit code {exitCode}");
return false;
}
else
{
return true;
}
}
}
catch (Exception ex)
{
Trace.Error($"Fail to restore dotnet runtime for trimmed package: {ex}");
return false;
}
finally
{
stopWatch.Stop();
_updateTrace.Enqueue($"{nameof(RestoreTrimmedDotnetRuntime)}Time: {stopWatch.ElapsedMilliseconds}ms");
}
}
private async Task CloneAndCalculateAssetsHash(string dotnetRuntimeCloneDirectory, string externalsCloneDirectory, CancellationToken token)
{
var runtimeCloneTask = CloneDotnetRuntime(dotnetRuntimeCloneDirectory, token);
var externalsCloneTask = CloneExternals(externalsCloneDirectory, token);
var waitingTasks = new Dictionary<string, Task>()
{
{nameof(CloneDotnetRuntime), runtimeCloneTask},
{nameof(CloneExternals),externalsCloneTask}
};
while (waitingTasks.Count > 0)
{
Trace.Info($"Waiting for {waitingTasks.Count} tasks to complete.");
var complatedTask = await Task.WhenAny(waitingTasks.Values);
if (waitingTasks.ContainsKey(nameof(CloneExternals)) &&
complatedTask == waitingTasks[nameof(CloneExternals)])
{
Trace.Info($"Externals clone finished.");
waitingTasks.Remove(nameof(CloneExternals));
try
{
if (await externalsCloneTask && !token.IsCancellationRequested)
{
var externalsHash = await HashFiles(externalsCloneDirectory, token);
Trace.Info($"Externals content hash: {externalsHash}");
_contentHashes[_externals] = externalsHash;
_updateTrace.Enqueue($"ExternalsHash: {_contentHashes[_externals]}");
}
else
{
Trace.Error($"Skip compute hash since clone externals failed/cancelled.");
}
}
catch (Exception ex)
{
Trace.Error($"Fail to hash externals content: {ex}");
}
}
else if (waitingTasks.ContainsKey(nameof(CloneDotnetRuntime)) &&
complatedTask == waitingTasks[nameof(CloneDotnetRuntime)])
{
Trace.Info($"Dotnet runtime clone finished.");
waitingTasks.Remove(nameof(CloneDotnetRuntime));
try
{
if (await runtimeCloneTask && !token.IsCancellationRequested)
{
var runtimeHash = await HashFiles(dotnetRuntimeCloneDirectory, token);
Trace.Info($"Runtime content hash: {runtimeHash}");
_contentHashes[_dotnetRuntime] = runtimeHash;
_updateTrace.Enqueue($"DotnetRuntimeHash: {_contentHashes[_dotnetRuntime]}");
}
else
{
Trace.Error($"Skip compute hash since clone dotnet runtime failed/cancelled.");
}
}
catch (Exception ex)
{
Trace.Error($"Fail to hash runtime content: {ex}");
}
}
Trace.Info($"Still waiting for {waitingTasks.Count} tasks to complete.");
}
}
private async Task<bool> CloneDotnetRuntime(string runtimeDir, CancellationToken token)
{
var stopWatch = Stopwatch.StartNew();
try
{
Trace.Info($"Cloning dotnet runtime to {runtimeDir}");
IOUtil.DeleteDirectory(runtimeDir, CancellationToken.None);
Directory.CreateDirectory(runtimeDir);
var assembly = Assembly.GetExecutingAssembly();
var assetsContent = default(string);
using (var stream = assembly.GetManifestResourceStream("GitHub.Runner.Listener.runnercoreassets"))
using (var streamReader = new StreamReader(stream))
{
assetsContent = await streamReader.ReadToEndAsync();
}
if (!string.IsNullOrEmpty(assetsContent))
{
var runnerCoreAssets = assetsContent.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
if (runnerCoreAssets.Length > 0)
{
var binDir = HostContext.GetDirectory(WellKnownDirectory.Bin);
IOUtil.CopyDirectory(binDir, runtimeDir, token);
var clonedFile = 0;
foreach (var file in Directory.EnumerateFiles(runtimeDir, "*", SearchOption.AllDirectories))
{
token.ThrowIfCancellationRequested();
if (runnerCoreAssets.Any(x => file.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).EndsWith(x.Trim())))
{
Trace.Verbose($"{file} is part of the runner core, delete from cloned runtime directory.");
IOUtil.DeleteFile(file);
}
else
{
clonedFile++;
}
}
Trace.Info($"Successfully cloned dotnet runtime to {runtimeDir}. Total files: {clonedFile}");
return true;
}
}
}
catch (Exception ex)
{
Trace.Error($"Fail to clone dotnet runtime to {runtimeDir}");
Trace.Error(ex);
}
finally
{
stopWatch.Stop();
_updateTrace.Enqueue($"{nameof(CloneDotnetRuntime)}Time: {stopWatch.ElapsedMilliseconds}ms");
}
return false;
}
private Task<bool> CloneExternals(string externalsDir, CancellationToken token)
{
var stopWatch = Stopwatch.StartNew();
try
{
Trace.Info($"Cloning externals to {externalsDir}");
IOUtil.DeleteDirectory(externalsDir, CancellationToken.None);
Directory.CreateDirectory(externalsDir);
IOUtil.CopyDirectory(HostContext.GetDirectory(WellKnownDirectory.Externals), externalsDir, token);
Trace.Info($"Successfully cloned externals to {externalsDir}.");
return Task.FromResult(true);
}
catch (Exception ex)
{
Trace.Error($"Fail to clone externals to {externalsDir}");
Trace.Error(ex);
}
finally
{
stopWatch.Stop();
_updateTrace.Enqueue($"{nameof(CloneExternals)}Time: {stopWatch.ElapsedMilliseconds}ms");
}
return Task.FromResult(false);
}
private async Task<string> HashFiles(string fileFolder, CancellationToken token)
{
Trace.Info($"Calculating hash for {fileFolder}");
var stopWatch = Stopwatch.StartNew();
string binDir = HostContext.GetDirectory(WellKnownDirectory.Bin);
string node = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
string hashFilesScript = Path.Combine(binDir, "hashFiles");
var hashResult = string.Empty;
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
{
processInvoker.ErrorDataReceived += (_, data) =>
{
if (!string.IsNullOrEmpty(data.Data) && data.Data.StartsWith("__OUTPUT__") && data.Data.EndsWith("__OUTPUT__"))
{
hashResult = data.Data.Substring(10, data.Data.Length - 20);
Trace.Info($"Hash result: '{hashResult}'");
}
else
{
Trace.Info(data.Data);
}
};
processInvoker.OutputDataReceived += (_, data) =>
{
Trace.Verbose(data.Data);
};
var env = new Dictionary<string, string>
{
["patterns"] = "**"
};
int exitCode = await processInvoker.ExecuteAsync(workingDirectory: fileFolder,
fileName: node,
arguments: $"\"{hashFilesScript.Replace("\"", "\\\"")}\"",
environment: env,
requireExitCodeZero: false,
outputEncoding: null,
killProcessOnCancel: true,
cancellationToken: token);
if (exitCode != 0)
{
Trace.Error($"hashFiles returns '{exitCode}' failed. Fail to hash files under directory '{fileFolder}'");
}
stopWatch.Stop();
_updateTrace.Enqueue($"{nameof(HashFiles)}{Path.GetFileName(fileFolder)}Time: {stopWatch.ElapsedMilliseconds}ms");
return hashResult;
}
}
}
}

View File

@@ -0,0 +1,568 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.Runner.Listener
{
// This class is a fork of SelfUpdater.cs and is intended to only be used for the
// new self-update flow where the PackageMetadata is sent in the message directly.
// Forking the class prevents us from accidentally breaking the old flow while it's still in production
[ServiceLocator(Default = typeof(SelfUpdaterV2))]
public interface ISelfUpdaterV2 : IRunnerService
{
bool Busy { get; }
Task<bool> SelfUpdate(RunnerRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token);
}
public class SelfUpdaterV2 : RunnerService, ISelfUpdaterV2
{
private static string _platform = BuildConstants.RunnerPackage.PackageName;
private ITerminal _terminal;
private IRunnerServer _runnerServer;
private int _poolId;
private ulong _agentId;
private const int _numberOfOldVersionsToKeep = 1;
private readonly ConcurrentQueue<string> _updateTrace = new();
public bool Busy { get; private set; }
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_terminal = hostContext.GetService<ITerminal>();
_runnerServer = HostContext.GetService<IRunnerServer>();
var configStore = HostContext.GetService<IConfigurationStore>();
var settings = configStore.GetSettings();
_poolId = settings.PoolId;
_agentId = settings.AgentId;
}
public async Task<bool> SelfUpdate(RunnerRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token)
{
Busy = true;
try
{
var totalUpdateTime = Stopwatch.StartNew();
Trace.Info($"An update is available.");
_updateTrace.Enqueue($"RunnerPlatform: {updateMessage.OS}");
// Print console line that warn user not shutdown runner.
_terminal.WriteLine("Runner update in progress, do not shutdown runner.");
_terminal.WriteLine($"Downloading {updateMessage.TargetVersion} runner");
await DownloadLatestRunner(token, updateMessage.TargetVersion, updateMessage.DownloadUrl, updateMessage.SHA256Checksum, updateMessage.OS);
Trace.Info($"Download latest runner and unzip into runner root.");
// wait till all running job finish
_terminal.WriteLine("Waiting for current job finish running.");
await jobDispatcher.WaitAsync(token);
Trace.Info($"All running job has exited.");
// We need to keep runner backup around for macOS until we fixed https://github.com/actions/runner/issues/743
// delete runner backup
var stopWatch = Stopwatch.StartNew();
DeletePreviousVersionRunnerBackup(token, updateMessage.TargetVersion);
Trace.Info($"Delete old version runner backup.");
stopWatch.Stop();
// generate update script from template
_updateTrace.Enqueue($"DeleteRunnerBackupTime: {stopWatch.ElapsedMilliseconds}ms");
_terminal.WriteLine("Generate and execute update script.");
string updateScript = GenerateUpdateScript(restartInteractiveRunner, updateMessage.TargetVersion);
Trace.Info($"Generate update script into: {updateScript}");
#if DEBUG
// For L0, we will skip execute update script.
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_EXECUTE_UPDATE_SCRIPT")))
#endif
{
string flagFile = "update.finished";
IOUtil.DeleteFile(flagFile);
// kick off update script
Process invokeScript = new();
#if OS_WINDOWS
invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace);
invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\"";
#elif (OS_OSX || OS_LINUX)
invokeScript.StartInfo.FileName = WhichUtil.Which("bash", trace: Trace);
invokeScript.StartInfo.Arguments = $"\"{updateScript}\"";
#endif
invokeScript.Start();
Trace.Info($"Update script start running");
}
totalUpdateTime.Stop();
_updateTrace.Enqueue($"TotalUpdateTime: {totalUpdateTime.ElapsedMilliseconds}ms");
_terminal.WriteLine("Runner will exit shortly for update, should be back online within 10 seconds.");
return true;
}
catch (Exception ex)
{
_updateTrace.Enqueue(ex.ToString());
throw;
}
finally
{
_terminal.WriteLine("Runner update process finished.");
Busy = false;
}
}
/// <summary>
/// _work
/// \_update
/// \bin
/// \externals
/// \run.sh
/// \run.cmd
/// \package.zip //temp download .zip/.tar.gz
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
private async Task DownloadLatestRunner(CancellationToken token, string targetVersion, string packageDownloadUrl, string packageHashValue, string targetPlatform)
{
string latestRunnerDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), Constants.Path.UpdateDirectory);
IOUtil.DeleteDirectory(latestRunnerDirectory, token);
Directory.CreateDirectory(latestRunnerDirectory);
string archiveFile = null;
_updateTrace.Enqueue($"DownloadUrl: {packageDownloadUrl}");
try
{
#if DEBUG
// Much of the update process (targetVersion, archive) is server-side, this is a way to control it from here for testing specific update scenarios
// Add files like 'runner2.281.2.tar.gz' or 'runner2.283.0.zip' (depending on your platform) to your runner root folder
// Note that runners still need to be older than the server's runner version in order to receive an 'AgentRefreshMessage' and trigger this update
// Wrapped in #if DEBUG as this should not be in the RELEASE build
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE")))
{
var waitForDebugger = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_IS_MOCK_UPDATE_WAIT_FOR_DEBUGGER"));
if (waitForDebugger)
{
int waitInSeconds = 20;
while (!Debugger.IsAttached && waitInSeconds-- > 0)
{
await Task.Delay(1000);
}
Debugger.Break();
}
if (targetPlatform.StartsWith("win"))
{
archiveFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"runner{targetVersion}.zip");
}
else
{
archiveFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"runner{targetVersion}.tar.gz");
}
if (File.Exists(archiveFile))
{
_updateTrace.Enqueue($"Mocking update with file: '{archiveFile}' and targetVersion: '{targetVersion}', nothing is downloaded");
_terminal.WriteLine($"Mocking update with file: '{archiveFile}' and targetVersion: '{targetVersion}', nothing is downloaded");
}
else
{
archiveFile = null;
_terminal.WriteLine($"Mock runner archive not found at {archiveFile} for target version {targetVersion}, proceeding with download instead");
_updateTrace.Enqueue($"Mock runner archive not found at {archiveFile} for target version {targetVersion}, proceeding with download instead");
}
}
#endif
// archiveFile is not null only if we mocked it above
if (string.IsNullOrEmpty(archiveFile))
{
archiveFile = await DownLoadRunner(latestRunnerDirectory, packageDownloadUrl, packageHashValue, targetPlatform, token);
if (string.IsNullOrEmpty(archiveFile))
{
throw new TaskCanceledException($"Runner package '{packageDownloadUrl}' failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts");
}
await ValidateRunnerHash(archiveFile, packageHashValue);
}
await ExtractRunnerPackage(archiveFile, latestRunnerDirectory, token);
}
finally
{
try
{
// delete .zip file
if (!string.IsNullOrEmpty(archiveFile) && File.Exists(archiveFile))
{
Trace.Verbose("Deleting latest runner package zip: {0}", archiveFile);
IOUtil.DeleteFile(archiveFile);
}
}
catch (Exception ex)
{
//it is not critical if we fail to delete the .zip file
Trace.Warning("Failed to delete runner package zip '{0}'. Exception: {1}", archiveFile, ex);
}
}
await CopyLatestRunnerToRoot(latestRunnerDirectory, targetVersion, token);
}
private async Task<string> DownLoadRunner(string downloadDirectory, string packageDownloadUrl, string packageHashValue, string packagePlatform, CancellationToken token)
{
var stopWatch = Stopwatch.StartNew();
int runnerSuffix = 1;
string archiveFile = null;
bool downloadSucceeded = false;
// Download the runner, using multiple attempts in order to be resilient against any networking/CDN issues
for (int attempt = 1; attempt <= Constants.RunnerDownloadRetryMaxAttempts; attempt++)
{
// Generate an available package name, and do our best effort to clean up stale local zip files
while (true)
{
if (packagePlatform.StartsWith("win"))
{
archiveFile = Path.Combine(downloadDirectory, $"runner{runnerSuffix}.zip");
}
else
{
archiveFile = Path.Combine(downloadDirectory, $"runner{runnerSuffix}.tar.gz");
}
try
{
// delete .zip file
if (!string.IsNullOrEmpty(archiveFile) && File.Exists(archiveFile))
{
Trace.Verbose("Deleting latest runner package zip '{0}'", archiveFile);
IOUtil.DeleteFile(archiveFile);
}
break;
}
catch (Exception ex)
{
// couldn't delete the file for whatever reason, so generate another name
Trace.Warning("Failed to delete runner package zip '{0}'. Exception: {1}", archiveFile, ex);
runnerSuffix++;
}
}
// Allow a 15-minute package download timeout, which is good enough to update the runner from a 1 Mbit/s ADSL connection.
if (!int.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_DOWNLOAD_TIMEOUT") ?? string.Empty, out int timeoutSeconds))
{
timeoutSeconds = 15 * 60;
}
Trace.Info($"Attempt {attempt}: save latest runner into {archiveFile}.");
using (var downloadTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)))
using (var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(downloadTimeout.Token, token))
{
try
{
Trace.Info($"Download runner: begin download");
long downloadSize = 0;
//open zip stream in async mode
using (HttpClient httpClient = new(HostContext.CreateHttpClientHandler()))
{
Trace.Info($"Downloading {packageDownloadUrl}");
using (FileStream fs = new(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
using (Stream result = await httpClient.GetStreamAsync(packageDownloadUrl))
{
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
await result.CopyToAsync(fs, 81920, downloadCts.Token);
await fs.FlushAsync(downloadCts.Token);
downloadSize = fs.Length;
}
}
Trace.Info($"Download runner: finished download");
downloadSucceeded = true;
stopWatch.Stop();
_updateTrace.Enqueue($"PackageDownloadTime: {stopWatch.ElapsedMilliseconds}ms");
_updateTrace.Enqueue($"Attempts: {attempt}");
_updateTrace.Enqueue($"PackageSize: {downloadSize / 1024 / 1024}MB");
break;
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
Trace.Info($"Runner download has been cancelled.");
throw;
}
catch (Exception ex)
{
if (downloadCts.Token.IsCancellationRequested)
{
Trace.Warning($"Runner download has timed out after {timeoutSeconds} seconds");
}
Trace.Warning($"Failed to get package '{archiveFile}' from '{packageDownloadUrl}'. Exception {ex}");
}
}
}
if (downloadSucceeded)
{
return archiveFile;
}
else
{
return null;
}
}
private async Task ValidateRunnerHash(string archiveFile, string packageHashValue)
{
var stopWatch = Stopwatch.StartNew();
// Validate Hash Matches if it is provided
using (FileStream stream = File.OpenRead(archiveFile))
{
if (!string.IsNullOrEmpty(packageHashValue))
{
using (SHA256 sha256 = SHA256.Create())
{
byte[] srcHashBytes = await sha256.ComputeHashAsync(stream);
var hash = PrimitiveExtensions.ConvertToHexString(srcHashBytes);
if (hash != packageHashValue)
{
// Hash did not match, we can't recover from this, just throw
throw new Exception($"Computed runner hash {hash} did not match expected Runner Hash {packageHashValue} for {archiveFile}");
}
stopWatch.Stop();
Trace.Info($"Validated Runner Hash matches {archiveFile} : {packageHashValue}");
_updateTrace.Enqueue($"ValidateHashTime: {stopWatch.ElapsedMilliseconds}ms");
}
}
}
}
private async Task ExtractRunnerPackage(string archiveFile, string extractDirectory, CancellationToken token)
{
var stopWatch = Stopwatch.StartNew();
if (archiveFile.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
{
ZipFile.ExtractToDirectory(archiveFile, extractDirectory);
}
else if (archiveFile.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase))
{
string tar = WhichUtil.Which("tar", trace: Trace);
if (string.IsNullOrEmpty(tar))
{
throw new NotSupportedException($"tar -xzf");
}
// tar -xzf
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
{
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
Trace.Info(args.Data);
}
});
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
Trace.Error(args.Data);
}
});
int exitCode = await processInvoker.ExecuteAsync(extractDirectory, tar, $"-xzf \"{archiveFile}\"", null, token);
if (exitCode != 0)
{
throw new NotSupportedException($"Can't use 'tar -xzf' to extract archive file: {archiveFile}. return code: {exitCode}.");
}
}
}
else
{
throw new NotSupportedException($"{archiveFile}");
}
stopWatch.Stop();
Trace.Info($"Finished getting latest runner package at: {extractDirectory}.");
_updateTrace.Enqueue($"PackageExtractTime: {stopWatch.ElapsedMilliseconds}ms");
}
private Task CopyLatestRunnerToRoot(string latestRunnerDirectory, string targetVersion, CancellationToken token)
{
var stopWatch = Stopwatch.StartNew();
// copy latest runner into runner root folder
// copy bin from _work/_update -> bin.version under root
string binVersionDir = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"{Constants.Path.BinDirectory}.{targetVersion}");
Directory.CreateDirectory(binVersionDir);
Trace.Info($"Copy {Path.Combine(latestRunnerDirectory, Constants.Path.BinDirectory)} to {binVersionDir}.");
IOUtil.CopyDirectory(Path.Combine(latestRunnerDirectory, Constants.Path.BinDirectory), binVersionDir, token);
// copy externals from _work/_update -> externals.version under root
string externalsVersionDir = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"{Constants.Path.ExternalsDirectory}.{targetVersion}");
Directory.CreateDirectory(externalsVersionDir);
Trace.Info($"Copy {Path.Combine(latestRunnerDirectory, Constants.Path.ExternalsDirectory)} to {externalsVersionDir}.");
IOUtil.CopyDirectory(Path.Combine(latestRunnerDirectory, Constants.Path.ExternalsDirectory), externalsVersionDir, token);
// copy and replace all .sh/.cmd files
Trace.Info($"Copy any remaining .sh/.cmd files into runner root.");
foreach (FileInfo file in new DirectoryInfo(latestRunnerDirectory).GetFiles() ?? new FileInfo[0])
{
string destination = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), file.Name);
// Removing the file instead of just trying to overwrite it works around permissions issues on linux.
// https://github.com/actions/runner/issues/981
Trace.Info($"Copy {file.FullName} to {destination}");
IOUtil.DeleteFile(destination);
file.CopyTo(destination, true);
}
stopWatch.Stop();
_updateTrace.Enqueue($"CopyRunnerToRootTime: {stopWatch.ElapsedMilliseconds}ms");
return Task.CompletedTask;
}
private void DeletePreviousVersionRunnerBackup(CancellationToken token, string targetVersion)
{
// delete previous backup runner (back compat, can be remove after serval sprints)
// bin.bak.2.99.0
// externals.bak.2.99.0
foreach (string existBackUp in Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "*.bak.*"))
{
Trace.Info($"Delete existing runner backup at {existBackUp}.");
try
{
IOUtil.DeleteDirectory(existBackUp, token);
}
catch (Exception ex) when (!(ex is OperationCanceledException))
{
Trace.Error(ex);
Trace.Info($"Catch exception during delete backup folder {existBackUp}, ignore this error try delete the backup folder on next auto-update.");
}
}
// delete old bin.2.99.0 folder, only leave the current version and the latest download version
var allBinDirs = Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "bin.*");
if (allBinDirs.Length > _numberOfOldVersionsToKeep)
{
// there are more than {_numberOfOldVersionsToKeep} bin.version folder.
// delete older bin.version folders.
foreach (var oldBinDir in allBinDirs)
{
if (string.Equals(oldBinDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"bin"), StringComparison.OrdinalIgnoreCase) ||
string.Equals(oldBinDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"bin.{BuildConstants.RunnerPackage.Version}"), StringComparison.OrdinalIgnoreCase) ||
string.Equals(oldBinDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"bin.{targetVersion}"), StringComparison.OrdinalIgnoreCase))
{
// skip for current runner version
continue;
}
Trace.Info($"Delete runner bin folder's backup at {oldBinDir}.");
try
{
IOUtil.DeleteDirectory(oldBinDir, token);
}
catch (Exception ex) when (!(ex is OperationCanceledException))
{
Trace.Error(ex);
Trace.Info($"Catch exception during delete backup folder {oldBinDir}, ignore this error try delete the backup folder on next auto-update.");
}
}
}
// delete old externals.2.99.0 folder, only leave the current version and the latest download version
var allExternalsDirs = Directory.GetDirectories(HostContext.GetDirectory(WellKnownDirectory.Root), "externals.*");
if (allExternalsDirs.Length > _numberOfOldVersionsToKeep)
{
// there are more than {_numberOfOldVersionsToKeep} externals.version folder.
// delete older externals.version folders.
foreach (var oldExternalDir in allExternalsDirs)
{
if (string.Equals(oldExternalDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"externals"), StringComparison.OrdinalIgnoreCase) ||
string.Equals(oldExternalDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"externals.{BuildConstants.RunnerPackage.Version}"), StringComparison.OrdinalIgnoreCase) ||
string.Equals(oldExternalDir, Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), $"externals.{targetVersion}"), StringComparison.OrdinalIgnoreCase))
{
// skip for current runner version
continue;
}
Trace.Info($"Delete runner externals folder's backup at {oldExternalDir}.");
try
{
IOUtil.DeleteDirectory(oldExternalDir, token);
}
catch (Exception ex) when (!(ex is OperationCanceledException))
{
Trace.Error(ex);
Trace.Info($"Catch exception during delete backup folder {oldExternalDir}, ignore this error try delete the backup folder on next auto-update.");
}
}
}
}
private string GenerateUpdateScript(bool restartInteractiveRunner, string targetVersion)
{
int processId = Process.GetCurrentProcess().Id;
string updateLog = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), $"SelfUpdate-{DateTime.UtcNow.ToString("yyyyMMdd-HHmmss")}.log");
string runnerRoot = HostContext.GetDirectory(WellKnownDirectory.Root);
#if OS_WINDOWS
string templateName = "update.cmd.template";
#else
string templateName = "update.sh.template";
#endif
string templatePath = Path.Combine(runnerRoot, $"bin.{targetVersion}", templateName);
string template = File.ReadAllText(templatePath);
template = template.Replace("_PROCESS_ID_", processId.ToString());
template = template.Replace("_RUNNER_PROCESS_NAME_", $"Runner.Listener{IOUtil.ExeExtension}");
template = template.Replace("_ROOT_FOLDER_", runnerRoot);
template = template.Replace("_EXIST_RUNNER_VERSION_", BuildConstants.RunnerPackage.Version);
template = template.Replace("_DOWNLOAD_RUNNER_VERSION_", targetVersion);
template = template.Replace("_UPDATE_LOG_", updateLog);
template = template.Replace("_RESTART_INTERACTIVE_RUNNER_", restartInteractiveRunner ? "1" : "0");
#if OS_WINDOWS
string scriptName = "_update.cmd";
#else
string scriptName = "_update.sh";
#endif
string updateScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Work), scriptName);
if (File.Exists(updateScript))
{
IOUtil.DeleteFile(updateScript);
}
File.WriteAllText(updateScript, template);
return updateScript;
}
}
}

View File

@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
<SelfContained>true</SelfContained>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<NoWarn>NU1701;NU1603</NoWarn>
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
<Version>$(Version)</Version>
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>

View File

@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
<SelfContained>true</SelfContained>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<NoWarn>NU1701;NU1603</NoWarn>
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
<Version>$(Version)</Version>
</PropertyGroup>

View File

@@ -1,11 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64</RuntimeIdentifiers>
<SelfContained>true</SelfContained>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<NoWarn>NU1701;NU1603</NoWarn>
<NoWarn>NU1701;NU1603;SYSLIB0050;SYSLIB0051</NoWarn>
<Version>$(Version)</Version>
</PropertyGroup>
@@ -14,9 +15,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
<PackageReference Include="System.Threading.Channels" Version="4.4.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

View File

@@ -459,6 +459,34 @@ namespace GitHub.Runner.Sdk
File.WriteAllText(path, null);
}
/// <summary>
/// Replaces invalid file name characters with '_'
/// </summary>
public static string ReplaceInvalidFileNameChars(string fileName)
{
var result = new StringBuilder();
var invalidChars = Path.GetInvalidFileNameChars();
var current = 0; // Current index
while (current < fileName?.Length)
{
var next = fileName.IndexOfAny(invalidChars, current);
if (next >= 0)
{
result.Append(fileName.Substring(current, next - current));
result.Append('_');
current = next + 1;
}
else
{
result.Append(fileName.Substring(current));
break;
}
}
return result.ToString();
}
/// <summary>
/// Recursively enumerates a directory without following directory reparse points.
/// </summary>

View File

@@ -60,5 +60,15 @@ namespace GitHub.Runner.Sdk
}
return string.Empty;
}
public static string GetVssRequestId(HttpResponseHeaders headers)
{
if (headers != null &&
headers.TryGetValues("x-vss-e2eid", out var headerValues))
{
return headerValues.FirstOrDefault();
}
return string.Empty;
}
}
}

View File

@@ -23,7 +23,13 @@ namespace GitHub.Runner.Sdk
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
{
headerValues.AddRange(VssClientHttpRequestSettings.Default.UserAgent);
foreach (var headerVal in VssClientHttpRequestSettings.Default.UserAgent)
{
if (!headerValues.Contains(headerVal))
{
headerValues.Add(headerVal);
}
}
}
VssClientHttpRequestSettings.Default.UserAgent = headerValues;
@@ -32,7 +38,25 @@ namespace GitHub.Runner.Sdk
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_TLS_NO_VERIFY")))
{
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
RawClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
var rawHeaderValues = new List<ProductInfoHeaderValue>();
rawHeaderValues.AddRange(additionalUserAgents);
rawHeaderValues.Add(new ProductInfoHeaderValue($"({StringUtil.SanitizeUserAgentHeader(RuntimeInformation.OSDescription)})"));
if (RawClientHttpRequestSettings.Default.UserAgent != null && RawClientHttpRequestSettings.Default.UserAgent.Count > 0)
{
foreach (var headerVal in RawClientHttpRequestSettings.Default.UserAgent)
{
if (!rawHeaderValues.Contains(headerVal))
{
rawHeaderValues.Add(headerVal);
}
}
}
RawClientHttpRequestSettings.Default.UserAgent = rawHeaderValues;
}
public static VssConnection CreateConnection(
@@ -62,11 +86,6 @@ namespace GitHub.Runner.Sdk
settings.SendTimeout = TimeSpan.FromSeconds(Math.Min(Math.Max(httpRequestTimeoutSeconds, 100), 1200));
}
if (StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("USE_BROKER_FLOW")))
{
settings.AllowAutoRedirectForBroker = true;
}
// Remove Invariant from the list of accepted languages.
//
// The constructor of VssHttpRequestSettings (base class of VssClientHttpRequestSettings) adds the current

View File

@@ -7,129 +7,6 @@ namespace GitHub.Runner.Sdk
public static class WhichUtil
{
public static string Which(string command, bool require = false, ITraceWriter trace = null, string prependPath = null)
{
ArgUtil.NotNullOrEmpty(command, nameof(command));
trace?.Info($"Which: '{command}'");
if (Path.IsPathFullyQualified(command) && File.Exists(command))
{
trace?.Info($"Fully qualified path: '{command}'");
return command;
}
string path = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
if (string.IsNullOrEmpty(path))
{
trace?.Info("PATH environment variable not defined.");
path = path ?? string.Empty;
}
if (!string.IsNullOrEmpty(prependPath))
{
path = PathUtil.PrependPath(prependPath, path);
}
string[] pathSegments = path.Split(new Char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < pathSegments.Length; i++)
{
pathSegments[i] = Environment.ExpandEnvironmentVariables(pathSegments[i]);
}
foreach (string pathSegment in pathSegments)
{
if (!string.IsNullOrEmpty(pathSegment) && Directory.Exists(pathSegment))
{
string[] matches = null;
#if OS_WINDOWS
string pathExt = Environment.GetEnvironmentVariable("PATHEXT");
if (string.IsNullOrEmpty(pathExt))
{
// XP's system default value for PATHEXT system variable
pathExt = ".com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh";
}
string[] pathExtSegments = pathExt.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
// if command already has an extension.
if (pathExtSegments.Any(ext => command.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
{
try
{
matches = Directory.GetFiles(pathSegment, command);
}
catch (UnauthorizedAccessException ex)
{
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
trace?.Verbose(ex.ToString());
}
if (matches != null && matches.Length > 0 && IsPathValid(matches.First(), trace))
{
trace?.Info($"Location: '{matches.First()}'");
return matches.First();
}
}
else
{
string searchPattern;
searchPattern = StringUtil.Format($"{command}.*");
try
{
matches = Directory.GetFiles(pathSegment, searchPattern);
}
catch (UnauthorizedAccessException ex)
{
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
trace?.Verbose(ex.ToString());
}
if (matches != null && matches.Length > 0)
{
// add extension.
for (int i = 0; i < pathExtSegments.Length; i++)
{
string fullPath = Path.Combine(pathSegment, $"{command}{pathExtSegments[i]}");
if (matches.Any(p => p.Equals(fullPath, StringComparison.OrdinalIgnoreCase)) && IsPathValid(fullPath, trace))
{
trace?.Info($"Location: '{fullPath}'");
return fullPath;
}
}
}
}
#else
try
{
matches = Directory.GetFiles(pathSegment, command);
}
catch (UnauthorizedAccessException ex)
{
trace?.Info("Ignore UnauthorizedAccess exception during Which.");
trace?.Verbose(ex.ToString());
}
if (matches != null && matches.Length > 0 && IsPathValid(matches.First(), trace))
{
trace?.Info($"Location: '{matches.First()}'");
return matches.First();
}
#endif
}
}
#if OS_WINDOWS
trace?.Info($"{command}: command not found. Make sure '{command}' is installed and its location included in the 'Path' environment variable.");
#else
trace?.Info($"{command}: command not found. Make sure '{command}' is installed and its location included in the 'PATH' environment variable.");
#endif
if (require)
{
throw new FileNotFoundException(
message: $"{command}: command not found",
fileName: command);
}
return null;
}
public static string Which2(string command, bool require = false, ITraceWriter trace = null, string prependPath = null)
{
ArgUtil.NotNullOrEmpty(command, nameof(command));
trace?.Info($"Which2: '{command}'");

Some files were not shown because too many files have changed in this diff Show More