Compare commits

...

89 Commits

Author SHA1 Message Date
eric sciple
8ecc737c6b . 2020-01-13 20:05:09 -05:00
eric sciple
ece9b6d04b . 2020-01-13 19:26:37 -05:00
eric sciple
7a18d3001d set context steps.<id>.outcome 2020-01-13 19:13:36 -05:00
eric sciple
02639a2092 translate problem matcher file to host path (#272) 2020-01-13 15:24:57 -05:00
eric sciple
a727194742 allow container to be null/empty (#266) 2020-01-12 23:02:36 -05:00
eric sciple
a9c58d7398 Handle escaped '%' in commands data section (#200) 2020-01-12 03:30:26 -05:00
Bryan MacFarlane
e15414eb5e proxy support ADR (#263) 2020-01-09 11:52:04 -05:00
Tingluo Huang
4ab1e645c3 Fix typo in error message. (#260) 2020-01-07 15:13:05 -05:00
Tingluo Huang
584f6b6ca3 upload log on runner force kill worker. (#255) 2020-01-06 13:04:23 -05:00
Tingluo Huang
abc65839f3 detect source file path without using env. (#257) 2020-01-06 12:56:15 -05:00
Tingluo Huang
06292aa118 Expose whether debug is on/off via RUNNER_DEBUG. (#253) 2020-01-03 21:23:46 -05:00
Joseph Petersen
ac1a076a3b Treat warnings as errors (#249)
* Treat warnings as errors

* fix warnings
2019-12-21 09:51:41 -05:00
Joseph Petersen
300bc67950 ignore .idea folder (#248)
created by rider
2019-12-21 09:50:07 -05:00
Tim Heuer
289c7f36a2 Minor typo (#247)
capitalize .NET
2019-12-20 20:20:44 -05:00
Julio Barba
b89d7fb8ef Remove old "v1" artifact download/publish code (#212)
* Remove old v1 artifact download/publish code
* Remove the Build2 REST API SDK
2019-12-19 16:02:00 -05:00
Josh Gross
5fd705bb84 Update LICENSE (#242) 2019-12-19 15:22:49 -05:00
Julio Barba
9e37732401 Verify that has Windows service started successfully (#236) 2019-12-19 14:34:26 -05:00
Bryan MacFarlane
6c70d53eea clean up some unneeded dockerfiles 2019-12-19 09:16:43 -05:00
Bryan MacFarlane
f791e2d512 update contributions md 2019-12-19 09:03:14 -05:00
Bryan MacFarlane
f1e36651ad build workflow ignores md changes 2019-12-19 08:54:43 -05:00
Bryan MacFarlane
be24fea81b update issue templates 2019-12-19 08:34:17 -05:00
Bryan MacFarlane
84ca2c05ce update readme 2019-12-19 08:20:39 -05:00
Bryan MacFarlane
2249560cec update readme 2019-12-18 23:15:30 -05:00
Bryan MacFarlane
2d4b821abe update readme 2019-12-18 23:13:22 -05:00
Bryan MacFarlane
371bf8e607 update readme and contributions 2019-12-18 22:49:31 -05:00
Tingluo Huang
9ba11da490 move .sln file. (#238) 2019-12-18 20:13:57 -05:00
Tingluo Huang
40302373ba Create releaseVersion (#237) 2019-12-18 15:39:26 -05:00
Tingluo Huang
9a08f7418f delete unused files. (#235) 2019-12-18 15:28:36 -05:00
Tingluo Huang
80b6038cdc consume dotnet core 3.1 in runner. (#213) 2019-12-18 15:09:03 -05:00
David Kale
70a09bc5ac shell from prependpath (#231)
* Prepend path before locating shell tool

* Join optional prepended path to path before searching it

* Use prepended path when whiching shell tool

* Addition prependPath location

* Also use prepended paths when writing out run details

* Small tweak to undo unnecessary change
2019-12-18 15:00:12 -05:00
Tingluo Huang
c6cf1eb3f1 Release runner using actions (#223)
* update runner release workflow

* trim script.

* feedback.
2019-12-18 14:56:37 -05:00
Julio Barba
50d979f1b2 Bring back tools folder fallback code (#232) 2019-12-17 18:21:13 -05:00
Tingluo Huang
91b7e7a07a delete more unused code. (#230)
* delete more unused code.

* pr feedback.
2019-12-17 16:47:14 -05:00
Tingluo Huang
d0a4a41a63 delete un-used code. (#218) 2019-12-16 17:05:26 -05:00
Julio Barba
c3c66bb14a Replace remaining Agent -> Runner references (#229) 2019-12-16 15:45:00 -05:00
Tingluo Huang
86df779fe9 expose github.run_id and github.run_number to action runtime env. (#224) 2019-12-16 15:23:55 -05:00
David Kale
1918906505 First pass (#221) 2019-12-16 14:53:19 -05:00
Julio Barba
9448135fcd Replace a few more instances Agent -> Runner (#228) 2019-12-16 11:51:08 -05:00
Tingluo Huang
f3aedd86fd Update AGENT_ALLOW_RUNASROOT to RUNNER_ALLOW_RUNASROOT (#227)
* Update config.sh

* Update run.sh
2019-12-16 11:27:46 -05:00
Mike Coutermarsh
d778f13dee Remove runner flow: Change from PAT to "deletion token" in prompt (#225)
* Updating prompt deletion token

Currently if you leave the token off the command, we're showing "Enter your personal access token:"

Which won't work. This updates prompt to "deletion token"

* Call correct function in test

* Fix command text in test
2019-12-15 21:38:42 -05:00
Tingluo Huang
9bbbca9e5d Prepare 2.163.0 runner release. 2019-12-12 15:20:39 -05:00
Tingluo Huang
2cac011558 Load and set env from .env file before creating HostContext. (#220) 2019-12-12 14:56:45 -05:00
Julio Barba
f78d35dc4e Trim Build2 SDK (#219)
* Trim Build2 SDK REST API methods
* Remove unused files
2019-12-12 13:53:12 -05:00
David Kale
181dac1c07 Update node external to 12.13.1 (#215)
* Update node external to 12.13.1

* Typo

* Typo2
2019-12-12 09:38:48 -05:00
eric sciple
ab87b39f53 better repo matching for issue file path (#208) 2019-12-11 14:21:26 -05:00
Julio Barba
a3c6a8c201 Introduce name config argument (#217) 2019-12-11 13:26:06 -05:00
Julio Barba
275ab753a1 Runner cleanup - continuation (#209)
* Agent/AgentCredential -> Runner/RunnerCredential
* Test trait rename: Agent -> Runner
* Enable remaining RunnerL0 tests
* Remove job message PII variable masking code
* Remove unused Agent.ToolsDirectory variable
* Misc test Agent -> Runner renaming
* Some more misc cleaning
2019-12-09 17:54:41 -05:00
Tingluo Huang
3ed80b7c10 Fix L0 tests, add/update runner release yaml. (#214) 2019-12-09 16:11:00 -05:00
Tingluo Huang
d81a7656a4 Add Proxy Support for self-hosted runner. (#206) 2019-12-09 15:15:54 -05:00
Julio Barba
56e18f3606 Update .NET install SH script (again) (#210) 2019-12-04 17:57:16 -05:00
Julio Barba
cd2cec8282 Another runner code cleanup round (#197)
* Remove remaining non-SDK references of capabilities/demands
* Remove unused Runner.Common constants
* Remove more variables
* Clean up RU link, and named-pipe support
* Remove NotificationSocketAddress
* Re-add legacy OnPremises JobDispatcher code (commented out)
* More misc cleanup
2019-12-04 10:18:37 -05:00
Julio Barba
f8829feb63 Update dotnet-install scripts (#207) 2019-12-03 17:13:03 -05:00
Julio Barba
b061ec410f Revert: Switch publish agent package task to direct to new pool 2019-12-02 16:54:19 -05:00
Julio Barba
2b63b9c379 Prepare 2.162.0 runner release (#204) 2019-12-02 16:16:35 -05:00
Julio Barba
d93fb70a3e Fix PrepareActions_DownloadActionFromGraph test (#205) 2019-12-02 16:03:42 -05:00
Julio Barba
4ce1bfb58a Switch publish agent package task to direct to new pool 2019-12-02 14:51:46 -05:00
Julio Barba
7a6d9dc5c8 Implement new runner service name convention (#193)
* Limit service name to 80 characters
* Add L0 tests
* New service name convention
* Make RepoOrOrgName a computed property
* Add service name sanitizing logic with L0 test
2019-11-27 14:44:29 -05:00
Julio Barba
de29a39d14 Support downloading/publishing artifacts from Pipelines endpoint (#188)
* Support downloading/publishing artifacts from Pipelines endpoint
* Remove `Path` from everywhere
* Remove unused JobId argument
* PR feedback
* More PR feedback
2019-11-25 13:30:44 -05:00
eric sciple
7d505f7f77 problem matcher default severity (#203) 2019-11-22 13:21:46 -05:00
Thomas Boop
159e4c506a Updated Release Notes to use CLI Downloads (#196)
* Updated Release Notes to use CLI Downloads

* Add Config and Run steps

* Specify Root Drive for Windows

* Remove unactionable steps from readme config
2019-11-19 16:27:36 -05:00
David Kale
45c19eb7cb 150: Support more cpu architectures (#184)
* Cross compile for win-x86, linux-arm, linux-arm64

* Build with actions instead

* Remove win-x86

* Preserve CURRENT_PLATFORM in dev.sh

* build.yaml

* Fix formatting. Remove piplines

* Use 4 space indent consistently

* x32 -> x86

* TEMP: Only test when platform === target runtime

Fix arm64 node externals url

* win-x86 externals

* Temporarily bench rhel

* Add RHEL6, skip L0 on arm for now

* Add stub for downloading new node externals when they are ready

* Remove RHEL6

* Package based on new runtime names

* Remove unused rhel from matrix includes

* Update release, add packages

* RID typo

* Cant cross test arm on x64 hosts

* New arch is a feature

Dont release x86 until we have an e2e test machine

* Fix version

* Get version from file to avoid exec error during package on x64 host for arm package

* Update Release Notes for 2.161.0 (#195)

* More cleanup

* Update release notes
2019-11-13 11:26:06 -05:00
Thomas Boop
9ba971592b V 2.160.2 Release notes and version bump (#190) 2019-11-11 15:45:37 -05:00
Thomas Boop
b27cfb18e6 2.160.1 Runner Release Notes (#171)
* 2.160.1 Runner Release Notes

* Minor verbiage updates to be consistent
2019-11-11 15:45:13 -05:00
Thomas Boop
ced4c2ca50 add-mask is leaking a secret in master if debug or ::echo::on is set (#158)
* Output after processing command to avoid leaking mask

* Remove extra noise output from echo changes

* Omit Echoing of add-mask command

* avoid echoing on debug/warning/error
2019-11-11 15:40:18 -05:00
Thomas Boop
54f21c641f Update dependency docs for OSX and Windows (#162)
* Update Dependency docs for .net 3.0 depedencies

* Update Supported Windows Versions

* Update Supported Mac OS link

* Update docs/start/envosx.md

Fix typo in OSX Version

Co-Authored-By: Lucas Costi <lucascosti@users.noreply.github.com>
2019-11-11 13:43:02 -05:00
Thomas Boop
c5cbac9796 Runner fails to run as a service on windows: Disable Delay Signing on the Runner Service (#185)
* test release

* Reverse test release changes

* Remove Unused Public Keys from Runner Service
2019-11-08 09:49:20 -05:00
eric sciple
8911283cdb fix problem matcher to treat fromPath as a file path (#183) 2019-11-07 11:26:29 -05:00
Eilon Lipton
76078b5c44 Add SLN file and make projects build in VS (#173)
* Add SLN file and make projects build in VS

- Added new ActionsRunner.sln file with all the CSPROJ's in it
- Added Directory.Build.props that gets auto-included in all CSPROJ files under it
- Made default runtime platforms for Windows and Linux (to be 64bit) so that you don't have to specify it in order to build

* Remove extra invalid parens
2019-11-06 16:57:46 -05:00
Eilon Lipton
ec9cb6c68d Fix build warnings in Test project (#178)
The build warnings were of these type (mostly reported by xUnit's Code Analyzers):
- Fixed wrong parameter order in xUnit assertions (can lead to poor error reporting in test failures)
- Unused code was removed
- Correct assertions were used (e.g. Assert.True/.Contains/.EndsWith)
- Public non-test methods on test classes were made private
2019-11-06 08:52:51 -05:00
Eilon Lipton
bcac4557a0 Fix Runner.Worker build warnings (#174)
Most of these warnings show up on only certain build OSes because of #ifdefs in the code. The fix is to suppress these warnings.
2019-11-06 08:47:56 -05:00
Eilon Lipton
19580bdaf8 Update contribute.md (#175)
Small cleanup of contribution guide
2019-11-05 17:21:50 -05:00
Eilon Lipton
96d3288553 Remove unused dreamlifter section (#177) 2019-11-05 17:21:00 -05:00
Tingluo Huang
5b6f9d3b93 Stop job container after all post actions. (#165)
* stop job container after all post actions.

* c

* c
2019-11-04 13:19:21 -05:00
eric sciple
51581ac865 root search pattern for hashfiles and allow forward slash on windows (#151) 2019-10-29 13:23:30 -04:00
eric sciple
e7dd2c6cc2 Update dotnet install script (#155) 2019-10-29 13:13:58 -04:00
Thomas Boop
08b9f6e045 Merge release 160.0 into master (#153)
* Update to Version 2.160.0 (#144)

* Revert "remove issue generation on warning/error commands (#137)" (#147)

* Revert "remove issue generation on warning/error commands (#137)"

This reverts commit 53da198867.

* Updated Release notes

* Users/thboop/port directory changes (#152)

* Clear action cache for local runner

* update release notes for actions directory cache changes
2019-10-28 15:45:27 -04:00
eric sciple
0129e8111f Clear action cache for local runner (#148) 2019-10-28 11:56:12 -04:00
eric sciple
ccca13ac07 restrict hashFiles to basic globbing and globstar (#150) 2019-10-28 11:52:22 -04:00
Julio Barba
82e9857d4f Implement new echo behavior and command (#139)
* Remove controlling echoing by command

* Add 'echo on' and 'echo off' action commands

* PR feedback and add L0 tests

* Register new command

* Eric's PR feedback

* Tweak logging a bit

* Rename EchoOnActionCommandSuccess -> EchoOnActionCommand

* More PR reaction

* Make warning messages in Action Commands not rely on context from echo commands
2019-10-25 10:38:56 -04:00
Tingluo Huang
afd233b735 consume dotnet core 3.0 (#127)
* consume dotnet core 3.0

* update linux dependency doc.
2019-10-24 16:52:29 -04:00
Thomas Boop
83be145bfd Update build.yml 2019-10-24 14:05:10 -04:00
Thomas Boop
6e20aceaff Update build.yml 2019-10-24 14:00:56 -04:00
Lucas Killgore
e89148e33e Don't retry uploads when the http status code response from the server is in the 400's (#131)
* Don't retry uploads when the http status code
response from the server is in the 400's

* Don't retry on fast-fail

* Feedback from code review

* Always try to attach any uploaded files to the build
Don't fast-fail on 'Conflict'

* Add dispose

* Refactored upload code.
Called out specialized 'Conflict' logic.

* Added typed exception.
2019-10-23 12:48:10 -04:00
Thomas Boop
f1c58c33b6 Default to pwsh with powershell as a fallback (#142)
* Default to pwsh with powershell as a fallback

* Update releaseNote.md

* clean up code
2019-10-23 11:24:02 -04:00
Thomas Boop
7b158fff05 Revert "Need to update the release pool (#138)" (#141)
This reverts commit 3a43d853be.
2019-10-22 13:41:55 -04:00
Thomas Boop
6225b22870 Update to Version 2.159.2 (#140) 2019-10-22 13:14:58 -04:00
Thomas Boop
53da198867 remove issue generation on warning/error commands (#137) 2019-10-21 16:19:57 -04:00
Thomas Boop
3a43d853be Need to update the release pool (#138) 2019-10-21 15:32:57 -04:00
Thomas Boop
9d7b0a2405 set default shell to powershell in windows (#135)
* set default shell to powershell in windows

* Update error checking

* Update error message
2019-10-21 14:08:12 -04:00
872 changed files with 6225 additions and 96515 deletions

View File

@@ -1,10 +0,0 @@
## Runner Version and Platform
Version of your runner?
OS of the machine running the runner? OSX/Windows/Linux/...
## What's not working?
Please include error messages and screenshots.
## Runner and Worker's Diagnostic Logs
Logs are located in the runner's `_diag` folder. The runner logs are prefixed with `Runner_` and the worker logs are prefixed with `Worker_`. All sensitive information should already be masked out, but please double-check before pasting here.

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Run '....'
3. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
## Runner Version and Platform
Version of your runner?
OS of the machine running the runner? OSX/Windows/Linux/...
## What's not working?
Please include error messages and screenshots.
## Job Log Output
If applicable, include the relevant part of the job / step log output here. All sensitive information should already be masked out, but please double-check before pasting here.
## Runner and Worker's Diagnostic Logs
If applicable, add relevant diagnostic log information. Logs are located in the runner's `_diag` folder. The runner logs are prefixed with `Runner_` and the worker logs are prefixed with `Worker_`. Each job run correlates to a worker log. All sensitive information should already be masked out, but please double-check before pasting here.

View File

@@ -0,0 +1,27 @@
---
name: Feature Request
about: Create a request to help us improve
title: ''
labels: enhancement
assignees: ''
---
Thank you 🙇‍♀ for wanting to create a feature in this repository. Before you do, please ensure you are filing the issue in the right place. Issues should only be opened on if the issue **relates to code in this repository**.
* If you have found a security issue [please submit it here](https://hackerone.com/github)
* If you have questions or issues with the service, writing workflows or actions, then please [visit the GitHub Community Forum's Actions Board](https://github.community/t5/GitHub-Actions/bd-p/actions)
* If you are having an issue or question about GitHub Actions then please [contact customer support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions#contacting-support)
If you have a feature request that is relevant to this repository, the runner, then please include the information below:
**Describe the enhancement**
A clear and concise description of what the features or enhancement you need.
**Code Snippet**
If applicable, add a code snippet.
**Additional information**
Add any other context about the feature here.
NOTE: if the feature request has been agreed upon then the assignee will create an ADR. See docs/adrs/README.md

View File

@@ -5,22 +5,40 @@ on:
branches:
- master
- releases/*
paths-ignore:
- '**.md'
pull_request:
branches:
- '*'
paths-ignore:
- '**.md'
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64 ]
include:
- os: ubuntu-latest
- runtime: linux-x64
os: ubuntu-latest
devScript: ./dev.sh
- os: macOS-latest
- runtime: linux-arm64
os: ubuntu-latest
devScript: ./dev.sh
- os: windows-latest
devScript: dev.cmd
- runtime: linux-arm
os: ubuntu-latest
devScript: ./dev.sh
- runtime: osx-x64
os: macOS-latest
devScript: ./dev.sh
- runtime: win-x64
os: windows-latest
devScript: ./dev
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
@@ -28,7 +46,7 @@ jobs:
# Build runner layout
- name: Build & Layout Release
run: |
${{ matrix.devScript }} layout Release
${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
working-directory: src
# Run tests
@@ -36,6 +54,7 @@ jobs:
run: |
${{ matrix.devScript }} test
working-directory: src
if: matrix.runtime != 'linux-arm64' && matrix.runtime != 'linux-arm'
# Create runner package tar.gz/zip
- name: Package Release
@@ -49,5 +68,5 @@ jobs:
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v1
with:
name: runner-package-${{ matrix.os }}
name: runner-package-${{ matrix.runtime }}
path: _package

195
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,195 @@
name: Runner CD
on:
push:
paths:
- releaseVersion
jobs:
check:
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Make sure ./releaseVersion match ./src/runnerversion
# Query GitHub release ensure version is not used
- name: Check version
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('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
const releaseVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
if (runnerVersion != releaseVersion) {
console.log('Request Release Version: ' + releaseVersion + '\nCurrent Runner Version: ' + runnerVersion)
core.setFailed('Version mismatch! Make sure ./releaseVersion match ./src/runnerVersion')
return
}
try {
const release = await github.repos.getReleaseByTag({
owner: '${{ github.event.repository.owner.name }}',
repo: '${{ github.event.repository.name }}',
tag: 'v' + runnerVersion
})
core.setFailed('Release with same tag already created: ' + release.data.html_url)
} catch (e) {
// We are good to create the release if release with same tag doesn't exists
if (e.status != 404) {
throw e
}
}
build:
needs: check
strategy:
matrix:
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64 ]
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: win-x64
os: windows-latest
devScript: ./dev
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
# Build runner layout
- name: Build & Layout Release
run: |
${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
working-directory: src
# Run tests
- name: L0
run: |
${{ matrix.devScript }} test
working-directory: src
if: matrix.runtime != 'linux-arm64' && matrix.runtime != 'linux-arm'
# Create runner package tar.gz/zip
- name: Package Release
if: github.event_name != 'pull_request'
run: |
${{ matrix.devScript }} package Release ${{ matrix.runtime }}
working-directory: src
# 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@v1
with:
name: runner-packages
path: _package
release:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Download runner package tar.gz/zip produced by 'build' job
- name: Download Artifact
uses: actions/download-artifact@v1
with:
name: runner-packages
path: ./
# Create ReleaseNote file
- name: Create ReleaseNote
id: releaseNote
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('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
const releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
console.log(releaseNote)
core.setOutput('version', runnerVersion);
core.setOutput('note', releaseNote);
# Create GitHub release
- uses: actions/create-release@master
id: createRelease
name: Create ${{ steps.releaseNote.outputs.version }} Runner Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: "v${{ steps.releaseNote.outputs.version }}"
release_name: "v${{ steps.releaseNote.outputs.version }}"
body: |
${{ steps.releaseNote.outputs.note }}
prerelease: true
# Upload release assets
- name: Upload Release Asset (win-x64)
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 }}/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 (linux-x64)
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 }}/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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
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 (linux-arm)
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 }}/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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
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

8
.gitignore vendored
View File

@@ -1,13 +1,18 @@
# build output
**/bin
**/obj
**/libs
# editors
**/*.xproj
**/*.xproj.user
**/*.sln
**/.vs
**/.vscode
**/*.error
**/*.json.pretty
.idea/
# output
node_modules
_downloads
_layout
@@ -20,4 +25,3 @@ TestLogs
#generated
src/Runner.Sdk/BuildConstants.cs

View File

@@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright (c) Microsoft Corporation
Copyright (c) 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,31 +1,25 @@
# GitHub Actions Runner
<p align="center">
<img src="docs/res/github-graph.png">
</p>
# GitHub Actions Runner
[![Actions Status](https://github.com/actions/runner/workflows/Runner%20CI/badge.svg)](https://github.com/actions/runner/actions)
The runner is the application that runs a job from a GitHub Actions workflow. The runner can run on the [hosted machine pools](https://github.com/actions/virtual-environments) or run on [self-hosted environments](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners).
## Get Started
![win](docs/res/win_sm.png) [Pre-reqs](docs/start/envwin.md) | [Download](https://github.com/actions/runner/releases/latest)
For more information about installing and using self-hosted runners, see [Adding self-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/adding-self-hosted-runners) and [Using self-hosted runners in a workflow](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-self-hosted-runners-in-a-workflow)
![macOS](docs/res/apple_sm.png) [Pre-reqs](docs/start/envosx.md) | [Download](https://github.com/actions/runner/releases/latest)
Runner releases:
![linux](docs/res/linux_sm.png) [Pre-reqs](docs/start/envlinux.md) | [Download](https://github.com/actions/runner/releases/latest)
![win](docs/res/win_sm.png) [Pre-reqs](docs/start/envwin.md) | [Download](https://github.com/actions/runner/releases)
**Configure:**
![macOS](docs/res/apple_sm.png) [Pre-reqs](docs/start/envosx.md) | [Download](https://github.com/actions/runner/releases)
*MacOS and Linux*
```bash
./config.sh
```
*Windows*
```bash
config.cmd
```
![linux](docs/res/linux_sm.png) [Pre-reqs](docs/start/envlinux.md) | [Download](https://github.com/actions/runner/releases)
## Contribute
For developers that want to contribute, [read here](docs/contribute.md) on how to build and test.
We accept contributions in the form of issues and pull requests. [Read more here](docs/contribute.md) before contributing.

View File

@@ -1,20 +0,0 @@
[
{
"name": "actions-runner-win-x64-<RUNNER_VERSION>.zip",
"platform": "win-x64",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip"
},
{
"name": "actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz",
"platform": "osx-x64",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz"
},
{
"name": "actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz",
"platform": "linux-x64",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz"
}
]

View File

@@ -1,177 +0,0 @@
stages:
- stage: Build
jobs:
################################################################################
- job: build_windows_agent_x64
################################################################################
displayName: Windows Agent (x64)
pool:
vmImage: vs2017-win2016
steps:
# Steps template for windows platform
- template: windows.template.yml
# Package dotnet core windows dependency (VC++ Redistributable)
- powershell: |
Write-Host "Downloading 'VC++ Redistributable' package."
$outDir = Join-Path -Path $env:TMP -ChildPath ([Guid]::NewGuid())
New-Item -Path $outDir -ItemType directory
$outFile = Join-Path -Path $outDir -ChildPath "ucrt.zip"
Invoke-WebRequest -Uri https://vstsagenttools.blob.core.windows.net/tools/ucrt/ucrt_x64.zip -OutFile $outFile
Write-Host "Unzipping 'VC++ Redistributable' package to agent layout."
$unzipDir = Join-Path -Path $outDir -ChildPath "unzip"
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($outFile, $unzipDir)
$agentLayoutBin = Join-Path -Path $(Build.SourcesDirectory) -ChildPath "_layout\bin"
Copy-Item -Path $unzipDir -Destination $agentLayoutBin -Force
displayName: Package UCRT
# Create agent package zip
- script: dev.cmd package Release
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Windows)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
################################################################################
- job: build_linux_agent_x64
################################################################################
displayName: Linux Agent (x64)
pool:
vmImage: ubuntu-16.04
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
# Create agent package zip
- script: ./dev.sh package Release
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Linux)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
################################################################################
- job: build_osx_agent
################################################################################
displayName: macOS Agent (x64)
pool:
vmImage: macOS-10.13
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
# Create agent package zip
- script: ./dev.sh package Release
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (OSX)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
- stage: Release
dependsOn: Build
jobs:
################################################################################
- job: publish_agent_packages
################################################################################
displayName: Publish Agents (Windows/Linux/OSX)
pool:
name: ProductionRMAgents
steps:
# Download all agent packages from all previous phases
- task: DownloadBuildArtifacts@0
displayName: Download Agent Packages
inputs:
artifactName: runners
# Upload agent packages to Azure blob storage and refresh Azure CDN
- powershell: |
Write-Host "Preloading Azure modules." # This is for better performance, to avoid module-autoloading.
Import-Module AzureRM, AzureRM.profile, AzureRM.Storage, Azure.Storage, AzureRM.Cdn -ErrorAction Ignore -PassThru
Enable-AzureRmAlias -Scope CurrentUser
$uploadFiles = New-Object System.Collections.ArrayList
$certificateThumbprint = (Get-ItemProperty -Path "$(ServicePrincipalReg)").ServicePrincipalCertThumbprint
$clientId = (Get-ItemProperty -Path "$(ServicePrincipalReg)").ServicePrincipalClientId
Write-Host "##vso[task.setsecret]$certificateThumbprint"
Write-Host "##vso[task.setsecret]$clientId"
Login-AzureRmAccount -ServicePrincipal -CertificateThumbprint $certificateThumbprint -ApplicationId $clientId -TenantId $(GitHubTenantId)
Select-AzureRmSubscription -SubscriptionId $(GitHubSubscriptionId)
$storage = Get-AzureRmStorageAccount -ResourceGroupName githubassets -AccountName githubassets
Get-ChildItem -LiteralPath "$(System.ArtifactsDirectory)/runners" | ForEach-Object {
$versionDir = $_.Name.Trim('.zip').Trim('.tar.gz')
$versionDir = $versionDir.SubString($versionDir.LastIndexOf('-') + 1)
Write-Host "##vso[task.setvariable variable=ReleaseAgentVersion;]$versionDir"
Write-Host "Uploading $_ to BlobStorage githubassets/runners/$versionDir"
Set-AzureStorageBlobContent -Context $storage.Context -Container runners -File "$(System.ArtifactsDirectory)/runners/$_" -Blob "$versionDir/$_" -Force
$uploadFiles.Add("/runners/$versionDir/$_")
}
Write-Host "Get CDN info"
Get-AzureRmCdnEndpoint -ProfileName githubassets -ResourceGroupName githubassets
Write-Host "Purge Azure CDN Cache"
Unpublish-AzureRmCdnEndpointContent -EndpointName githubassets -ProfileName githubassets -ResourceGroupName githubassets -PurgeContent $uploadFiles
Write-Host "Pull assets through Azure CDN"
$uploadFiles | ForEach-Object {
$downloadUrl = "https://githubassets.azureedge.net" + $_
Write-Host $downloadUrl
Invoke-WebRequest -Uri $downloadUrl -OutFile $_.SubString($_.LastIndexOf('/') + 1)
}
displayName: Upload to Azure Blob
# Create agent release on Github
- powershell: |
Write-Host "Creating github release."
$releaseNotes = [System.IO.File]::ReadAllText("$(Build.SourcesDirectory)\releaseNote.md").Replace("<RUNNER_VERSION>","$(ReleaseAgentVersion)")
$releaseData = @{
tag_name = "v$(ReleaseAgentVersion)";
target_commitish = "$(Build.SourceVersion)";
name = "v$(ReleaseAgentVersion)";
body = $releaseNotes;
draft = $false;
prerelease = $true;
}
$releaseParams = @{
Uri = "https://api.github.com/repos/actions/runner/releases";
Method = 'POST';
Headers = @{
Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("github:$(GithubToken)"));
}
ContentType = 'application/json';
Body = (ConvertTo-Json $releaseData -Compress)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$releaseCreated = Invoke-RestMethod @releaseParams
Write-Host $releaseCreated
$releaseId = $releaseCreated.id
$assets = [System.IO.File]::ReadAllText("$(Build.SourcesDirectory)\assets.json").Replace("<RUNNER_VERSION>","$(ReleaseAgentVersion)")
$assetsParams = @{
Uri = "https://uploads.github.com/repos/actions/runner/releases/$releaseId/assets?name=assets.json"
Method = 'POST';
Headers = @{
Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("github:$(GithubToken)"));
}
ContentType = 'application/octet-stream';
Body = [system.Text.Encoding]::UTF8.GetBytes($assets)
}
Invoke-RestMethod @assetsParams
displayName: Create agent release on Github

View File

@@ -1,95 +0,0 @@
jobs:
################################################################################
- job: build_windows_x64_agent
################################################################################
displayName: Windows Agent (x64)
pool:
vmImage: vs2017-win2016
steps:
# Steps template for windows platform
- template: windows.template.yml
# Package dotnet core windows dependency (VC++ Redistributable)
- powershell: |
Write-Host "Downloading 'VC++ Redistributable' package."
$outDir = Join-Path -Path $env:TMP -ChildPath ([Guid]::NewGuid())
New-Item -Path $outDir -ItemType directory
$outFile = Join-Path -Path $outDir -ChildPath "ucrt.zip"
Invoke-WebRequest -Uri https://vstsagenttools.blob.core.windows.net/tools/ucrt/ucrt_x64.zip -OutFile $outFile
Write-Host "Unzipping 'VC++ Redistributable' package to agent layout."
$unzipDir = Join-Path -Path $outDir -ChildPath "unzip"
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($outFile, $unzipDir)
$agentLayoutBin = Join-Path -Path $(Build.SourcesDirectory) -ChildPath "_layout\bin"
Copy-Item -Path $unzipDir -Destination $agentLayoutBin -Force
displayName: Package UCRT
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
# Create agent package zip
- script: dev.cmd package Release
workingDirectory: src
displayName: Package Release
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Windows x64)
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
inputs:
pathToPublish: _package
artifactName: agent
artifactType: container
################################################################################
- job: build_linux_x64_agent
################################################################################
displayName: Linux Agent (x64)
pool:
vmImage: ubuntu-16.04
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
# Create agent package zip
- script: ./dev.sh package Release
workingDirectory: src
displayName: Package Release
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Linux x64)
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
inputs:
pathToPublish: _package
artifactName: agent
artifactType: container
################################################################################
- job: build_osx_agent
################################################################################
displayName: macOS Agent (x64)
pool:
vmImage: macOS-10.14
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
# Create agent package zip
- script: ./dev.sh package Release
workingDirectory: src
displayName: Package Release
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (OSX)
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
inputs:
pathToPublish: _package
artifactName: agent
artifactType: container

View File

@@ -0,0 +1,61 @@
# ADR 263: Self Hosted Runner Proxies
**Date**: 2019-11-13
**Status**: Accepted
## Context
- Proxy support is required for some enterprises and organizations to start using their own self hosted runners
- While there is not a standard convention, many applications support setting proxies via the environmental variables `http_proxy`, `https_proxy`, `no_proxy`, such as curl, wget, perl, python, docker, git, R, ect
- Some of these applications use `HTTPS_PROXY` versus `https_proxy`, but most understand or primarily support the lowercase variant
## Decisions
We will update the Runner to use the conventional environment variables for proxies: `http_proxy`, `https_proxy` and `no_proxy` if they are set.
These are described in detail below:
- `https_proxy` a proxy URL for all https traffic. It may contain basic authentication credentials. For example:
- http://proxy.com
- http://127.0.0.1:8080
- http://user:password@proxy.com
- `http_proxy` a proxy URL for all http traffic. It may contain basic authentication credentials. For example:
- http://proxy.com
- http://127.0.0.1:8080
- http://user:password@proxy.com
- `no_proxy` a comma seperated list of hosts that should not use the proxy. An optional port may be specified
- `google.com`
- `yahoo.com:443`
- `google.com,bing.com`
We won't use `http_proxy` for https traffic when `https_proxy` is not set, this behavior lines up with any libcurl based tools (curl, git) and wget.
Otherwise action authors and workflow users need to adjust to differences between the runner proxy convention, and tools used by their actions and scripts.
Example:
Customer set `http_proxy=http://127.0.0.1:8888` and configure the runner against `https://github.com/owner/repo`, with the `https_proxy` -> `http_proxy` fallback, the runner will connect to server without any problem. However, if user runs `git push` to `https://github.com/owner/repo`, `git` won't use the proxy since it require `https_proxy` to be set for any https traffic.
> `golang`, `node.js` and other dev tools from the linux community use `http_proxy` for both http and https traffic base on my research.
A majority of our users are using Linux where these variables are commonly required to be set by various programs. By reading these values, we simplify the process for self hosted runners to set up proxy, and expose it in a way users are already familiar with.
A password provided for a proxy will be masked in the logs.
We will support the lowercase and uppercase variants, with lowercase taking priority if both are set.
### No Proxy Format
While exact implementations are different per application on handle `no_proxy` env, most applications accept a comma separated list of hosts. Some accept wildcard characters (*). We are going to do exact case-insentive matches, and not support wildcards at this time.
For example:
- example.com will match example.com, foo.example.com, foo.bar.example.com
- foo.example.com will match bar.foo.example.com and foo.example.com
We will not support IP addresses for `no_proxy`, only hostnames.
## Consequences
1. Enterprises and organizations needing proxy support will be able to embrace self hosted runners
2. Users will need to set these environmental variables before configuring the runner in order to use a proxy when configuring
3. The runner will read from the environmental variables during config and runtime and use the provided proxy if it exists
4. Users may need to pass these environmental variables into other applications if they do not natively take these variables
5. Action authors may need to update their workflows to react to the these environment variables
6. We will document the way of setting environmental variables for runners using the environmental variables and how the runner uses them
7. Like all other secrets, users will be able to relatively easily figure out proxy password if they can modify a workflow file running on a self hosted machine

19
docs/adrs/README.md Normal file
View File

@@ -0,0 +1,19 @@
# ADRs
ADR, short for "Architecture Decision Record" is a way of capturing important architectural decisions, along with their context and consequences.
This folder includes ADRs for the actions runner. ADRs are proposed in the form of a pull request, and they commonly follow this format:
* **Title**: short present tense imperative phrase, less than 50 characters, like a git commit message.
* **Status**: proposed, accepted, rejected, deprecated, superseded, etc.
* **Context**: what is the issue that we're seeing that is motivating this decision or change.
* **Decision**: what is the change that we're actually proposing or doing.
* **Consequences**: what becomes easier or more difficult to do because of this change.
---
- More information about ADRs can be found [here](https://github.com/joelparkerhenderson/architecture_decision_record).

View File

@@ -1,12 +1,33 @@
# Contribute (Dev)
# Contributions
## Dev Dependencies
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.
> 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.
## Issues
Log issues for both bugs and enhancement requests. Logging issues are important for the open community.
Issues in this repository should be for the runner application. Note that the VM and virtual machine images (including the developer toolsets) installed on the actions hosted machine pools are located [in this repository](https://github.com/actions/virtual-environments)
## Enhancements and Feature Requests
We ask that before significant effort is put into code changes, that we have agreement on taking the change before time is invested in code changes.
1. Create a feature request. Once agreed we will take the enhancment
2. Create an ADR to agree on the details of the change.
An ADR is an Architectural Decision Record. This allows consensus on the direction forward and also serves as a record of the change and motivation. [Read more here](adrs/README.md)
## Development Life Cycle
### Required Dev Dependencies
![Win](res/win_sm.png) Git for Windows [Install Here](https://git-scm.com/downloads) (needed for dev sh script)
## Build, Test, Layout
### To Build, Test, Layout
From src:
Navigate to the `src` directory and run the following command:
![Win](res/win_sm.png) `dev {command}`
@@ -14,28 +35,27 @@ From src:
**Commands:**
`layout` (`l`): Run first time to create a full agent layout in {root}/_layout
* `layout` (`l`): Run first time to create a full runner layout in `{root}/_layout`
* `build` (`b`): Build everything and update runner layout folder
* `test` (`t`): Build runner binaries and run unit tests
`build` (`b`): build everything and update agent layout folder
Sample developer flow:
`test` (`t`): build agent binaries and run unit tests
Normal dev flow:
```bash
git clone https://github.com/actions/runner
cd ./src
./dev.(sh/cmd) layout # the agent that build from source is in {root}/_layout
./dev.(sh/cmd) layout # the runner that build from source is in {root}/_layout
<make code changes>
./dev.(sh/cmd) build # {root}/_layout will get updated
./dev.(sh/cmd) test # run all unit tests before git commit/push
```
## Editors
### Editors
[Using Visual Studio 2019](https://www.visualstudio.com/vs/)
[Using Visual Studio Code](https://code.visualstudio.com/)
[Using Visual Studio 2019](https://www.visualstudio.com/vs/)
## Styling
### Styling
We use the dotnet foundation and CoreCLR style guidelines [located here](
We use the .NET Foundation and CoreCLR style guidelines [located here](
https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/coding-style.md)

View File

@@ -5,36 +5,55 @@
## Supported Distributions and Versions
x64
- Red Hat Enterprise Linux 6 (see note 1), 7
- CentOS 6 (see note 1), 7
- Red Hat Enterprise Linux 7
- CentOS 7
- Oracle Linux 7
- Fedora 28, 27
- Debian 9, 8.7 or later versions
- Ubuntu 18.04, Ubuntu 16.04, Ubuntu 14.04
- Linux Mint 18, 17
- openSUSE 42.3 or later versions
- SUSE Enterprise Linux (SLES) 12 SP2 or later versions
- Fedora 29+
- Debian 9+
- Ubuntu 16.04+
- Linux Mint 18+
- openSUSE 15+
- SUSE Enterprise Linux (SLES) 12 SP2+
ARM32 (see note 2)
- Debian 9 or later versions
- Ubuntu 18.04 or later versions
## Install .Net Core 3.x Linux Dependencies
> Note 1: Red Hat Enterprise Linux 6 and CentOS 6 require installing the specialized "rhel.6-x64" agent package
> Note 2: ARM instruction set [ARMv7](https://en.wikipedia.org/wiki/List_of_ARM_microarchitectures) or above is required, you can get your device's information by executing `uname -a`
## Install .Net Core 2.x Linux Dependencies
The `./config.sh` will check .Net Core 2.x dependencies during agent configuration.
The `./config.sh` will check .Net Core 3.x dependencies during runner configuration.
You might see something like this which indicate a dependency's missing.
```bash
./config.sh
libunwind.so.8 => not found
libunwind-x86_64.so.8 => not found
Dependencies is missing for Dotnet Core 2.1
Execute ./bin/installdependencies.sh to install any missing Dotnet Core 2.1 dependencies.
Dependencies is missing for Dotnet Core 3.0
Execute ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies.
```
You can easily correct the problem by executing `./bin/installdependencies.sh`.
The `installdependencies.sh` script should install all required dependencies on all supported Linux versions
> Note: The `installdependencies.sh` script will try to use the default package management mechanism on your Linux flavor (ex. `yum`/`apt-get`/`apt`). You might need to deal with error coming from the package management mechanism related to your setup, like [#1353](https://github.com/Microsoft/vsts-agent/issues/1353)
> Note: The `installdependencies.sh` script will try to use the default package management mechanism on your Linux flavor (ex. `yum`/`apt-get`/`apt`).
### Full dependencies list
Debian based OS (Debian, Ubuntu, Linux Mint)
- liblttng-ust0
- libkrb5-3
- zlib1g
- libssl1.1, libssl1.0.2 or libssl1.0.0
- libicu63, libicu60, libicu57 or libicu55
Fedora based OS (Fedora, Redhat, Centos, Oracle Linux 7)
- lttng-ust
- openssl-libs
- krb5-libs
- zlib
- libicu
SUSE based OS (OpenSUSE, SUSE Enterprise)
- lttng-ust
- libopenssl1_1
- krb5
- zlib
- libicu60_2
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x)

View File

@@ -4,7 +4,7 @@
## Supported Versions
- macOS Sierra (10.12) and later versions
- macOS High Sierra (10.13) and later versions
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore2x)
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/macos-prerequisites?tabs=netcore30)

View File

@@ -5,8 +5,8 @@
- Windows 7 64-bit
- Windows 8.1 64-bit
- Windows 10 64-bit
- Windows Server 2008 R2 SP1 64-bit
- Windows Server 2012 R2 64-bit
- Windows Server 2016 64-bit
- Windows Server 2019 64-bit
## [More .Net Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)
## [More .NET Core Prerequisites Information](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore30)

View File

@@ -1,7 +0,0 @@
FROM mcr.microsoft.com/dotnet/core/runtime-deps:2.1
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
git \
&& rm -rf /var/lib/apt/lists/*

View File

@@ -1,150 +0,0 @@
FROM centos:6
# Install dependencies
RUN yum install -y \
centos-release-SCL \
epel-release \
wget \
unzip \
&& \
rpm --import http://linuxsoft.cern.ch/cern/slc6X/x86_64/RPM-GPG-KEY-cern && \
wget -O /etc/yum.repos.d/slc6-devtoolset.repo http://linuxsoft.cern.ch/cern/devtoolset/slc6-devtoolset.repo && \
yum install -y \
"perl(Time::HiRes)" \
autoconf \
cmake \
cmake3 \
devtoolset-2-toolchain \
doxygen \
expat-devel \
gcc \
gcc-c++ \
gdb \
gettext-devel \
krb5-devel \
libedit-devel \
libidn-devel \
libmetalink-devel \
libnghttp2-devel \
libssh2-devel \
libunwind-devel \
libuuid-devel \
lttng-ust-devel \
lzma \
ncurses-devel \
openssl-devel \
perl-devel \
python-argparse \
python27 \
readline-devel \
swig \
xz \
zlib-devel \
&& \
yum clean all
# Build and install clang and lldb 3.9.1
RUN wget ftp://sourceware.org/pub/binutils/snapshots/binutils-2.29.1.tar.xz && \
wget http://releases.llvm.org/3.9.1/cfe-3.9.1.src.tar.xz && \
wget http://releases.llvm.org/3.9.1/llvm-3.9.1.src.tar.xz && \
wget http://releases.llvm.org/3.9.1/lldb-3.9.1.src.tar.xz && \
wget http://releases.llvm.org/3.9.1/compiler-rt-3.9.1.src.tar.xz && \
\
tar -xf binutils-2.29.1.tar.xz && \
tar -xf llvm-3.9.1.src.tar.xz && \
mkdir llvm-3.9.1.src/tools/clang && \
mkdir llvm-3.9.1.src/tools/lldb && \
mkdir llvm-3.9.1.src/projects/compiler-rt && \
tar -xf cfe-3.9.1.src.tar.xz --strip 1 -C llvm-3.9.1.src/tools/clang && \
tar -xf lldb-3.9.1.src.tar.xz --strip 1 -C llvm-3.9.1.src/tools/lldb && \
tar -xf compiler-rt-3.9.1.src.tar.xz --strip 1 -C llvm-3.9.1.src/projects/compiler-rt && \
rm binutils-2.29.1.tar.xz && \
rm cfe-3.9.1.src.tar.xz && \
rm lldb-3.9.1.src.tar.xz && \
rm llvm-3.9.1.src.tar.xz && \
rm compiler-rt-3.9.1.src.tar.xz && \
\
mkdir llvmbuild && \
cd llvmbuild && \
scl enable python27 devtoolset-2 \
' \
cmake3 \
-DCMAKE_CXX_COMPILER=/opt/rh/devtoolset-2/root/usr/bin/g++ \
-DCMAKE_C_COMPILER=/opt/rh/devtoolset-2/root/usr/bin/gcc \
-DCMAKE_LINKER=/opt/rh/devtoolset-2/root/usr/bin/ld \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_LIBDIR_SUFFIX=64 \
-DLLVM_ENABLE_EH=1 \
-DLLVM_ENABLE_RTTI=1 \
-DLLVM_BINUTILS_INCDIR=../binutils-2.29.1/include \
../llvm-3.9.1.src \
&& \
make -j $(($(getconf _NPROCESSORS_ONLN)+1)) && \
make install \
' && \
cd .. && \
rm -r llvmbuild && \
rm -r llvm-3.9.1.src && \
rm -r binutils-2.29.1
# Build and install curl 7.45.0
RUN wget https://curl.haxx.se/download/curl-7.45.0.tar.lzma && \
tar -xf curl-7.45.0.tar.lzma && \
rm curl-7.45.0.tar.lzma && \
cd curl-7.45.0 && \
scl enable python27 devtoolset-2 \
' \
./configure \
--disable-dict \
--disable-ftp \
--disable-gopher \
--disable-imap \
--disable-ldap \
--disable-ldaps \
--disable-libcurl-option \
--disable-manual \
--disable-pop3 \
--disable-rtsp \
--disable-smb \
--disable-smtp \
--disable-telnet \
--disable-tftp \
--enable-ipv6 \
--enable-optimize \
--enable-symbol-hiding \
--with-ca-bundle=/etc/pki/tls/certs/ca-bundle.crt \
--with-nghttp2 \
--with-gssapi \
--with-ssl \
--without-librtmp \
&& \
make install \
' && \
cd .. && \
rm -r curl-7.45.0
# Install ICU 57.1
RUN wget http://download.icu-project.org/files/icu4c/57.1/icu4c-57_1-RHEL6-x64.tgz && \
tar -xf icu4c-57_1-RHEL6-x64.tgz -C / && \
rm icu4c-57_1-RHEL6-x64.tgz
# Compile and install a version of the git that supports the features that cli repo build needs
# NOTE: The git needs to be built after the curl so that it can use the libcurl to add https
# protocol support.
RUN \
wget https://www.kernel.org/pub/software/scm/git/git-2.9.5.tar.gz && \
tar -xf git-2.9.5.tar.gz && \
rm git-2.9.5.tar.gz && \
cd git-2.9.5 && \
make configure && \
./configure --prefix=/usr/local --without-tcltk && \
make -j $(nproc --all) all && \
make install && \
cd .. && \
rm -r git-2.9.5
ENV LD_LIBRARY_PATH=/usr/local/lib

View File

@@ -1,29 +0,0 @@
steps:
# Build agent layout
- script: ./dev.sh layout Release
workingDirectory: src
displayName: Build & Layout Release
# Run test
- script: ./dev.sh test
workingDirectory: src
displayName: Test
# # Publish test results
# - task: PublishTestResults@2
# displayName: Publish Test Results **/*.trx
# condition: always()
# inputs:
# testRunner: VSTest
# testResultsFiles: '**/*.trx'
# testRunTitle: 'Agent Tests'
# # Upload test log
# - task: PublishBuildArtifacts@1
# displayName: Publish Test logs
# condition: always()
# inputs:
# pathToPublish: src/Test/TestLogs
# artifactName: $(System.JobId)
# artifactType: container

View File

@@ -1,41 +1,70 @@
## Features
- Added support for OS and Architecture Labels for self hosted runners (#130)
- Remove runner flow: Change from PAT to "deletion token" in prompt (#225)
- Expose github.run_id and github.run_number to action runtime env. (#224)
## Bugs
- N/A
- Clean up error messages for container scenarios (#221)
- Pick shell from prependpath (#231)
## Misc
- Updated Prompts for self-hosted runners (#126)
- Optimized Secret Masking of Base64 Encoded Secrets (#128)
- Added additional tests for secret masking (#129)
## Agent Downloads
| | Package |
| ------- | ----------------------------------------------------------------------------------------------------------- |
| Windows x64 | [actions-runner-win-x64-<RUNNER_VERSION>.zip](https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip) |
| macOS | [actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz](https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz) |
| Linux x64 | [actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz](https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz) |
After Download:
- Runner code cleanup (#218 #227, #228, #229, #230)
- Consume dotnet core 3.1 in runner. (#213)
## Windows x64
``` bash
C:\> mkdir myagent && cd myagent
C:\myagent> Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::ExtractToDirectory("$HOME\Downloads\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
We recommend configuring the runner under "<DRIVE>:\actions-runner". This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows
```
// Create a folder under the drive root
mkdir \actions-runner ; cd \actions-runner
// Download the latest runner package
Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip -OutFile actions-runner-win-x64-<RUNNER_VERSION>.zip
// Extract the installer
Add-Type -AssemblyName System.IO.Compression.FileSystem ;
[System.IO.Compression.ZipFile]::ExtractToDirectory("$HOME\Downloads\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
```
## OSX
``` bash
~/$ mkdir myagent && cd myagent
~/myagent$ tar xzf ~/Downloads/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
```
## Linux x64
``` bash
~/$ mkdir myagent && cd myagent
~/myagent$ tar xzf ~/Downloads/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
```
## Linux arm64 (Pre-release)
``` bash
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
```
## Linux arm (Pre-release)
``` bash
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
// Extract the installer
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)

1
releaseVersion Normal file
View File

@@ -0,0 +1 @@
2.164.0

73
src/ActionsRunner.sln Normal file
View File

@@ -0,0 +1,73 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29411.138
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Runner.Common", "Runner.Common\Runner.Common.csproj", "{084289A3-CD7A-42E0-9219-4348B4B7E19B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Runner.Listener", "Runner.Listener\Runner.Listener.csproj", "{7D461AEE-BF2A-4855-BD96-56921160B36A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Runner.PluginHost", "Runner.PluginHost\Runner.PluginHost.csproj", "{D0320EB1-CB6D-4179-BFDC-2F2B664A370C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Runner.Plugins", "Runner.Plugins\Runner.Plugins.csproj", "{C23AFD6F-4DCD-4243-BC61-865BE31B9168}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Runner.Sdk", "Runner.Sdk\Runner.Sdk.csproj", "{D0484633-DA97-4C34-8E47-1DADE212A57A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RunnerService", "Runner.Service\Windows\RunnerService.csproj", "{D12EBD71-0464-46D0-8394-40BCFBA0A6F2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Runner.Worker", "Runner.Worker\Runner.Worker.csproj", "{C2F5B9FA-2621-411F-8EB2-273ED276F503}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk", "Sdk\Sdk.csproj", "{D2EE812B-E4DF-49BB-AE87-12BC49949B5F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{C932061F-F6A1-4F1E-B854-A6C6B30DC3EF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{084289A3-CD7A-42E0-9219-4348B4B7E19B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{084289A3-CD7A-42E0-9219-4348B4B7E19B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{084289A3-CD7A-42E0-9219-4348B4B7E19B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{084289A3-CD7A-42E0-9219-4348B4B7E19B}.Release|Any CPU.Build.0 = Release|Any CPU
{7D461AEE-BF2A-4855-BD96-56921160B36A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7D461AEE-BF2A-4855-BD96-56921160B36A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7D461AEE-BF2A-4855-BD96-56921160B36A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7D461AEE-BF2A-4855-BD96-56921160B36A}.Release|Any CPU.Build.0 = Release|Any CPU
{D0320EB1-CB6D-4179-BFDC-2F2B664A370C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0320EB1-CB6D-4179-BFDC-2F2B664A370C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0320EB1-CB6D-4179-BFDC-2F2B664A370C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0320EB1-CB6D-4179-BFDC-2F2B664A370C}.Release|Any CPU.Build.0 = Release|Any CPU
{C23AFD6F-4DCD-4243-BC61-865BE31B9168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C23AFD6F-4DCD-4243-BC61-865BE31B9168}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C23AFD6F-4DCD-4243-BC61-865BE31B9168}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C23AFD6F-4DCD-4243-BC61-865BE31B9168}.Release|Any CPU.Build.0 = Release|Any CPU
{D0484633-DA97-4C34-8E47-1DADE212A57A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0484633-DA97-4C34-8E47-1DADE212A57A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0484633-DA97-4C34-8E47-1DADE212A57A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0484633-DA97-4C34-8E47-1DADE212A57A}.Release|Any CPU.Build.0 = Release|Any CPU
{D12EBD71-0464-46D0-8394-40BCFBA0A6F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D12EBD71-0464-46D0-8394-40BCFBA0A6F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D12EBD71-0464-46D0-8394-40BCFBA0A6F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D12EBD71-0464-46D0-8394-40BCFBA0A6F2}.Release|Any CPU.Build.0 = Release|Any CPU
{C2F5B9FA-2621-411F-8EB2-273ED276F503}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2F5B9FA-2621-411F-8EB2-273ED276F503}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2F5B9FA-2621-411F-8EB2-273ED276F503}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2F5B9FA-2621-411F-8EB2-273ED276F503}.Release|Any CPU.Build.0 = Release|Any CPU
{D2EE812B-E4DF-49BB-AE87-12BC49949B5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D2EE812B-E4DF-49BB-AE87-12BC49949B5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2EE812B-E4DF-49BB-AE87-12BC49949B5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2EE812B-E4DF-49BB-AE87-12BC49949B5F}.Release|Any CPU.Build.0 = Release|Any CPU
{C932061F-F6A1-4F1E-B854-A6C6B30DC3EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C932061F-F6A1-4F1E-B854-A6C6B30DC3EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C932061F-F6A1-4F1E-B854-A6C6B30DC3EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C932061F-F6A1-4F1E-B854-A6C6B30DC3EF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4A831DDA-3860-45E5-930E-BB3A7833AE80}
EndGlobalSection
EndGlobal

54
src/Directory.Build.props Normal file
View File

@@ -0,0 +1,54 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Detect OS for build -->
<PropertyGroup>
<BUILD_OS Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">Windows</BUILD_OS>
<BUILD_OS Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">OSX</BUILD_OS>
<BUILD_OS Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">Linux</BUILD_OS>
</PropertyGroup>
<!-- Set OS vars -->
<PropertyGroup Condition="'$(BUILD_OS)' == 'Windows'">
<DefineConstants>$(DefineConstants);OS_WINDOWS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'OSX'">
<DefineConstants>$(DefineConstants);OS_OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux'">
<DefineConstants>$(DefineConstants);OS_LINUX</DefineConstants>
</PropertyGroup>
<!-- Set Platform/bitness vars -->
<PropertyGroup Condition="'$(BUILD_OS)' == 'Windows' AND ('$(PackageRuntime)' == 'win-x64' OR '$(PackageRuntime)' == '')">
<DefineConstants>$(DefineConstants);X64</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'Windows' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>$(DefineConstants);X86</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'OSX'">
<DefineConstants>$(DefineConstants);X64</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux' AND ('$(PackageRuntime)' == 'linux-x64' OR '$(PackageRuntime)' == '')">
<DefineConstants>$(DefineConstants);X64</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>$(DefineConstants);ARM</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(BUILD_OS)' == 'Linux' AND '$(PackageRuntime)' == 'linux-arm64'">
<DefineConstants>$(DefineConstants);ARM64</DefineConstants>
</PropertyGroup>
<!-- Set TRACE/DEBUG vars -->
<PropertyGroup>
<DefineConstants>$(DefineConstants);TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DefineConstants>$(DefineConstants);DEBUG</DefineConstants>
</PropertyGroup>
<!-- Set Treat tarnings as errors -->
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

View File

@@ -37,10 +37,7 @@
.PARAMETER SharedRuntime
This parameter is obsolete and may be removed in a future version of this script.
The recommended alternative is '-Runtime dotnet'.
Default: false
Installs just the shared runtime bits, not the entire SDK.
This is equivalent to specifying `-Runtime dotnet`.
.PARAMETER Runtime
Installs just a shared runtime, not the entire SDK.
Possible values:
@@ -77,11 +74,15 @@
Skips installing non-versioned files if they already exist, such as dotnet.exe.
.PARAMETER NoCdn
Disable downloading from the Azure CDN, and use the uncached feed directly.
.PARAMETER JSonFile
Determines the SDK version from a user specified global.json file
Note: global.json must have a value for 'SDK:Version'
#>
[cmdletbinding()]
param(
[string]$Channel="LTS",
[string]$Version="Latest",
[string]$JSonFile,
[string]$InstallDir="<auto>",
[string]$Architecture="<auto>",
[ValidateSet("dotnet", "aspnetcore", "windowsdesktop", IgnoreCase = $false)]
@@ -166,7 +167,7 @@ function Get-CLIArchitecture-From-Architecture([string]$Architecture) {
{ $_ -eq "x86" } { return "x86" }
{ $_ -eq "arm" } { return "arm" }
{ $_ -eq "arm64" } { return "arm64" }
default { throw "Architecture not supported. If you think this is a bug, report it at https://github.com/dotnet/cli/issues" }
default { throw "Architecture not supported. If you think this is a bug, report it at https://github.com/dotnet/sdk/issues" }
}
}
@@ -258,7 +259,6 @@ function GetHTTPResponse([Uri] $Uri)
})
}
function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) {
Say-Invocation $MyInvocation
@@ -304,10 +304,45 @@ function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Co
return $VersionInfo
}
function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version) {
function Parse-Jsonfile-For-Version([string]$JSonFile) {
Say-Invocation $MyInvocation
If (-Not (Test-Path $JSonFile)) {
throw "Unable to find '$JSonFile'"
}
try {
$JSonContent = Get-Content($JSonFile) -Raw | ConvertFrom-Json | Select-Object -expand "sdk" -ErrorAction SilentlyContinue
}
catch {
throw "Json file unreadable: '$JSonFile'"
}
if ($JSonContent) {
try {
$JSonContent.PSObject.Properties | ForEach-Object {
$PropertyName = $_.Name
if ($PropertyName -eq "version") {
$Version = $_.Value
Say-Verbose "Version = $Version"
}
}
}
catch {
throw "Unable to parse the SDK node in '$JSonFile'"
}
}
else {
throw "Unable to find the SDK node in '$JSonFile'"
}
If ($Version -eq $null) {
throw "Unable to find the SDK:version node in '$JSonFile'"
}
return $Version
}
function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version, [string]$JSonFile) {
Say-Invocation $MyInvocation
if (-not $JSonFile) {
switch ($Version.ToLower()) {
{ $_ -eq "latest" } {
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False
@@ -320,6 +355,10 @@ function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel,
default { return $Version }
}
}
else {
return Parse-Jsonfile-For-Version $JSonFile
}
}
function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) {
Say-Invocation $MyInvocation
@@ -382,28 +421,11 @@ function Resolve-Installation-Path([string]$InstallDir) {
return $InstallDir
}
function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$RelativePathToVersionFile) {
Say-Invocation $MyInvocation
$VersionFile = Join-Path -Path $InstallRoot -ChildPath $RelativePathToVersionFile
Say-Verbose "Local version file: $VersionFile"
if (Test-Path $VersionFile) {
$VersionText = cat $VersionFile
Say-Verbose "Local version file text: $VersionText"
return Get-Version-Info-From-Version-Text $VersionText
}
Say-Verbose "Local version file not found."
return $null
}
function Is-Dotnet-Package-Installed([string]$InstallRoot, [string]$RelativePathToPackage, [string]$SpecificVersion) {
Say-Invocation $MyInvocation
$DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion
Say-Verbose "Is-Dotnet-Package-Installed: Path to a package: $DotnetPackagePath"
Say-Verbose "Is-Dotnet-Package-Installed: DotnetPackagePath=$DotnetPackagePath"
return Test-Path $DotnetPackagePath -PathType Container
}
@@ -534,7 +556,7 @@ function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolde
}
$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture
$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version
$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version -JSonFile $JSonFile
$DownloadLink = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
@@ -636,8 +658,22 @@ if ($DownloadFailed) {
Say "Extracting zip from $DownloadLink"
Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot
# Check if the SDK version is now installed; if not, fail the installation.
# Check if the SDK version is installed; if not, fail the installation.
$isAssetInstalled = $false
# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
if ($SpecificVersion -Match "rtm" -or $SpecificVersion -Match "servicing") {
$ReleaseVersion = $SpecificVersion.Split("-")[0]
Say-Verbose "Checking installation: version = $ReleaseVersion"
$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $ReleaseVersion
}
# Check if the SDK version is installed.
if (!$isAssetInstalled) {
Say-Verbose "Checking installation: version = $SpecificVersion"
$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion
}
if (!$isAssetInstalled) {
throw "`"$assetName`" with version = $SpecificVersion failed to install with an unknown error."
}

View File

@@ -144,7 +144,7 @@ get_linux_platform_name() {
else
if [ -e /etc/os-release ]; then
. /etc/os-release
echo "$ID.$VERSION_ID"
echo "$ID${VERSION_ID:+.${VERSION_ID}}"
return 0
elif [ -e /etc/redhat-release ]; then
local redhatRelease=$(</etc/redhat-release)
@@ -159,6 +159,10 @@ get_linux_platform_name() {
return 1
}
is_musl_based_distro() {
(ldd --version 2>&1 || true) | grep -q musl
}
get_current_os_name() {
eval $invocation
@@ -173,10 +177,10 @@ get_current_os_name() {
local linux_platform_name
linux_platform_name="$(get_linux_platform_name)" || { echo "linux" && return 0 ; }
if [[ $linux_platform_name == "rhel.6" ]]; then
if [ "$linux_platform_name" = "rhel.6" ]; then
echo $linux_platform_name
return 0
elif [[ $linux_platform_name == alpine* ]]; then
elif is_musl_based_distro; then
echo "linux-musl"
return 0
else
@@ -202,7 +206,7 @@ get_legacy_os_name() {
else
if [ -e /etc/os-release ]; then
. /etc/os-release
os=$(get_legacy_os_name_from_platform "$ID.$VERSION_ID" || echo "")
os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "")
if [ -n "$os" ]; then
echo "$os"
return 0
@@ -245,20 +249,29 @@ check_pre_reqs() {
fi
if [ "$(uname)" = "Linux" ]; then
if is_musl_based_distro; then
if ! command -v scanelf > /dev/null; then
say_warning "scanelf not found, please install pax-utils package."
return 0
fi
LDCONFIG_COMMAND="scanelf --ldpath -BF '%f'"
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libintl)" ] && say_warning "Unable to locate libintl. Probable prerequisite missing; install libintl (or gettext)."
else
if [ ! -x "$(command -v ldconfig)" ]; then
echo "ldconfig is not in PATH, trying /sbin/ldconfig."
say_verbose "ldconfig is not in PATH, trying /sbin/ldconfig."
LDCONFIG_COMMAND="/sbin/ldconfig"
else
LDCONFIG_COMMAND="ldconfig"
fi
local librarypath=${LD_LIBRARY_PATH:-}
LDCONFIG_COMMAND="$LDCONFIG_COMMAND -NXv ${librarypath//:/ }"
fi
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libunwind)" ] && say_warning "Unable to locate libunwind. Probable prerequisite missing; install libunwind."
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libssl)" ] && say_warning "Unable to locate libssl. Probable prerequisite missing; install libssl."
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep zlib)" ] && say_warning "Unable to locate zlib. Probable prerequisite missing; install zlib."
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep ssl)" ] && say_warning "Unable to locate libssl. Probable prerequisite missing; install libssl."
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libicu)" ] && say_warning "Unable to locate libicu. Probable prerequisite missing; install libicu."
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep -F libcurl.so)" ] && say_warning "Unable to locate libcurl. Probable prerequisite missing; install libcurl."
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep lttng)" ] && say_warning "Unable to locate liblttng. Probable prerequisite missing; install libcurl."
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libcurl)" ] && say_warning "Unable to locate libcurl. Probable prerequisite missing; install libcurl."
fi
return 0
@@ -360,7 +373,7 @@ get_normalized_architecture_from_architecture() {
;;
esac
say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/cli/issues"
say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/sdk/issues"
return 1
}
@@ -435,11 +448,53 @@ get_latest_version_info() {
return $?
}
# args:
# json_file - $1
parse_jsonfile_for_version() {
eval $invocation
local json_file="$1"
if [ ! -f "$json_file" ]; then
say_err "Unable to find \`$json_file\`"
return 1
fi
sdk_section=$(cat $json_file | awk '/"sdk"/,/}/')
if [ -z "$sdk_section" ]; then
say_err "Unable to parse the SDK node in \`$json_file\`"
return 1
fi
sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}')
sdk_list=${sdk_list//[\" ]/}
sdk_list=${sdk_list//,/$'\n'}
sdk_list="$(echo -e "${sdk_list}" | tr -d '[[:space:]]')"
local version_info=""
while read -r line; do
IFS=:
while read -r key value; do
if [[ "$key" == "version" ]]; then
version_info=$value
fi
done <<< "$line"
done <<< "$sdk_list"
if [ -z "$version_info" ]; then
say_err "Unable to find the SDK:version node in \`$json_file\`"
return 1
fi
unset IFS;
echo "$version_info"
return 0
}
# args:
# azure_feed - $1
# channel - $2
# normalized_architecture - $3
# version - $4
# json_file - $5
get_specific_version_from_version() {
eval $invocation
@@ -447,7 +502,9 @@ get_specific_version_from_version() {
local channel="$2"
local normalized_architecture="$3"
local version="$(to_lowercase "$4")"
local json_file="$5"
if [ -z "$json_file" ]; then
case "$version" in
latest)
local version_info
@@ -468,6 +525,12 @@ get_specific_version_from_version() {
return 0
;;
esac
else
local version_info
version_info="$(parse_jsonfile_for_version "$json_file")" || return 1
echo "$version_info"
return 0
fi
}
# args:
@@ -558,24 +621,6 @@ resolve_installation_path() {
return 0
}
# args:
# install_root - $1
get_installed_version_info() {
eval $invocation
local install_root="$1"
local version_file="$(combine_paths "$install_root" "$local_version_file_relative_path")"
say_verbose "Local version file: $version_file"
if [ ! -z "$version_file" ] | [ -r "$version_file" ]; then
local version_info="$(cat "$version_file")"
echo "$version_info"
return 0
fi
say_verbose "Local version file not found."
return 0
}
# args:
# relative_or_absolute_path - $1
get_absolute_path() {
@@ -600,7 +645,7 @@ copy_files_or_dirs_from_list() {
local osname="$(get_current_os_name)"
local override_switch=$(
if [ "$override" = false ]; then
if [[ "$osname" == "linux-musl" ]]; then
if [ "$osname" = "linux-musl" ]; then
printf -- "-u";
else
printf -- "-n";
@@ -724,7 +769,7 @@ calculate_vars() {
normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")"
say_verbose "normalized_architecture=$normalized_architecture"
specific_version="$(get_specific_version_from_version "$azure_feed" "$channel" "$normalized_architecture" "$version")"
specific_version="$(get_specific_version_from_version "$azure_feed" "$channel" "$normalized_architecture" "$version" "$json_file")"
say_verbose "specific_version=$specific_version"
if [ -z "$specific_version" ]; then
say_err "Could not resolve version information."
@@ -809,13 +854,27 @@ install_dotnet() {
say "Extracting zip from $download_link"
extract_dotnet_package "$zip_path" "$install_root"
# Check if the SDK version is now installed; if not, fail the installation.
if ! is_dotnet_package_installed "$install_root" "$asset_relative_path" "$specific_version"; then
say_err "\`$asset_name\` with version = $specific_version failed to install with an unknown error."
return 1
# Check if the SDK version is installed; if not, fail the installation.
# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then
IFS='-'
read -ra verArr <<< "$specific_version"
release_version="${verArr[0]}"
unset IFS;
say_verbose "Checking installation: version = $release_version"
if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then
return 0
fi
fi
# Check if the standard SDK version is installed.
say_verbose "Checking installation: version = $specific_version"
if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$specific_version"; then
return 0
fi
say_err "\`$asset_name\` with version = $specific_version failed to install with an unknown error."
return 1
}
args=("$@")
@@ -826,6 +885,7 @@ temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX"
channel="LTS"
version="Latest"
json_file=""
install_dir="<auto>"
architecture="<auto>"
dry_run=false
@@ -912,6 +972,10 @@ do
runtime_id="$1"
non_dynamic_parameters+=" $name "\""$1"\"""
;;
--jsonfile|-[Jj][Ss]on[Ff]ile)
shift
json_file="$1"
;;
--skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles)
override_non_versioned_files=false
non_dynamic_parameters+=" $name"
@@ -953,22 +1017,25 @@ do
echo " Possible values:"
echo " - dotnet - the Microsoft.NETCore.App shared runtime"
echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime"
echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable."
echo " -SkipNonVersionedFiles"
echo " --dry-run,-DryRun Do not perform installation. Display download link."
echo " --no-path, -NoPath Do not set PATH for the current process."
echo " --verbose,-Verbose Display diagnostics information."
echo " --azure-feed,-AzureFeed Azure feed location. Defaults to $azure_feed, This parameter typically is not changed by the user."
echo " --uncached-feed,-UncachedFeed Uncached feed location. This parameter typically is not changed by the user."
echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly."
echo " --feed-credential,-FeedCredential Azure feed shared access token. This parameter typically is not specified."
echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable."
echo " -SkipNonVersionedFiles"
echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly."
echo " --jsonfile <JSONFILE> Determines the SDK version from a user specified global.json file."
echo " Note: global.json must have a value for 'SDK:Version'"
echo " --runtime-id Installs the .NET Tools for the given platform (use linux-x64 for portable linux)."
echo " -RuntimeId"
echo " -?,--?,-h,--help,-Help Shows this help message"
echo ""
echo "Obsolete parameters:"
echo " --shared-runtime The recommended alternative is '--runtime dotnet'."
echo " -SharedRuntime Installs just the shared runtime bits, not the entire SDK."
echo " This parameter is obsolete and may be removed in a future version of this script."
echo " Installs just the shared runtime bits, not the entire SDK."
echo ""
echo "Install Location:"
echo " Location is chosen in following order:"

View File

@@ -3,7 +3,7 @@ PACKAGERUNTIME=$1
PRECACHE=$2
NODE_URL=https://nodejs.org/dist
NODE12_VERSION="12.4.0"
NODE12_VERSION="12.13.1"
get_abs_path() {
# exploits the fact that pwd will print abs path when no args
@@ -123,9 +123,9 @@ function acquireExternalTool() {
}
# Download the external tools only for Windows.
if [[ "$PACKAGERUNTIME" == "win-x64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/win-x64/node.exe" node12/bin
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/win-x64/node.lib" node12/bin
if [[ "$PACKAGERUNTIME" == "win-x64" || "$PACKAGERUNTIME" == "win-x86" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/$PACKAGERUNTIME/node.exe" node12/bin
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/$PACKAGERUNTIME/node.lib" node12/bin
if [[ "$PRECACHE" != "" ]]; then
acquireExternalTool "https://github.com/microsoft/vswhere/releases/download/2.6.7/vswhere.exe" vswhere
fi
@@ -136,11 +136,14 @@ if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-darwin-x64.tar.gz" node12 fix_nested_dir
fi
# Download the external tools common across Linux PACKAGERUNTIMEs (excluding OSX).
if [[ "$PACKAGERUNTIME" == "linux-x64" || "$PACKAGERUNTIME" == "rhel.6-x64" ]]; then
# Download the external tools for Linux PACKAGERUNTIMEs.
if [[ "$PACKAGERUNTIME" == "linux-x64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-x64.tar.gz" node12 fix_nested_dir
# TODO: Repath this blob to use a consistent version format (_ vs .)
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/12_4_0/alpine/node-v${NODE12_VERSION}-alpine.tar.gz" node12_alpine
acquireExternalTool "https://vstsagenttools.blob.core.windows.net/tools/nodejs/${NODE12_VERSION}/alpine/x64/node-${NODE12_VERSION}-alpine-x64.tar.gz" node12_alpine
fi
if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then
acquireExternalTool "$NODE_URL/v${NODE12_VERSION}/node-v${NODE12_VERSION}-linux-arm64.tar.gz" node12 fix_nested_dir
fi
if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then

View File

@@ -61,9 +61,8 @@ then
exit 1
fi
# ubuntu 18 uses libcurl4
# ubuntu 14, 16 and other linux use libcurl3
apt install -y libcurl3 || apt install -y libcurl4
# libissl version prefer: libssl1.1 -> libssl1.0.2 -> libssl1.0.0
apt install -y libssl1.1$ || apt install -y libssl1.0.2$ || apt install -y libssl1.0.0$
if [ $? -ne 0 ]
then
echo "'apt' failed with exit code '$?'"
@@ -71,18 +70,8 @@ then
exit 1
fi
# debian 9 use libssl1.0.2
# other debian linux use libssl1.0.0
apt install -y libssl1.0.0 || apt install -y libssl1.0.2
if [ $? -ne 0 ]
then
echo "'apt' failed with exit code '$?'"
print_errormessage
exit 1
fi
# libicu version prefer: libicu52 -> libicu55 -> libicu57 -> libicu60
apt install -y libicu52 || apt install -y libicu55 || apt install -y libicu57 || apt install -y libicu60
# libicu version prefer: libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
apt install -y libicu63 || apt install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
if [ $? -ne 0 ]
then
echo "'apt' failed with exit code '$?'"
@@ -101,9 +90,8 @@ then
exit 1
fi
# ubuntu 18 uses libcurl4
# ubuntu 14, 16 and other linux use libcurl3
apt-get install -y libcurl3 || apt-get install -y libcurl4
# libissl version prefer: libssl1.1 -> libssl1.0.2 -> libssl1.0.0
apt-get install -y libssl1.1$ || apt-get install -y libssl1.0.2$ || apt install -y libssl1.0.0$
if [ $? -ne 0 ]
then
echo "'apt-get' failed with exit code '$?'"
@@ -111,18 +99,8 @@ then
exit 1
fi
# debian 9 use libssl1.0.2
# other debian linux use libssl1.0.0
apt-get install -y libssl1.0.0 || apt install -y libssl1.0.2
if [ $? -ne 0 ]
then
echo "'apt-get' failed with exit code '$?'"
print_errormessage
exit 1
fi
# libicu version prefer: libicu52 -> libicu55 -> libicu57 -> libicu60
apt-get install -y libicu52 || apt install -y libicu55 || apt install -y libicu57 || apt install -y libicu60
# libicu version prefer: libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
apt-get install -y libicu63 || apt-get install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
if [ $? -ne 0 ]
then
echo "'apt-get' failed with exit code '$?'"
@@ -149,46 +127,7 @@ then
command -v dnf
if [ $? -eq 0 ]
then
useCompatSsl=0
grep -i 'fedora release 28' /etc/fedora-release
if [ $? -eq 0 ]
then
useCompatSsl=1
else
grep -i 'fedora release 27' /etc/fedora-release
if [ $? -eq 0 ]
then
useCompatSsl=1
else
grep -i 'fedora release 26' /etc/fedora-release
if [ $? -eq 0 ]
then
useCompatSsl=1
fi
fi
fi
if [ $useCompatSsl -eq 1 ]
then
echo "Use compat-openssl10-devel instead of openssl-devel for Fedora 27/28 (dotnet core requires openssl 1.0.x)"
dnf install -y compat-openssl10
if [ $? -ne 0 ]
then
echo "'dnf' failed with exit code '$?'"
print_errormessage
exit 1
fi
else
dnf install -y openssl-libs
if [ $? -ne 0 ]
then
echo "'dnf' failed with exit code '$?'"
print_errormessage
exit 1
fi
fi
dnf install -y lttng-ust libcurl krb5-libs zlib libicu
dnf install -y lttng-ust openssl-libs krb5-libs zlib libicu
if [ $? -ne 0 ]
then
echo "'dnf' failed with exit code '$?'"
@@ -204,22 +143,13 @@ then
command -v yum
if [ $? -eq 0 ]
then
yum install -y openssl-libs libcurl krb5-libs zlib libicu
yum install -y lttng-ust openssl-libs krb5-libs zlib libicu
if [ $? -ne 0 ]
then
echo "'yum' failed with exit code '$?'"
print_errormessage
exit 1
fi
# install lttng-ust separately since it's not part of offical package repository
yum install -y wget && wget -P /etc/yum.repos.d/ https://packages.efficios.com/repo.files/EfficiOS-RHEL7-x86-64.repo && rpmkeys --import https://packages.efficios.com/rhel/repo.key && yum updateinfo && yum install -y lttng-ust
if [ $? -ne 0 ]
then
echo "'lttng-ust' installation failed with exit code '$?'"
print_errormessage
exit 1
fi
else
echo "Can not find 'yum'"
print_errormessage
@@ -230,13 +160,14 @@ then
# we might on OpenSUSE
OSTYPE=$(grep ID_LIKE /etc/os-release | cut -f2 -d=)
echo $OSTYPE
if [ $OSTYPE == '"suse"' ]
echo $OSTYPE | grep "suse"
if [ $? -eq 0 ]
then
echo "The current OS is SUSE based"
command -v zypper
if [ $? -eq 0 ]
then
zypper -n install lttng-ust libopenssl1_0_0 libcurl4 krb5 zlib libicu52_1
zypper -n install lttng-ust libopenssl1_1 krb5 zlib libicu60_2
if [ $? -ne 0 ]
then
echo "'zypper' failed with exit code '$?'"

View File

@@ -3,12 +3,12 @@
user_id=`id -u`
# we want to snapshot the environment of the config user
if [ $user_id -eq 0 -a -z "$AGENT_ALLOW_RUNASROOT" ]; then
if [ $user_id -eq 0 -a -z "$RUNNER_ALLOW_RUNASROOT" ]; then
echo "Must not run with sudo"
exit 1
fi
# Check dotnet core 2.1 dependencies for Linux
# Check dotnet core 3.0 dependencies for Linux
if [[ (`uname` == "Linux") ]]
then
command -v ldd > /dev/null
@@ -20,29 +20,22 @@ then
ldd ./bin/libcoreclr.so | grep 'not found'
if [ $? -eq 0 ]; then
echo "Dependencies is missing for Dotnet Core 2.1"
echo "Execute ./bin/installdependencies.sh to install any missing Dotnet Core 2.1 dependencies."
echo "Dependencies is missing for Dotnet Core 3.0"
echo "Execute ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies."
exit 1
fi
ldd ./bin/System.Security.Cryptography.Native.OpenSsl.so | grep 'not found'
if [ $? -eq 0 ]; then
echo "Dependencies is missing for Dotnet Core 2.1"
echo "Execute ./bin/installdependencies.sh to install any missing Dotnet Core 2.1 dependencies."
echo "Dependencies is missing for Dotnet Core 3.0"
echo "Execute ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies."
exit 1
fi
ldd ./bin/System.IO.Compression.Native.so | grep 'not found'
if [ $? -eq 0 ]; then
echo "Dependencies is missing for Dotnet Core 2.1"
echo "Execute ./bin/installdependencies.sh to install any missing Dotnet Core 2.1 dependencies."
exit 1
fi
ldd ./bin/System.Net.Http.Native.so | grep 'not found'
if [ $? -eq 0 ]; then
echo "Dependencies is missing for Dotnet Core 2.1"
echo "Execute ./bin/installdependencies.sh to install any missing Dotnet Core 2.1 dependencies."
echo "Dependencies is missing for Dotnet Core 3.0"
echo "Execute ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies."
exit 1
fi
@@ -59,8 +52,8 @@ then
libpath=${LD_LIBRARY_PATH:-}
$LDCONFIG_COMMAND -NXv ${libpath//:/} 2>&1 | grep libicu >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Libicu's dependencies is missing for Dotnet Core 2.1"
echo "Execute ./bin/installdependencies.sh to install any missing Dotnet Core 2.1 dependencies."
echo "Libicu's dependencies is missing for Dotnet Core 3.0"
echo "Execute ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies."
exit 1
fi
fi

View File

@@ -9,9 +9,6 @@ varCheckList=(
'GRADLE_HOME'
'NVM_BIN'
'NVM_PATH'
'VSTS_HTTP_PROXY'
'VSTS_HTTP_PROXY_USERNAME'
'VSTS_HTTP_PROXY_PASSWORD'
'LD_LIBRARY_PATH'
'PERL5LIB'
)

View File

@@ -2,7 +2,7 @@
# Validate not sudo
user_id=`id -u`
if [ $user_id -eq 0 -a -z "$AGENT_ALLOW_RUNASROOT" ]; then
if [ $user_id -eq 0 -a -z "$RUNNER_ALLOW_RUNASROOT" ]; then
echo "Must not run interactively with sudo"
exit 1
fi
@@ -26,8 +26,8 @@ if [[ "$1" == "localRun" ]]; then
else
"$DIR"/bin/Runner.Listener run $*
# Return code 4 means the run once agent received an update message.
# Sleep 5 seconds to wait for the update process finish and run the agent again.
# Return code 4 means the run once runner received an update message.
# Sleep 5 seconds to wait for the update process finish and run the runner again.
returnCode=$?
if [[ $returnCode == 4 ]]; then
if [ ! -x "$(command -v sleep)" ]; then

View File

@@ -9,26 +9,27 @@ namespace GitHub.Runner.Common
{
private static readonly EscapeMapping[] _escapeMappings = new[]
{
new EscapeMapping(token: "%", replacement: "%25"),
new EscapeMapping(token: ";", replacement: "%3B"),
new EscapeMapping(token: "\r", replacement: "%0D"),
new EscapeMapping(token: "\n", replacement: "%0A"),
new EscapeMapping(token: "]", replacement: "%5D"),
new EscapeMapping(token: "%", replacement: "%25"),
};
private static readonly EscapeMapping[] _escapeDataMappings = new[]
{
new EscapeMapping(token: "\r", replacement: "%0D"),
new EscapeMapping(token: "\n", replacement: "%0A"),
new EscapeMapping(token: "%", replacement: "%25"),
};
private static readonly EscapeMapping[] _escapePropertyMappings = new[]
{
new EscapeMapping(token: "%", replacement: "%25"),
new EscapeMapping(token: "\r", replacement: "%0D"),
new EscapeMapping(token: "\n", replacement: "%0A"),
new EscapeMapping(token: ":", replacement: "%3A"),
new EscapeMapping(token: ",", replacement: "%2C"),
new EscapeMapping(token: "%", replacement: "%25"),
};
private readonly Dictionary<string, string> _properties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

View File

@@ -1,33 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
namespace GitHub.Runner.Common
{
//Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266920.aspx
public class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return m_tcs.Task; }
public void Set()
{
var tcs = m_tcs;
Task.Factory.StartNew(s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default);
tcs.Task.Wait();
}
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
}

View File

@@ -1,6 +1,8 @@
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
@@ -13,24 +15,12 @@ namespace GitHub.Runner.Common
[DataContract]
public sealed class RunnerSettings
{
[DataMember(EmitDefaultValue = false)]
public bool AcceptTeeEula { get; set; }
[DataMember(EmitDefaultValue = false)]
public int AgentId { get; set; }
[DataMember(EmitDefaultValue = false)]
public string AgentName { get; set; }
[DataMember(EmitDefaultValue = false)]
public string NotificationPipeName { get; set; }
[DataMember(EmitDefaultValue = false)]
public string NotificationSocketAddress { get; set; }
[DataMember(EmitDefaultValue = false)]
public bool SkipCapabilitiesScan { get; set; }
[DataMember(EmitDefaultValue = false)]
public bool SkipSessionRecover { get; set; }
@@ -51,15 +41,34 @@ namespace GitHub.Runner.Common
[DataMember(EmitDefaultValue = false)]
public string MonitorSocketAddress { get; set; }
/// <summary>
// Computed property for convenience. Can either return:
// 1. If runner was configured at the repo level, returns something like: "myorg/myrepo"
// 2. If runner was configured at the org level, returns something like: "myorg"
/// </summary>
public string RepoOrOrgName
{
get
{
Uri accountUri = new Uri(this.ServerUrl);
string repoOrOrgName = string.Empty;
if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase))
{
Uri gitHubUrl = new Uri(this.GitHubUrl);
// Use the "NWO part" from the GitHub URL path
repoOrOrgName = gitHubUrl.AbsolutePath.Trim('/');
}
else
{
repoOrOrgName = accountUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
}
[DataContract]
public sealed class RunnerRuntimeOptions
{
#if OS_WINDOWS
[DataMember(EmitDefaultValue = false)]
public bool GitUseSecureChannel { get; set; }
#endif
return repoOrOrgName;
}
}
}
[ServiceLocator(Default = typeof(ConfigurationStore))]
@@ -74,9 +83,6 @@ namespace GitHub.Runner.Common
void SaveSettings(RunnerSettings settings);
void DeleteCredential();
void DeleteSettings();
RunnerRuntimeOptions GetRunnerRuntimeOptions();
void SaveRunnerRuntimeOptions(RunnerRuntimeOptions options);
void DeleteRunnerRuntimeOptions();
}
public sealed class ConfigurationStore : RunnerService, IConfigurationStore
@@ -85,11 +91,9 @@ namespace GitHub.Runner.Common
private string _configFilePath;
private string _credFilePath;
private string _serviceConfigFilePath;
private string _runtimeOptionsFilePath;
private CredentialData _creds;
private RunnerSettings _settings;
private RunnerRuntimeOptions _runtimeOptions;
public override void Initialize(IHostContext hostContext)
{
@@ -112,16 +116,12 @@ namespace GitHub.Runner.Common
_serviceConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Service);
Trace.Info("ServiceConfigFilePath: {0}", _serviceConfigFilePath);
_runtimeOptionsFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Options);
Trace.Info("RuntimeOptionsFilePath: {0}", _runtimeOptionsFilePath);
}
public string RootFolder { get; private set; }
public bool HasCredentials()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info("HasCredentials()");
bool credsStored = (new FileInfo(_credFilePath)).Exists;
Trace.Info("stored {0}", credsStored);
@@ -131,14 +131,13 @@ namespace GitHub.Runner.Common
public bool IsConfigured()
{
Trace.Info("IsConfigured()");
bool configured = HostContext.RunMode == RunMode.Local || (new FileInfo(_configFilePath)).Exists;
bool configured = new FileInfo(_configFilePath).Exists;
Trace.Info("IsConfigured: {0}", configured);
return configured;
}
public bool IsServiceConfigured()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info("IsServiceConfigured()");
bool serviceConfigured = (new FileInfo(_serviceConfigFilePath)).Exists;
Trace.Info($"IsServiceConfigured: {serviceConfigured}");
@@ -147,7 +146,6 @@ namespace GitHub.Runner.Common
public CredentialData GetCredentials()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
if (_creds == null)
{
_creds = IOUtil.LoadObject<CredentialData>(_credFilePath);
@@ -177,7 +175,6 @@ namespace GitHub.Runner.Common
public void SaveCredential(CredentialData credential)
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info("Saving {0} credential @ {1}", credential.Scheme, _credFilePath);
if (File.Exists(_credFilePath))
{
@@ -193,7 +190,6 @@ namespace GitHub.Runner.Common
public void SaveSettings(RunnerSettings settings)
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info("Saving runner settings.");
if (File.Exists(_configFilePath))
{
@@ -209,44 +205,12 @@ namespace GitHub.Runner.Common
public void DeleteCredential()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
IOUtil.Delete(_credFilePath, default(CancellationToken));
}
public void DeleteSettings()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
IOUtil.Delete(_configFilePath, default(CancellationToken));
}
public RunnerRuntimeOptions GetRunnerRuntimeOptions()
{
if (_runtimeOptions == null && File.Exists(_runtimeOptionsFilePath))
{
_runtimeOptions = IOUtil.LoadObject<RunnerRuntimeOptions>(_runtimeOptionsFilePath);
}
return _runtimeOptions;
}
public void SaveRunnerRuntimeOptions(RunnerRuntimeOptions options)
{
Trace.Info("Saving runtime options.");
if (File.Exists(_runtimeOptionsFilePath))
{
// Delete existing runtime options file first, since the file is hidden and not able to overwrite.
Trace.Info("Delete exist runtime options file.");
IOUtil.DeleteFile(_runtimeOptionsFilePath);
}
IOUtil.SaveObject(options, _runtimeOptionsFilePath);
Trace.Info("Options Saved.");
File.SetAttributes(_runtimeOptionsFilePath, File.GetAttributes(_runtimeOptionsFilePath) | FileAttributes.Hidden);
}
public void DeleteRunnerRuntimeOptions()
{
IOUtil.Delete(_runtimeOptionsFilePath, default(CancellationToken));
}
}
}

View File

@@ -2,12 +2,6 @@
namespace GitHub.Runner.Common
{
public enum RunMode
{
Normal, // Keep "Normal" first (default value).
Local,
}
public enum WellKnownDirectory
{
Bin,
@@ -29,9 +23,6 @@ namespace GitHub.Runner.Common
Service,
CredentialStore,
Certificates,
Proxy,
ProxyCredentials,
ProxyBypass,
Options,
}
@@ -93,44 +84,21 @@ namespace GitHub.Runner.Common
//validArgs array as well present in the CommandSettings.cs
public static class Args
{
public static readonly string Agent = "agent";
public static readonly string Auth = "auth";
public static readonly string CollectionName = "collectionname";
public static readonly string DeploymentGroupName = "deploymentgroupname";
public static readonly string DeploymentPoolName = "deploymentpoolname";
public static readonly string DeploymentGroupTags = "deploymentgrouptags";
public static readonly string MachineGroupName = "machinegroupname";
public static readonly string MachineGroupTags = "machinegrouptags";
public static readonly string Matrix = "matrix";
public static readonly string MonitorSocketAddress = "monitorsocketaddress";
public static readonly string NotificationPipeName = "notificationpipename";
public static readonly string NotificationSocketAddress = "notificationsocketaddress";
public static readonly string Name = "name";
public static readonly string Pool = "pool";
public static readonly string ProjectName = "projectname";
public static readonly string ProxyUrl = "proxyurl";
public static readonly string ProxyUserName = "proxyusername";
public static readonly string SslCACert = "sslcacert";
public static readonly string SslClientCert = "sslclientcert";
public static readonly string SslClientCertKey = "sslclientcertkey";
public static readonly string SslClientCertArchive = "sslclientcertarchive";
public static readonly string SslClientCertPassword = "sslclientcertpassword";
public static readonly string StartupType = "startuptype";
public static readonly string Url = "url";
public static readonly string UserName = "username";
public static readonly string WindowsLogonAccount = "windowslogonaccount";
public static readonly string Work = "work";
public static readonly string Yml = "yml";
// Secret args. Must be added to the "Secrets" getter as well.
public static readonly string Password = "password";
public static readonly string ProxyPassword = "proxypassword";
public static readonly string Token = "token";
public static readonly string WindowsLogonPassword = "windowslogonpassword";
public static string[] Secrets => new[]
{
Password,
ProxyPassword,
SslClientCertPassword,
Token,
WindowsLogonPassword,
};
@@ -139,7 +107,6 @@ namespace GitHub.Runner.Common
public static class Commands
{
public static readonly string Configure = "configure";
public static readonly string LocalRun = "localRun";
public static readonly string Remove = "remove";
public static readonly string Run = "run";
public static readonly string Warmup = "warmup";
@@ -149,26 +116,13 @@ namespace GitHub.Runner.Common
//validFlags array as well present in the CommandSettings.cs
public static class Flags
{
public static readonly string AcceptTeeEula = "acceptteeeula";
public static readonly string AddDeploymentGroupTags = "adddeploymentgrouptags";
public static readonly string AddMachineGroupTags = "addmachinegrouptags";
public static readonly string Commit = "commit";
public static readonly string DeploymentGroup = "deploymentgroup";
public static readonly string DeploymentPool = "deploymentpool";
public static readonly string OverwriteAutoLogon = "overwriteautologon";
public static readonly string GitUseSChannel = "gituseschannel";
public static readonly string Help = "help";
public static readonly string MachineGroup = "machinegroup";
public static readonly string Replace = "replace";
public static readonly string NoRestart = "norestart";
public static readonly string LaunchBrowser = "launchbrowser";
public static readonly string Once = "once";
public static readonly string RunAsAutoLogon = "runasautologon";
public static readonly string RunAsService = "runasservice";
public static readonly string SslSkipCertValidation = "sslskipcertvalidation";
public static readonly string Unattended = "unattended";
public static readonly string Version = "version";
public static readonly string WhatIf = "whatif";
}
}
@@ -193,23 +147,16 @@ namespace GitHub.Runner.Common
public static class Configuration
{
public static readonly string AAD = "AAD";
public static readonly string OAuthAccessToken = "OAuthAccessToken";
public static readonly string PAT = "PAT";
public static readonly string OAuth = "OAuth";
}
public static class Expressions
{
public static readonly string Always = "always";
public static readonly string Canceled = "canceled";
public static readonly string Cancelled = "cancelled";
public static readonly string Failed = "failed";
public static readonly string Failure = "failure";
public static readonly string Success = "success";
public static readonly string Succeeded = "succeeded";
public static readonly string SucceededOrFailed = "succeededOrFailed";
public static readonly string Variables = "variables";
}
public static class Path
@@ -221,9 +168,7 @@ namespace GitHub.Runner.Common
public static readonly string ExternalsDirectory = "externals";
public static readonly string RunnerDiagnosticLogPrefix = "Runner_";
public static readonly string TempDirectory = "_temp";
public static readonly string TeeDirectory = "tee";
public static readonly string ToolDirectory = "_tool";
public static readonly string TaskJsonFile = "task.json";
public static readonly string UpdateDirectory = "_update";
public static readonly string WorkDirectory = "_work";
public static readonly string WorkerDiagnosticLogPrefix = "Worker_";
@@ -246,97 +191,17 @@ namespace GitHub.Runner.Common
public static class Agent
{
//
// Keep alphabetical
//
public static readonly string AcceptTeeEula = "agent.acceptteeeula";
public static readonly string AllowAllEndpoints = "agent.allowAllEndpoints"; // remove after sprint 120 or so.
public static readonly string AllowAllSecureFiles = "agent.allowAllSecureFiles"; // remove after sprint 121 or so.
public static readonly string BuildDirectory = "agent.builddirectory";
public static readonly string ContainerId = "agent.containerid";
public static readonly string ContainerNetwork = "agent.containernetwork";
public static readonly string HomeDirectory = "agent.homedirectory";
public static readonly string Id = "agent.id";
public static readonly string GitUseSChannel = "agent.gituseschannel";
public static readonly string JobName = "agent.jobname";
public static readonly string MachineName = "agent.machinename";
public static readonly string Name = "agent.name";
public static readonly string OS = "agent.os";
public static readonly string OSArchitecture = "agent.osarchitecture";
public static readonly string OSVersion = "agent.osversion";
public static readonly string ProxyUrl = "agent.proxyurl";
public static readonly string ProxyUsername = "agent.proxyusername";
public static readonly string ProxyPassword = "agent.proxypassword";
public static readonly string ProxyBypassList = "agent.proxybypasslist";
public static readonly string RetainDefaultEncoding = "agent.retainDefaultEncoding";
public static readonly string RootDirectory = "agent.RootDirectory";
public static readonly string RunMode = "agent.runmode";
public static readonly string ServerOMDirectory = "agent.ServerOMDirectory";
public static readonly string ServicePortPrefix = "agent.services";
public static readonly string SslCAInfo = "agent.cainfo";
public static readonly string SslClientCert = "agent.clientcert";
public static readonly string SslClientCertKey = "agent.clientcertkey";
public static readonly string SslClientCertArchive = "agent.clientcertarchive";
public static readonly string SslClientCertPassword = "agent.clientcertpassword";
public static readonly string SslSkipCertValidation = "agent.skipcertvalidation";
public static readonly string TempDirectory = "agent.TempDirectory";
public static readonly string ToolsDirectory = "agent.ToolsDirectory";
public static readonly string Version = "agent.version";
public static readonly string WorkFolder = "agent.workfolder";
public static readonly string WorkingDirectory = "agent.WorkingDirectory";
}
public static class Build
{
//
// Keep alphabetical
//
public static readonly string ArtifactStagingDirectory = "build.artifactstagingdirectory";
public static readonly string BinariesDirectory = "build.binariesdirectory";
public static readonly string Number = "build.buildNumber";
public static readonly string Clean = "build.clean";
public static readonly string DefinitionName = "build.definitionname";
public static readonly string GatedRunCI = "build.gated.runci";
public static readonly string GatedShelvesetName = "build.gated.shelvesetname";
public static readonly string RepoClean = "build.repository.clean";
public static readonly string RepoGitSubmoduleCheckout = "build.repository.git.submodulecheckout";
public static readonly string RepoId = "build.repository.id";
public static readonly string RepoLocalPath = "build.repository.localpath";
public static readonly string RepoName = "build.Repository.name";
public static readonly string RepoProvider = "build.repository.provider";
public static readonly string RepoTfvcWorkspace = "build.repository.tfvc.workspace";
public static readonly string RepoUri = "build.repository.uri";
public static readonly string SourceBranch = "build.sourcebranch";
public static readonly string SourceTfvcShelveset = "build.sourcetfvcshelveset";
public static readonly string SourceVersion = "build.sourceversion";
public static readonly string SourcesDirectory = "build.sourcesdirectory";
public static readonly string StagingDirectory = "build.stagingdirectory";
public static readonly string SyncSources = "build.syncSources";
}
public static class System
{
//
// Keep alphabetical
//
public static readonly string AccessToken = "system.accessToken";
public static readonly string ArtifactsDirectory = "system.artifactsdirectory";
public static readonly string CollectionId = "system.collectionid";
public static readonly string Culture = "system.culture";
public static readonly string DefaultWorkingDirectory = "system.defaultworkingdirectory";
public static readonly string DefinitionId = "system.definitionid";
public static readonly string EnableAccessToken = "system.enableAccessToken";
public static readonly string HostType = "system.hosttype";
public static readonly string PhaseDisplayName = "system.phaseDisplayName";
public static readonly string PreferGitFromPath = "system.prefergitfrompath";
public static readonly string PullRequestTargetBranchName = "system.pullrequest.targetbranch";
public static readonly string SelfManageGitCreds = "system.selfmanagegitcreds";
public static readonly string ServerType = "system.servertype";
public static readonly string TFServerUrl = "system.TeamFoundationServerUri"; // back compat variable, do not document
public static readonly string TeamProject = "system.teamproject";
public static readonly string TeamProjectId = "system.teamProjectId";
public static readonly string WorkFolder = "system.workfolder";
}
}
}

View File

@@ -54,6 +54,7 @@ namespace GitHub.Runner.Common
Add<T>(extensions, "GitHub.Runner.Worker.DebugCommandExtension, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.GroupCommandExtension, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.EndGroupCommandExtension, Runner.Worker");
Add<T>(extensions, "GitHub.Runner.Worker.EchoCommandExtension, Runner.Worker");
break;
default:
// This should never happen.

View File

@@ -20,12 +20,12 @@ namespace GitHub.Runner.Common
{
public interface IHostContext : IDisposable
{
RunMode RunMode { get; set; }
StartupType StartupType { get; set; }
CancellationToken RunnerShutdownToken { get; }
ShutdownReason RunnerShutdownReason { get; }
ISecretMasker SecretMasker { get; }
ProductInfoHeaderValue UserAgent { get; }
RunnerWebProxy WebProxy { get; }
string GetDirectory(WellKnownDirectory directory);
string GetConfigFile(WellKnownConfigFile configFile);
Tracing GetTrace(string name);
@@ -57,22 +57,23 @@ namespace GitHub.Runner.Common
private readonly ProductInfoHeaderValue _userAgent = new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version);
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
private object _perfLock = new object();
private RunMode _runMode = RunMode.Normal;
private Tracing _trace;
private Tracing _vssTrace;
private Tracing _httpTrace;
private Tracing _actionsHttpTrace;
private Tracing _netcoreHttpTrace;
private ITraceManager _traceManager;
private AssemblyLoadContext _loadContext;
private IDisposable _httpTraceSubscription;
private IDisposable _diagListenerSubscription;
private StartupType _startupType;
private string _perfFile;
private RunnerWebProxy _webProxy = new RunnerWebProxy();
public event EventHandler Unloading;
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
public ShutdownReason RunnerShutdownReason { get; private set; }
public ISecretMasker SecretMasker => _secretMasker;
public ProductInfoHeaderValue UserAgent => _userAgent;
public RunnerWebProxy WebProxy => _webProxy;
public HostContext(string hostType, string logFile = null)
{
// Validate args.
@@ -116,8 +117,7 @@ namespace GitHub.Runner.Common
}
_trace = GetTrace(nameof(HostContext));
_vssTrace = GetTrace("GitHubActionsRunner"); // VisualStudioService
_actionsHttpTrace = GetTrace("GitHubActionsService");
// Enable Http trace
bool enableHttpTrace;
if (bool.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTPTRACE"), out enableHttpTrace) && enableHttpTrace)
@@ -129,7 +129,7 @@ namespace GitHub.Runner.Common
_trace.Warning("** **");
_trace.Warning("*****************************************************************************************");
_httpTrace = GetTrace("HttpTrace");
_netcoreHttpTrace = GetTrace("HttpTrace");
_diagListenerSubscription = DiagnosticListener.AllListeners.Subscribe(this);
}
@@ -147,19 +147,47 @@ namespace GitHub.Runner.Common
_trace.Error(ex);
}
}
// Check and trace proxy info
if (!string.IsNullOrEmpty(WebProxy.HttpProxyAddress))
{
if (string.IsNullOrEmpty(WebProxy.HttpProxyUsername) && string.IsNullOrEmpty(WebProxy.HttpProxyPassword))
{
_trace.Info($"Configuring anonymous proxy {WebProxy.HttpProxyAddress} for all HTTP requests.");
}
else
{
// Register proxy password as secret
if (!string.IsNullOrEmpty(WebProxy.HttpProxyPassword))
{
this.SecretMasker.AddValue(WebProxy.HttpProxyPassword);
}
public RunMode RunMode
{
get
{
return _runMode;
_trace.Info($"Configuring authenticated proxy {WebProxy.HttpProxyAddress} for all HTTP requests.");
}
}
set
if (!string.IsNullOrEmpty(WebProxy.HttpsProxyAddress))
{
_trace.Info($"Set run mode: {value}");
_runMode = value;
if (string.IsNullOrEmpty(WebProxy.HttpsProxyUsername) && string.IsNullOrEmpty(WebProxy.HttpsProxyPassword))
{
_trace.Info($"Configuring anonymous proxy {WebProxy.HttpsProxyAddress} for all HTTPS requests.");
}
else
{
// Register proxy password as secret
if (!string.IsNullOrEmpty(WebProxy.HttpsProxyPassword))
{
this.SecretMasker.AddValue(WebProxy.HttpsProxyPassword);
}
_trace.Info($"Configuring authenticated proxy {WebProxy.HttpsProxyAddress} for all HTTPS requests.");
}
}
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)");
}
}
@@ -203,6 +231,7 @@ namespace GitHub.Runner.Common
case WellKnownDirectory.Tools:
// TODO: Coallesce to just check RUNNER_TOOL_CACHE when images stabilize
path = Environment.GetEnvironmentVariable("RUNNER_TOOL_CACHE") ?? Environment.GetEnvironmentVariable("RUNNER_TOOLSDIRECTORY") ?? Environment.GetEnvironmentVariable("AGENT_TOOLSDIRECTORY") ?? Environment.GetEnvironmentVariable(Constants.Variables.Agent.ToolsDirectory);
if (string.IsNullOrEmpty(path))
{
path = Path.Combine(
@@ -282,24 +311,6 @@ namespace GitHub.Runner.Common
".certificates");
break;
case WellKnownConfigFile.Proxy:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxy");
break;
case WellKnownConfigFile.ProxyCredentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxycredentials");
break;
case WellKnownConfigFile.ProxyBypass:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxybypass");
break;
case WellKnownConfigFile.Options:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
@@ -467,12 +478,12 @@ namespace GitHub.Runner.Common
void IObserver<DiagnosticListener>.OnCompleted()
{
_httpTrace.Info("DiagListeners finished transmitting data.");
_netcoreHttpTrace.Info("DiagListeners finished transmitting data.");
}
void IObserver<DiagnosticListener>.OnError(Exception error)
{
_httpTrace.Error(error);
_netcoreHttpTrace.Error(error);
}
void IObserver<DiagnosticListener>.OnNext(DiagnosticListener listener)
@@ -485,22 +496,22 @@ namespace GitHub.Runner.Common
void IObserver<KeyValuePair<string, object>>.OnCompleted()
{
_httpTrace.Info("HttpHandlerDiagnosticListener finished transmitting data.");
_netcoreHttpTrace.Info("HttpHandlerDiagnosticListener finished transmitting data.");
}
void IObserver<KeyValuePair<string, object>>.OnError(Exception error)
{
_httpTrace.Error(error);
_netcoreHttpTrace.Error(error);
}
void IObserver<KeyValuePair<string, object>>.OnNext(KeyValuePair<string, object> value)
{
_httpTrace.Info($"Trace {value.Key} event:{Environment.NewLine}{value.Value.ToString()}");
_netcoreHttpTrace.Info($"Trace {value.Key} event:{Environment.NewLine}{value.Value.ToString()}");
}
protected override void OnEventSourceCreated(EventSource source)
{
if (source.Name.Equals("Microsoft-VSS-Http"))
if (source.Name.Equals("GitHub-Actions-Http"))
{
EnableEvents(source, EventLevel.Verbose);
}
@@ -540,24 +551,24 @@ namespace GitHub.Runner.Common
{
case EventLevel.Critical:
case EventLevel.Error:
_vssTrace.Error(message);
_actionsHttpTrace.Error(message);
break;
case EventLevel.Warning:
_vssTrace.Warning(message);
_actionsHttpTrace.Warning(message);
break;
case EventLevel.Informational:
_vssTrace.Info(message);
_actionsHttpTrace.Info(message);
break;
default:
_vssTrace.Verbose(message);
_actionsHttpTrace.Verbose(message);
break;
}
}
catch (Exception ex)
{
_vssTrace.Error(ex);
_vssTrace.Info(eventData.Message);
_vssTrace.Info(string.Join(", ", eventData.Payload?.ToArray() ?? new string[0]));
_actionsHttpTrace.Error(ex);
_actionsHttpTrace.Info(eventData.Message);
_actionsHttpTrace.Info(string.Join(", ", eventData.Payload?.ToArray() ?? new string[0]));
}
}
@@ -580,8 +591,7 @@ namespace GitHub.Runner.Common
public static HttpClientHandler CreateHttpClientHandler(this IHostContext context)
{
HttpClientHandler clientHandler = new HttpClientHandler();
var runnerWebProxy = context.GetService<IRunnerWebProxy>();
clientHandler.Proxy = runnerWebProxy.WebProxy;
clientHandler.Proxy = context.WebProxy;
return clientHandler;
}
}

View File

@@ -12,53 +12,21 @@ namespace GitHub.Runner.Common
[ServiceLocator(Default = typeof(JobNotification))]
public interface IJobNotification : IRunnerService, IDisposable
{
Task JobStarted(Guid jobId, string accessToken, Uri serverUrl);
void JobStarted(Guid jobId, string accessToken, Uri serverUrl);
Task JobCompleted(Guid jobId);
void StartClient(string pipeName, string monitorSocketAddress, CancellationToken cancellationToken);
void StartClient(string socketAddress, string monitorSocketAddress);
void StartClient(string monitorSocketAddress);
}
public sealed class JobNotification : RunnerService, IJobNotification
{
private NamedPipeClientStream _outClient;
private StreamWriter _writeStream;
private Socket _socket;
private Socket _monitorSocket;
private bool _configured = false;
private bool _useSockets = false;
private bool _isMonitorConfigured = false;
public async Task JobStarted(Guid jobId, string accessToken, Uri serverUrl)
public void JobStarted(Guid jobId, string accessToken, Uri serverUrl)
{
Trace.Info("Entering JobStarted Notification");
StartMonitor(jobId, accessToken, serverUrl);
if (_configured)
{
String message = $"Starting job: {jobId.ToString()}";
if (_useSockets)
{
try
{
Trace.Info("Writing JobStarted to socket");
_socket.Send(Encoding.UTF8.GetBytes(message));
Trace.Info("Finished JobStarted writing to socket");
}
catch (SocketException e)
{
Trace.Error($"Failed sending message \"{message}\" on socket!");
Trace.Error(e);
}
}
else
{
Trace.Info("Writing JobStarted to pipe");
await _writeStream.WriteLineAsync(message);
await _writeStream.FlushAsync();
Trace.Info("Finished JobStarted writing to pipe");
}
}
}
public async Task JobCompleted(Guid jobId)
@@ -66,95 +34,10 @@ namespace GitHub.Runner.Common
Trace.Info("Entering JobCompleted Notification");
await EndMonitor();
if (_configured)
{
String message = $"Finished job: {jobId.ToString()}";
if (_useSockets)
{
try
{
Trace.Info("Writing JobCompleted to socket");
_socket.Send(Encoding.UTF8.GetBytes(message));
Trace.Info("Finished JobCompleted writing to socket");
}
catch (SocketException e)
{
Trace.Error($"Failed sending message \"{message}\" on socket!");
Trace.Error(e);
}
}
else
{
Trace.Info("Writing JobCompleted to pipe");
await _writeStream.WriteLineAsync(message);
await _writeStream.FlushAsync();
Trace.Info("Finished JobCompleted writing to pipe");
}
}
}
public async void StartClient(string pipeName, string monitorSocketAddress, CancellationToken cancellationToken)
public void StartClient(string monitorSocketAddress)
{
if (pipeName != null && !_configured)
{
Trace.Info("Connecting to named pipe {0}", pipeName);
_outClient = new NamedPipeClientStream(".", pipeName, PipeDirection.Out, PipeOptions.Asynchronous);
await _outClient.ConnectAsync(cancellationToken);
_writeStream = new StreamWriter(_outClient, Encoding.UTF8);
_configured = true;
Trace.Info("Connection successful to named pipe {0}", pipeName);
}
ConnectMonitor(monitorSocketAddress);
}
public void StartClient(string socketAddress, string monitorSocketAddress)
{
if (!_configured)
{
try
{
string[] splitAddress = socketAddress.Split(':');
if (splitAddress.Length != 2)
{
Trace.Error("Invalid socket address {0}. Job Notification will be disabled.", socketAddress);
return;
}
IPAddress address;
try
{
address = IPAddress.Parse(splitAddress[0]);
}
catch (FormatException e)
{
Trace.Error("Invalid socket ip address {0}. Job Notification will be disabled",splitAddress[0]);
Trace.Error(e);
return;
}
int port = -1;
Int32.TryParse(splitAddress[1], out port);
if (port < IPEndPoint.MinPort || port > IPEndPoint.MaxPort)
{
Trace.Error("Invalid tcp socket port {0}. Job Notification will be disabled.", splitAddress[1]);
return;
}
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
_socket.Connect(address, port);
Trace.Info("Connection successful to socket {0}", socketAddress);
_useSockets = true;
_configured = true;
}
catch (SocketException e)
{
Trace.Error("Connection to socket {0} failed!", socketAddress);
Trace.Error(e);
}
}
ConnectMonitor(monitorSocketAddress);
}
@@ -275,15 +158,6 @@ namespace GitHub.Runner.Common
{
if (disposing)
{
_outClient?.Dispose();
if (_socket != null)
{
_socket.Send(Encoding.UTF8.GetBytes("<EOF>"));
_socket.Shutdown(SocketShutdown.Both);
_socket = null;
}
if (_monitorSocket != null)
{
_monitorSocket.Send(Encoding.UTF8.GetBytes("<EOF>"));

View File

@@ -32,11 +32,6 @@ namespace GitHub.Runner.Common
public async Task ConnectAsync(VssConnection jobConnection)
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
_connection = jobConnection;
int attemptCount = 5;
while (!_connection.HasAuthenticated && attemptCount-- > 0)
@@ -73,88 +68,48 @@ namespace GitHub.Runner.Common
public Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<TaskLog>(null);
}
CheckConnection();
return _taskClient.AppendLogContentAsync(scopeIdentifier, hubName, planId, logId, uploadStream, cancellationToken: cancellationToken);
}
public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.CompletedTask;
}
CheckConnection();
return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken);
}
public Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, string type, string name, Stream uploadStream, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<TaskAttachment>(null);
}
CheckConnection();
return _taskClient.CreateAttachmentAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, type, name, uploadStream, cancellationToken: cancellationToken);
}
public Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<TaskLog>(null);
}
CheckConnection();
return _taskClient.CreateLogAsync(scopeIdentifier, hubName, planId, log, cancellationToken: cancellationToken);
}
public Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<Timeline>(null);
}
CheckConnection();
return _taskClient.CreateTimelineAsync(scopeIdentifier, hubName, planId, new Timeline(timelineId), cancellationToken: cancellationToken);
}
public Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<List<TimelineRecord>>(null);
}
CheckConnection();
return _taskClient.UpdateTimelineRecordsAsync(scopeIdentifier, hubName, planId, timelineId, records, cancellationToken: cancellationToken);
}
public Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.CompletedTask;
}
CheckConnection();
return _taskClient.RaisePlanEventAsync(scopeIdentifier, hubName, planId, eventData, cancellationToken: cancellationToken);
}
public Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<Timeline>(null);
}
CheckConnection();
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
}

View File

@@ -63,7 +63,6 @@ namespace GitHub.Runner.Common
private Task[] _allDequeueTasks;
private readonly TaskCompletionSource<int> _jobCompletionSource = new TaskCompletionSource<int>();
private bool _queueInProcess = false;
private ITerminal _term;
public event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
@@ -85,11 +84,6 @@ namespace GitHub.Runner.Common
public void Start(Pipelines.AgentJobRequestMessage jobRequest)
{
Trace.Entering();
if (HostContext.RunMode == RunMode.Local)
{
_term = HostContext.GetService<ITerminal>();
return;
}
if (_queueInProcess)
{
@@ -129,11 +123,6 @@ namespace GitHub.Runner.Common
// TimelineUpdate queue error will become critical when timeline records contain output variabls.
public async Task ShutdownAsync()
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
if (!_queueInProcess)
{
Trace.Info("No-op, all queue process tasks have been stopped.");
@@ -169,32 +158,11 @@ namespace GitHub.Runner.Common
public void QueueWebConsoleLine(Guid stepRecordId, string line)
{
Trace.Verbose("Enqueue web console line queue: {0}", line);
if (HostContext.RunMode == RunMode.Local)
{
if ((line ?? string.Empty).StartsWith("##[section]"))
{
Console.WriteLine("******************************************************************************");
Console.WriteLine(line.Substring("##[section]".Length));
Console.WriteLine("******************************************************************************");
}
else
{
Console.WriteLine(line);
}
return;
}
_webConsoleLineQueue.Enqueue(new ConsoleLineInfo(stepRecordId, line));
}
public void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource)
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
ArgUtil.NotEmpty(timelineId, nameof(timelineId));
ArgUtil.NotEmpty(timelineRecordId, nameof(timelineRecordId));
@@ -215,11 +183,6 @@ namespace GitHub.Runner.Common
public void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord)
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
ArgUtil.NotEmpty(timelineId, nameof(timelineId));
ArgUtil.NotNull(timelineRecord, nameof(timelineRecord));
ArgUtil.NotEmpty(timelineRecord.Id, nameof(timelineRecord.Id));

View File

@@ -24,7 +24,6 @@ namespace GitHub.Runner.Common
private Guid _timelineId;
private Guid _timelineRecordId;
private string _pageId;
private FileStream _pageData;
private StreamWriter _pageWriter;
private int _byteCount;
@@ -40,7 +39,6 @@ namespace GitHub.Runner.Common
{
base.Initialize(hostContext);
_totalLines = 0;
_pageId = Guid.NewGuid().ToString();
_pagesFolder = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Diag), PagingFolder);
_jobServerQueue = HostContext.GetService<IJobServerQueue>();
Directory.CreateDirectory(_pagesFolder);
@@ -102,7 +100,7 @@ namespace GitHub.Runner.Common
{
EndPage();
_byteCount = 0;
_dataFileName = Path.Combine(_pagesFolder, $"{_pageId}_{++_pageCount}.log");
_dataFileName = Path.Combine(_pagesFolder, $"{_timelineRecordId}_{++_pageCount}.log");
_pageData = new FileStream(_dataFileName, FileMode.CreateNew);
_pageWriter = new StreamWriter(_pageData, System.Text.Encoding.UTF8);
}

View File

@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
</PropertyGroup>
<ItemGroup>
@@ -27,42 +28,4 @@
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">
<DefineConstants>OS_OSX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true' AND '$(Configuration)' == 'Debug'">
<DefineConstants>OS_OSX;DEBUG;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -1,231 +0,0 @@
using System;
using GitHub.Runner.Common.Util;
using System.IO;
using System.Runtime.Serialization;
using GitHub.Services.Common;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Net.Security;
using System.Net.Http;
using GitHub.Services.WebApi;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common
{
[ServiceLocator(Default = typeof(RunnerCertificateManager))]
public interface IRunnerCertificateManager : IRunnerService
{
bool SkipServerCertificateValidation { get; }
string CACertificateFile { get; }
string ClientCertificateFile { get; }
string ClientCertificatePrivateKeyFile { get; }
string ClientCertificateArchiveFile { get; }
string ClientCertificatePassword { get; }
IVssClientCertificateManager VssClientCertificateManager { get; }
}
public class RunnerCertificateManager : RunnerService, IRunnerCertificateManager
{
private RunnerClientCertificateManager _runnerClientCertificateManager = new RunnerClientCertificateManager();
public bool SkipServerCertificateValidation { private set; get; }
public string CACertificateFile { private set; get; }
public string ClientCertificateFile { private set; get; }
public string ClientCertificatePrivateKeyFile { private set; get; }
public string ClientCertificateArchiveFile { private set; get; }
public string ClientCertificatePassword { private set; get; }
public IVssClientCertificateManager VssClientCertificateManager => _runnerClientCertificateManager;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
LoadCertificateSettings();
}
// This should only be called from config
public void SetupCertificate(bool skipCertValidation, string caCert, string clientCert, string clientCertPrivateKey, string clientCertArchive, string clientCertPassword)
{
Trace.Info("Setup runner certificate setting base on configuration inputs.");
if (skipCertValidation)
{
Trace.Info("Ignore SSL server certificate validation error");
SkipServerCertificateValidation = true;
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
if (!string.IsNullOrEmpty(caCert))
{
ArgUtil.File(caCert, nameof(caCert));
Trace.Info($"Self-Signed CA '{caCert}'");
}
if (!string.IsNullOrEmpty(clientCert))
{
ArgUtil.File(clientCert, nameof(clientCert));
ArgUtil.File(clientCertPrivateKey, nameof(clientCertPrivateKey));
ArgUtil.File(clientCertArchive, nameof(clientCertArchive));
Trace.Info($"Client cert '{clientCert}'");
Trace.Info($"Client cert private key '{clientCertPrivateKey}'");
Trace.Info($"Client cert archive '{clientCertArchive}'");
}
CACertificateFile = caCert;
ClientCertificateFile = clientCert;
ClientCertificatePrivateKeyFile = clientCertPrivateKey;
ClientCertificateArchiveFile = clientCertArchive;
ClientCertificatePassword = clientCertPassword;
_runnerClientCertificateManager.AddClientCertificate(ClientCertificateArchiveFile, ClientCertificatePassword);
}
// This should only be called from config
public void SaveCertificateSetting()
{
string certSettingFile = HostContext.GetConfigFile(WellKnownConfigFile.Certificates);
IOUtil.DeleteFile(certSettingFile);
var setting = new RunnerCertificateSetting();
if (SkipServerCertificateValidation)
{
Trace.Info($"Store Skip ServerCertificateValidation setting to '{certSettingFile}'");
setting.SkipServerCertValidation = true;
}
if (!string.IsNullOrEmpty(CACertificateFile))
{
Trace.Info($"Store CA cert setting to '{certSettingFile}'");
setting.CACert = CACertificateFile;
}
if (!string.IsNullOrEmpty(ClientCertificateFile) &&
!string.IsNullOrEmpty(ClientCertificatePrivateKeyFile) &&
!string.IsNullOrEmpty(ClientCertificateArchiveFile))
{
Trace.Info($"Store client cert settings to '{certSettingFile}'");
setting.ClientCert = ClientCertificateFile;
setting.ClientCertPrivatekey = ClientCertificatePrivateKeyFile;
setting.ClientCertArchive = ClientCertificateArchiveFile;
if (!string.IsNullOrEmpty(ClientCertificatePassword))
{
string lookupKey = Guid.NewGuid().ToString("D").ToUpperInvariant();
Trace.Info($"Store client cert private key password with lookup key {lookupKey}");
var credStore = HostContext.GetService<IRunnerCredentialStore>();
credStore.Write($"GITHUB_ACTIONS_RUNNER_CLIENT_CERT_PASSWORD_{lookupKey}", "GitHub", ClientCertificatePassword);
setting.ClientCertPasswordLookupKey = lookupKey;
}
}
if (SkipServerCertificateValidation ||
!string.IsNullOrEmpty(CACertificateFile) ||
!string.IsNullOrEmpty(ClientCertificateFile))
{
IOUtil.SaveObject(setting, certSettingFile);
File.SetAttributes(certSettingFile, File.GetAttributes(certSettingFile) | FileAttributes.Hidden);
}
}
// This should only be called from unconfig
public void DeleteCertificateSetting()
{
string certSettingFile = HostContext.GetConfigFile(WellKnownConfigFile.Certificates);
if (File.Exists(certSettingFile))
{
Trace.Info($"Load runner certificate setting from '{certSettingFile}'");
var certSetting = IOUtil.LoadObject<RunnerCertificateSetting>(certSettingFile);
if (certSetting != null && !string.IsNullOrEmpty(certSetting.ClientCertPasswordLookupKey))
{
Trace.Info("Delete client cert private key password from credential store.");
var credStore = HostContext.GetService<IRunnerCredentialStore>();
credStore.Delete($"GITHUB_ACTIONS_RUNNER_CLIENT_CERT_PASSWORD_{certSetting.ClientCertPasswordLookupKey}");
}
Trace.Info($"Delete cert setting file: {certSettingFile}");
IOUtil.DeleteFile(certSettingFile);
}
}
public void LoadCertificateSettings()
{
string certSettingFile = HostContext.GetConfigFile(WellKnownConfigFile.Certificates);
if (File.Exists(certSettingFile))
{
Trace.Info($"Load runner certificate setting from '{certSettingFile}'");
var certSetting = IOUtil.LoadObject<RunnerCertificateSetting>(certSettingFile);
ArgUtil.NotNull(certSetting, nameof(RunnerCertificateSetting));
if (certSetting.SkipServerCertValidation)
{
Trace.Info("Ignore SSL server certificate validation error");
SkipServerCertificateValidation = true;
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
if (!string.IsNullOrEmpty(certSetting.CACert))
{
// make sure all settings file exist
ArgUtil.File(certSetting.CACert, nameof(certSetting.CACert));
Trace.Info($"CA '{certSetting.CACert}'");
CACertificateFile = certSetting.CACert;
}
if (!string.IsNullOrEmpty(certSetting.ClientCert))
{
// make sure all settings file exist
ArgUtil.File(certSetting.ClientCert, nameof(certSetting.ClientCert));
ArgUtil.File(certSetting.ClientCertPrivatekey, nameof(certSetting.ClientCertPrivatekey));
ArgUtil.File(certSetting.ClientCertArchive, nameof(certSetting.ClientCertArchive));
Trace.Info($"Client cert '{certSetting.ClientCert}'");
Trace.Info($"Client cert private key '{certSetting.ClientCertPrivatekey}'");
Trace.Info($"Client cert archive '{certSetting.ClientCertArchive}'");
ClientCertificateFile = certSetting.ClientCert;
ClientCertificatePrivateKeyFile = certSetting.ClientCertPrivatekey;
ClientCertificateArchiveFile = certSetting.ClientCertArchive;
if (!string.IsNullOrEmpty(certSetting.ClientCertPasswordLookupKey))
{
var cerdStore = HostContext.GetService<IRunnerCredentialStore>();
ClientCertificatePassword = cerdStore.Read($"GITHUB_ACTIONS_RUNNER_CLIENT_CERT_PASSWORD_{certSetting.ClientCertPasswordLookupKey}").Password;
HostContext.SecretMasker.AddValue(ClientCertificatePassword);
}
_runnerClientCertificateManager.AddClientCertificate(ClientCertificateArchiveFile, ClientCertificatePassword);
}
}
else
{
Trace.Info("No certificate setting found.");
}
}
}
[DataContract]
internal class RunnerCertificateSetting
{
[DataMember]
public bool SkipServerCertValidation { get; set; }
[DataMember]
public string CACert { get; set; }
[DataMember]
public string ClientCert { get; set; }
[DataMember]
public string ClientCertPrivatekey { get; set; }
[DataMember]
public string ClientCertArchive { get; set; }
[DataMember]
public string ClientCertPasswordLookupKey { get; set; }
}
}

View File

@@ -1,948 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using GitHub.Runner.Common.Util;
using Newtonsoft.Json;
using System.IO;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common
{
// The purpose of this class is to store user's credential during runner configuration and retrive the credential back at runtime.
#if OS_WINDOWS
[ServiceLocator(Default = typeof(WindowsRunnerCredentialStore))]
#elif OS_OSX
[ServiceLocator(Default = typeof(MacOSRunnerCredentialStore))]
#else
[ServiceLocator(Default = typeof(LinuxRunnerCredentialStore))]
#endif
public interface IRunnerCredentialStore : IRunnerService
{
NetworkCredential Write(string target, string username, string password);
// throw exception when target not found from cred store
NetworkCredential Read(string target);
// throw exception when target not found from cred store
void Delete(string target);
}
#if OS_WINDOWS
// Windows credential store is per user.
// This is a limitation for user configure the runner run as windows service, when user's current login account is different with the service run as account.
// Ex: I login the box as domain\admin, configure the runner as windows service and run as domian\buildserver
// domain\buildserver won't read the stored credential from domain\admin's windows credential store.
// To workaround this limitation.
// Anytime we try to save a credential:
// 1. store it into current user's windows credential store
// 2. use DP-API do a machine level encrypt and store the encrypted content on disk.
// At the first time we try to read the credential:
// 1. read from current user's windows credential store, delete the DP-API encrypted backup content on disk if the windows credential store read succeed.
// 2. if credential not found in current user's windows credential store, read from the DP-API encrypted backup content on disk,
// write the credential back the current user's windows credential store and delete the backup on disk.
public sealed class WindowsRunnerCredentialStore : RunnerService, IRunnerCredentialStore
{
private string _credStoreFile;
private Dictionary<string, string> _credStore;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_credStoreFile = hostContext.GetConfigFile(WellKnownConfigFile.CredentialStore);
if (File.Exists(_credStoreFile))
{
_credStore = IOUtil.LoadObject<Dictionary<string, string>>(_credStoreFile);
}
else
{
_credStore = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
public NetworkCredential Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
ArgUtil.NotNullOrEmpty(username, nameof(username));
ArgUtil.NotNullOrEmpty(password, nameof(password));
// save to .credential_store file first, then Windows credential store
string usernameBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(username));
string passwordBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(password));
// Base64Username:Base64Password -> DP-API machine level encrypt -> Base64Encoding
string encryptedUsernamePassword = Convert.ToBase64String(ProtectedData.Protect(Encoding.UTF8.GetBytes($"{usernameBase64}:{passwordBase64}"), null, DataProtectionScope.LocalMachine));
Trace.Info($"Credentials for '{target}' written to credential store file.");
_credStore[target] = encryptedUsernamePassword;
// save to .credential_store file
SyncCredentialStoreFile();
// save to Windows Credential Store
return WriteInternal(target, username, password);
}
public NetworkCredential Read(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
IntPtr credPtr = IntPtr.Zero;
try
{
if (CredRead(target, CredentialType.Generic, 0, out credPtr))
{
Credential credStruct = (Credential)Marshal.PtrToStructure(credPtr, typeof(Credential));
int passwordLength = (int)credStruct.CredentialBlobSize;
string password = passwordLength > 0 ? Marshal.PtrToStringUni(credStruct.CredentialBlob, passwordLength / sizeof(char)) : String.Empty;
string username = Marshal.PtrToStringUni(credStruct.UserName);
Trace.Info($"Credentials for '{target}' read from windows credential store.");
// delete from .credential_store file since we are able to read it from windows credential store
if (_credStore.Remove(target))
{
Trace.Info($"Delete credentials for '{target}' from credential store file.");
SyncCredentialStoreFile();
}
return new NetworkCredential(username, password);
}
else
{
// Can't read from Windows Credential Store, fail back to .credential_store file
if (_credStore.ContainsKey(target) && !string.IsNullOrEmpty(_credStore[target]))
{
Trace.Info($"Credentials for '{target}' read from credential store file.");
// Base64Decode -> DP-API machine level decrypt -> Base64Username:Base64Password -> Base64Decode
string decryptedUsernamePassword = Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(_credStore[target]), null, DataProtectionScope.LocalMachine));
string[] credential = decryptedUsernamePassword.Split(':');
if (credential.Length == 2 && !string.IsNullOrEmpty(credential[0]) && !string.IsNullOrEmpty(credential[1]))
{
string username = Encoding.UTF8.GetString(Convert.FromBase64String(credential[0]));
string password = Encoding.UTF8.GetString(Convert.FromBase64String(credential[1]));
// store back to windows credential store for current user
NetworkCredential creds = WriteInternal(target, username, password);
// delete from .credential_store file since we are able to write the credential to windows credential store for current user.
if (_credStore.Remove(target))
{
Trace.Info($"Delete credentials for '{target}' from credential store file.");
SyncCredentialStoreFile();
}
return creds;
}
else
{
throw new ArgumentOutOfRangeException(nameof(decryptedUsernamePassword));
}
}
throw new Win32Exception(Marshal.GetLastWin32Error(), $"CredRead throw an error for '{target}'");
}
}
finally
{
if (credPtr != IntPtr.Zero)
{
CredFree(credPtr);
}
}
}
public void Delete(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
// remove from .credential_store file
if (_credStore.Remove(target))
{
Trace.Info($"Delete credentials for '{target}' from credential store file.");
SyncCredentialStoreFile();
}
// remove from windows credential store
if (!CredDelete(target, CredentialType.Generic, 0))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Failed to delete credentials for {target}");
}
else
{
Trace.Info($"Credentials for '{target}' deleted from windows credential store.");
}
}
private NetworkCredential WriteInternal(string target, string username, string password)
{
// save to Windows Credential Store
Credential credential = new Credential()
{
Type = CredentialType.Generic,
Persist = (UInt32)CredentialPersist.LocalMachine,
TargetName = Marshal.StringToCoTaskMemUni(target),
UserName = Marshal.StringToCoTaskMemUni(username),
CredentialBlob = Marshal.StringToCoTaskMemUni(password),
CredentialBlobSize = (UInt32)Encoding.Unicode.GetByteCount(password),
AttributeCount = 0,
Comment = IntPtr.Zero,
Attributes = IntPtr.Zero,
TargetAlias = IntPtr.Zero
};
try
{
if (CredWrite(ref credential, 0))
{
Trace.Info($"Credentials for '{target}' written to windows credential store.");
return new NetworkCredential(username, password);
}
else
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error, "Failed to write credentials");
}
}
finally
{
if (credential.CredentialBlob != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(credential.CredentialBlob);
}
if (credential.TargetName != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(credential.TargetName);
}
if (credential.UserName != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(credential.UserName);
}
}
}
private void SyncCredentialStoreFile()
{
Trace.Info("Sync in-memory credential store with credential store file.");
// delete the cred store file first anyway, since it's a readonly file.
IOUtil.DeleteFile(_credStoreFile);
// delete cred store file when all creds gone
if (_credStore.Count == 0)
{
return;
}
else
{
IOUtil.SaveObject(_credStore, _credStoreFile);
File.SetAttributes(_credStoreFile, File.GetAttributes(_credStoreFile) | FileAttributes.Hidden);
}
}
[DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CredDelete(string target, CredentialType type, int reservedFlag);
[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CredRead(string target, CredentialType type, int reservedFlag, out IntPtr CredentialPtr);
[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CredWrite([In] ref Credential userCredential, [In] UInt32 flags);
[DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
internal static extern bool CredFree([In] IntPtr cred);
internal enum CredentialPersist : UInt32
{
Session = 0x01,
LocalMachine = 0x02
}
internal enum CredentialType : uint
{
Generic = 0x01,
DomainPassword = 0x02,
DomainCertificate = 0x03
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct Credential
{
public UInt32 Flags;
public CredentialType Type;
public IntPtr TargetName;
public IntPtr Comment;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
public UInt32 CredentialBlobSize;
public IntPtr CredentialBlob;
public UInt32 Persist;
public UInt32 AttributeCount;
public IntPtr Attributes;
public IntPtr TargetAlias;
public IntPtr UserName;
}
}
#elif OS_OSX
public sealed class MacOSRunnerCredentialStore : RunnerService, IRunnerCredentialStore
{
private const string _osxRunnerCredStoreKeyChainName = "_GITHUB_ACTIONS_RUNNER_CREDSTORE_INTERNAL_";
// Keychain requires a password, but this is not intended to add security
private const string _osxRunnerCredStoreKeyChainPassword = "C46F23C36AF94B72B1EAEE32C68670A0";
private string _securityUtil;
private string _runnerCredStoreKeyChain;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_securityUtil = WhichUtil.Which("security", true, Trace);
_runnerCredStoreKeyChain = hostContext.GetConfigFile(WellKnownConfigFile.CredentialStore);
// Create osx key chain if it doesn't exists.
if (!File.Exists(_runnerCredStoreKeyChain))
{
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
// make sure the 'security' has access to the key so we won't get prompt at runtime.
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"create-keychain -p {_osxRunnerCredStoreKeyChainPassword} \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully create-keychain for {_runnerCredStoreKeyChain}");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security create-keychain' failed with exit code {exitCode}.");
}
}
}
else
{
// Try unlock and lock the keychain, make sure it's still in good stage
UnlockKeyChain();
LockKeyChain();
}
}
public NetworkCredential Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
ArgUtil.NotNullOrEmpty(username, nameof(username));
ArgUtil.NotNullOrEmpty(password, nameof(password));
try
{
UnlockKeyChain();
// base64encode username + ':' + base64encode password
// OSX keychain requires you provide -s target and -a username to retrieve password
// So, we will trade both username and password as 'secret' store into keychain
string usernameBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(username));
string passwordBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(password));
string secretForKeyChain = $"{usernameBase64}:{passwordBase64}";
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
// make sure the 'security' has access to the key so we won't get prompt at runtime.
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"add-generic-password -s {target} -a GITHUBACTIONSRUNNER -w {secretForKeyChain} -T \"{_securityUtil}\" \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully add-generic-password for {target} (GITHUBACTIONSRUNNER)");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security add-generic-password' failed with exit code {exitCode}.");
}
}
return new NetworkCredential(username, password);
}
finally
{
LockKeyChain();
}
}
public NetworkCredential Read(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
try
{
UnlockKeyChain();
string username;
string password;
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"find-generic-password -s {target} -a GITHUBACTIONSRUNNER -w -g \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
string keyChainSecret = securityOut.First();
string[] secrets = keyChainSecret.Split(':');
if (secrets.Length == 2 && !string.IsNullOrEmpty(secrets[0]) && !string.IsNullOrEmpty(secrets[1]))
{
Trace.Info($"Successfully find-generic-password for {target} (GITHUBACTIONSRUNNER)");
username = Encoding.UTF8.GetString(Convert.FromBase64String(secrets[0]));
password = Encoding.UTF8.GetString(Convert.FromBase64String(secrets[1]));
return new NetworkCredential(username, password);
}
else
{
throw new ArgumentOutOfRangeException(nameof(keyChainSecret));
}
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security find-generic-password' failed with exit code {exitCode}.");
}
}
}
finally
{
LockKeyChain();
}
}
public void Delete(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
try
{
UnlockKeyChain();
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"delete-generic-password -s {target} -a GITHUBACTIONSRUNNER \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully delete-generic-password for {target} (GITHUBACTIONSRUNNER)");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security delete-generic-password' failed with exit code {exitCode}.");
}
}
}
finally
{
LockKeyChain();
}
}
private void UnlockKeyChain()
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(_securityUtil, nameof(_securityUtil));
ArgUtil.NotNullOrEmpty(_runnerCredStoreKeyChain, nameof(_runnerCredStoreKeyChain));
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
// make sure the 'security' has access to the key so we won't get prompt at runtime.
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"unlock-keychain -p {_osxRunnerCredStoreKeyChainPassword} \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully unlock-keychain for {_runnerCredStoreKeyChain}");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security unlock-keychain' failed with exit code {exitCode}.");
}
}
}
private void LockKeyChain()
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(_securityUtil, nameof(_securityUtil));
ArgUtil.NotNullOrEmpty(_runnerCredStoreKeyChain, nameof(_runnerCredStoreKeyChain));
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
// make sure the 'security' has access to the key so we won't get prompt at runtime.
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"lock-keychain \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully lock-keychain for {_runnerCredStoreKeyChain}");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security lock-keychain' failed with exit code {exitCode}.");
}
}
}
}
#else
public sealed class LinuxRunnerCredentialStore : RunnerService, IRunnerCredentialStore
{
// 'ghrunner' 128 bits iv
private readonly byte[] iv = new byte[] { 0x67, 0x68, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x67, 0x68, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72 };
// 256 bits key
private byte[] _symmetricKey;
private string _credStoreFile;
private Dictionary<string, Credential> _credStore;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_credStoreFile = hostContext.GetConfigFile(WellKnownConfigFile.CredentialStore);
if (File.Exists(_credStoreFile))
{
_credStore = IOUtil.LoadObject<Dictionary<string, Credential>>(_credStoreFile);
}
else
{
_credStore = new Dictionary<string, Credential>(StringComparer.OrdinalIgnoreCase);
}
string machineId;
if (File.Exists("/etc/machine-id"))
{
// try use machine-id as encryption key
// this helps avoid accidental information disclosure, but isn't intended for true security
machineId = File.ReadAllLines("/etc/machine-id").FirstOrDefault();
Trace.Info($"machine-id length {machineId?.Length ?? 0}.");
// machine-id doesn't exist or machine-id is not 256 bits
if (string.IsNullOrEmpty(machineId) || machineId.Length != 32)
{
Trace.Warning("Can not get valid machine id from '/etc/machine-id'.");
machineId = "43e7fe5da07740cf914b90f1dac51c2a";
}
}
else
{
// /etc/machine-id not exist
Trace.Warning("/etc/machine-id doesn't exist.");
machineId = "43e7fe5da07740cf914b90f1dac51c2a";
}
List<byte> keyBuilder = new List<byte>();
foreach (var c in machineId)
{
keyBuilder.Add(Convert.ToByte(c));
}
_symmetricKey = keyBuilder.ToArray();
}
public NetworkCredential Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
ArgUtil.NotNullOrEmpty(username, nameof(username));
ArgUtil.NotNullOrEmpty(password, nameof(password));
Trace.Info($"Store credential for '{target}' to cred store.");
Credential cred = new Credential(username, Encrypt(password));
_credStore[target] = cred;
SyncCredentialStoreFile();
return new NetworkCredential(username, password);
}
public NetworkCredential Read(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
Trace.Info($"Read credential for '{target}' from cred store.");
if (_credStore.ContainsKey(target))
{
Credential cred = _credStore[target];
if (!string.IsNullOrEmpty(cred.UserName) && !string.IsNullOrEmpty(cred.Password))
{
Trace.Info($"Return credential for '{target}' from cred store.");
return new NetworkCredential(cred.UserName, Decrypt(cred.Password));
}
}
throw new KeyNotFoundException(target);
}
public void Delete(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
if (_credStore.ContainsKey(target))
{
Trace.Info($"Delete credential for '{target}' from cred store.");
_credStore.Remove(target);
SyncCredentialStoreFile();
}
else
{
throw new KeyNotFoundException(target);
}
}
private void SyncCredentialStoreFile()
{
Trace.Entering();
Trace.Info("Sync in-memory credential store with credential store file.");
// delete cred store file when all creds gone
if (_credStore.Count == 0)
{
IOUtil.DeleteFile(_credStoreFile);
return;
}
if (!File.Exists(_credStoreFile))
{
CreateCredentialStoreFile();
}
IOUtil.SaveObject(_credStore, _credStoreFile);
}
private string Encrypt(string secret)
{
using (Aes aes = Aes.Create())
{
aes.Key = _symmetricKey;
aes.IV = iv;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aes.CreateEncryptor();
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(secret);
}
return Convert.ToBase64String(msEncrypt.ToArray());
}
}
}
}
private string Decrypt(string encryptedText)
{
using (Aes aes = Aes.Create())
{
aes.Key = _symmetricKey;
aes.IV = iv;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aes.CreateDecryptor();
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(encryptedText)))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream and place them in a string.
return srDecrypt.ReadToEnd();
}
}
}
}
}
private void CreateCredentialStoreFile()
{
File.WriteAllText(_credStoreFile, "");
File.SetAttributes(_credStoreFile, File.GetAttributes(_credStoreFile) | FileAttributes.Hidden);
// Try to lock down the .credentials_store file to the owner/group
var chmodPath = WhichUtil.Which("chmod", trace: Trace);
if (!String.IsNullOrEmpty(chmodPath))
{
var arguments = $"600 {new FileInfo(_credStoreFile).FullName}";
using (var invoker = HostContext.CreateService<IProcessInvoker>())
{
var exitCode = invoker.ExecuteAsync(HostContext.GetDirectory(WellKnownDirectory.Root), chmodPath, arguments, null, default(CancellationToken)).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info("Successfully set permissions for credentials store file {0}", _credStoreFile);
}
else
{
Trace.Warning("Unable to successfully set permissions for credentials store file {0}. Received exit code {1} from {2}", _credStoreFile, exitCode, chmodPath);
}
}
}
else
{
Trace.Warning("Unable to locate chmod to set permissions for credentials store file {0}.", _credStoreFile);
}
}
}
[DataContract]
internal class Credential
{
public Credential()
{ }
public Credential(string userName, string password)
{
UserName = userName;
Password = password;
}
[DataMember(IsRequired = true)]
public string UserName { get; set; }
[DataMember(IsRequired = true)]
public string Password { get; set; }
}
#endif
}

View File

@@ -66,11 +66,6 @@ namespace GitHub.Runner.Common
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
var createGenericConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
var createMessageConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
var createRequestConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
@@ -303,29 +298,18 @@ namespace GitHub.Runner.Common
public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, CancellationToken cancellationToken = default(CancellationToken))
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult(JsonUtility.FromString<TaskAgentJobRequest>("{ lockedUntil: \"" + DateTime.Now.Add(TimeSpan.FromMinutes(5)).ToString("u") + "\" }"));
}
CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, cancellationToken: cancellationToken);
}
public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken))
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<TaskAgentJobRequest>(null);
}
CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.FinishAgentRequestAsync(poolId, requestId, lockToken, finishTime, result, cancellationToken: cancellationToken);
}
public Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken = default(CancellationToken))
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.GetAgentRequestAsync(poolId, requestId, cancellationToken: cancellationToken);
}
@@ -335,7 +319,6 @@ namespace GitHub.Runner.Common
//-----------------------------------------------------------------
public Task<List<PackageMetadata>> GetPackagesAsync(string packageType, string platform, int top, CancellationToken cancellationToken)
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.GetPackagesAsync(packageType, platform, top, cancellationToken: cancellationToken);
}

View File

@@ -1,196 +0,0 @@
using GitHub.Runner.Common.Util;
using System;
using System.Linq;
using System.Net;
using System.IO;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common
{
[ServiceLocator(Default = typeof(RunnerWebProxy))]
public interface IRunnerWebProxy : IRunnerService
{
string ProxyAddress { get; }
string ProxyUsername { get; }
string ProxyPassword { get; }
List<string> ProxyBypassList { get; }
IWebProxy WebProxy { get; }
}
public class RunnerWebProxy : RunnerService, IRunnerWebProxy
{
private readonly List<Regex> _regExBypassList = new List<Regex>();
private readonly List<string> _bypassList = new List<string>();
private RunnerWebProxyCore _runnerWebProxy = new RunnerWebProxyCore();
public string ProxyAddress { get; private set; }
public string ProxyUsername { get; private set; }
public string ProxyPassword { get; private set; }
public List<string> ProxyBypassList => _bypassList;
public IWebProxy WebProxy => _runnerWebProxy;
public override void Initialize(IHostContext context)
{
base.Initialize(context);
LoadProxySetting();
}
// This should only be called from config
public void SetupProxy(string proxyAddress, string proxyUsername, string proxyPassword)
{
ArgUtil.NotNullOrEmpty(proxyAddress, nameof(proxyAddress));
Trace.Info($"Update proxy setting from '{ProxyAddress ?? string.Empty}' to'{proxyAddress}'");
ProxyAddress = proxyAddress;
ProxyUsername = proxyUsername;
ProxyPassword = proxyPassword;
if (string.IsNullOrEmpty(ProxyUsername) || string.IsNullOrEmpty(ProxyPassword))
{
Trace.Info($"Config proxy use DefaultNetworkCredentials.");
}
else
{
Trace.Info($"Config authentication proxy as: {ProxyUsername}.");
}
_runnerWebProxy.Update(ProxyAddress, ProxyUsername, ProxyPassword, ProxyBypassList);
}
// This should only be called from config
public void SaveProxySetting()
{
if (!string.IsNullOrEmpty(ProxyAddress))
{
string proxyConfigFile = HostContext.GetConfigFile(WellKnownConfigFile.Proxy);
IOUtil.DeleteFile(proxyConfigFile);
Trace.Info($"Store proxy configuration to '{proxyConfigFile}' for proxy '{ProxyAddress}'");
File.WriteAllText(proxyConfigFile, ProxyAddress);
File.SetAttributes(proxyConfigFile, File.GetAttributes(proxyConfigFile) | FileAttributes.Hidden);
string proxyCredFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyCredentials);
IOUtil.DeleteFile(proxyCredFile);
if (!string.IsNullOrEmpty(ProxyUsername) && !string.IsNullOrEmpty(ProxyPassword))
{
string lookupKey = Guid.NewGuid().ToString("D").ToUpperInvariant();
Trace.Info($"Store proxy credential lookup key '{lookupKey}' to '{proxyCredFile}'");
File.WriteAllText(proxyCredFile, lookupKey);
File.SetAttributes(proxyCredFile, File.GetAttributes(proxyCredFile) | FileAttributes.Hidden);
var credStore = HostContext.GetService<IRunnerCredentialStore>();
credStore.Write($"GITHUB_ACTIONS_RUNNER_PROXY_{lookupKey}", ProxyUsername, ProxyPassword);
}
}
else
{
Trace.Info("No proxy configuration exist.");
}
}
// This should only be called from unconfig
public void DeleteProxySetting()
{
string proxyCredFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyCredentials);
if (File.Exists(proxyCredFile))
{
Trace.Info("Delete proxy credential from credential store.");
string lookupKey = File.ReadAllLines(proxyCredFile).FirstOrDefault();
if (!string.IsNullOrEmpty(lookupKey))
{
var credStore = HostContext.GetService<IRunnerCredentialStore>();
credStore.Delete($"GITHUB_ACTIONS_RUNNER_PROXY_{lookupKey}");
}
Trace.Info($"Delete .proxycredentials file: {proxyCredFile}");
IOUtil.DeleteFile(proxyCredFile);
}
string proxyBypassFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyBypass);
if (File.Exists(proxyBypassFile))
{
Trace.Info($"Delete .proxybypass file: {proxyBypassFile}");
IOUtil.DeleteFile(proxyBypassFile);
}
string proxyConfigFile = HostContext.GetConfigFile(WellKnownConfigFile.Proxy);
Trace.Info($"Delete .proxy file: {proxyConfigFile}");
IOUtil.DeleteFile(proxyConfigFile);
}
private void LoadProxySetting()
{
string proxyConfigFile = HostContext.GetConfigFile(WellKnownConfigFile.Proxy);
if (File.Exists(proxyConfigFile))
{
// we expect the first line of the file is the proxy url
Trace.Verbose($"Try read proxy setting from file: {proxyConfigFile}.");
ProxyAddress = File.ReadLines(proxyConfigFile).FirstOrDefault() ?? string.Empty;
ProxyAddress = ProxyAddress.Trim();
Trace.Verbose($"{ProxyAddress}");
}
if (!string.IsNullOrEmpty(ProxyAddress) && !Uri.IsWellFormedUriString(ProxyAddress, UriKind.Absolute))
{
Trace.Info($"The proxy url is not a well formed absolute uri string: {ProxyAddress}.");
ProxyAddress = string.Empty;
}
if (!string.IsNullOrEmpty(ProxyAddress))
{
Trace.Info($"Config proxy at: {ProxyAddress}.");
string proxyCredFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyCredentials);
if (File.Exists(proxyCredFile))
{
string lookupKey = File.ReadAllLines(proxyCredFile).FirstOrDefault();
if (!string.IsNullOrEmpty(lookupKey))
{
var credStore = HostContext.GetService<IRunnerCredentialStore>();
var proxyCred = credStore.Read($"GITHUB_ACTIONS_RUNNER_PROXY_{lookupKey}");
ProxyUsername = proxyCred.UserName;
ProxyPassword = proxyCred.Password;
}
}
if (!string.IsNullOrEmpty(ProxyPassword))
{
HostContext.SecretMasker.AddValue(ProxyPassword);
}
if (string.IsNullOrEmpty(ProxyUsername) || string.IsNullOrEmpty(ProxyPassword))
{
Trace.Info($"Config proxy use DefaultNetworkCredentials.");
}
else
{
Trace.Info($"Config authentication proxy as: {ProxyUsername}.");
}
string proxyBypassFile = HostContext.GetConfigFile(WellKnownConfigFile.ProxyBypass);
if (File.Exists(proxyBypassFile))
{
Trace.Verbose($"Try read proxy bypass list from file: {proxyBypassFile}.");
foreach (string bypass in File.ReadAllLines(proxyBypassFile))
{
if (string.IsNullOrWhiteSpace(bypass))
{
continue;
}
else
{
Trace.Info($"Bypass proxy for: {bypass}.");
ProxyBypassList.Add(bypass.Trim());
}
}
}
_runnerWebProxy.Update(ProxyAddress, ProxyUsername, ProxyPassword, ProxyBypassList);
}
else
{
Trace.Info($"No proxy setting found.");
}
}
}
}

View File

@@ -50,6 +50,13 @@ namespace GitHub.Runner.Common
public void Error(Exception exception)
{
Trace(TraceEventType.Error, exception.ToString());
var innerEx = exception.InnerException;
while (innerEx != null)
{
Trace(TraceEventType.Error, "#####################################################");
Trace(TraceEventType.Error, innerEx.ToString());
innerEx = innerEx.InnerException;
}
}
// Do not remove the non-format overload.

View File

@@ -1,4 +1,5 @@
using GitHub.DistributedTask.WebApi;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using System;
namespace GitHub.Runner.Common.Util
@@ -75,5 +76,29 @@ namespace GitHub.Runner.Common.Util
throw new NotSupportedException(result.ToString());
}
}
public static StringContextData ToContextData(this TaskResult result)
{
string str;
switch (result)
{
case TaskResult.Succeeded:
str = "success";
break;
case TaskResult.Failed:
str = "failure";
break;
case TaskResult.Canceled:
str = "cancelled";
break;
case TaskResult.Skipped:
str = "skipped";
break;
default:
throw new NotSupportedException($"Unexpected task result '{result}' when converting to context data");
}
return str;
}
}
}

View File

@@ -54,6 +54,8 @@ namespace GitHub.Runner.Common.Util
return "X64";
case Constants.Architecture.Arm:
return "ARM";
case Constants.Architecture.Arm64:
return "ARM64";
default:
throw new NotSupportedException(); // Should never reach here.
}

View File

@@ -28,34 +28,20 @@ namespace GitHub.Runner.Listener
private readonly string[] validFlags =
{
Constants.Runner.CommandLine.Flags.Commit,
#if OS_WINDOWS
Constants.Runner.CommandLine.Flags.GitUseSChannel,
#endif
Constants.Runner.CommandLine.Flags.Help,
Constants.Runner.CommandLine.Flags.Replace,
Constants.Runner.CommandLine.Flags.RunAsService,
Constants.Runner.CommandLine.Flags.Once,
Constants.Runner.CommandLine.Flags.SslSkipCertValidation,
Constants.Runner.CommandLine.Flags.Unattended,
Constants.Runner.CommandLine.Flags.Version
};
private readonly string[] validArgs =
{
Constants.Runner.CommandLine.Args.Agent,
Constants.Runner.CommandLine.Args.Auth,
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
Constants.Runner.CommandLine.Args.NotificationPipeName,
Constants.Runner.CommandLine.Args.Password,
Constants.Runner.CommandLine.Args.Name,
Constants.Runner.CommandLine.Args.Pool,
Constants.Runner.CommandLine.Args.ProxyPassword,
Constants.Runner.CommandLine.Args.ProxyUrl,
Constants.Runner.CommandLine.Args.ProxyUserName,
Constants.Runner.CommandLine.Args.SslCACert,
Constants.Runner.CommandLine.Args.SslClientCert,
Constants.Runner.CommandLine.Args.SslClientCertKey,
Constants.Runner.CommandLine.Args.SslClientCertArchive,
Constants.Runner.CommandLine.Args.SslClientCertPassword,
Constants.Runner.CommandLine.Args.StartupType,
Constants.Runner.CommandLine.Args.Token,
Constants.Runner.CommandLine.Args.Url,
@@ -77,9 +63,6 @@ namespace GitHub.Runner.Listener
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
#if OS_WINDOWS
public bool GitUseSChannel => TestFlag(Constants.Runner.CommandLine.Flags.GitUseSChannel);
#endif
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);
// Constructor.
@@ -164,25 +147,9 @@ namespace GitHub.Runner.Listener
defaultValue: false);
}
public bool GetAutoLaunchBrowser()
{
return TestFlagOrPrompt(
name: Constants.Runner.CommandLine.Flags.LaunchBrowser,
description: "Would you like to launch your browser for AAD Device Code Flow? (Y/N)",
defaultValue: true);
}
//
// Args.
//
public string GetAgentName()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Agent,
description: "Enter the name of runner:",
defaultValue: Environment.MachineName ?? "myagent",
validator: Validators.NonEmptyValidator);
}
public string GetAuth(string defaultValue)
{
return GetArgOrPrompt(
@@ -192,21 +159,12 @@ namespace GitHub.Runner.Listener
validator: Validators.AuthSchemeValidator);
}
public string GetPassword()
public string GetRunnerName()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Password,
description: "What is your GitHub password?",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
public string GetPool()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Pool,
description: "Enter the name of your runner pool:",
defaultValue: "default",
name: Constants.Runner.CommandLine.Args.Name,
description: "Enter the name of runner:",
defaultValue: Environment.MachineName ?? "myrunner",
validator: Validators.NonEmptyValidator);
}
@@ -214,7 +172,7 @@ namespace GitHub.Runner.Listener
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Token,
description: "Enter your personal access token:",
description: "What is your pool admin oauth access token?",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
@@ -223,7 +181,16 @@ namespace GitHub.Runner.Listener
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Token,
description: "Enter runner register token:",
description: "What is your runner register token?",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
public string GetRunnerDeletionToken()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Token,
description: "Enter runner deletion token:",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
@@ -244,15 +211,6 @@ namespace GitHub.Runner.Listener
validator: Validators.ServerUrlValidator);
}
public string GetUserName()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.UserName,
description: "What is your GitHub username?",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
public string GetWindowsLogonAccount(string defaultValue, string descriptionMsg)
{
return GetArgOrPrompt(
@@ -285,67 +243,12 @@ namespace GitHub.Runner.Listener
return GetArg(Constants.Runner.CommandLine.Args.MonitorSocketAddress);
}
public string GetNotificationPipeName()
{
return GetArg(Constants.Runner.CommandLine.Args.NotificationPipeName);
}
public string GetNotificationSocketAddress()
{
return GetArg(Constants.Runner.CommandLine.Args.NotificationSocketAddress);
}
// This is used to find out the source from where the Runner.Listener.exe was launched at the time of run
public string GetStartupType()
{
return GetArg(Constants.Runner.CommandLine.Args.StartupType);
}
public string GetProxyUrl()
{
return GetArg(Constants.Runner.CommandLine.Args.ProxyUrl);
}
public string GetProxyUserName()
{
return GetArg(Constants.Runner.CommandLine.Args.ProxyUserName);
}
public string GetProxyPassword()
{
return GetArg(Constants.Runner.CommandLine.Args.ProxyPassword);
}
public bool GetSkipCertificateValidation()
{
return TestFlag(Constants.Runner.CommandLine.Flags.SslSkipCertValidation);
}
public string GetCACertificate()
{
return GetArg(Constants.Runner.CommandLine.Args.SslCACert);
}
public string GetClientCertificate()
{
return GetArg(Constants.Runner.CommandLine.Args.SslClientCert);
}
public string GetClientCertificatePrivateKey()
{
return GetArg(Constants.Runner.CommandLine.Args.SslClientCertKey);
}
public string GetClientCertificateArchrive()
{
return GetArg(Constants.Runner.CommandLine.Args.SslClientCertArchive);
}
public string GetClientCertificatePassword()
{
return GetArg(Constants.Runner.CommandLine.Args.SslClientCertPassword);
}
//
// Private helpers.
//

View File

@@ -79,79 +79,12 @@ namespace GitHub.Runner.Listener.Configuration
_term.WriteLine("| |", ConsoleColor.White);
_term.WriteLine("--------------------------------------------------------------------------------", ConsoleColor.White);
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info(nameof(ConfigureAsync));
if (IsConfigured())
{
throw new InvalidOperationException("Cannot configure the runner because it is already configured. To reconfigure the runner, run 'config.cmd remove' or './config.sh remove' first.");
}
// Populate proxy setting from commandline args
var runnerProxy = HostContext.GetService<IRunnerWebProxy>();
bool saveProxySetting = false;
string proxyUrl = command.GetProxyUrl();
if (!string.IsNullOrEmpty(proxyUrl))
{
if (!Uri.IsWellFormedUriString(proxyUrl, UriKind.Absolute))
{
throw new ArgumentOutOfRangeException(nameof(proxyUrl));
}
Trace.Info("Reset proxy base on commandline args.");
string proxyUserName = command.GetProxyUserName();
string proxyPassword = command.GetProxyPassword();
(runnerProxy as RunnerWebProxy).SetupProxy(proxyUrl, proxyUserName, proxyPassword);
saveProxySetting = true;
}
// Populate cert setting from commandline args
var runnerCertManager = HostContext.GetService<IRunnerCertificateManager>();
bool saveCertSetting = false;
bool skipCertValidation = command.GetSkipCertificateValidation();
string caCert = command.GetCACertificate();
string clientCert = command.GetClientCertificate();
string clientCertKey = command.GetClientCertificatePrivateKey();
string clientCertArchive = command.GetClientCertificateArchrive();
string clientCertPassword = command.GetClientCertificatePassword();
// We require all Certificate files are under agent root.
// So we can set ACL correctly when configure as service
if (!string.IsNullOrEmpty(caCert))
{
caCert = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), caCert);
ArgUtil.File(caCert, nameof(caCert));
}
if (!string.IsNullOrEmpty(clientCert) &&
!string.IsNullOrEmpty(clientCertKey) &&
!string.IsNullOrEmpty(clientCertArchive))
{
// Ensure all client cert pieces are there.
clientCert = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCert);
clientCertKey = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCertKey);
clientCertArchive = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCertArchive);
ArgUtil.File(clientCert, nameof(clientCert));
ArgUtil.File(clientCertKey, nameof(clientCertKey));
ArgUtil.File(clientCertArchive, nameof(clientCertArchive));
}
else if (!string.IsNullOrEmpty(clientCert) ||
!string.IsNullOrEmpty(clientCertKey) ||
!string.IsNullOrEmpty(clientCertArchive))
{
// Print out which args are missing.
ArgUtil.NotNullOrEmpty(Constants.Runner.CommandLine.Args.SslClientCert, Constants.Runner.CommandLine.Args.SslClientCert);
ArgUtil.NotNullOrEmpty(Constants.Runner.CommandLine.Args.SslClientCertKey, Constants.Runner.CommandLine.Args.SslClientCertKey);
ArgUtil.NotNullOrEmpty(Constants.Runner.CommandLine.Args.SslClientCertArchive, Constants.Runner.CommandLine.Args.SslClientCertArchive);
}
if (skipCertValidation || !string.IsNullOrEmpty(caCert) || !string.IsNullOrEmpty(clientCert))
{
Trace.Info("Reset runner cert setting base on commandline args.");
(runnerCertManager as RunnerCertificateManager).SetupCertificate(skipCertValidation, caCert, clientCert, clientCertKey, clientCertArchive, clientCertPassword);
saveCertSetting = true;
}
RunnerSettings runnerSettings = new RunnerSettings();
bool isHostedServer = false;
@@ -232,7 +165,7 @@ namespace GitHub.Runner.Listener.Configuration
TaskAgent agent;
while (true)
{
runnerSettings.AgentName = command.GetAgentName();
runnerSettings.AgentName = command.GetRunnerName();
_term.WriteLine();
@@ -367,46 +300,14 @@ namespace GitHub.Runner.Listener.Configuration
// We will Combine() what's stored with root. Defaults to string a relative path
runnerSettings.WorkFolder = command.GetWork();
// notificationPipeName for Hosted agent provisioner.
runnerSettings.NotificationPipeName = command.GetNotificationPipeName();
runnerSettings.MonitorSocketAddress = command.GetMonitorSocketAddress();
runnerSettings.NotificationSocketAddress = command.GetNotificationSocketAddress();
_store.SaveSettings(runnerSettings);
if (saveProxySetting)
{
Trace.Info("Save proxy setting to disk.");
(runnerProxy as RunnerWebProxy).SaveProxySetting();
}
if (saveCertSetting)
{
Trace.Info("Save agent cert setting to disk.");
(runnerCertManager as RunnerCertificateManager).SaveCertificateSetting();
}
_term.WriteLine();
_term.WriteSuccessMessage("Settings Saved.");
_term.WriteLine();
bool saveRuntimeOptions = false;
var runtimeOptions = new RunnerRuntimeOptions();
#if OS_WINDOWS
if (command.GitUseSChannel)
{
saveRuntimeOptions = true;
runtimeOptions.GitUseSecureChannel = true;
}
#endif
if (saveRuntimeOptions)
{
Trace.Info("Save agent runtime options to disk.");
_store.SaveRunnerRuntimeOptions(runtimeOptions);
}
#if OS_WINDOWS
// config windows service
bool runAsService = command.GetRunAsService();
@@ -426,7 +327,6 @@ namespace GitHub.Runner.Listener.Configuration
public async Task UnconfigureAsync(CommandSettings command)
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
string currentAction = string.Empty;
_term.WriteSection("Runner removal");
@@ -472,7 +372,7 @@ namespace GitHub.Runner.Listener.Configuration
}
else
{
var githubToken = command.GetToken();
var githubToken = command.GetRunnerDeletionToken();
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, githubToken);
creds = authResult.ToVssCredentials();
Trace.Info("cred retrieved via GitHub auth");
@@ -520,15 +420,6 @@ namespace GitHub.Runner.Listener.Configuration
currentAction = "Removing .runner";
if (isConfigured)
{
// delete proxy setting
(HostContext.GetService<IRunnerWebProxy>() as RunnerWebProxy).DeleteProxySetting();
// delete agent cert setting
(HostContext.GetService<IRunnerCertificateManager>() as RunnerCertificateManager).DeleteCertificateSetting();
// delete agent runtime option
_store.DeleteRunnerRuntimeOptions();
_store.DeleteSettings();
_term.WriteSuccessMessage("Removed .runner");
}
@@ -551,7 +442,7 @@ namespace GitHub.Runner.Listener.Configuration
Trace.Info(nameof(GetCredentialProvider));
var credentialManager = HostContext.GetService<ICredentialManager>();
string authType = command.GetAuth(defaultValue: Constants.Configuration.AAD);
string authType = command.GetAuth(defaultValue: Constants.Configuration.OAuthAccessToken);
// Create the credential.
Trace.Info("Creating credential for auth: {0}", authType);
@@ -574,7 +465,7 @@ namespace GitHub.Runner.Listener.Configuration
PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus),
};
// update - update instead of delete so we don't lose user capabilities etc...
// update - update instead of delete so we don't lose labels etc...
agent.Version = BuildConstants.RunnerPackage.Version;
agent.OSDescription = RuntimeInformation.OSDescription;

View File

@@ -20,8 +20,6 @@ namespace GitHub.Runner.Listener.Configuration
{
public static readonly Dictionary<string, Type> CredentialTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
{ Constants.Configuration.AAD, typeof(AadDeviceCodeAccessToken)},
{ Constants.Configuration.PAT, typeof(PersonalAccessToken)},
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
};
@@ -80,7 +78,7 @@ namespace GitHub.Runner.Listener.Configuration
if (string.Equals(TokenSchema, "OAuthAccessToken", StringComparison.OrdinalIgnoreCase))
{
return new VssCredentials(null, new VssOAuthAccessTokenCredential(Token), CredentialPromptType.DoNotPrompt);
return new VssCredentials(new VssOAuthAccessTokenCredential(Token), CredentialPromptType.DoNotPrompt);
}
else
{

View File

@@ -1,13 +1,5 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using GitHub.Runner.Common.Util;
using GitHub.Services.Client;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.Services.OAuth;
@@ -37,125 +29,6 @@ namespace GitHub.Runner.Listener.Configuration
public abstract void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
}
public sealed class AadDeviceCodeAccessToken : CredentialProvider
{
private string _azureDevOpsClientId = "97877f11-0fc6-4aee-b1ff-febb0519dd00";
public override Boolean RequireInteractive => true;
public AadDeviceCodeAccessToken() : base(Constants.Configuration.AAD) { }
public override VssCredentials GetVssCredentials(IHostContext context)
{
ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(AadDeviceCodeAccessToken));
trace.Info(nameof(GetVssCredentials));
ArgUtil.NotNull(CredentialData, nameof(CredentialData));
CredentialData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Url, out string serverUrl);
ArgUtil.NotNullOrEmpty(serverUrl, nameof(serverUrl));
var tenantAuthorityUrl = GetTenantAuthorityUrl(context, serverUrl);
if (tenantAuthorityUrl == null)
{
throw new NotSupportedException($"'{serverUrl}' is not backed by Azure Active Directory.");
}
LoggerCallbackHandler.LogCallback = ((LogLevel level, string message, bool containsPii) =>
{
switch (level)
{
case LogLevel.Information:
trace.Info(message);
break;
case LogLevel.Error:
trace.Error(message);
break;
case LogLevel.Warning:
trace.Warning(message);
break;
default:
trace.Verbose(message);
break;
}
});
LoggerCallbackHandler.UseDefaultLogging = false;
AuthenticationContext ctx = new AuthenticationContext(tenantAuthorityUrl.AbsoluteUri);
var queryParameters = $"redirect_uri={Uri.EscapeDataString(new Uri(serverUrl).GetLeftPart(UriPartial.Authority))}";
DeviceCodeResult codeResult = ctx.AcquireDeviceCodeAsync("https://management.core.windows.net/", _azureDevOpsClientId, queryParameters).GetAwaiter().GetResult();
var term = context.GetService<ITerminal>();
term.WriteLine($"Please finish AAD device code flow in browser ({codeResult.VerificationUrl}), user code: {codeResult.UserCode}");
if (string.Equals(CredentialData.Data[Constants.Runner.CommandLine.Flags.LaunchBrowser], bool.TrueString, StringComparison.OrdinalIgnoreCase))
{
try
{
#if OS_WINDOWS
Process.Start(new ProcessStartInfo() { FileName = codeResult.VerificationUrl, UseShellExecute = true });
#elif OS_LINUX
Process.Start(new ProcessStartInfo() { FileName = "xdg-open", Arguments = codeResult.VerificationUrl });
#else
Process.Start(new ProcessStartInfo() { FileName = "open", Arguments = codeResult.VerificationUrl });
#endif
}
catch (Exception ex)
{
// not able to open browser, ex: xdg-open/open is not installed.
trace.Error(ex);
term.WriteLine($"Fail to open browser. {codeResult.Message}");
}
}
AuthenticationResult authResult = ctx.AcquireTokenByDeviceCodeAsync(codeResult).GetAwaiter().GetResult();
ArgUtil.NotNull(authResult, nameof(authResult));
trace.Info($"receive AAD auth result with {authResult.AccessTokenType} token");
var aadCred = new VssAadCredential(new VssAadToken(authResult));
VssCredentials creds = new VssCredentials(null, aadCred, CredentialPromptType.DoNotPrompt);
trace.Info("cred created");
return creds;
}
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl)
{
ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(AadDeviceCodeAccessToken));
trace.Info(nameof(EnsureCredential));
ArgUtil.NotNull(command, nameof(command));
CredentialData.Data[Constants.Runner.CommandLine.Args.Url] = serverUrl;
CredentialData.Data[Constants.Runner.CommandLine.Flags.LaunchBrowser] = command.GetAutoLaunchBrowser().ToString();
}
private Uri GetTenantAuthorityUrl(IHostContext context, string serverUrl)
{
using (var client = new HttpClient(context.CreateHttpClientHandler()))
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.AddRange(VssClientHttpRequestSettings.Default.UserAgent);
var requestMessage = new HttpRequestMessage(HttpMethod.Head, $"{serverUrl.Trim('/')}/_apis/connectiondata");
var response = client.SendAsync(requestMessage).GetAwaiter().GetResult();
// Get the tenant from the Login URL, MSA backed accounts will not return `Bearer` www-authenticate header.
var bearerResult = response.Headers.WwwAuthenticate.Where(p => p.Scheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
if (bearerResult != null && bearerResult.Parameter.StartsWith("authorization_uri=", StringComparison.OrdinalIgnoreCase))
{
var authorizationUri = bearerResult.Parameter.Substring("authorization_uri=".Length);
if (Uri.TryCreate(authorizationUri, UriKind.Absolute, out Uri aadTenantUrl))
{
return aadTenantUrl;
}
}
return null;
}
}
}
public sealed class OAuthAccessTokenCredential : CredentialProvider
{
public OAuthAccessTokenCredential() : base(Constants.Configuration.OAuthAccessToken) { }
@@ -175,7 +48,7 @@ namespace GitHub.Runner.Listener.Configuration
ArgUtil.NotNullOrEmpty(token, nameof(token));
trace.Info("token retrieved: {0} chars", token.Length);
VssCredentials creds = new VssCredentials(null, new VssOAuthAccessTokenCredential(token), CredentialPromptType.DoNotPrompt);
VssCredentials creds = new VssCredentials(new VssOAuthAccessTokenCredential(token), CredentialPromptType.DoNotPrompt);
trace.Info("cred created");
return creds;
@@ -190,42 +63,4 @@ namespace GitHub.Runner.Listener.Configuration
CredentialData.Data[Constants.Runner.CommandLine.Args.Token] = command.GetToken();
}
}
public sealed class PersonalAccessToken : CredentialProvider
{
public PersonalAccessToken() : base(Constants.Configuration.PAT) { }
public override VssCredentials GetVssCredentials(IHostContext context)
{
ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(PersonalAccessToken));
trace.Info(nameof(GetVssCredentials));
ArgUtil.NotNull(CredentialData, nameof(CredentialData));
string token;
if (!CredentialData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Token, out token))
{
token = null;
}
ArgUtil.NotNullOrEmpty(token, nameof(token));
trace.Info("token retrieved: {0} chars", token.Length);
// PAT uses a basic credential
VssBasicCredential basicCred = new VssBasicCredential("ActionsRunner", token);
VssCredentials creds = new VssCredentials(null, basicCred, CredentialPromptType.DoNotPrompt);
trace.Info("cred created");
return creds;
}
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl)
{
ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(PersonalAccessToken));
trace.Info(nameof(EnsureCredential));
ArgUtil.NotNull(command, nameof(command));
CredentialData.Data[Constants.Runner.CommandLine.Args.Token] = command.GetToken();
}
}
}

View File

@@ -6,7 +6,7 @@ using GitHub.Runner.Common;
namespace GitHub.Runner.Listener.Configuration
{
/// <summary>
/// Manages an RSA key for the agent using the most appropriate store for the target platform.
/// Manages an RSA key for the runner using the most appropriate store for the target platform.
/// </summary>
#if OS_WINDOWS
[ServiceLocator(Default = typeof(RSAEncryptedFileKeyManager))]
@@ -16,10 +16,10 @@ namespace GitHub.Runner.Listener.Configuration
public interface IRSAKeyManager : IRunnerService
{
/// <summary>
/// Creates a new <c>RSACryptoServiceProvider</c> instance for the current agent. If a key file is found then the current
/// Creates a new <c>RSACryptoServiceProvider</c> instance for the current runner. If a key file is found then the current
/// key is returned to the caller.
/// </summary>
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the agent</returns>
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
RSACryptoServiceProvider CreateKey();
/// <summary>
@@ -30,7 +30,7 @@ namespace GitHub.Runner.Listener.Configuration
/// <summary>
/// Gets the <c>RSACryptoServiceProvider</c> instance currently stored by the key manager.
/// </summary>
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the agent</returns>
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
/// <exception cref="CryptographicException">No key exists in the store</exception>
RSACryptoServiceProvider GetKey();
}

View File

@@ -447,7 +447,7 @@ namespace GitHub.Runner.Listener.Configuration
{
Trace.Entering();
string agentServiceExecutable = "\"" + Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), WindowsServiceControlManager.WindowsServiceControllerName) + "\"";
string runnerServiceExecutable = "\"" + Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), WindowsServiceControlManager.WindowsServiceControllerName) + "\"";
IntPtr scmHndl = IntPtr.Zero;
IntPtr svcHndl = IntPtr.Zero;
IntPtr tmpBuf = IntPtr.Zero;
@@ -468,7 +468,7 @@ namespace GitHub.Runner.Listener.Configuration
};
processInvoker.ExecuteAsync(workingDirectory: string.Empty,
fileName: agentServiceExecutable,
fileName: runnerServiceExecutable,
arguments: "init",
environment: null,
requireExitCodeZero: true,
@@ -490,7 +490,7 @@ namespace GitHub.Runner.Listener.Configuration
SERVICE_WIN32_OWN_PROCESS,
ServiceBootFlag.AutoStart,
ServiceError.Normal,
agentServiceExecutable,
runnerServiceExecutable,
null,
IntPtr.Zero,
null,
@@ -678,6 +678,17 @@ namespace GitHub.Runner.Listener.Configuration
if (service != null)
{
service.Start();
try
{
_term.WriteLine("Waiting for service to start...");
service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(60));
}
catch (System.ServiceProcess.TimeoutException)
{
throw new InvalidOperationException($"Cannot start the service {serviceName} in a timely fashion.");
}
_term.WriteLine($"Service {serviceName} started successfully");
}
else

View File

@@ -43,7 +43,7 @@ namespace GitHub.Runner.Listener.Configuration
// Construct a credentials cache with a single OAuth credential for communication. The windows credential
// is explicitly set to null to ensure we never do that negotiation.
return new VssCredentials(null, agentCredential, CredentialPromptType.DoNotPrompt);
return new VssCredentials(agentCredential, CredentialPromptType.DoNotPrompt);
}
}
}

View File

@@ -12,8 +12,8 @@ namespace GitHub.Runner.Listener.Configuration
public class OsxServiceControlManager : ServiceControlManager, ILinuxServiceControlManager
{
// This is the name you would see when you do `systemctl list-units | grep runner`
private const string _svcNamePattern = "actions.runner.{0}.{1}.{2}";
private const string _svcDisplayPattern = "GitHub Actions Runner ({0}.{1}.{2})";
private const string _svcNamePattern = "actions.runner.{0}.{1}";
private const string _svcDisplayPattern = "GitHub Actions Runner ({0}.{1})";
private const string _shTemplate = "darwin.svc.sh.template";
private const string _svcShName = "svc.sh";

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
@@ -37,25 +38,38 @@ namespace GitHub.Runner.Listener.Configuration
serviceName = string.Empty;
serviceDisplayName = string.Empty;
Uri accountUri = new Uri(settings.ServerUrl);
string accountName = string.Empty;
if (accountUri.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrEmpty(settings.RepoOrOrgName))
{
accountName = accountUri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
}
else
{
accountName = accountUri.Host.Split('.').FirstOrDefault();
throw new InvalidOperationException($"Cannot find GitHub repository/organization name from server url: '{settings.ServerUrl}'");
}
if (string.IsNullOrEmpty(accountName))
// For the service name, replace any characters outside of the alpha-numeric set and ".", "_", "-" with "-"
Regex regex = new Regex(@"[^0-9a-zA-Z._\-]");
string repoOrOrgName = regex.Replace(settings.RepoOrOrgName, "-");
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgName, settings.AgentName);
if (serviceName.Length > 80)
{
throw new InvalidOperationException($"Cannot find GitHub organization name from server url: '{settings.ServerUrl}'");
Trace.Verbose($"Calculated service name is too long (> 80 chars). Trying again by calculating a shorter name.");
int exceededCharLength = serviceName.Length - 80;
string repoOrOrgNameSubstring = StringUtil.SubstringPrefix(repoOrOrgName, 45);
exceededCharLength -= repoOrOrgName.Length - repoOrOrgNameSubstring.Length;
string runnerNameSubstring = settings.AgentName;
// Only trim runner name if it's really necessary
if (exceededCharLength > 0)
{
runnerNameSubstring = StringUtil.SubstringPrefix(settings.AgentName, settings.AgentName.Length - exceededCharLength);
}
serviceName = StringUtil.Format(serviceNamePattern, accountName, settings.PoolName, settings.AgentName);
serviceDisplayName = StringUtil.Format(serviceDisplayNamePattern, accountName, settings.PoolName, settings.AgentName);
serviceName = StringUtil.Format(serviceNamePattern, repoOrOrgNameSubstring, runnerNameSubstring);
}
serviceDisplayName = StringUtil.Format(serviceDisplayNamePattern, repoOrOrgName, settings.AgentName);
Trace.Info($"Service name '{serviceName}' display name '{serviceDisplayName}' will be used for service configuration.");
}

View File

@@ -13,8 +13,8 @@ namespace GitHub.Runner.Listener.Configuration
public class SystemDControlManager : ServiceControlManager, ILinuxServiceControlManager
{
// This is the name you would see when you do `systemctl list-units | grep runner`
private const string _svcNamePattern = "actions.runner.{0}.{1}.{2}.service";
private const string _svcDisplayPattern = "GitHub Actions Runner ({0}.{1}.{2})";
private const string _svcNamePattern = "actions.runner.{0}.{1}.service";
private const string _svcDisplayPattern = "GitHub Actions Runner ({0}.{1})";
private const string _shTemplate = "systemd.svc.sh.template";
private const string _shName = "svc.sh";

View File

@@ -38,25 +38,6 @@ namespace GitHub.Runner.Listener.Configuration
return CredentialManager.CredentialTypes.ContainsKey(value);
}
public static bool FilePathValidator(string value)
{
var directoryInfo = new DirectoryInfo(value);
if (!directoryInfo.Exists)
{
try
{
Directory.CreateDirectory(value);
}
catch (Exception)
{
return false;
}
}
return true;
}
public static bool BoolValidator(string value)
{
return string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) ||

View File

@@ -15,8 +15,8 @@ namespace GitHub.Runner.Listener.Configuration
{
public const string WindowsServiceControllerName = "RunnerService.exe";
private const string ServiceNamePattern = "actionsrunner.{0}.{1}.{2}";
private const string ServiceDisplayNamePattern = "GitHub Actions Runner ({0}.{1}.{2})";
private const string ServiceNamePattern = "actions.runner.{0}.{1}";
private const string ServiceDisplayNamePattern = "GitHub Actions Runner ({0}.{1})";
private INativeWindowsServiceHelper _windowsServiceHelper;
private ITerminal _term;

View File

@@ -22,7 +22,6 @@ namespace GitHub.Runner.Listener
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
bool Cancel(JobCancelMessage message);
Task WaitAsync(CancellationToken token);
TaskResult GetLocalRunJobResult(AgentJobRequestMessage message);
Task ShutdownAsync();
}
@@ -165,11 +164,6 @@ namespace GitHub.Runner.Listener
}
}
public TaskResult GetLocalRunJobResult(AgentJobRequestMessage message)
{
return _localRunJobResult.Value[message.RequestId];
}
public async Task ShutdownAsync()
{
Trace.Info($"Shutting down JobDispatcher. Make sure all WorkerDispatcher has finished.");
@@ -373,8 +367,6 @@ namespace GitHub.Runner.Listener
ArgUtil.NotNullOrEmpty(pipeHandleOut, nameof(pipeHandleOut));
ArgUtil.NotNullOrEmpty(pipeHandleIn, nameof(pipeHandleIn));
if (HostContext.RunMode == RunMode.Normal)
{
// Save STDOUT from worker, worker will use STDOUT report unhandle exception.
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
@@ -398,12 +390,6 @@ namespace GitHub.Runner.Listener
}
}
};
}
else if (HostContext.RunMode == RunMode.Local)
{
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) => Console.WriteLine(e.Data);
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) => Console.WriteLine(e.Data);
}
// Start the child process.
HostContext.WritePerfCounter("StartingWorkerProcess");
@@ -468,7 +454,7 @@ namespace GitHub.Runner.Listener
// send notification to machine provisioner.
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
var accessToken = systemConnection?.Authorization?.Parameters["AccessToken"];
await notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
HostContext.WritePerfCounter($"SentJobToWorker_{requestId.ToString()}");
@@ -582,6 +568,10 @@ namespace GitHub.Runner.Listener
{
Trace.Info("worker process has been killed.");
}
// When worker doesn't exit within cancel timeout, the runner will kill the worker process and worker won't finish upload job logs.
// The runner will try to upload these logs at this time.
await TryUploadUnfinishedLogs(message);
}
Trace.Info($"finish job request for job {message.JobId} with result: {resultOnAbandonOrCancel}");
@@ -726,15 +716,120 @@ namespace GitHub.Runner.Listener
}
}
// Best effort upload any logs for this job.
private async Task TryUploadUnfinishedLogs(Pipelines.AgentJobRequestMessage message)
{
Trace.Entering();
var logFolder = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), PagingLogger.PagingFolder);
if (!Directory.Exists(logFolder))
{
return;
}
var logs = Directory.GetFiles(logFolder);
if (logs.Length == 0)
{
return;
}
try
{
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection));
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
var jobServer = HostContext.GetService<IJobServer>();
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
await jobServer.ConnectAsync(jobConnection);
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
var updatedRecords = new List<TimelineRecord>();
var logPages = new Dictionary<Guid, Dictionary<int, string>>();
var logRecords = new Dictionary<Guid, TimelineRecord>();
foreach (var log in logs)
{
var logName = Path.GetFileNameWithoutExtension(log);
var logPageSeperator = logName.IndexOf('_');
var logRecordId = Guid.Empty;
var pageNumber = 0;
if (logPageSeperator < 0)
{
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_INT'.");
continue;
}
else
{
if (!Guid.TryParse(logName.Substring(0, logPageSeperator), out logRecordId))
{
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_INT'.");
continue;
}
if (!int.TryParse(logName.Substring(logPageSeperator + 1), out pageNumber))
{
Trace.Warning($"log file '{log}' doesn't follow naming convension 'GUID_INT'.");
continue;
}
}
var record = timeline.Records.FirstOrDefault(x => x.Id == logRecordId);
if (record != null)
{
if (!logPages.ContainsKey(record.Id))
{
logPages[record.Id] = new Dictionary<int, string>();
logRecords[record.Id] = record;
}
logPages[record.Id][pageNumber] = log;
}
}
foreach (var pages in logPages)
{
var record = logRecords[pages.Key];
if (record.Log == null)
{
// Create the log
record.Log = await jobServer.CreateLogAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, new TaskLog(String.Format(@"logs\{0:D}", record.Id)), default(CancellationToken));
// Need to post timeline record updates to reflect the log creation
updatedRecords.Add(record.Clone());
}
for (var i = 1; i <= pages.Value.Count; i++)
{
var logFile = pages.Value[i];
// Upload the contents
using (FileStream fs = File.Open(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
var logUploaded = await jobServer.AppendLogContentAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, record.Log.Id, fs, default(CancellationToken));
}
Trace.Info($"Uploaded unfinished log '{logFile}' for current job.");
IOUtil.DeleteFile(logFile);
}
}
if (updatedRecords.Count > 0)
{
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, updatedRecords, CancellationToken.None);
}
}
catch (Exception ex)
{
// Ignore any error during log upload since it's best effort
Trace.Error(ex);
}
}
// TODO: We need send detailInfo back to DT in order to add an issue for the job
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null)
{
Trace.Entering();
if (HostContext.RunMode == RunMode.Local)
{
_localRunJobResult.Value[message.RequestId] = result;
return;
}
if (PlanUtil.GetFeatures(message.Plan).HasFlag(PlanFeatures.JobCompletedPlanEvent))
{
@@ -787,8 +882,10 @@ namespace GitHub.Runner.Listener
var jobServer = HostContext.GetService<IJobServer>();
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
Uri jobServerUrl = systemConnection.Url;
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
/* Below is the legacy 'OnPremises' code that is currently unused by the runner
ToDo: re-implement code as appropriate once GHES support is added.
// Make sure SystemConnection Url match Config Url base for OnPremises server
if (!message.Variables.ContainsKey(Constants.Variables.System.ServerType) ||
string.Equals(message.Variables[Constants.Variables.System.ServerType]?.Value, "OnPremises", StringComparison.OrdinalIgnoreCase))
@@ -814,11 +911,12 @@ namespace GitHub.Runner.Listener
//cannot parse the Uri - not a fatal error
Trace.Error(ex);
}
}
} */
VssConnection jobConnection = VssUtil.CreateConnection(jobServerUrl, jobServerCredential);
await jobServer.ConnectAsync(jobConnection);
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));

View File

@@ -80,7 +80,7 @@ namespace GitHub.Runner.Listener
Trace.Info($"Attempt to create session.");
try
{
Trace.Info("Connecting to the Agent Server...");
Trace.Info("Connecting to the Runner Server...");
await _runnerServer.ConnectAsync(new Uri(serverUrl), creds);
Trace.Info("VssConnection created");
@@ -110,7 +110,7 @@ namespace GitHub.Runner.Listener
}
catch (TaskAgentAccessTokenExpiredException)
{
Trace.Info("Agent OAuth token has been revoked. Session creation failed.");
Trace.Info("Runner OAuth token has been revoked. Session creation failed.");
throw;
}
catch (Exception ex)
@@ -190,7 +190,7 @@ namespace GitHub.Runner.Listener
}
catch (TaskAgentAccessTokenExpiredException)
{
Trace.Info("Agent OAuth token has been revoked. Unable to pull message.");
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
throw;
}
catch (Exception ex)
@@ -336,7 +336,7 @@ namespace GitHub.Runner.Listener
{
if (ex is TaskAgentNotFoundException)
{
Trace.Info("The agent no longer exists on the server. Stopping the runner.");
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;
}
@@ -364,7 +364,7 @@ namespace GitHub.Runner.Listener
}
else if (ex is VssOAuthTokenRequestException && ex.Message.Contains("Current server time is"))
{
Trace.Info("Local clock might skewed.");
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)))
{

View File

@@ -4,6 +4,7 @@ using GitHub.Runner.Sdk;
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -14,6 +15,9 @@ namespace GitHub.Runner.Listener
{
public static int Main(string[] args)
{
// Add environment variables from .env file
LoadAndSetEnv();
using (HostContext context = new HostContext("Runner"))
{
return MainAsync(context, args).GetAwaiter().GetResult();
@@ -25,7 +29,7 @@ namespace GitHub.Runner.Listener
// 1: Terminate failure
// 2: Retriable failure
// 3: Exit for self update
public async static Task<int> MainAsync(IHostContext context, string[] args)
private async static Task<int> MainAsync(IHostContext context, string[] args)
{
Tracing trace = context.GetTrace(nameof(GitHub.Runner.Listener));
trace.Info($"Runner is built for {Constants.Runner.Platform} ({Constants.Runner.PlatformArchitecture}) - {BuildConstants.RunnerPackage.PackageName}.");
@@ -83,22 +87,6 @@ namespace GitHub.Runner.Listener
return Constants.Runner.ReturnCode.TerminatedError;
}
// Add environment variables from .env file
string envFile = Path.Combine(context.GetDirectory(WellKnownDirectory.Root), ".env");
if (File.Exists(envFile))
{
var envContents = File.ReadAllLines(envFile);
foreach (var env in envContents)
{
if (!string.IsNullOrEmpty(env) && env.IndexOf('=') > 0)
{
string envKey = env.Substring(0, env.IndexOf('='));
string envValue = env.Substring(env.IndexOf('=') + 1);
Environment.SetEnvironmentVariable(envKey, envValue);
}
}
}
// Parse the command line args.
var command = new CommandSettings(context, args);
trace.Info("Arguments parsed");
@@ -136,5 +124,34 @@ namespace GitHub.Runner.Listener
return Constants.Runner.ReturnCode.RetryableError;
}
}
private static void LoadAndSetEnv()
{
var binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var rootDir = new DirectoryInfo(binDir).Parent.FullName;
string envFile = Path.Combine(rootDir, ".env");
if (File.Exists(envFile))
{
var envContents = File.ReadAllLines(envFile);
foreach (var env in envContents)
{
if (!string.IsNullOrEmpty(env))
{
var separatorIndex = env.IndexOf('=');
if (separatorIndex > 0)
{
string envKey = env.Substring(0, separatorIndex);
string envValue = null;
if (env.Length > separatorIndex + 1)
{
envValue = env.Substring(separatorIndex + 1);
}
Environment.SetEnvironmentVariable(envKey, envValue);
}
}
}
}
}
}
}

View File

@@ -1,13 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
<ItemGroup>
@@ -22,49 +24,10 @@
<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" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="3.19.4" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">
<DefineConstants>OS_OSX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true' AND '$(Configuration)' == 'Debug'">
<DefineConstants>OS_OSX;DEBUG;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -37,9 +37,7 @@ namespace GitHub.Runner.Listener
{
try
{
var runnerWebProxy = HostContext.GetService<IRunnerWebProxy>();
var runnerCertManager = HostContext.GetService<IRunnerCertificateManager>();
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, runnerWebProxy.WebProxy, runnerCertManager.VssClientCertificateManager);
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy);
_inConfigStage = true;
_completedCommand.Reset();
@@ -191,25 +189,6 @@ namespace GitHub.Runner.Listener
}
}
#if !OS_WINDOWS
// Fix the work folder setting on Linux
if (settings.WorkFolder.Contains("vsts", StringComparison.OrdinalIgnoreCase))
{
var workFolder = "/runner/work";
var unix = HostContext.GetService<IUnixUtil>();
// create new work folder /runner/work
await unix.ExecAsync(HostContext.GetDirectory(WellKnownDirectory.Root), "sh", $"-c \"sudo mkdir -p {workFolder}\"");
// fix permission
await unix.ExecAsync(HostContext.GetDirectory(WellKnownDirectory.Root), "sh", $"-c \"sudo chown -R $USER {workFolder}\"");
// update settings
settings.WorkFolder = workFolder;
store.SaveSettings(settings);
}
#endif
Trace.Info($"Set runner startup type - {startType}");
HostContext.StartupType = startType;
@@ -293,14 +272,8 @@ namespace GitHub.Runner.Listener
try
{
var notification = HostContext.GetService<IJobNotification>();
if (!String.IsNullOrEmpty(settings.NotificationSocketAddress))
{
notification.StartClient(settings.NotificationSocketAddress, settings.MonitorSocketAddress);
}
else
{
notification.StartClient(settings.NotificationPipeName, settings.MonitorSocketAddress, HostContext.RunnerShutdownToken);
}
notification.StartClient(settings.MonitorSocketAddress);
bool autoUpdateInProgress = false;
Task<bool> selfUpdateTask = null;
@@ -460,7 +433,7 @@ namespace GitHub.Runner.Listener
}
catch (TaskAgentAccessTokenExpiredException)
{
Trace.Info("Agent OAuth token has been revoked. Shutting down.");
Trace.Info("Runner OAuth token has been revoked. Shutting down.");
}
return Constants.Runner.ReturnCode.Success;

View File

@@ -1,13 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
<ItemGroup>
@@ -22,42 +24,4 @@
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">
<DefineConstants>OS_OSX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true' AND '$(Configuration)' == 'Debug'">
<DefineConstants>OS_OSX;DEBUG;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -1,58 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Runner.Sdk;
using GitHub.Services.WebApi;
using GitHub.Build.WebApi;
namespace GitHub.Runner.Plugins.Artifact
{
// A client wrapper interacting with Build's Artifact API
public class BuildServer
{
private readonly BuildHttpClient _buildHttpClient;
public BuildServer(VssConnection connection)
{
ArgUtil.NotNull(connection, nameof(connection));
_buildHttpClient = connection.GetClient<BuildHttpClient>();
}
// Associate the specified artifact with a build, along with custom data.
public async Task<BuildArtifact> AssociateArtifact(
Guid projectId,
int pipelineId,
string jobId,
string name,
string type,
string data,
Dictionary<string, string> propertiesDictionary,
CancellationToken cancellationToken = default(CancellationToken))
{
BuildArtifact artifact = new BuildArtifact()
{
Name = name,
Source = jobId,
Resource = new ArtifactResource()
{
Data = data,
Type = type,
Properties = propertiesDictionary
}
};
return await _buildHttpClient.CreateArtifactAsync(artifact, projectId, pipelineId, cancellationToken: cancellationToken);
}
// Get named artifact from a build
public async Task<BuildArtifact> GetArtifact(
Guid projectId,
int pipelineId,
string name,
CancellationToken cancellationToken)
{
return await _buildHttpClient.GetArtifactAsync(projectId, pipelineId, name, cancellationToken: cancellationToken);
}
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Build.WebApi;
using GitHub.Services.Common;
using GitHub.Runner.Sdk;
@@ -40,38 +39,32 @@ namespace GitHub.Runner.Plugins.Artifact
targetPath = Path.IsPathFullyQualified(targetPath) ? targetPath : Path.GetFullPath(Path.Combine(defaultWorkingDirectory, targetPath));
// Project ID
Guid projectId = new Guid(context.Variables.GetValueOrDefault(BuildVariables.TeamProjectId)?.Value ?? Guid.Empty.ToString());
// Build ID
string buildIdStr = context.Variables.GetValueOrDefault(BuildVariables.BuildId)?.Value ?? string.Empty;
string buildIdStr = context.Variables.GetValueOrDefault(SdkConstants.Variables.Build.BuildId)?.Value ?? string.Empty;
if (!int.TryParse(buildIdStr, out int buildId))
{
throw new ArgumentException($"Run Id is not an Int32: {buildIdStr}");
}
context.Output($"Download artifact '{artifactName}' to: '{targetPath}'");
context.Output($"Downloading artifact '{artifactName}' to: '{targetPath}'");
BuildServer buildHelper = new BuildServer(context.VssConnection);
BuildArtifact buildArtifact = await buildHelper.GetArtifact(projectId, buildId, artifactName, token);
// Definition ID is a dummy value only used by HTTP client routing purposes
int definitionId = 1;
if (string.Equals(buildArtifact.Resource.Type, "Container", StringComparison.OrdinalIgnoreCase))
var pipelinesHelper = new PipelinesServer(context.VssConnection);
var actionsStorageArtifact = await pipelinesHelper.GetActionsStorageArtifact(definitionId, buildId, artifactName, token);
if (actionsStorageArtifact == null)
{
string containerUrl = buildArtifact.Resource.Data;
string[] parts = containerUrl.Split(new[] { '/' }, 3);
if (parts.Length < 3 || !long.TryParse(parts[1], out long containerId))
{
throw new ArgumentOutOfRangeException($"Invalid container url '{containerUrl}' for artifact '{buildArtifact.Name}'");
throw new Exception($"The actions storage artifact for '{artifactName}' could not be found, or is no longer available");
}
string containerPath = parts[2];
FileContainerServer fileContainerServer = new FileContainerServer(context.VssConnection, projectId, containerId, containerPath);
string containerPath = actionsStorageArtifact.Name; // In actions storage artifacts, name equals the path
long containerId = actionsStorageArtifact.ContainerId;
FileContainerServer fileContainerServer = new FileContainerServer(context.VssConnection, projectId: new Guid(), containerId, containerPath);
await fileContainerServer.DownloadFromContainerAsync(context, targetPath, token);
}
else
{
throw new NotSupportedException($"Invalid artifact type: {buildArtifact.Resource.Type}");
}
context.Output("Artifact download finished.");
}

View File

@@ -236,15 +236,15 @@ namespace GitHub.Runner.Plugins.Artifact
// try upload all files for the first time.
UploadResult uploadResult = await ParallelUploadAsync(context, files, maxConcurrentUploads, _uploadCancellationTokenSource.Token);
if (uploadResult.FailedFiles.Count == 0)
if (uploadResult.RetryFiles.Count == 0)
{
// all files have been upload succeed.
context.Output("File upload succeed.");
context.Output("File upload complete.");
return uploadResult.TotalFileSizeUploaded;
}
else
{
context.Output($"{uploadResult.FailedFiles.Count} files failed to upload, retry these files after a minute.");
context.Output($"{uploadResult.RetryFiles.Count} files failed to upload, retry these files after a minute.");
}
// Delay 1 min then retry failed files.
@@ -255,13 +255,13 @@ namespace GitHub.Runner.Plugins.Artifact
}
// Retry upload all failed files.
context.Output($"Start retry {uploadResult.FailedFiles.Count} failed files upload.");
UploadResult retryUploadResult = await ParallelUploadAsync(context, uploadResult.FailedFiles, maxConcurrentUploads, _uploadCancellationTokenSource.Token);
context.Output($"Start retry {uploadResult.RetryFiles.Count} failed files upload.");
UploadResult retryUploadResult = await ParallelUploadAsync(context, uploadResult.RetryFiles, maxConcurrentUploads, _uploadCancellationTokenSource.Token);
if (retryUploadResult.FailedFiles.Count == 0)
if (retryUploadResult.RetryFiles.Count == 0)
{
// all files have been upload succeed after retry.
context.Output("File upload succeed after retry.");
context.Output("File upload complete after retry.");
return uploadResult.TotalFileSizeUploaded + retryUploadResult.TotalFileSizeUploaded;
}
else
@@ -465,76 +465,62 @@ namespace GitHub.Runner.Plugins.Artifact
using (FileStream fs = File.Open(fileToUpload, FileMode.Open, FileAccess.Read, FileShare.Read))
{
string itemPath = (_containerPath.TrimEnd('/') + "/" + fileToUpload.Remove(0, _sourceParentDirectory.Length + 1)).Replace('\\', '/');
uploadTimer.Restart();
bool catchExceptionDuringUpload = false;
HttpResponseMessage response = null;
bool failAndExit = false;
try
{
response = await _fileContainerHttpClient.UploadFileAsync(_containerId, itemPath, fs, _projectId, cancellationToken: token, chunkSize: 4 * 1024 * 1024);
uploadTimer.Restart();
using (HttpResponseMessage response = await _fileContainerHttpClient.UploadFileAsync(_containerId, itemPath, fs, _projectId, cancellationToken: token, chunkSize: 4 * 1024 * 1024))
{
if (response == null || response.StatusCode != HttpStatusCode.Created)
{
context.Output($"Unable to copy file to server StatusCode={response?.StatusCode}: {response?.ReasonPhrase}. Source file path: {fileToUpload}. Target server path: {itemPath}");
if (response?.StatusCode == HttpStatusCode.Conflict)
{
// fail upload task but continue with any other files
context.Error($"Error '{fileToUpload}' has already been uploaded.");
}
else if (_fileContainerHttpClient.IsFastFailResponse(response))
{
// Fast fail: we received an http status code where we should abandon our efforts
context.Output($"Cannot continue uploading files, so draining upload queue of {_fileUploadQueue.Count} items.");
DrainUploadQueue(context);
failedFiles.Clear();
failAndExit = true;
throw new UploadFailedException($"Critical failure uploading '{fileToUpload}'");
}
else
{
context.Debug($"Adding '{fileToUpload}' to retry list.");
failedFiles.Add(fileToUpload);
}
throw new UploadFailedException($"Http failure response '{response?.StatusCode}': '{response?.ReasonPhrase}' while uploading '{fileToUpload}'");
}
uploadTimer.Stop();
context.Debug($"File: '{fileToUpload}' took {uploadTimer.ElapsedMilliseconds} milliseconds to finish upload");
uploadedSize += fs.Length;
OutputLogForFile(context, fileToUpload, $"Detail upload trace for file: {itemPath}", context.Debug);
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
context.Output($"File upload has been cancelled during upload file: '{fileToUpload}'.");
if (response != null)
{
response.Dispose();
response = null;
}
throw;
}
catch (Exception ex)
{
catchExceptionDuringUpload = true;
context.Output($"Fail to upload '{fileToUpload}' due to '{ex.Message}'.");
context.Output(ex.ToString());
}
uploadTimer.Stop();
if (catchExceptionDuringUpload || (response != null && response.StatusCode != HttpStatusCode.Created))
{
if (response != null)
{
context.Output($"Unable to copy file to server StatusCode={response.StatusCode}: {response.ReasonPhrase}. Source file path: {fileToUpload}. Target server path: {itemPath}");
}
OutputLogForFile(context, fileToUpload, $"Detail upload trace for file that fail to upload: {itemPath}", context.Output);
// output detail upload trace for the file.
ConcurrentQueue<string> logQueue;
if (_fileUploadTraceLog.TryGetValue(itemPath, out logQueue))
if (failAndExit)
{
context.Output($"Detail upload trace for file that fail to upload: {itemPath}");
string message;
while (logQueue.TryDequeue(out message))
{
context.Output(message);
context.Debug("Exiting upload.");
throw;
}
}
// tracking file that failed to upload.
failedFiles.Add(fileToUpload);
}
else
{
context.Debug($"File: '{fileToUpload}' took {uploadTimer.ElapsedMilliseconds} milliseconds to finish upload");
uploadedSize += fs.Length;
// debug detail upload trace for the file.
ConcurrentQueue<string> logQueue;
if (_fileUploadTraceLog.TryGetValue(itemPath, out logQueue))
{
context.Debug($"Detail upload trace for file: {itemPath}");
string message;
while (logQueue.TryDequeue(out message))
{
context.Debug(message);
}
}
}
if (response != null)
{
response.Dispose();
response = null;
}
}
Interlocked.Increment(ref _uploadFilesProcessed);
@@ -590,6 +576,30 @@ namespace GitHub.Runner.Plugins.Artifact
}
}
private void DrainUploadQueue(RunnerActionPluginExecutionContext context)
{
while (_fileUploadQueue.TryDequeue(out string fileToUpload))
{
context.Debug($"Clearing upload queue: '{fileToUpload}'");
Interlocked.Increment(ref _uploadFilesProcessed);
}
}
private void OutputLogForFile(RunnerActionPluginExecutionContext context, string itemPath, string logDescription, Action<string> log)
{
// output detail upload trace for the file.
ConcurrentQueue<string> logQueue;
if (_fileUploadTraceLog.TryGetValue(itemPath, out logQueue))
{
log(logDescription);
string message;
while (logQueue.TryDequeue(out message))
{
log(message);
}
}
}
private void UploadFileTraceReportReceived(object sender, ReportTraceEventArgs e)
{
ConcurrentQueue<string> logQueue = _fileUploadTraceLog.GetOrAdd(e.File, new ConcurrentQueue<string>());
@@ -607,22 +617,22 @@ namespace GitHub.Runner.Plugins.Artifact
{
public UploadResult()
{
FailedFiles = new List<string>();
RetryFiles = new List<string>();
TotalFileSizeUploaded = 0;
}
public UploadResult(List<string> failedFiles, long totalFileSizeUploaded)
public UploadResult(List<string> retryFiles, long totalFileSizeUploaded)
{
FailedFiles = failedFiles;
RetryFiles = retryFiles ?? new List<string>();
TotalFileSizeUploaded = totalFileSizeUploaded;
}
public List<string> FailedFiles { get; set; }
public List<string> RetryFiles { get; set; }
public long TotalFileSizeUploaded { get; set; }
public void AddUploadResult(UploadResult resultToAdd)
{
this.FailedFiles.AddRange(resultToAdd.FailedFiles);
this.RetryFiles.AddRange(resultToAdd.RetryFiles);
this.TotalFileSizeUploaded += resultToAdd.TotalFileSizeUploaded;
}
}
@@ -657,4 +667,19 @@ namespace GitHub.Runner.Plugins.Artifact
this.FailedFiles.AddRange(resultToAdd.FailedFiles);
}
}
public class UploadFailedException : Exception
{
public UploadFailedException()
: base()
{ }
public UploadFailedException(string message)
: base(message)
{ }
public UploadFailedException(string message, Exception inner)
: base(message, inner)
{ }
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Actions.Pipelines.WebApi;
using GitHub.Services.WebApi;
using GitHub.Runner.Sdk;
using Pipelines = GitHub.Actions.Pipelines.WebApi;
namespace GitHub.Runner.Plugins.Artifact
{
// A client wrapper interacting with Pipelines's Artifact API
public class PipelinesServer
{
private readonly PipelinesHttpClient _pipelinesHttpClient;
public PipelinesServer(VssConnection connection)
{
ArgUtil.NotNull(connection, nameof(connection));
_pipelinesHttpClient = connection.GetClient<PipelinesHttpClient>();
}
// Associate the specified Actions Storage artifact with a pipeline
public async Task<Pipelines.ActionsStorageArtifact> AssociateActionsStorageArtifactAsync(
int pipelineId,
int runId,
long containerId,
string name,
long size,
CancellationToken cancellationToken = default(CancellationToken))
{
CreateArtifactParameters parameters = new CreateActionsStorageArtifactParameters()
{
Name = name,
ContainerId = containerId,
Size = size
};
return await _pipelinesHttpClient.CreateArtifactAsync(
parameters,
pipelineId,
runId,
cancellationToken: cancellationToken) as Pipelines.ActionsStorageArtifact;
}
// Get named Actions Storage artifact for a pipeline
public async Task<Pipelines.ActionsStorageArtifact> GetActionsStorageArtifact(
int pipelineId,
int runId,
string name,
CancellationToken cancellationToken)
{
return await _pipelinesHttpClient.GetArtifactAsync(
pipelineId,
runId,
name,
cancellationToken: cancellationToken) as Pipelines.ActionsStorageArtifact;
}
}
}

View File

@@ -4,9 +4,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Build.WebApi;
using GitHub.Services.Common;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Plugins.Artifact
@@ -45,11 +43,8 @@ namespace GitHub.Runner.Plugins.Artifact
throw new ArgumentException($"Artifact name is not valid: {artifactName}. It cannot contain '\\', '/', \"', ':', '<', '>', '|', '*', and '?'");
}
// Project ID
Guid projectId = new Guid(context.Variables.GetValueOrDefault(BuildVariables.TeamProjectId)?.Value ?? Guid.Empty.ToString());
// Build ID
string buildIdStr = context.Variables.GetValueOrDefault(BuildVariables.BuildId)?.Value ?? string.Empty;
string buildIdStr = context.Variables.GetValueOrDefault(SdkConstants.Variables.Build.BuildId)?.Value ?? string.Empty;
if (!int.TryParse(buildIdStr, out int buildId))
{
throw new ArgumentException($"Run Id is not an Int32: {buildIdStr}");
@@ -65,26 +60,45 @@ namespace GitHub.Runner.Plugins.Artifact
}
// Container ID
string containerIdStr = context.Variables.GetValueOrDefault(BuildVariables.ContainerId)?.Value ?? string.Empty;
string containerIdStr = context.Variables.GetValueOrDefault(SdkConstants.Variables.Build.ContainerId)?.Value ?? string.Empty;
if (!long.TryParse(containerIdStr, out long containerId))
{
throw new ArgumentException($"Container Id is not a Int64: {containerIdStr}");
throw new ArgumentException($"Container Id is not an Int64: {containerIdStr}");
}
context.Output($"Uploading artifact '{artifactName}' from '{fullPath}' for run #{buildId}");
FileContainerServer fileContainerHelper = new FileContainerServer(context.VssConnection, projectId, containerId, artifactName);
long size = await fileContainerHelper.CopyToContainerAsync(context, fullPath, token);
FileContainerServer fileContainerHelper = new FileContainerServer(context.VssConnection, projectId: Guid.Empty, containerId, artifactName);
var propertiesDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
long size = 0;
try
{
size = await fileContainerHelper.CopyToContainerAsync(context, fullPath, token);
propertiesDictionary.Add("artifactsize", size.ToString());
string fileContainerFullPath = StringUtil.Format($"#/{containerId}/{artifactName}");
context.Output($"Uploaded '{fullPath}' to server");
context.Output($"Uploaded '{size}' bytes from '{fullPath}' to server");
}
// if any of the results were successful, make sure to attach them to the build
finally
{
// Definition ID is a dummy value only used by HTTP client routing purposes
int definitionId = 1;
BuildServer buildHelper = new BuildServer(context.VssConnection);
string jobId = context.Variables.GetValueOrDefault(WellKnownDistributedTaskVariables.JobId).Value ?? string.Empty;
var artifact = await buildHelper.AssociateArtifact(projectId, buildId, jobId, artifactName, ArtifactResourceTypes.Container, fileContainerFullPath, propertiesDictionary, token);
context.Output($"Associated artifact {artifactName} ({artifact.Id}) with run #{buildId}");
PipelinesServer pipelinesHelper = new PipelinesServer(context.VssConnection);
var artifact = await pipelinesHelper.AssociateActionsStorageArtifactAsync(
definitionId,
buildId,
containerId,
artifactName,
size,
token);
context.Output($"Associated artifact {artifactName} ({artifact.ContainerId}) with run #{buildId}");
}
}
}
}

View File

@@ -79,13 +79,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
{
// Validate args.
ArgUtil.NotNull(executionContext, nameof(executionContext));
Uri proxyUrlWithCred = null;
string proxyUrlWithCredString = null;
bool useSelfSignedCACert = false;
bool useClientCert = false;
string clientCertPrivateKeyAskPassFile = null;
bool acceptUntrustedCerts = false;
executionContext.Output($"Syncing repository: {repoFullName}");
Uri repositoryUrl = new Uri($"https://github.com/{repoFullName}");
if (!repositoryUrl.IsAbsoluteUri)
@@ -114,9 +107,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
}
}
var runnerCert = executionContext.GetCertConfiguration();
acceptUntrustedCerts = runnerCert?.SkipServerCertificateValidation ?? false;
executionContext.Debug($"repository url={repositoryUrl}");
executionContext.Debug($"targetPath={targetPath}");
executionContext.Debug($"sourceBranch={sourceBranch}");
@@ -126,12 +116,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
executionContext.Debug($"checkoutNestedSubmodules={checkoutNestedSubmodules}");
executionContext.Debug($"fetchDepth={fetchDepth}");
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
executionContext.Debug($"acceptUntrustedCerts={acceptUntrustedCerts}");
#if OS_WINDOWS
bool schannelSslBackend = StringUtil.ConvertToBoolean(executionContext.GetRunnerContext("gituseschannel"));
executionContext.Debug($"schannelSslBackend={schannelSslBackend}");
#endif
// Initialize git command manager with additional environment variables.
Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -164,75 +148,8 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
// 3. git version greater than 2.14.2 if use SChannel for SSL backend (Windows only)
RequirementCheck(executionContext, gitCommandManager, gitLfsSupport);
// prepare credentail embedded urls
var runnerProxy = executionContext.GetProxyConfiguration();
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
proxyUrlWithCred = UrlUtil.GetCredentialEmbeddedUrl(new Uri(runnerProxy.ProxyAddress), runnerProxy.ProxyUsername, runnerProxy.ProxyPassword);
// uri.absoluteuri will not contains port info if the scheme is http/https and the port is 80/443
// however, git.exe always require you provide port info, if nothing passed in, it will use 1080 as default
// as result, we need prefer the uri.originalstring when it's different than uri.absoluteuri.
if (string.Equals(proxyUrlWithCred.AbsoluteUri, proxyUrlWithCred.OriginalString, StringComparison.OrdinalIgnoreCase))
{
proxyUrlWithCredString = proxyUrlWithCred.AbsoluteUri;
}
else
{
proxyUrlWithCredString = proxyUrlWithCred.OriginalString;
}
}
// prepare askpass for client cert private key, if the repository's endpoint url match the runner config url
var systemConnection = executionContext.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
if (runnerCert != null && Uri.Compare(repositoryUrl, systemConnection.Url, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0)
{
if (!string.IsNullOrEmpty(runnerCert.CACertificateFile))
{
useSelfSignedCACert = true;
}
if (!string.IsNullOrEmpty(runnerCert.ClientCertificateFile) &&
!string.IsNullOrEmpty(runnerCert.ClientCertificatePrivateKeyFile))
{
useClientCert = true;
// prepare askpass for client cert password
if (!string.IsNullOrEmpty(runnerCert.ClientCertificatePassword))
{
clientCertPrivateKeyAskPassFile = Path.Combine(executionContext.GetRunnerContext("temp"), $"{Guid.NewGuid()}.sh");
List<string> askPass = new List<string>();
askPass.Add("#!/bin/sh");
askPass.Add($"echo \"{runnerCert.ClientCertificatePassword}\"");
File.WriteAllLines(clientCertPrivateKeyAskPassFile, askPass);
#if !OS_WINDOWS
string toolPath = WhichUtil.Which("chmod", true);
string argLine = $"775 {clientCertPrivateKeyAskPassFile}";
executionContext.Command($"chmod {argLine}");
var processInvoker = new ProcessInvoker(executionContext);
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
executionContext.Output(args.Data);
}
};
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
executionContext.Output(args.Data);
}
};
string workingDirectory = executionContext.GetRunnerContext("workspace");
await processInvoker.ExecuteAsync(workingDirectory, toolPath, argLine, null, true, CancellationToken.None);
#endif
}
}
}
// Check the current contents of the root folder to see if there is already a repo
// If there is a repo, see if it matches the one we are expecting to be there based on the remote fetch url
@@ -373,13 +290,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
}
// always remove any possible left proxy setting from git config, the proxy setting may contains credential
if (await gitCommandManager.GitConfigExist(executionContext, targetPath, $"http.proxy"))
{
executionContext.Debug("Remove any proxy setting from git config.");
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.proxy", string.Empty);
}
List<string> additionalFetchArgs = new List<string>();
List<string> additionalLfsFetchArgs = new List<string>();
@@ -389,55 +299,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
additionalFetchArgs.Add($"-c http.extraheader=\"AUTHORIZATION: {GenerateBasicAuthHeader(executionContext, accessToken)}\"");
}
// Prepare proxy config for fetch.
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
executionContext.Debug($"Config proxy server '{runnerProxy.ProxyAddress}' for git fetch.");
ArgUtil.NotNullOrEmpty(proxyUrlWithCredString, nameof(proxyUrlWithCredString));
additionalFetchArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
additionalLfsFetchArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
additionalFetchArgs.Add($"-c http.sslVerify=false");
additionalLfsFetchArgs.Add($"-c http.sslVerify=false");
}
// Prepare self-signed CA cert config for fetch from server.
if (useSelfSignedCACert)
{
executionContext.Debug($"Use self-signed certificate '{runnerCert.CACertificateFile}' for git fetch.");
additionalFetchArgs.Add($"-c http.sslcainfo=\"{runnerCert.CACertificateFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcainfo=\"{runnerCert.CACertificateFile}\"");
}
// Prepare client cert config for fetch from server.
if (useClientCert)
{
executionContext.Debug($"Use client certificate '{runnerCert.ClientCertificateFile}' for git fetch.");
if (!string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
additionalFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
}
else
{
additionalFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
}
}
#if OS_WINDOWS
if (schannelSslBackend)
{
executionContext.Debug("Use SChannel SslBackend for git fetch.");
additionalFetchArgs.Add("-c http.sslbackend=\"schannel\"");
additionalLfsFetchArgs.Add("-c http.sslbackend=\"schannel\"");
}
#endif
// Prepare gitlfs url for fetch and checkout
if (gitLfsSupport)
{
@@ -539,63 +400,12 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.extraheader=\"AUTHORIZATION: {GenerateBasicAuthHeader(executionContext, accessToken)}\"");
}
// Prepare proxy config for submodule update.
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
executionContext.Debug($"Config proxy server '{runnerProxy.ProxyAddress}' for git submodule update.");
ArgUtil.NotNullOrEmpty(proxyUrlWithCredString, nameof(proxyUrlWithCredString));
additionalSubmoduleUpdateArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
additionalSubmoduleUpdateArgs.Add($"-c http.sslVerify=false");
}
// Prepare self-signed CA cert config for submodule update.
if (useSelfSignedCACert)
{
executionContext.Debug($"Use self-signed CA certificate '{runnerCert.CACertificateFile}' for git submodule update.");
string authorityUrl = repositoryUrl.AbsoluteUri.Replace(repositoryUrl.PathAndQuery, string.Empty);
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcainfo=\"{runnerCert.CACertificateFile}\"");
}
// Prepare client cert config for submodule update.
if (useClientCert)
{
executionContext.Debug($"Use client certificate '{runnerCert.ClientCertificateFile}' for git submodule update.");
string authorityUrl = repositoryUrl.AbsoluteUri.Replace(repositoryUrl.PathAndQuery, string.Empty);
if (!string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.{authorityUrl}.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.{authorityUrl}.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
}
else
{
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.{authorityUrl}.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
}
}
#if OS_WINDOWS
if (schannelSslBackend)
{
executionContext.Debug("Use SChannel SslBackend for git submodule update.");
additionalSubmoduleUpdateArgs.Add("-c http.sslbackend=\"schannel\"");
}
#endif
int exitCode_submoduleUpdate = await gitCommandManager.GitSubmoduleUpdate(executionContext, targetPath, fetchDepth, string.Join(" ", additionalSubmoduleUpdateArgs), checkoutNestedSubmodules, cancellationToken);
if (exitCode_submoduleUpdate != 0)
{
throw new InvalidOperationException($"Git submodule update failed with exit code: {exitCode_submoduleUpdate}");
}
}
if (useClientCert && !string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
executionContext.Debug("Remove git.sslkey askpass file.");
IOUtil.DeleteFile(clientCertPrivateKeyAskPassFile);
}
}
private async Task<bool> IsRepositoryOriginUrlMatch(RunnerActionPluginExecutionContext context, GitCliManager gitCommandManager, string repositoryPath, Uri expectedRepositoryOriginUrl)
@@ -637,7 +447,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
int exitCode_configUnset = await gitCommandManager.GitConfigUnset(executionContext, targetPath, configKey);
if (exitCode_configUnset != 0)
{
// if unable to use git.exe unset http.extraheader, http.proxy or core.askpass, modify git config file on disk. make sure we don't left credential.
// if unable to use git.exe unset http.extraheader or core.askpass, modify git config file on disk. make sure we don't left credential.
if (!string.IsNullOrEmpty(configValue))
{
executionContext.Warning("An unsuccessful attempt was made using git command line to remove \"http.extraheader\" from the git config. Attempting to modify the git config file directly to remove the credential.");
@@ -650,9 +460,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
string setting = $"extraheader = {configValue}";
gitConfigContent = Regex.Replace(gitConfigContent, setting, string.Empty, RegexOptions.IgnoreCase);
setting = $"proxy = {configValue}";
gitConfigContent = Regex.Replace(gitConfigContent, setting, string.Empty, RegexOptions.IgnoreCase);
setting = $"askpass = {configValue}";
gitConfigContent = Regex.Replace(gitConfigContent, setting, string.Empty, RegexOptions.IgnoreCase);

View File

@@ -65,13 +65,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
// Validate args.
ArgUtil.NotNull(executionContext, nameof(executionContext));
Dictionary<string, string> configModifications = new Dictionary<string, string>();
Uri proxyUrlWithCred = null;
string proxyUrlWithCredString = null;
bool useSelfSignedCACert = false;
bool useClientCert = false;
string clientCertPrivateKeyAskPassFile = null;
bool acceptUntrustedCerts = false;
executionContext.Output($"Syncing repository: {repoFullName}");
Uri repositoryUrl = new Uri($"https://github.com/{repoFullName}");
if (!repositoryUrl.IsAbsoluteUri)
@@ -100,9 +93,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
}
}
var runnerCert = executionContext.GetCertConfiguration();
acceptUntrustedCerts = runnerCert?.SkipServerCertificateValidation ?? false;
executionContext.Debug($"repository url={repositoryUrl}");
executionContext.Debug($"targetPath={targetPath}");
executionContext.Debug($"sourceBranch={sourceBranch}");
@@ -112,12 +102,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
executionContext.Debug($"checkoutNestedSubmodules={checkoutNestedSubmodules}");
executionContext.Debug($"fetchDepth={fetchDepth}");
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
executionContext.Debug($"acceptUntrustedCerts={acceptUntrustedCerts}");
#if OS_WINDOWS
bool schannelSslBackend = StringUtil.ConvertToBoolean(executionContext.GetRunnerContext("gituseschannel"));
executionContext.Debug($"schannelSslBackend={schannelSslBackend}");
#endif
// Initialize git command manager with additional environment variables.
Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -153,68 +137,8 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
// 3. git version greater than 2.14.2 if use SChannel for SSL backend (Windows only)
RequirementCheck(executionContext, gitCommandManager, gitLfsSupport);
// prepare credentail embedded urls
var runnerProxy = executionContext.GetProxyConfiguration();
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
proxyUrlWithCred = UrlUtil.GetCredentialEmbeddedUrl(new Uri(runnerProxy.ProxyAddress), runnerProxy.ProxyUsername, runnerProxy.ProxyPassword);
// uri.absoluteuri will not contains port info if the scheme is http/https and the port is 80/443
// however, git.exe always require you provide port info, if nothing passed in, it will use 1080 as default
// as result, we need prefer the uri.originalstring over uri.absoluteuri.
proxyUrlWithCredString = proxyUrlWithCred.OriginalString;
}
// prepare askpass for client cert private key, if the repository's endpoint url match the runner config url
var systemConnection = executionContext.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
if (runnerCert != null && Uri.Compare(repositoryUrl, systemConnection.Url, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0)
{
if (!string.IsNullOrEmpty(runnerCert.CACertificateFile))
{
useSelfSignedCACert = true;
}
if (!string.IsNullOrEmpty(runnerCert.ClientCertificateFile) &&
!string.IsNullOrEmpty(runnerCert.ClientCertificatePrivateKeyFile))
{
useClientCert = true;
// prepare askpass for client cert password
if (!string.IsNullOrEmpty(runnerCert.ClientCertificatePassword))
{
clientCertPrivateKeyAskPassFile = Path.Combine(executionContext.GetRunnerContext("temp"), $"{Guid.NewGuid()}.sh");
List<string> askPass = new List<string>();
askPass.Add("#!/bin/sh");
askPass.Add($"echo \"{runnerCert.ClientCertificatePassword}\"");
File.WriteAllLines(clientCertPrivateKeyAskPassFile, askPass);
#if !OS_WINDOWS
string toolPath = WhichUtil.Which("chmod", true);
string argLine = $"775 {clientCertPrivateKeyAskPassFile}";
executionContext.Command($"chmod {argLine}");
var processInvoker = new ProcessInvoker(executionContext);
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
executionContext.Output(args.Data);
}
};
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
executionContext.Output(args.Data);
}
};
string workingDirectory = executionContext.GetRunnerContext("workspace");
await processInvoker.ExecuteAsync(workingDirectory, toolPath, argLine, null, true, CancellationToken.None);
#endif
}
}
}
// Check the current contents of the root folder to see if there is already a repo
// If there is a repo, see if it matches the one we are expecting to be there based on the remote fetch url
@@ -355,13 +279,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.{repositoryUrl.AbsoluteUri}.extraheader", string.Empty);
}
// always remove any possible left proxy setting from git config, the proxy setting may contains credential
if (await gitCommandManager.GitConfigExist(executionContext, targetPath, $"http.proxy"))
{
executionContext.Debug("Remove any proxy setting from git config.");
await RemoveGitConfig(executionContext, gitCommandManager, targetPath, $"http.proxy", string.Empty);
}
List<string> additionalFetchArgs = new List<string>();
List<string> additionalLfsFetchArgs = new List<string>();
@@ -376,55 +293,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
throw new InvalidOperationException($"Git config failed with exit code: {exitCode_config}");
}
// Prepare proxy config for fetch.
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
executionContext.Debug($"Config proxy server '{runnerProxy.ProxyAddress}' for git fetch.");
ArgUtil.NotNullOrEmpty(proxyUrlWithCredString, nameof(proxyUrlWithCredString));
additionalFetchArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
additionalLfsFetchArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
additionalFetchArgs.Add($"-c http.sslVerify=false");
additionalLfsFetchArgs.Add($"-c http.sslVerify=false");
}
// Prepare self-signed CA cert config for fetch from server.
if (useSelfSignedCACert)
{
executionContext.Debug($"Use self-signed certificate '{runnerCert.CACertificateFile}' for git fetch.");
additionalFetchArgs.Add($"-c http.sslcainfo=\"{runnerCert.CACertificateFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcainfo=\"{runnerCert.CACertificateFile}\"");
}
// Prepare client cert config for fetch from server.
if (useClientCert)
{
executionContext.Debug($"Use client certificate '{runnerCert.ClientCertificateFile}' for git fetch.");
if (!string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
additionalFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
}
else
{
additionalFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
}
}
#if OS_WINDOWS
if (schannelSslBackend)
{
executionContext.Debug("Use SChannel SslBackend for git fetch.");
additionalFetchArgs.Add("-c http.sslbackend=\"schannel\"");
additionalLfsFetchArgs.Add("-c http.sslbackend=\"schannel\"");
}
#endif
// Prepare gitlfs url for fetch and checkout
if (gitLfsSupport)
{
@@ -514,51 +382,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
List<string> additionalSubmoduleUpdateArgs = new List<string>();
// Prepare proxy config for submodule update.
if (runnerProxy != null && !string.IsNullOrEmpty(runnerProxy.ProxyAddress) && !runnerProxy.WebProxy.IsBypassed(repositoryUrl))
{
executionContext.Debug($"Config proxy server '{runnerProxy.ProxyAddress}' for git submodule update.");
ArgUtil.NotNullOrEmpty(proxyUrlWithCredString, nameof(proxyUrlWithCredString));
additionalSubmoduleUpdateArgs.Add($"-c http.proxy=\"{proxyUrlWithCredString}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
additionalSubmoduleUpdateArgs.Add($"-c http.sslVerify=false");
}
// Prepare self-signed CA cert config for submodule update.
if (useSelfSignedCACert)
{
executionContext.Debug($"Use self-signed CA certificate '{runnerCert.CACertificateFile}' for git submodule update.");
string authorityUrl = repositoryUrl.AbsoluteUri.Replace(repositoryUrl.PathAndQuery, string.Empty);
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcainfo=\"{runnerCert.CACertificateFile}\"");
}
// Prepare client cert config for submodule update.
if (useClientCert)
{
executionContext.Debug($"Use client certificate '{runnerCert.ClientCertificateFile}' for git submodule update.");
string authorityUrl = repositoryUrl.AbsoluteUri.Replace(repositoryUrl.PathAndQuery, string.Empty);
if (!string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.{authorityUrl}.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.{authorityUrl}.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
}
else
{
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.{authorityUrl}.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
}
}
#if OS_WINDOWS
if (schannelSslBackend)
{
executionContext.Debug("Use SChannel SslBackend for git submodule update.");
additionalSubmoduleUpdateArgs.Add("-c http.sslbackend=\"schannel\"");
}
#endif
int exitCode_submoduleUpdate = await gitCommandManager.GitSubmoduleUpdate(executionContext, targetPath, fetchDepth, string.Join(" ", additionalSubmoduleUpdateArgs), checkoutNestedSubmodules, cancellationToken);
if (exitCode_submoduleUpdate != 0)
{
@@ -566,12 +389,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
}
}
if (useClientCert && !string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
executionContext.Debug("Remove git.sslkey askpass file.");
IOUtil.DeleteFile(clientCertPrivateKeyAskPassFile);
}
// Set intra-task variable for post job cleanup
executionContext.SetIntraActionState("repositoryPath", targetPath);
executionContext.SetIntraActionState("modifiedgitconfig", JsonUtility.ToString(configModifications.Keys));
@@ -592,7 +409,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
GitCliManager gitCommandManager = new GitCliManager();
await gitCommandManager.LoadGitExecutionInfo(executionContext);
executionContext.Debug("Remove any extraheader and proxy setting from git config.");
executionContext.Debug("Remove any extraheader setting from git config.");
var configKeys = JsonUtility.FromString<List<string>>(Environment.GetEnvironmentVariable("STATE_modifiedgitconfig"));
if (configKeys?.Count > 0)
{
@@ -677,7 +494,7 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
int exitCode_configUnset = await gitCommandManager.GitConfigUnset(executionContext, targetPath, configKey);
if (exitCode_configUnset != 0)
{
// if unable to use git.exe unset http.extraheader, http.proxy or core.askpass, modify git config file on disk. make sure we don't left credential.
// if unable to use git.exe unset http.extraheader or core.askpass, modify git config file on disk. make sure we don't left credential.
if (!string.IsNullOrEmpty(configValue))
{
executionContext.Warning("An unsuccessful attempt was made using git command line to remove \"http.extraheader\" from the git config. Attempting to modify the git config file directly to remove the credential.");

View File

@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
</PropertyGroup>
<ItemGroup>
@@ -19,42 +20,4 @@
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">
<DefineConstants>OS_OSX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true' AND '$(Configuration)' == 'Debug'">
<DefineConstants>OS_OSX;DEBUG;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -24,6 +24,7 @@ namespace GitHub.Runner.Sdk
{
private readonly string DebugEnvironmentalVariable = "ACTIONS_STEP_DEBUG";
private VssConnection _connection;
private RunnerWebProxy _webProxy;
private readonly object _stdoutLock = new object();
private readonly ITraceWriter _trace; // for unit tests
@@ -57,6 +58,19 @@ namespace GitHub.Runner.Sdk
}
}
[JsonIgnore]
public RunnerWebProxy WebProxy
{
get
{
if (_webProxy == null)
{
_webProxy = new RunnerWebProxy();
}
return _webProxy;
}
}
public VssConnection InitializeVssConnection()
{
var headerValues = new List<ProductInfoHeaderValue>();
@@ -69,37 +83,7 @@ namespace GitHub.Runner.Sdk
}
VssClientHttpRequestSettings.Default.UserAgent = headerValues;
#if OS_LINUX || OS_OSX
// The .NET Core 2.1 runtime switched its HTTP default from HTTP 1.1 to HTTP 2.
// This causes problems with some versions of the Curl handler.
// See GitHub issue https://github.com/dotnet/corefx/issues/32376
VssClientHttpRequestSettings.Default.UseHttp11 = true;
#endif
var certSetting = GetCertConfiguration();
if (certSetting != null)
{
if (!string.IsNullOrEmpty(certSetting.ClientCertificateArchiveFile))
{
VssClientHttpRequestSettings.Default.ClientCertificateManager = new RunnerClientCertificateManager(certSetting.ClientCertificateArchiveFile, certSetting.ClientCertificatePassword);
}
if (certSetting.SkipServerCertificateValidation)
{
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
}
var proxySetting = GetProxyConfiguration();
if (proxySetting != null)
{
if (!string.IsNullOrEmpty(proxySetting.ProxyAddress))
{
VssHttpMessageHandler.DefaultWebProxy = new RunnerWebProxyCore(proxySetting.ProxyAddress, proxySetting.ProxyUsername, proxySetting.ProxyPassword, proxySetting.ProxyBypassList);
}
}
VssHttpMessageHandler.DefaultWebProxy = this.WebProxy;
ServiceEndpoint systemConnection = this.Endpoints.FirstOrDefault(e => string.Equals(e.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
ArgUtil.NotNull(systemConnection.Url, nameof(systemConnection.Url));
@@ -228,63 +212,6 @@ namespace GitHub.Runner.Sdk
}
}
public RunnerCertificateSettings GetCertConfiguration()
{
bool skipCertValidation = StringUtil.ConvertToBoolean(GetRunnerContext("SkipCertValidation"));
string caFile = GetRunnerContext("CAInfo");
string clientCertFile = GetRunnerContext("ClientCert");
if (!string.IsNullOrEmpty(caFile) || !string.IsNullOrEmpty(clientCertFile) || skipCertValidation)
{
var certConfig = new RunnerCertificateSettings();
certConfig.SkipServerCertificateValidation = skipCertValidation;
certConfig.CACertificateFile = caFile;
if (!string.IsNullOrEmpty(clientCertFile))
{
certConfig.ClientCertificateFile = clientCertFile;
string clientCertKey = GetRunnerContext("ClientCertKey");
string clientCertArchive = GetRunnerContext("ClientCertArchive");
string clientCertPassword = GetRunnerContext("ClientCertPassword");
certConfig.ClientCertificatePrivateKeyFile = clientCertKey;
certConfig.ClientCertificateArchiveFile = clientCertArchive;
certConfig.ClientCertificatePassword = clientCertPassword;
certConfig.VssClientCertificateManager = new RunnerClientCertificateManager(clientCertArchive, clientCertPassword);
}
return certConfig;
}
else
{
return null;
}
}
public RunnerWebProxySettings GetProxyConfiguration()
{
string proxyUrl = GetRunnerContext("ProxyUrl");
if (!string.IsNullOrEmpty(proxyUrl))
{
string proxyUsername = GetRunnerContext("ProxyUsername");
string proxyPassword = GetRunnerContext("ProxyPassword");
List<string> proxyBypassHosts = StringUtil.ConvertFromJson<List<string>>(GetRunnerContext("ProxyBypassList") ?? "[]");
return new RunnerWebProxySettings()
{
ProxyAddress = proxyUrl,
ProxyUsername = proxyUsername,
ProxyPassword = proxyPassword,
ProxyBypassList = proxyBypassHosts,
WebProxy = new RunnerWebProxyCore(proxyUrl, proxyUsername, proxyPassword, proxyBypassHosts)
};
}
else
{
return null;
}
}
private string Escape(string input)
{
foreach (var mapping in _commandEscapeMappings)

View File

@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603</NoWarn>
<Version>$(Version)</Version>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
</PropertyGroup>
<ItemGroup>
@@ -24,42 +25,4 @@
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">
<DefineConstants>OS_OSX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true' AND '$(Configuration)' == 'Debug'">
<DefineConstants>OS_OSX;DEBUG;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -1,40 +0,0 @@

using System.Security.Cryptography.X509Certificates;
using GitHub.Services.Common;
namespace GitHub.Runner.Sdk
{
public class RunnerCertificateSettings
{
public bool SkipServerCertificateValidation { get; set; }
public string CACertificateFile { get; set; }
public string ClientCertificateFile { get; set; }
public string ClientCertificatePrivateKeyFile { get; set; }
public string ClientCertificateArchiveFile { get; set; }
public string ClientCertificatePassword { get; set; }
public IVssClientCertificateManager VssClientCertificateManager { get; set; }
}
public class RunnerClientCertificateManager : IVssClientCertificateManager
{
private readonly X509Certificate2Collection _clientCertificates = new X509Certificate2Collection();
public X509Certificate2Collection ClientCertificates => _clientCertificates;
public RunnerClientCertificateManager()
{
}
public RunnerClientCertificateManager(string clientCertificateArchiveFile, string clientCertificatePassword)
{
AddClientCertificate(clientCertificateArchiveFile, clientCertificatePassword);
}
public void AddClientCertificate(string clientCertificateArchiveFile, string clientCertificatePassword)
{
if (!string.IsNullOrEmpty(clientCertificateArchiveFile))
{
_clientCertificates.Add(new X509Certificate2(clientCertificateArchiveFile, clientCertificatePassword));
}
}
}
}

View File

@@ -0,0 +1,224 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
namespace GitHub.Runner.Sdk
{
public struct ByPassInfo
{
public string Host { get; set; }
public string Port { get; set; }
};
public class RunnerWebProxy : IWebProxy
{
private string _httpProxyAddress;
private string _httpProxyUsername;
private string _httpProxyPassword;
private string _httpsProxyAddress;
private string _httpsProxyUsername;
private string _httpsProxyPassword;
private readonly List<ByPassInfo> _noProxyList = new List<ByPassInfo>();
private readonly HashSet<string> _noProxyUnique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly Regex _validIpRegex = new Regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", RegexOptions.Compiled);
public string HttpProxyAddress => _httpProxyAddress;
public string HttpProxyUsername => _httpProxyUsername;
public string HttpProxyPassword => _httpProxyPassword;
public string HttpsProxyAddress => _httpsProxyAddress;
public string HttpsProxyUsername => _httpsProxyUsername;
public string HttpsProxyPassword => _httpsProxyPassword;
public List<ByPassInfo> NoProxyList => _noProxyList;
public ICredentials Credentials { get; set; }
public RunnerWebProxy()
{
Credentials = new CredentialCache();
var httpProxyAddress = Environment.GetEnvironmentVariable("http_proxy");
if (string.IsNullOrEmpty(httpProxyAddress))
{
httpProxyAddress = Environment.GetEnvironmentVariable("HTTP_PROXY");
}
httpProxyAddress = httpProxyAddress?.Trim();
var httpsProxyAddress = Environment.GetEnvironmentVariable("https_proxy");
if (string.IsNullOrEmpty(httpsProxyAddress))
{
httpsProxyAddress = Environment.GetEnvironmentVariable("HTTPS_PROXY");
}
httpsProxyAddress = httpsProxyAddress?.Trim();
var noProxyList = Environment.GetEnvironmentVariable("no_proxy");
if (string.IsNullOrEmpty(noProxyList))
{
noProxyList = Environment.GetEnvironmentVariable("NO_PROXY");
}
if (string.IsNullOrEmpty(httpProxyAddress) && string.IsNullOrEmpty(httpsProxyAddress))
{
return;
}
if (!string.IsNullOrEmpty(httpProxyAddress) && Uri.TryCreate(httpProxyAddress, UriKind.Absolute, out var proxyHttpUri))
{
_httpProxyAddress = proxyHttpUri.AbsoluteUri;
// the proxy url looks like http://[user:pass@]127.0.0.1:8888
var userInfo = Uri.UnescapeDataString(proxyHttpUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
if (userInfo.Length == 2)
{
_httpProxyUsername = userInfo[0];
_httpProxyPassword = userInfo[1];
}
else if (userInfo.Length == 1)
{
_httpProxyUsername = userInfo[0];
}
if (!string.IsNullOrEmpty(_httpProxyUsername) || !string.IsNullOrEmpty(_httpProxyPassword))
{
var credentials = new NetworkCredential(_httpProxyUsername, _httpProxyPassword);
// Replace the entry in the credential cache if it exists
(Credentials as CredentialCache).Remove(proxyHttpUri, "Basic");
(Credentials as CredentialCache).Add(proxyHttpUri, "Basic", credentials);
}
}
if (!string.IsNullOrEmpty(httpsProxyAddress) && Uri.TryCreate(httpsProxyAddress, UriKind.Absolute, out var proxyHttpsUri))
{
_httpsProxyAddress = proxyHttpsUri.AbsoluteUri;
// the proxy url looks like http://[user:pass@]127.0.0.1:8888
var userInfo = Uri.UnescapeDataString(proxyHttpsUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
if (userInfo.Length == 2)
{
_httpsProxyUsername = userInfo[0];
_httpsProxyPassword = userInfo[1];
}
else if (userInfo.Length == 1)
{
_httpsProxyUsername = userInfo[0];
}
if (!string.IsNullOrEmpty(_httpsProxyUsername) || !string.IsNullOrEmpty(_httpsProxyPassword))
{
var credentials = new NetworkCredential(_httpsProxyUsername, _httpsProxyPassword);
// Replace the entry in the credential cache if it exists
(Credentials as CredentialCache).Remove(proxyHttpsUri, "Basic");
(Credentials as CredentialCache).Add(proxyHttpsUri, "Basic", credentials);
}
}
if (!string.IsNullOrEmpty(noProxyList))
{
var noProxyListSplit = noProxyList.Split(',', StringSplitOptions.RemoveEmptyEntries);
foreach (string noProxy in noProxyListSplit)
{
var noProxyTrim = noProxy.Trim();
if (string.IsNullOrEmpty(noProxyTrim))
{
continue;
}
else if (_noProxyUnique.Add(noProxyTrim))
{
var noProxyInfo = new ByPassInfo();
var noProxyHostPort = noProxyTrim.Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
if (noProxyHostPort.Length == 1)
{
noProxyInfo.Host = noProxyHostPort[0];
}
else if (noProxyHostPort.Length == 2)
{
noProxyInfo.Host = noProxyHostPort[0];
noProxyInfo.Port = noProxyHostPort[1];
}
// We don't support IP address for no_proxy
if (_validIpRegex.IsMatch(noProxyInfo.Host))
{
continue;
}
_noProxyList.Add(noProxyInfo);
}
}
}
}
public Uri GetProxy(Uri destination)
{
if (IsBypassed(destination))
{
return null;
}
if (destination.Scheme == Uri.UriSchemeHttps)
{
return new Uri(_httpsProxyAddress);
}
else
{
return new Uri(_httpProxyAddress);
}
}
public bool IsBypassed(Uri uri)
{
if (uri.Scheme == Uri.UriSchemeHttps && string.IsNullOrEmpty(_httpsProxyAddress))
{
return true;
}
if (uri.Scheme == Uri.UriSchemeHttp && string.IsNullOrEmpty(_httpProxyAddress))
{
return true;
}
return uri.IsLoopback || IsUriInBypassList(uri);
}
private bool IsUriInBypassList(Uri input)
{
foreach (var noProxy in _noProxyList)
{
var matchHost = false;
var matchPort = false;
if (string.IsNullOrEmpty(noProxy.Port))
{
matchPort = true;
}
else
{
matchPort = string.Equals(noProxy.Port, input.Port.ToString());
}
if (noProxy.Host.StartsWith('.'))
{
matchHost = input.Host.EndsWith(noProxy.Host, StringComparison.OrdinalIgnoreCase);
}
else
{
matchHost = string.Equals(input.Host, noProxy.Host, StringComparison.OrdinalIgnoreCase) || input.Host.EndsWith($".{noProxy.Host}", StringComparison.OrdinalIgnoreCase);
}
if (matchHost && matchPort)
{
return true;
}
}
return false;
}
}
}

View File

@@ -1,104 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
namespace GitHub.Runner.Sdk
{
public class RunnerWebProxySettings
{
public string ProxyAddress { get; set; }
public string ProxyUsername { get; set; }
public string ProxyPassword { get; set; }
public List<string> ProxyBypassList { get; set; }
public IWebProxy WebProxy { get; set; }
}
public class RunnerWebProxyCore : IWebProxy
{
private string _proxyAddress;
private readonly List<Regex> _regExBypassList = new List<Regex>();
public ICredentials Credentials { get; set; }
public RunnerWebProxyCore()
{
}
public RunnerWebProxyCore(string proxyAddress, string proxyUsername, string proxyPassword, List<string> proxyBypassList)
{
Update(proxyAddress, proxyUsername, proxyPassword, proxyBypassList);
}
public void Update(string proxyAddress, string proxyUsername, string proxyPassword, List<string> proxyBypassList)
{
_proxyAddress = proxyAddress?.Trim();
if (string.IsNullOrEmpty(proxyUsername) || string.IsNullOrEmpty(proxyPassword))
{
Credentials = CredentialCache.DefaultNetworkCredentials;
}
else
{
Credentials = new NetworkCredential(proxyUsername, proxyPassword);
}
if (proxyBypassList != null)
{
foreach (string bypass in proxyBypassList)
{
if (string.IsNullOrWhiteSpace(bypass))
{
continue;
}
else
{
try
{
Regex bypassRegex = new Regex(bypass.Trim(), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.ECMAScript);
_regExBypassList.Add(bypassRegex);
}
catch (Exception)
{
// eat all exceptions
}
}
}
}
}
public Uri GetProxy(Uri destination)
{
if (IsBypassed(destination))
{
return destination;
}
else
{
return new Uri(_proxyAddress);
}
}
public bool IsBypassed(Uri uri)
{
return string.IsNullOrEmpty(_proxyAddress) || uri.IsLoopback || IsMatchInBypassList(uri);
}
private bool IsMatchInBypassList(Uri input)
{
string matchUriString = input.IsDefaultPort ?
input.Scheme + "://" + input.Host :
input.Scheme + "://" + input.Host + ":" + input.Port.ToString();
foreach (Regex r in _regExBypassList)
{
if (r.IsMatch(matchUriString))
{
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
namespace GitHub.Runner.Sdk
{
public class SdkConstants
{
public static class Variables
{
public static class Build
{
// Legacy "build" variables historically used by the runner
// DO NOT add new variables here -- instead use either the Actions or Runner namespaces
public const String BuildId = "build.buildId";
public const String BuildNumber = "build.buildNumber";
public const String ContainerId = "build.containerId";
}
}
}
}

View File

@@ -122,5 +122,10 @@ namespace GitHub.Runner.Sdk
return format;
}
}
public static string SubstringPrefix(string value, int count)
{
return value?.Substring(0, Math.Min(value.Length, count));
}
}
}

View File

@@ -14,7 +14,7 @@ namespace GitHub.Runner.Sdk
{
public static class VssUtil
{
public static void InitializeVssClientSettings(ProductInfoHeaderValue additionalUserAgent, IWebProxy proxy, IVssClientCertificateManager clientCert)
public static void InitializeVssClientSettings(ProductInfoHeaderValue additionalUserAgent, IWebProxy proxy)
{
var headerValues = new List<ProductInfoHeaderValue>();
headerValues.Add(additionalUserAgent);
@@ -26,14 +26,6 @@ namespace GitHub.Runner.Sdk
}
VssClientHttpRequestSettings.Default.UserAgent = headerValues;
VssClientHttpRequestSettings.Default.ClientCertificateManager = clientCert;
#if OS_LINUX || OS_OSX
// The .NET Core 2.1 runtime switched its HTTP default from HTTP 1.1 to HTTP 2.
// This causes problems with some versions of the Curl handler.
// See GitHub issue https://github.com/dotnet/corefx/issues/32376
VssClientHttpRequestSettings.Default.UseHttp11 = true;
#endif
VssHttpMessageHandler.DefaultWebProxy = proxy;
}
@@ -90,7 +82,7 @@ namespace GitHub.Runner.Sdk
if (serviceEndpoint.Authorization.Scheme == EndpointAuthorizationSchemes.OAuth &&
serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out accessToken))
{
credentials = new VssCredentials(null, new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
credentials = new VssCredentials(new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
}
return credentials;

View File

@@ -7,7 +7,7 @@ namespace GitHub.Runner.Sdk
{
public static class WhichUtil
{
public static string Which(string command, bool require = false, ITraceWriter trace = null)
public static string Which(string command, bool require = false, ITraceWriter trace = null, string prependPath = null)
{
ArgUtil.NotNullOrEmpty(command, nameof(command));
trace?.Info($"Which: '{command}'");
@@ -17,6 +17,10 @@ namespace GitHub.Runner.Sdk
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++)

View File

@@ -9,9 +9,8 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RunnerService</RootNamespace>
<AssemblyName>RunnerService</AssemblyName>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>FinalPublicKey.snk</AssemblyOriginatorKeyFile>
<DelaySign>true</DelaySign>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@@ -64,7 +63,6 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="FinalPublicKey.snk" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resource.resx">

View File

@@ -73,7 +73,7 @@ namespace GitHub.Runner.Worker
return false;
}
// process action command in serialize oreder.
// process action command in serialize order.
lock (_commandSerializeLock)
{
if (_stopProcessCommand)
@@ -107,26 +107,22 @@ namespace GitHub.Runner.Worker
}
else if (_commandExtensions.TryGetValue(actionCommand.Command, out IActionCommandExtension extension))
{
bool omitEcho;
if (context.EchoOnActionCommand && !extension.OmitEcho)
{
context.Output(input);
}
try
{
extension.ProcessCommand(context, input, actionCommand, out omitEcho);
extension.ProcessCommand(context, input, actionCommand);
}
catch (Exception ex)
{
omitEcho = true;
context.Output(input);
context.Error($"Unable to process command '{input}' successfully.");
var commandInformation = extension.OmitEcho ? extension.Command : input;
context.Error($"Unable to process command '{commandInformation}' successfully.");
context.Error(ex);
context.CommandResult = TaskResult.Failed;
}
if (!omitEcho)
{
context.Output(input);
context.Debug($"Processed command");
}
}
else
{
@@ -142,17 +138,19 @@ namespace GitHub.Runner.Worker
public interface IActionCommandExtension : IExtension
{
string Command { get; }
bool OmitEcho { get; }
void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho);
void ProcessCommand(IExecutionContext context, string line, ActionCommand command);
}
public sealed class InternalPluginSetRepoPathCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "internal-set-repo-path";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SetRepoPathCommandProperties.repoFullName, out string repoFullName) || string.IsNullOrEmpty(repoFullName))
{
@@ -166,8 +164,6 @@ namespace GitHub.Runner.Worker
var directoryManager = HostContext.GetService<IPipelineDirectoryManager>();
var trackingConfig = directoryManager.UpdateRepositoryDirectory(context, repoFullName, command.Data, StringUtil.ConvertToBoolean(workspaceRepo));
omitEcho = true;
}
private static class SetRepoPathCommandProperties
@@ -180,10 +176,11 @@ namespace GitHub.Runner.Worker
public sealed class SetEnvCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "set-env";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SetEnvCommandProperties.Name, out string envName) || string.IsNullOrEmpty(envName))
{
@@ -192,9 +189,7 @@ namespace GitHub.Runner.Worker
context.EnvironmentVariables[envName] = command.Data;
context.SetEnvContext(envName, command.Data);
context.Output(line);
context.Debug($"{envName}='{command.Data}'");
omitEcho = true;
}
private static class SetEnvCommandProperties
@@ -206,10 +201,11 @@ namespace GitHub.Runner.Worker
public sealed class SetOutputCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "set-output";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
{
@@ -217,9 +213,7 @@ namespace GitHub.Runner.Worker
}
context.SetOutput(outputName, command.Data, out var reference);
context.Output(line);
context.Debug($"{reference}='{command.Data}'");
omitEcho = true;
}
private static class SetOutputCommandProperties
@@ -231,10 +225,11 @@ namespace GitHub.Runner.Worker
public sealed class SaveStateCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "save-state";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
{
@@ -243,7 +238,6 @@ namespace GitHub.Runner.Worker
context.IntraActionState[stateName] = command.Data;
context.Debug($"Save intra-action state {stateName} = {command.Data}");
omitEcho = true;
}
private static class SaveStateCommandProperties
@@ -255,49 +249,53 @@ namespace GitHub.Runner.Worker
public sealed class AddMaskCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "add-mask";
public bool OmitEcho => true;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
if (string.IsNullOrWhiteSpace(command.Data))
{
context.Warning("Can't add secret mask for empty string.");
context.Warning("Can't add secret mask for empty string in ##[add-mask] command.");
}
else
{
if (context.EchoOnActionCommand)
{
context.Output($"::{Command}::***");
}
HostContext.SecretMasker.AddValue(command.Data);
Trace.Info($"Add new secret mask with length of {command.Data.Length}");
}
omitEcho = true;
}
}
public sealed class AddPathCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "add-path";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
ArgUtil.NotNullOrEmpty(command.Data, "path");
context.PrependPath.RemoveAll(x => string.Equals(x, command.Data, StringComparison.CurrentCulture));
context.PrependPath.Add(command.Data);
omitEcho = false;
}
}
public sealed class AddMatcherCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "add-matcher";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
omitEcho = false;
var file = command.Data;
// File is required
@@ -339,26 +337,26 @@ namespace GitHub.Runner.Worker
public sealed class RemoveMatcherCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "remove-matcher";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
omitEcho = false;
command.Properties.TryGetValue(RemoveMatcherCommandProperties.Owner, out string owner);
var file = command.Data;
// Owner and file are mutually exclusive
if (!string.IsNullOrEmpty(owner) && !string.IsNullOrEmpty(file))
{
context.Warning("Either specify a matcher owner name or a file path. Both values cannot be set.");
context.Warning("Either specify an owner name or a file path in ##[remove-matcher] command. Both values cannot be set.");
return;
}
// Owner or file is required
if (string.IsNullOrEmpty(owner) && string.IsNullOrEmpty(file))
{
context.Warning("Either a matcher owner name or a file path must be specified.");
context.Warning("Either an owner name or a file path must be specified in ##[remove-matcher] command.");
return;
}
@@ -407,12 +405,12 @@ namespace GitHub.Runner.Worker
public sealed class DebugCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "debug";
public bool OmitEcho => true;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command)
{
omitEcho = true;
context.Debug(command.Data);
}
}
@@ -435,12 +433,12 @@ namespace GitHub.Runner.Worker
{
public abstract IssueType Type { get; }
public abstract string Command { get; }
public bool OmitEcho => true;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command)
{
omitEcho = true;
command.Properties.TryGetValue(IssueCommandProperties.File, out string file);
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
command.Properties.TryGetValue(IssueCommandProperties.Column, out string column);
@@ -515,13 +513,41 @@ namespace GitHub.Runner.Worker
public abstract class GroupingCommandExtension : RunnerService, IActionCommandExtension
{
public abstract string Command { get; }
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, out bool omitEcho)
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
var data = this is GroupCommandExtension ? command.Data : string.Empty;
context.Output($"##[{Command}]{data}");
omitEcho = true;
}
}
public sealed class EchoCommandExtension : RunnerService, IActionCommandExtension
{
public string Command => "echo";
public bool OmitEcho => false;
public Type ExtensionType => typeof(IActionCommandExtension);
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
{
ArgUtil.NotNullOrEmpty(command.Data, "value");
switch (command.Data.Trim().ToUpperInvariant())
{
case "ON":
context.EchoOnActionCommand = true;
context.Debug("Setting echo command value to 'on'");
break;
case "OFF":
context.EchoOnActionCommand = false;
context.Debug("Setting echo command value to 'off'");
break;
default:
throw new Exception($"Invalid echo command value. Possible values can be: 'on', 'off'. Current value is: '{command.Data}'.");
}
}
}
}

View File

@@ -58,6 +58,9 @@ namespace GitHub.Runner.Worker
executionContext.Warning("The 'PREVIEW_ACTION_TOKEN' secret is depreciated. Please remove it from the repository's secrets");
}
// Clear the cache (local runner)
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
foreach (var action in actions)
{
if (action.Reference.Type == Pipelines.ActionSourceType.ContainerRegistry)
@@ -445,7 +448,7 @@ namespace GitHub.Runner.Worker
}
else
{
// make sure we get an clean folder ready to use.
// make sure we get a clean folder ready to use.
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
Directory.CreateDirectory(destDirectory);
executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'");

View File

@@ -116,7 +116,7 @@ namespace GitHub.Runner.Worker
if (actionDefinition.Execution == null)
{
executionContext.Debug($"Loaded action.yml file: {StringUtil.ConvertToJson(actionDefinition)}");
throw new ArgumentException($"Top level 'run:' section is required for {manifestFile}");
throw new ArgumentException($"Top level 'runs:' section is required for {manifestFile}");
}
else
{

View File

@@ -94,7 +94,21 @@ namespace GitHub.Runner.Worker
{
postDisplayName = $"Post {this.DisplayName}";
}
ExecutionContext.RegisterPostJobAction(postDisplayName, handlerData.CleanupCondition, Action);
var repositoryReference = Action.Reference as RepositoryPathReference;
var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}";
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
$"{repositoryReference.Name}{pathString}@{repositoryReference.Ref}";
ExecutionContext.Debug($"Register post job cleanup for action: {repoString}");
var actionRunner = HostContext.CreateService<IActionRunner>();
actionRunner.Action = Action;
actionRunner.Stage = ActionRunStage.Post;
actionRunner.Condition = handlerData.CleanupCondition;
actionRunner.DisplayName = postDisplayName;
ExecutionContext.RegisterPostJobStep($"{actionRunner.Action.Name}_post", actionRunner);
}
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();

View File

@@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.IO;
using GitHub.Runner.Common.Util;
using Pipelines = GitHub.DistributedTask.Pipelines;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker.Container
{
@@ -19,7 +19,6 @@ namespace GitHub.Runner.Worker.Container
public ContainerInfo()
{
}
public ContainerInfo(IHostContext hostContext, Pipelines.JobContainer container, bool isJobContainer = true, string networkAlias = null)

View File

@@ -307,9 +307,10 @@ namespace GitHub.Runner.Worker.Container
}
};
#if OS_WINDOWS || OS_OSX
throw new NotSupportedException($"Container operation is only supported on Linux");
#else
if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
throw new NotSupportedException("Container operations are only supported on Linux runners");
}
return await processInvoker.ExecuteAsync(
workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Work),
fileName: DockerPath,
@@ -318,7 +319,6 @@ namespace GitHub.Runner.Worker.Container
requireExitCodeZero: false,
outputEncoding: null,
cancellationToken: CancellationToken.None);
#endif
}
public async Task<List<string>> DockerInspect(IExecutionContext context, string dockerObject, string options)
@@ -347,9 +347,10 @@ namespace GitHub.Runner.Worker.Container
processInvoker.ErrorDataReceived += stderrDataReceived;
#if OS_WINDOWS || OS_OSX
throw new NotSupportedException($"Container operation is only supported on Linux");
#else
if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
throw new NotSupportedException("Container operations are only supported on Linux runners");
}
return await processInvoker.ExecuteAsync(
workingDirectory: context.GetGitHubContext("workspace"),
fileName: DockerPath,
@@ -359,7 +360,6 @@ namespace GitHub.Runner.Worker.Container
outputEncoding: null,
killProcessOnCancel: false,
cancellationToken: cancellationToken);
#endif
}
private async Task<int> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options, string workingDirectory, CancellationToken cancellationToken = default(CancellationToken))
@@ -378,9 +378,10 @@ namespace GitHub.Runner.Worker.Container
context.Output(message.Data);
};
#if OS_WINDOWS || OS_OSX
throw new NotSupportedException($"Container operation is only supported on Linux");
#else
if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
throw new NotSupportedException("Container operations are only supported on Linux runners");
}
return await processInvoker.ExecuteAsync(
workingDirectory: workingDirectory ?? context.GetGitHubContext("workspace"),
fileName: DockerPath,
@@ -391,7 +392,6 @@ namespace GitHub.Runner.Worker.Container
killProcessOnCancel: false,
redirectStandardIn: null,
cancellationToken: cancellationToken);
#endif
}
private async Task<List<string>> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options)

View File

@@ -11,6 +11,7 @@ using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.DistributedTask.Pipelines.ContextData;
using Microsoft.Win32;
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
namespace GitHub.Runner.Worker
{
@@ -34,10 +35,22 @@ namespace GitHub.Runner.Worker
public async Task StartContainersAsync(IExecutionContext executionContext, object data)
{
Trace.Entering();
if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
throw new NotSupportedException("Container operations are only supported on Linux runners");
}
ArgUtil.NotNull(executionContext, nameof(executionContext));
List<ContainerInfo> containers = data as List<ContainerInfo>;
ArgUtil.NotNull(containers, nameof(containers));
var postJobStep = new JobExtensionRunner(runAsync: this.StopContainersAsync,
condition: $"{PipelineTemplateConstants.Always}()",
displayName: "Stop containers",
data: data);
executionContext.Debug($"Register post job cleanup for stopping/deleting containers.");
executionContext.RegisterPostJobStep(nameof(StopContainersAsync), postJobStep);
// Check whether we are inside a container.
// Our container feature requires to map working directory from host to the container.
// If we are already inside a container, we will not able to find out the real working direcotry path on the host.
@@ -48,9 +61,6 @@ namespace GitHub.Runner.Worker
{
throw new NotSupportedException("Container feature is not supported when runner is already running inside container.");
}
#elif OS_RHEL6
// Red Hat and CentOS 6 do not support the container feature
throw new NotSupportedException("Runner does not support the container feature on Red Hat Enterprise Linux 6 or CentOS 6.");
#else
var initProcessCgroup = File.ReadLines("/proc/1/cgroup");
if (initProcessCgroup.Any(x => x.IndexOf(":/docker/", StringComparison.OrdinalIgnoreCase) >= 0))
@@ -119,7 +129,7 @@ namespace GitHub.Runner.Worker
executionContext.Warning($"Delete stale container networks failed, docker network prune fail with exit code {networkPruneExitCode}");
}
// Create local docker network for this job to avoid port conflict when multiple agents run on same machine.
// Create local docker network for this job to avoid port conflict when multiple runners run on same machine.
// All containers within a job join the same network
var containerNetwork = $"github_network_{Guid.NewGuid().ToString("N")}";
await CreateContainerNetworkAsync(executionContext, containerNetwork);

View File

@@ -15,7 +15,7 @@ namespace GitHub.Runner.Worker
[ServiceLocator(Default = typeof(DiagnosticLogManager))]
public interface IDiagnosticLogManager : IRunnerService
{
Task UploadDiagnosticLogsAsync(IExecutionContext executionContext,
void UploadDiagnosticLogs(IExecutionContext executionContext,
IExecutionContext parentContext,
Pipelines.AgentJobRequestMessage message,
DateTime jobStartTimeUtc);
@@ -31,7 +31,7 @@ namespace GitHub.Runner.Worker
public sealed class DiagnosticLogManager : RunnerService, IDiagnosticLogManager
{
private static string DateTimeFormat = "yyyyMMdd-HHmmss";
public async Task UploadDiagnosticLogsAsync(IExecutionContext executionContext,
public void UploadDiagnosticLogs(IExecutionContext executionContext,
IExecutionContext parentContext,
Pipelines.AgentJobRequestMessage message,
DateTime jobStartTimeUtc)

View File

@@ -12,14 +12,14 @@ using GitHub.Services.WebApi;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using Newtonsoft.Json;
using System.Text;
using System.Collections;
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker
{
@@ -41,7 +41,6 @@ namespace GitHub.Runner.Worker
TaskResult? CommandResult { get; set; }
CancellationToken CancellationToken { get; }
List<ServiceEndpoint> Endpoints { get; }
List<SecureFile> SecureFiles { get; }
PlanFeatures Features { get; }
Variables Variables { get; }
@@ -62,6 +61,8 @@ namespace GitHub.Runner.Worker
// Only job level ExecutionContext has PostJobSteps
Stack<IStep> PostJobSteps { get; }
bool EchoOnActionCommand { get; set; }
// Initialize
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
void CancelToken();
@@ -96,7 +97,7 @@ namespace GitHub.Runner.Worker
// others
void ForceTaskComplete();
void RegisterPostJobAction(string displayName, string condition, Pipelines.ActionStep action);
void RegisterPostJobStep(string refName, IStep step);
}
public sealed class ExecutionContext : RunnerService, IExecutionContext
@@ -134,7 +135,6 @@ namespace GitHub.Runner.Worker
public Task ForceCompleted => _forceCompleted.Task;
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
public List<ServiceEndpoint> Endpoints { get; private set; }
public List<SecureFile> SecureFiles { get; private set; }
public Variables Variables { get; private set; }
public Dictionary<string, string> IntraActionState { get; private set; }
public HashSet<string> OutputVariables => _outputvariables;
@@ -153,6 +153,8 @@ namespace GitHub.Runner.Worker
// Only job level ExecutionContext has PostJobSteps
public Stack<IStep> PostJobSteps { get; private set; }
public bool EchoOnActionCommand { get; set; }
public TaskResult? Result
{
@@ -236,27 +238,10 @@ namespace GitHub.Runner.Worker
});
}
public void RegisterPostJobAction(string displayName, string condition, Pipelines.ActionStep action)
public void RegisterPostJobStep(string refName, IStep step)
{
if (action.Reference.Type != ActionSourceType.Repository)
{
throw new NotSupportedException("Only action that has `action.yml` can define post job execution.");
}
var repositoryReference = action.Reference as RepositoryPathReference;
var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}";
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
$"{repositoryReference.Name}{pathString}@{repositoryReference.Ref}";
this.Debug($"Register post job cleanup for action: {repoString}");
var actionRunner = HostContext.CreateService<IActionRunner>();
actionRunner.Action = action;
actionRunner.Stage = ActionRunStage.Post;
actionRunner.Condition = condition;
actionRunner.DisplayName = displayName;
actionRunner.ExecutionContext = Root.CreatePostChild(displayName, $"{actionRunner.Action.Name}_post", IntraActionState);
Root.PostJobSteps.Push(actionRunner);
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, refName, IntraActionState);
Root.PostJobSteps.Push(step);
}
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null)
@@ -270,7 +255,6 @@ namespace GitHub.Runner.Worker
child.Features = Features;
child.Variables = Variables;
child.Endpoints = Endpoints;
child.SecureFiles = SecureFiles;
if (intraActionState == null)
{
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -292,6 +276,7 @@ namespace GitHub.Runner.Worker
child.PrependPath = PrependPath;
child.Container = Container;
child.ServiceContainers = ServiceContainers;
child.EchoOnActionCommand = EchoOnActionCommand;
if (recordOrder != null)
{
@@ -357,6 +342,12 @@ namespace GitHub.Runner.Worker
_logger.End();
// update context
if (!string.IsNullOrEmpty(ContextName))
{
StepsContext.SetConclusion(ScopeName, ContextName, Result.Value.ToContextData());
}
return Result.Value;
}
@@ -561,9 +552,6 @@ namespace GitHub.Runner.Worker
// Endpoints
Endpoints = message.Resources.Endpoints;
// SecureFiles
SecureFiles = message.Resources.SecureFiles;
// Variables
Variables = new Variables(HostContext, message.Variables);
@@ -628,67 +616,6 @@ namespace GitHub.Runner.Worker
// PostJobSteps for job ExecutionContext
PostJobSteps = new Stack<IStep>();
// Proxy variables
// var agentWebProxy = HostContext.GetService<IRunnerWebProxy>();
// if (!string.IsNullOrEmpty(agentWebProxy.ProxyAddress))
// {
// SetRunnerContext("proxyurl", agentWebProxy.ProxyAddress);
// if (!string.IsNullOrEmpty(agentWebProxy.ProxyUsername))
// {
// SetRunnerContext("proxyusername", agentWebProxy.ProxyUsername);
// }
// if (!string.IsNullOrEmpty(agentWebProxy.ProxyPassword))
// {
// HostContext.SecretMasker.AddValue(agentWebProxy.ProxyPassword);
// SetRunnerContext("proxypassword", agentWebProxy.ProxyPassword);
// }
// if (agentWebProxy.ProxyBypassList.Count > 0)
// {
// SetRunnerContext("proxybypasslist", JsonUtility.ToString(agentWebProxy.ProxyBypassList));
// }
// }
// // Certificate variables
// var agentCert = HostContext.GetService<IRunnerCertificateManager>();
// if (agentCert.SkipServerCertificateValidation)
// {
// SetRunnerContext("sslskipcertvalidation", bool.TrueString);
// }
// if (!string.IsNullOrEmpty(agentCert.CACertificateFile))
// {
// SetRunnerContext("sslcainfo", agentCert.CACertificateFile);
// }
// if (!string.IsNullOrEmpty(agentCert.ClientCertificateFile) &&
// !string.IsNullOrEmpty(agentCert.ClientCertificatePrivateKeyFile) &&
// !string.IsNullOrEmpty(agentCert.ClientCertificateArchiveFile))
// {
// SetRunnerContext("clientcertfile", agentCert.ClientCertificateFile);
// SetRunnerContext("clientcertprivatekey", agentCert.ClientCertificatePrivateKeyFile);
// SetRunnerContext("clientcertarchive", agentCert.ClientCertificateArchiveFile);
// if (!string.IsNullOrEmpty(agentCert.ClientCertificatePassword))
// {
// HostContext.SecretMasker.AddValue(agentCert.ClientCertificatePassword);
// SetRunnerContext("clientcertpassword", agentCert.ClientCertificatePassword);
// }
// }
// // Runtime option variables
// var runtimeOptions = HostContext.GetService<IConfigurationStore>().GetRunnerRuntimeOptions();
// if (runtimeOptions != null)
// {
// #if OS_WINDOWS
// if (runtimeOptions.GitUseSecureChannel)
// {
// SetRunnerContext("gituseschannel", runtimeOptions.GitUseSecureChannel.ToString());
// }
// #endif
// }
// Job timeline record.
InitializeTimelineRecord(
@@ -704,6 +631,9 @@ namespace GitHub.Runner.Worker
_logger = HostContext.CreateService<IPagingLogger>();
_logger.Setup(_mainTimelineId, _record.Id);
// Initialize 'echo on action command success' property, default to false, unless Step_Debug is set
EchoOnActionCommand = Variables.Step_Debug ?? false;
// Verbosity (from GitHub.Step_Debug).
WriteDebug = Variables.Step_Debug ?? false;
@@ -883,33 +813,6 @@ namespace GitHub.Runner.Worker
{
this.Warning(string.Format("The job is currently being throttled by the server. You may experience delays in console line output, job status reporting, and action log uploads."));
if (!String.IsNullOrEmpty(this.Variables.System_TFCollectionUrl))
{
// Construct a URL to the resource utilization page, to aid the user debug throttling issues
UriBuilder uriBuilder = new UriBuilder(Variables.System_TFCollectionUrl);
NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query);
DateTime endTime = DateTime.UtcNow;
string queryDate = endTime.AddHours(-1).ToString("s") + "," + endTime.ToString("s");
uriBuilder.Path += (Variables.System_TFCollectionUrl.EndsWith("/") ? "" : "/") + "_usersSettings/usage";
query["tab"] = "pipelines";
query["queryDate"] = queryDate;
// Global RU link
uriBuilder.Query = query.ToString();
string global = $"Link to resource utilization page (global 1-hour view): {uriBuilder.ToString()}.";
if (!String.IsNullOrEmpty(this.Variables.Build_DefinitionName))
{
query["keywords"] = this.Variables.Build_Number;
query["definition"] = this.Variables.Build_DefinitionName;
}
// RU link scoped for the build/release
uriBuilder.Query = query.ToString();
this.Warning($"{global}\nLink to resource utilization page (1-hour view by pipeline): {uriBuilder.ToString()}.");
}
_throttlingReported = true;
}
}

View File

@@ -16,6 +16,8 @@ namespace GitHub.Runner.Worker
"head_ref",
"ref",
"repository",
"run_id",
"run_number",
"sha",
"workflow",
"workspace",

View File

@@ -22,7 +22,9 @@ namespace GitHub.Runner.Worker.Handlers
{
public ContainerActionExecutionData Data { get; set; }
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously (method has async logic on only certain platforms)
public async Task RunAsync(ActionRunStage stage)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
// Validate args.
Trace.Entering();
@@ -187,8 +189,8 @@ namespace GitHub.Runner.Worker.Handlers
container.ContainerEnvironmentVariables[variable.Key] = container.TranslateToContainerPath(variable.Value);
}
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager))
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager, container))
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager, container))
{
var runExitCode = await dockerManger.DockerRun(ExecutionContext, container, stdoutManager.OnDataReceived, stderrManager.OnDataReceived);
if (runExitCode != 0)

View File

@@ -62,7 +62,7 @@ namespace GitHub.Runner.Worker.Handlers
}
else if (data.ExecutionType == ActionExecutionType.Plugin)
{
// Agent plugin
// Runner plugin
handler = HostContext.CreateService<IRunnerPluginHandler>();
(handler as IRunnerPluginHandler).Data = data as PluginActionExecutionData;
}

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container;
using DTWebApi = GitHub.DistributedTask.WebApi;
namespace GitHub.Runner.Worker.Handlers
@@ -17,45 +18,49 @@ namespace GitHub.Runner.Worker.Handlers
private const string _timeoutKey = "GITHUB_ACTIONS_RUNNER_ISSUE_MATCHER_TIMEOUT";
private static readonly Regex _colorCodeRegex = new Regex(@"\x0033\[[0-9;]*m?", RegexOptions.Compiled | RegexOptions.CultureInvariant);
private readonly IActionCommandManager _commandManager;
private readonly ContainerInfo _container;
private readonly IExecutionContext _executionContext;
private readonly int _failsafe = 50;
private readonly object _matchersLock = new object();
private readonly TimeSpan _timeout;
private IssueMatcher[] _matchers = Array.Empty<IssueMatcher>();
// Mapping that indicates whether a directory belongs to the workflow repository
private readonly Dictionary<string, string> _directoryMap = new Dictionary<string, string>();
public OutputManager(IExecutionContext executionContext, IActionCommandManager commandManager)
public OutputManager(IExecutionContext executionContext, IActionCommandManager commandManager, ContainerInfo container = null)
{
//executionContext.Debug("ENTERING OutputManager ctor");
_executionContext = executionContext;
_commandManager = commandManager;
_container = container ?? executionContext.Container;
// Recursion failsafe (test override)
var failsafeString = Environment.GetEnvironmentVariable("RUNNER_TEST_GET_REPOSITORY_PATH_FAILSAFE");
if (!string.IsNullOrEmpty(failsafeString))
{
_failsafe = int.Parse(failsafeString, NumberStyles.None);
}
//_executionContext.Debug("OutputManager ctor - determine timeout from variable");
// Determine the timeout
var timeoutStr = _executionContext.Variables.Get(_timeoutKey);
if (string.IsNullOrEmpty(timeoutStr) ||
!TimeSpan.TryParse(timeoutStr, CultureInfo.InvariantCulture, out _timeout) ||
_timeout <= TimeSpan.Zero)
{
//_executionContext.Debug("OutputManager ctor - determine timeout from env var");
timeoutStr = Environment.GetEnvironmentVariable(_timeoutKey);
if (string.IsNullOrEmpty(timeoutStr) ||
!TimeSpan.TryParse(timeoutStr, CultureInfo.InvariantCulture, out _timeout) ||
_timeout <= TimeSpan.Zero)
{
//_executionContext.Debug("OutputManager ctor - set timeout to default");
_timeout = TimeSpan.FromSeconds(1);
}
}
//_executionContext.Debug("OutputManager ctor - adding matchers");
// Lock
lock (_matchersLock)
{
//_executionContext.Debug("OutputManager ctor - adding OnMatcherChanged");
_executionContext.Add(OnMatcherChanged);
//_executionContext.Debug("OutputManager ctor - getting matchers");
_matchers = _executionContext.GetMatchers().Select(x => new IssueMatcher(x, _timeout)).ToArray();
}
//_executionContext.Debug("LEAVING OutputManager ctor");
}
public void Dispose()
@@ -71,7 +76,6 @@ namespace GitHub.Runner.Worker.Handlers
public void OnDataReceived(object sender, ProcessDataReceivedEventArgs e)
{
//_executionContext.Debug("ENTERING OutputManager OnDataReceived");
var line = e.Data;
// ## commands
@@ -82,7 +86,6 @@ namespace GitHub.Runner.Worker.Handlers
// The logging queues and command handlers are thread-safe.
if (_commandManager.TryProcessCommand(_executionContext, line))
{
//_executionContext.Debug("LEAVING OutputManager OnDataReceived - command processed");
return;
}
}
@@ -142,7 +145,6 @@ namespace GitHub.Runner.Worker.Handlers
// Log issue
_executionContext.AddIssue(issue, stripped);
//_executionContext.Debug("LEAVING OutputManager OnDataReceived - issue logged");
return;
}
}
@@ -151,7 +153,6 @@ namespace GitHub.Runner.Worker.Handlers
// Regular output
_executionContext.Output(line);
//_executionContext.Debug("LEAVING OutputManager OnDataReceived");
}
private void OnMatcherChanged(object sender, MatcherChangedEventArgs e)
@@ -259,49 +260,56 @@ namespace GitHub.Runner.Worker.Handlers
if (!string.IsNullOrWhiteSpace(match.File))
{
var file = match.File;
var translate = _container != null;
// Root using fromPath
if (!string.IsNullOrWhiteSpace(match.FromPath) && !Path.IsPathRooted(file))
if (!string.IsNullOrWhiteSpace(match.FromPath) && !Path.IsPathFullyQualified(file))
{
file = Path.Combine(match.FromPath, file);
var fromDirectory = Path.GetDirectoryName(match.FromPath);
if (!string.IsNullOrWhiteSpace(fromDirectory))
{
file = Path.Combine(fromDirectory, file);
}
}
// Root using system.defaultWorkingDirectory
if (!Path.IsPathRooted(file))
// Root using workspace
if (!Path.IsPathFullyQualified(file))
{
var githubContext = _executionContext.ExpressionValues["github"] as GitHubContext;
ArgUtil.NotNull(githubContext, nameof(githubContext));
var workspace = githubContext["workspace"].ToString();
var workspace = _executionContext.GetGitHubContext("workspace");
ArgUtil.NotNullOrEmpty(workspace, "workspace");
file = Path.Combine(workspace, file);
translate = false;
}
// Normalize slashes
file = file.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
// Remove relative pathing and normalize slashes
file = Path.GetFullPath(file);
// File exists
// Translate to host
if (translate)
{
file = _container.TranslateToHostPath(file);
file = Path.GetFullPath(file);
}
// Check whether the file exists
if (File.Exists(file))
{
// Repository path
var repositoryPath = _executionContext.GetGitHubContext("workspace");
ArgUtil.NotNullOrEmpty(repositoryPath, nameof(repositoryPath));
// Normalize slashes
repositoryPath = repositoryPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
if (!file.StartsWith(repositoryPath, IOUtil.FilePathStringComparison))
// Check whether the file is under the workflow repository
var repositoryPath = GetRepositoryPath(file);
if (!string.IsNullOrEmpty(repositoryPath))
{
// File is not under repo
_executionContext.Debug($"Dropping file value '{file}'. Path is not under the repo.");
// Get the relative file path
var relativePath = file.Substring(repositoryPath.Length).TrimStart(Path.DirectorySeparatorChar);
// Prefer `/` on all platforms
issue.Data["file"] = relativePath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
else
{
// prefer `/` on all platforms
issue.Data["file"] = file.Substring(repositoryPath.Length).TrimStart(Path.DirectorySeparatorChar).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
_executionContext.Debug($"Dropping file value '{file}'. Path is not under the workflow repo.");
}
}
// File does not exist
else
{
_executionContext.Debug($"Dropping file value '{file}'. Path does not exist");
@@ -315,5 +323,60 @@ namespace GitHub.Runner.Worker.Handlers
return issue;
}
private string GetRepositoryPath(string filePath, int recursion = 0)
{
// Prevent the cache from growing too much
if (_directoryMap.Count > 100)
{
_directoryMap.Clear();
}
// Empty directory means we hit the root of the drive
var directoryPath = Path.GetDirectoryName(filePath);
if (string.IsNullOrEmpty(directoryPath) || recursion > _failsafe)
{
return null;
}
// Check the cache
if (_directoryMap.TryGetValue(directoryPath, out string repositoryPath))
{
return repositoryPath;
}
try
{
// Check if .git/config exists
var gitConfigPath = Path.Combine(directoryPath, ".git", "config");
if (File.Exists(gitConfigPath))
{
// Check if the config contains the workflow repository url
var qualifiedRepository = _executionContext.GetGitHubContext("repository");
var configMatch = $"url = https://github.com/{qualifiedRepository}";
var content = File.ReadAllText(gitConfigPath);
foreach (var line in content.Split("\n").Select(x => x.Trim()))
{
if (String.Equals(line, configMatch, StringComparison.OrdinalIgnoreCase))
{
repositoryPath = directoryPath;
break;
}
}
}
else
{
// Recursive call
repositoryPath = GetRepositoryPath(directoryPath, recursion + 1);
}
}
catch (Exception ex)
{
_executionContext.Debug($"Error when attempting to determine whether the path '{filePath}' is under the workflow repository: {ex.Message}");
}
_directoryMap[directoryPath] = repositoryPath;
return repositoryPath;
}
}
}

View File

@@ -2,6 +2,7 @@
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
@@ -56,20 +57,27 @@ namespace GitHub.Runner.Worker.Handlers
string shellCommand;
string shellCommandPath = null;
bool validateShellOnHost = !(StepHost is ContainerStepHost);
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
Inputs.TryGetValue("shell", out var shell);
if (string.IsNullOrEmpty(shell))
{
#if OS_WINDOWS
shellCommand = "cmd";
shellCommand = "pwsh";
if(validateShellOnHost)
{
shellCommandPath = System.Environment.GetEnvironmentVariable("ComSpec");
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
if (string.IsNullOrEmpty(shellCommandPath))
{
shellCommand = "powershell";
Trace.Info($"Defaulting to {shellCommand}");
shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
}
}
#else
shellCommand = "sh";
if (validateShellOnHost)
{
shellCommandPath = WhichUtil.Which("bash") ?? WhichUtil.Which("sh", true, Trace);
shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
}
#endif
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
@@ -80,7 +88,7 @@ namespace GitHub.Runner.Worker.Handlers
shellCommand = parsed.shellCommand;
if (validateShellOnHost)
{
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace);
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath);
}
argFormat = $"{parsed.shellArgs}".TrimStart();
@@ -138,17 +146,24 @@ namespace GitHub.Runner.Worker.Handlers
Inputs.TryGetValue("shell", out var shell);
var isContainerStepHost = StepHost is ContainerStepHost;
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
string commandPath, argFormat, shellCommand;
// Set up default command and arguments
if (string.IsNullOrEmpty(shell))
{
#if OS_WINDOWS
shellCommand = "cmd";
commandPath = System.Environment.GetEnvironmentVariable("ComSpec");
ArgUtil.NotNullOrEmpty(commandPath, "%ComSpec%");
shellCommand = "pwsh";
commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
if (string.IsNullOrEmpty(commandPath))
{
shellCommand = "powershell";
Trace.Info($"Defaulting to {shellCommand}");
commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
}
ArgUtil.NotNullOrEmpty(commandPath, "Default Shell");
#else
shellCommand = "sh";
commandPath = WhichUtil.Which("bash", false, Trace) ?? WhichUtil.Which("sh", true, Trace);
commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
#endif
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
}
@@ -157,7 +172,7 @@ namespace GitHub.Runner.Worker.Handlers
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
shellCommand = parsed.shellCommand;
// For non-ContainerStepHost, the command must be located on the host by Which
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace);
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
argFormat = $"{parsed.shellArgs}".TrimStart();
if (string.IsNullOrEmpty(argFormat))
{

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