Compare commits

...

38 Commits

Author SHA1 Message Date
TingluoHuang
eed84f6cf3 c 2020-06-07 22:48:57 -04:00
Nick Fields
f994ae0542 Reduce input validation warnings (#506)
* Only raise a single warning for unexpected inputs

* Update invalid input test to raise single warning
2020-06-05 23:09:14 -04:00
Tingluo Huang
3c5aef791c Fix null ref exception in SecretMasker caused by hashfiles timeout. (#516) 2020-06-05 23:02:10 -04:00
Tingluo Huang
c4626d0c3a Remove SPS/Token migration code. Remove GHES url manipulate code. (#513)
* Remove SPS/Token migration code. Remove GHES url manipulate code.

* feedback.
2020-06-03 23:24:53 -04:00
eric sciple
416a7ac4b8 prepare to switch to service resolves archive download info (#508) 2020-06-02 17:21:50 -04:00
TingluoHuang
11435857e4 prepare 2.263.0 runner release. 2020-05-21 15:49:10 -04:00
Tingluo Huang
6f260012a3 Fix inputs validation warning, fix post step display name, fix worker crash due to error in step.env (#490) 2020-05-21 11:09:50 -04:00
eric sciple
4fc87ddfc6 fix problem matcher for GHES (#488) 2020-05-19 16:15:03 -04:00
eric sciple
b45c1b9440 switch GITHUB_URL to GITHUB_SERVER_URL (#482) 2020-05-18 13:02:30 -04:00
Quan TRAN
73307c0a30 jq returns "null" if the field does not exist (#478) 2020-05-14 11:09:20 -04:00
Tingluo Huang
cd8e4ddba1 Validate inputs only for repo action, no warning for small delay. (#476)
* validate inputs only for repo action, no warning for small delay.

* l0
2020-05-12 16:09:13 -04:00
Tingluo Huang
abf59bdcb6 Fix configure as service with runner name has space. (#474) 2020-05-12 16:08:50 -04:00
Brian Cristante
09cf59c1e0 Use an env var to point to an Actions Service dev instance (#468)
* Use an environment variable for the testing backdoor

* Make the env var a boolean

We'll still have to pass the URL on the command line

* Empty commit to rerun CI
2020-05-11 17:14:02 -04:00
TingluoHuang
7a65236022 prepare 2.262.0 runner release. 2020-05-11 15:05:59 -04:00
David Kale
462b5117c8 docker build using -f instead of implied default (#471)
* pass -f to docker build

* Wrong place

* build path

* Also pass docker context path

* Tidy up format

* PR Feedback
2020-05-11 13:57:31 -04:00
Tingluo Huang
6922f3cb86 sps/token migration tweak, ActionResult casing. (#462) 2020-05-11 12:36:35 -04:00
Tingluo Huang
911135e66c add help info for '--labels' (#472) 2020-05-11 12:36:16 -04:00
PJ Quirk
01c9a8a8af Remove community actions munging and add dotcom fallback for downloading actions (#469)
* Fallback to dotcom rather than munged community actions

* Encapsulate the link and the auth details

* Rename the method to be clearer

* Remove BOM
2020-05-11 12:23:02 -04:00
eric sciple
33d2d2c328 update checkout@v1 for GHES (#470) 2020-05-11 11:41:16 -04:00
Justin Hutchings
a246b3b29d Add CodeQL Analysis workflow (#459)
* Add CodeQL Analysis workflow

* Fix path

* Add manual build step

Import manual build step from build.yml
2020-05-07 11:17:34 -04:00
Brian Cristante
c7768d4a7b Adapt create-latest-svc.sh to work with GHES (#452)
* Add README

* Adapt create-latest-svc to work with GHES

* Fix inverted conditions
2020-04-27 23:53:53 -04:00
Tingluo Huang
70729fb3c4 Help trace worker crash in Kusto. (#450)
* Help trace worker crash in Kusto.

* more

* feedback.
2020-04-27 23:44:17 -04:00
Tingluo Huang
1470a3b6e2 better error when runner removed from service. (#441) 2020-04-27 23:02:00 -04:00
eric sciple
2fadf430e4 post-alpha fixes for github.url github.api_url and github.graphql_url (#451) 2020-04-24 13:38:59 -04:00
PJ Quirk
f798f5606b Use the API_URL and munge action URLs for GHES (#437)
* First pass at logic for GHES, not all correct

* Need to mock out file downloading

* Allowed for mocking of HTTP responses

* Added test for builtin GHES action download

* More tests

* Don't retry on action 404

* Remove commented out code

* Add a using statement back, because Windows

* Make windows happy again

* Another windows fix

* Always delete the cache since it isn't fully implemented

* Use RunnerService base class

* Add examples, update URL path

* Remove forceDotCom

* Fix a bug

* Remove a test that's no longer relevant

* PR feedback

* Add missing return

* More trace info

* Use the new agreed-upon format

* Use the auth token since we're hitting GHES directly

* Fixing tests on windows

* Fixed one more test
2020-04-23 17:02:13 -04:00
Tingluo Huang
3f7a01af93 add secret masker for trimming double qoutes. (#440) 2020-04-21 22:07:55 -04:00
Tingluo Huang
d5c54f9819 Raise warning when action input does not match action.yml. (#429) 2020-04-21 19:42:53 -04:00
Mathias LANG
9f78ad3b34 Make release notes code blocks copy-paste-able (#430)
The release notes used C-style comments (`//`) instead of shell-style (`#`),
causing the code snippet to not be easily copy-paste-able.

Additionally, the code fence for Windows had the extra `powershell` added,
as well as a comment to direct users towards `powershell` over `cmd`.
2020-04-19 22:11:44 -04:00
Jan Pazdziora
97883c8cd5 Fix spelling of RHEL and CentOS. (#436) 2020-04-19 16:53:06 -04:00
Tingluo Huang
c5fa9fb062 Print node version in debug instead of output. (#433) 2020-04-17 11:53:51 -04:00
Bryan MacFarlane
b2dcdc21dc Sample scripts to automate scaleable runners (#427) 2020-04-17 11:08:45 -04:00
David Kale
c126b52fe5 ArgumentNullException: Value cannot be null, for anonymous volume mounts (#426)
* Dont check if path starts with null

* Check SourceVolumePath not MountVolume obj

* Prefer string.IsNullOrEmpty
2020-04-14 14:36:39 -04:00
Lokesh Gopu
117ec1fff9 Fix optional parameter for unattended (#425) 2020-04-14 12:14:26 -04:00
Tingluo Huang
d5c7097d2c support 'pre' execution for actions. (#389) 2020-04-13 21:46:30 -04:00
Lokesh Gopu
f9baec4b32 Added support for custom labels (#414)
* Added support for custom labels

* ignore case

* Added interactive config for labels

* Fixing L0s

* pr comments
2020-04-13 21:33:13 -04:00
chenrui
a20ad4e121 Update releaseVersion to v2.168.0 (#420)
v2.168.0 is the latest release version, update the meta file to reflect that.
2020-04-11 13:59:20 -04:00
Tingluo Huang
2bd0b1af0e launch middle man process on macOS to workaround SIP limit (#416) 2020-04-09 16:13:06 -04:00
Tingluo Huang
baa6ded3bc Better Kusto Tracing for self-hosted runner. (#405) 2020-04-09 14:33:16 -04:00
83 changed files with 4546 additions and 2000 deletions

35
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: "Code Scanning - Action"
on:
push:
schedule:
- cron: '0 0 * * 0'
jobs:
CodeQL-Build:
strategy:
fail-fast: false
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
- name: Manual build
run : |
./dev.sh layout Release linux-x64
working-directory: src
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

57
docs/automate.md Normal file
View File

@@ -0,0 +1,57 @@
# Automate Configuring Self-Hosted Runners
## Export PAT
Before running any of these sample scripts, create a GitHub PAT and export it before running the script
```bash
export RUNNER_CFG_PAT=yourPAT
```
## Create running as a service
**Scenario**: Run on a machine or VM (not container) which automates:
- Resolving latest released runner
- Download and extract latest
- Acquire a registration token
- Configure the runner
- Run as a systemd (linux) or Launchd (osx) service
:point_right: [Sample script here](../scripts/create-latest-svc.sh) :point_left:
Run as a one-liner. NOTE: replace with yourorg/yourrepo (repo level) or just yourorg (org level)
```bash
curl -s https://raw.githubusercontent.com/actions/runner/automate/scripts/create-latest-svc.sh | bash -s yourorg/yourrepo
```
## Uninstall running as service
**Scenario**: Run on a machine or VM (not container) which automates:
- Stops and uninstalls the systemd (linux) or Launchd (osx) service
- Acquires a removal token
- Removes the runner
:point_right: [Sample script here](../scripts/remove-svc.sh) :point_left:
Repo level one liner. NOTE: replace with yourorg/yourrepo (repo level) or just yourorg (org level)
```bash
curl -s https://raw.githubusercontent.com/actions/runner/automate/scripts/remove-svc.sh | bash -s yourorg/yourrepo
```
### Delete an offline runner
**Scenario**: Deletes a registered runner that is offline:
- Ensures the runner is offline
- Resolves id from name
- Deletes the runner
:point_right: [Sample script here](../scripts/delete.sh) :point_left:
Repo level one-liner. NOTE: replace with yourorg/yourrepo (repo level) or just yourorg (org level) and replace runnername
```bash
curl -s https://raw.githubusercontent.com/actions/runner/automate/scripts/delete.sh | bash -s yourorg/yourrepo runnername
```

View File

@@ -40,7 +40,7 @@ Debian based OS (Debian, Ubuntu, Linux Mint)
- libssl1.1, libssl1.0.2 or libssl1.0.0 - libssl1.1, libssl1.0.2 or libssl1.0.0
- libicu63, libicu60, libicu57 or libicu55 - libicu63, libicu60, libicu57 or libicu55
Fedora based OS (Fedora, Redhat, Centos, Oracle Linux 7) Fedora based OS (Fedora, Red Hat Enterprise Linux, CentOS, Oracle Linux 7)
- lttng-ust - lttng-ust
- openssl-libs - openssl-libs

View File

@@ -1,20 +1,25 @@
## Features ## Features
- Runner support for GHES Alpha (#381 #386 #390 #393 $401) - N/A
- Allow secrets context in Container.env (#388)
## Bugs ## Bugs
- Raise warning when volume mount root. (#413) - Handle `jq` returns "null" if the field does not exist in create-latest-svc.sh (#478)
- Fix typo (#394) - Switch GITHUB_URL to GITHUB_SERVER_URL (#482)
- Fix problem matcher for GHES (#488)
- Fix container action inputs validation warning (#490)
- Fix post step display name (#490)
- Fix worker crash due to exception from evaluating step.env (#490)
## Misc ## Misc
- N/A - N/A
## Windows x64 ## Windows x64
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
```
// Create a folder under the drive root The following snipped needs to be run on `powershell`:
``` powershell
# Create a folder under the drive root
mkdir \actions-runner ; cd \actions-runner mkdir \actions-runner ; cd \actions-runner
// Download the latest runner package # 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 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 # Extract the installer
Add-Type -AssemblyName System.IO.Compression.FileSystem ; Add-Type -AssemblyName System.IO.Compression.FileSystem ;
[System.IO.Compression.ZipFile]::ExtractToDirectory("$PWD\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD") [System.IO.Compression.ZipFile]::ExtractToDirectory("$PWD\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
``` ```
@@ -22,44 +27,44 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem ;
## OSX ## OSX
``` bash ``` bash
// Create a folder # Create a folder
mkdir actions-runner && cd actions-runner mkdir actions-runner && cd actions-runner
// Download the latest runner package # Download the latest runner package
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
// Extract the installer # Extract the installer
tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
``` ```
## Linux x64 ## Linux x64
``` bash ``` bash
// Create a folder # Create a folder
mkdir actions-runner && cd actions-runner mkdir actions-runner && cd actions-runner
// Download the latest runner package # Download the latest runner package
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
// Extract the installer # Extract the installer
tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
``` ```
## Linux arm64 (Pre-release) ## Linux arm64 (Pre-release)
``` bash ``` bash
// Create a folder # Create a folder
mkdir actions-runner && cd actions-runner mkdir actions-runner && cd actions-runner
// Download the latest runner package # Download the latest runner package
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
// Extract the installer # Extract the installer
tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
``` ```
## Linux arm (Pre-release) ## Linux arm (Pre-release)
``` bash ``` bash
// Create a folder # Create a folder
mkdir actions-runner && cd actions-runner mkdir actions-runner && cd actions-runner
// Download the latest runner package # Download the latest runner package
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
// Extract the installer # Extract the installer
tar xzf ./actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz tar xzf ./actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
``` ```

View File

@@ -1 +1 @@
2.164.0 <Update to ./src/runnerversion when creating release>

4
scripts/README.md Normal file
View File

@@ -0,0 +1,4 @@
# Sample scripts for self-hosted runners
Here are some examples to work from if you'd like to automate your use of self-hosted runners.
See the docs [here](../docs/automate.md).

147
scripts/create-latest-svc.sh Executable file
View File

@@ -0,0 +1,147 @@
#/bin/bash
set -e
#
# Downloads latest releases (not pre-release) runner
# Configures as a service
#
# Examples:
# RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myuser/myrepo my.ghe.deployment.net
# RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myorg my.ghe.deployment.net
#
# Usage:
# export RUNNER_CFG_PAT=<yourPAT>
# ./create-latest-svc scope [ghe_domain] [name] [user]
#
# scope required repo (:owner/:repo) or org (:organization)
# ghe_domain optional the fully qualified domain name of your GitHub Enterprise Server deployment
# name optional defaults to hostname
# user optional user svc will run as. defaults to current
#
# Notes:
# PATS over envvars are more secure
# Should be used on VMs and not containers
# Works on OSX and Linux
# Assumes x64 arch
#
runner_scope=${1}
ghe_hostname=${2}
runner_name=${3:-$(hostname)}
svc_user=${4:-$USER}
echo "Configuring runner @ ${runner_scope}"
sudo echo
#---------------------------------------
# Validate Environment
#---------------------------------------
runner_plat=linux
[ ! -z "$(which sw_vers)" ] && runner_plat=osx;
function fatal()
{
echo "error: $1" >&2
exit 1
}
if [ -z "${runner_scope}" ]; then fatal "supply scope as argument 1"; fi
if [ -z "${RUNNER_CFG_PAT}" ]; then fatal "RUNNER_CFG_PAT must be set before calling"; fi
which curl || fatal "curl required. Please install in PATH with apt-get, brew, etc"
which jq || fatal "jq required. Please install in PATH with apt-get, brew, etc"
# bail early if there's already a runner there. also sudo early
if [ -d ./runner ]; then
fatal "Runner already exists. Use a different directory or delete ./runner"
fi
sudo -u ${svc_user} mkdir runner
# TODO: validate not in a container
# TODO: validate systemd or osx svc installer
#--------------------------------------
# Get a config token
#--------------------------------------
echo
echo "Generating a registration token..."
base_api_url="https://api.github.com"
if [ -n "${ghe_hostname}" ]; then
base_api_url="https://${ghe_hostname}/api/v3"
fi
# if the scope has a slash, it's a repo runner
orgs_or_repos="orgs"
if [[ "$runner_scope" == *\/* ]]; then
orgs_or_repos="repos"
fi
export RUNNER_TOKEN=$(curl -s -X POST ${base_api_url}/${orgs_or_repos}/${runner_scope}/actions/runners/registration-token -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" | jq -r '.token')
if [ "null" == "$RUNNER_TOKEN" -o -z "$RUNNER_TOKEN" ]; then fatal "Failed to get a token"; fi
#---------------------------------------
# Download latest released and extract
#---------------------------------------
echo
echo "Downloading latest runner ..."
# For the GHES Alpha, download the runner from github.com
latest_version_label=$(curl -s -X GET 'https://api.github.com/repos/actions/runner/releases/latest' | jq -r '.tag_name')
latest_version=$(echo ${latest_version_label:1})
runner_file="actions-runner-${runner_plat}-x64-${latest_version}.tar.gz"
if [ -f "${runner_file}" ]; then
echo "${runner_file} exists. skipping download."
else
runner_url="https://github.com/actions/runner/releases/download/${latest_version_label}/${runner_file}"
echo "Downloading ${latest_version_label} for ${runner_plat} ..."
echo $runner_url
curl -O -L ${runner_url}
fi
ls -la *.tar.gz
#---------------------------------------------------
# extract to runner directory in this directory
#---------------------------------------------------
echo
echo "Extracting ${runner_file} to ./runner"
tar xzf "./${runner_file}" -C runner
# export of pass
sudo chown -R $svc_user ./runner
pushd ./runner
#---------------------------------------
# Unattend config
#---------------------------------------
runner_url="https://github.com/${runner_scope}"
if [ -n "${ghe_hostname}" ]; then
runner_url="https://${ghe_hostname}/${runner_scope}"
fi
echo
echo "Configuring ${runner_name} @ $runner_url"
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name"
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name
#---------------------------------------
# Configuring as a service
#---------------------------------------
echo
echo "Configuring as a service ..."
prefix=""
if [ "${runner_plat}" == "linux" ]; then
prefix="sudo "
fi
${prefix}./svc.sh install ${svc_user}
${prefix}./svc.sh start

83
scripts/delete.sh Executable file
View File

@@ -0,0 +1,83 @@
#/bin/bash
set -e
#
# Force deletes a runner from the service
# The caller should have already ensured the runner is gone and/or stopped
#
# Examples:
# RUNNER_CFG_PAT=<yourPAT> ./delete.sh myuser/myrepo myname
# RUNNER_CFG_PAT=<yourPAT> ./delete.sh myorg
#
# Usage:
# export RUNNER_CFG_PAT=<yourPAT>
# ./delete.sh scope name
#
# scope required repo (:owner/:repo) or org (:organization)
# name optional defaults to hostname. name to delete
#
# Notes:
# PATS over envvars are more secure
# Works on OSX and Linux
# Assumes x64 arch
#
runner_scope=${1}
runner_name=${2}
echo "Deleting runner ${runner_name} @ ${runner_scope}"
function fatal()
{
echo "error: $1" >&2
exit 1
}
if [ -z "${runner_scope}" ]; then fatal "supply scope as argument 1"; fi
if [ -z "${runner_name}" ]; then fatal "supply name as argument 2"; fi
if [ -z "${RUNNER_CFG_PAT}" ]; then fatal "RUNNER_CFG_PAT must be set before calling"; fi
which curl || fatal "curl required. Please install in PATH with apt-get, brew, etc"
which jq || fatal "jq required. Please install in PATH with apt-get, brew, etc"
base_api_url="https://api.github.com/orgs"
if [[ "$runner_scope" == *\/* ]]; then
base_api_url="https://api.github.com/repos"
fi
#--------------------------------------
# Ensure offline
#--------------------------------------
runner_status=$(curl -s -X GET ${base_api_url}/${runner_scope}/actions/runners?per_page=100 -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" \
| jq -M -j ".runners | .[] | [select(.name == \"${runner_name}\")] | .[0].status")
if [ -z "${runner_status}" ]; then
fatal "Could not find runner with name ${runner_name}"
fi
echo "Status: ${runner_status}"
if [ "${runner_status}" != "offline" ]; then
fatal "Runner should be offline before removing"
fi
#--------------------------------------
# Get id of runner to remove
#--------------------------------------
runner_id=$(curl -s -X GET ${base_api_url}/${runner_scope}/actions/runners?per_page=100 -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" \
| jq -M -j ".runners | .[] | [select(.name == \"${runner_name}\")] | .[0].id")
if [ -z "${runner_id}" ]; then
fatal "Could not find runner with name ${runner_name}"
fi
echo "Removing id ${runner_id}"
#--------------------------------------
# Remove the runner
#--------------------------------------
curl -s -X DELETE ${base_api_url}/${runner_scope}/actions/runners/${runner_id} -H "authorization: token ${RUNNER_CFG_PAT}"
echo "Done."

76
scripts/remove-svc.sh Executable file
View File

@@ -0,0 +1,76 @@
#/bin/bash
set -e
#
# Removes a runner running as a service
# Must be run on the machine where the service is run
#
# Examples:
# RUNNER_CFG_PAT=<yourPAT> ./remove-svc.sh myuser/myrepo
# RUNNER_CFG_PAT=<yourPAT> ./remove-svc.sh myorg
#
# Usage:
# export RUNNER_CFG_PAT=<yourPAT>
# ./remove-svc scope name
#
# scope required repo (:owner/:repo) or org (:organization)
# name optional defaults to hostname. name to uninstall and remove
#
# Notes:
# PATS over envvars are more secure
# Should be used on VMs and not containers
# Works on OSX and Linux
# Assumes x64 arch
#
runner_scope=${1}
runner_name=${2:-$(hostname)}
echo "Uninstalling runner ${runner_name} @ ${runner_scope}"
sudo echo
function fatal()
{
echo "error: $1" >&2
exit 1
}
if [ -z "${runner_scope}" ]; then fatal "supply scope as argument 1"; fi
if [ -z "${RUNNER_CFG_PAT}" ]; then fatal "RUNNER_CFG_PAT must be set before calling"; fi
which curl || fatal "curl required. Please install in PATH with apt-get, brew, etc"
which jq || fatal "jq required. Please install in PATH with apt-get, brew, etc"
runner_plat=linux
[ ! -z "$(which sw_vers)" ] && runner_plat=osx;
#--------------------------------------
# Get a remove token
#--------------------------------------
echo
echo "Generating a removal token..."
# if the scope has a slash, it's an repo runner
base_api_url="https://api.github.com/orgs"
if [[ "$runner_scope" == *\/* ]]; then
base_api_url="https://api.github.com/repos"
fi
export REMOVE_TOKEN=$(curl -s -X POST ${base_api_url}/${runner_scope}/actions/runners/remove-token -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" | jq -r '.token')
if [ -z "$REMOVE_TOKEN" ]; then fatal "Failed to get a token"; fi
#---------------------------------------
# Stop and uninstall the service
#---------------------------------------
echo
echo "Uninstall the service ..."
pushd ./runner
prefix=""
if [ "${runner_plat}" == "linux" ]; then
prefix="sudo "
fi
${prefix}./svc.sh stop
${prefix}./svc.sh uninstall
${prefix}./config.sh remove --token $REMOVE_TOKEN

View File

@@ -684,3 +684,196 @@ Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath
Say "Installation finished" Say "Installation finished"
exit 0 exit 0
# SIG # Begin signature block
# MIIjkQYJKoZIhvcNAQcCoIIjgjCCI34CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAwp4UsNdAkvwY3
# VhbuN9D6NGOz+qNqW2+62YubWa4qJaCCDYEwggX/MIID56ADAgECAhMzAAABh3IX
# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB
# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH
# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d
# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ
# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV
# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy
# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K
# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV
# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr
# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx
# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe
# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g
# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf
# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI
# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5
# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea
# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZjCCFWICAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQga11B1DE+
# y9z0lmEO+MC+bhXPKfWALB7Snkn7G/wCUncwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQBIgx+sFXkLXf7Xbx7opCD3uhpQGEQ4x/LsqTax0bu1
# GC/cxiI+dodUz+T4hKj1ZQyUH0Zlce32GutY048O9tkr7fQyuohoFUgChdIATEOY
# qAIESFbDT07i7khJfO2pewlhgM+A5ClvBa8HAvV0wOd+2IVgv3pgow1LEJm0/5NB
# E3IFA+hFrqiWALOY0uUep4H20EHMrbqw3YoV3EodIkTj3fC76q4K/bF84EZLUgjY
# e4rmXac8n7A9qR18QzGl8usEJej4OHU4nlUT1J734m+AWIFmfb/Zr2MyXED0V4q4
# Vbmw3O7xD9STeNYrn5RjPmGPEN04akHxhNUSqLIc9vxQoYIS8DCCEuwGCisGAQQB
# gjcDAwExghLcMIIS2AYJKoZIhvcNAQcCoIISyTCCEsUCAQMxDzANBglghkgBZQME
# AgEFADCCAVQGCyqGSIb3DQEJEAEEoIIBQwSCAT8wggE7AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIPPK1A0D1n7ZEdgTjKPY4sWiOMtohMqGpFvG55NY
# SFHeAgZepuJh/dEYEjIwMjAwNTI5MTYyNzE1LjMxWjAEgAIB9KCB1KSB0TCBzjEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWlj
# cm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBU
# U1MgRVNOOjYwQkMtRTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T
# dGFtcCBTZXJ2aWNloIIORDCCBPUwggPdoAMCAQICEzMAAAEm37pLIrmCggcAAAAA
# ASYwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw
# HhcNMTkxMjE5MDExNDU5WhcNMjEwMzE3MDExNDU5WjCBzjELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJh
# dGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjYwQkMt
# RTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnjC+hpxO8w2VdBO18X8L
# Hk6XdfR9yNQ0y+MuBOY7n5YdgkVunvbk/f6q8UoNFAdYQjVLPSAHbi6tUMiNeMGH
# k1U0lUxAkja2W2/szj/ghuFklvfHNBbsuiUShlhRlqcFNS7KXL2iwKDijmOhWJPY
# a2bLEr4W/mQLbSXail5p6m138Ttx4MAVEzzuGI0Kwr8ofIL7z6zCeWDiBM57LrNC
# qHOA2wboeuMsG4O0Oz2LMAzBLbJZPRPnZAD2HdD4HUL2mzZ8wox74Mekb7RzrUP3
# hiHpxXZceJvhIEKfAgVkB5kTZQnio8A1JijMjw8f4TmsJPdJWpi8ei73sexe8/Yj
# cwIDAQABo4IBGzCCARcwHQYDVR0OBBYEFEmrrB8XsH6YQo3RWKZfxqM0DmFBMB8G
# A1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeG
# RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Rp
# bVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH
# MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3Rh
# UENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB
# BQUHAwgwDQYJKoZIhvcNAQELBQADggEBAECW+51o6W/0J/O/npudfjVzMXq0u0cs
# HjqXpdRyH6o03jlmY5MXAui3cmPBKufijJxD2pMRPVMUNh3VA0PQuJeYrP06oFdq
# LpLxd3IJARm98vzaMgCz2nCwBDpe9X2M3Js9K1GAX+w4Az8N7J+Z6P1OD0VxHBdq
# eTaqDN1lk1vwagTN7t/WitxMXRDz0hRdYiWbATBAVgXXCOfzs3hnEv1n/EDab9HX
# OLMXKVY/+alqYKdV9lkuRp8Us1Q1WZy9z72Azu9x4mzft3fJ1puTjBHo5tHfixZo
# ummbI+WwjVCrku7pskJahfNi5amSgrqgR6nWAwvpJELccpVLdSxxmG0wggZxMIIE
# WaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v
# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0y
# NTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RU
# ENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBE
# D/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50
# YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd
# /XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaR
# togINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB
# o4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8
# RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB
# hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO
# mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w
# a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr
# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSAB
# Af8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEF
# BQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBt
# AGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Eh
# b7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7
# uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqR
# UgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9
# Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8
# +n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+
# Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh
# 2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRy
# zR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoo
# uLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx
# 16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341
# Hgi62jbb01+P3nSISRKhggLSMIICOwIBATCB/KGB1KSB0TCBzjELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w
# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjYw
# QkMtRTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
# aWNloiMKAQEwBwYFKw4DAhoDFQAKZzI5aZnESumrToHx3Lqgxnr//KCBgzCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA
# 4nuQTDAiGA8yMDIwMDUyOTE3NDQ0NFoYDzIwMjAwNTMwMTc0NDQ0WjB3MD0GCisG
# AQQBhFkKBAExLzAtMAoCBQDie5BMAgEAMAoCAQACAiZJAgH/MAcCAQACAhEjMAoC
# BQDifOHMAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA
# AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAprmyJTXdH9FmQZ0I
# mRSJdjc/RrSqDm8DUEq/h3FL73G/xvg9MbQj1J/h3hdlSIPcQXjrhL8hud/vyF0j
# IFaTK5YOcixkX++9t7Vz3Mn0KkQo8F4DNSyZEPpz682AyKKwLMJDy52pFFFKNP5l
# NpOz6YY1Od1xvk4nyN1WwfLnGswxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMAITMwAAASbfuksiuYKCBwAAAAABJjANBglghkgBZQME
# AgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJ
# BDEiBCB0IE0Q6P23RQlh8TFyp57UQQUF/sbui7mOMStRgTFZxTCB+gYLKoZIhvcN
# AQkQAi8xgeowgecwgeQwgb0EIDb9z++evV5wDO9qk5ZnbEZ8CTOuR+kZyu8xbTsJ
# CXUPMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEm
# 37pLIrmCggcAAAAAASYwIgQgtwi02bvsGAOdpAxEF607G6g9PlyS8vc2bAUSHovH
# /IIwDQYJKoZIhvcNAQELBQAEggEAEMCfsXNudrjztjI6JNyNDVpdF1axRVcGiNy6
# 67pgb1EePsjA2EaBB+5ZjgO/73JxuiVgsoXgH7em8tKG5RQJtcm5obVDb+jKksK4
# qcFLA1f7seQRGfE06UAPnSFh2GqMtTNJGCXWwqWLH2LduTjOqPt8Nupo16ABFIT2
# akTzBSJ81EHBkEU0Et6CgeaZiBYrCCXUtD+ASvLDkPSrjweQGu3Zk1SSROEzxMY9
# jdlGfMkK2krMd9ub9UZ13RcQDijJqo+h6mz76pAuiFFvuQl6wMoSGFaaUQwfd+WQ
# gXlVVX/A9JFBihrxnDVglEPlsIOxCHkTeIxLfnAkCbax+9pevA==
# SIG # End signature block

View File

@@ -1,6 +1,7 @@
#!/bin/bash #!/bin/bash
SVC_NAME="{{SvcNameVar}}" SVC_NAME="{{SvcNameVar}}"
SVC_NAME=${SVC_NAME// /_}
SVC_DESCRIPTION="{{SvcDescription}}" SVC_DESCRIPTION="{{SvcDescription}}"
user_id=`id -u` user_id=`id -u`

View File

@@ -9,7 +9,7 @@ fi
# Determine OS type # Determine OS type
# Debian based OS (Debian, Ubuntu, Linux Mint) has /etc/debian_version # Debian based OS (Debian, Ubuntu, Linux Mint) has /etc/debian_version
# Fedora based OS (Fedora, Redhat, Centos, Oracle Linux 7) has /etc/redhat-release # Fedora based OS (Fedora, Red Hat Enterprise Linux, CentOS, Oracle Linux 7) has /etc/redhat-release
# SUSE based OS (OpenSUSE, SUSE Enterprise) has ID_LIKE=suse in /etc/os-release # SUSE based OS (OpenSUSE, SUSE Enterprise) has ID_LIKE=suse in /etc/os-release
function print_errormessage() function print_errormessage()
@@ -116,12 +116,12 @@ then
elif [ -e /etc/redhat-release ] elif [ -e /etc/redhat-release ]
then then
echo "The current OS is Fedora based" echo "The current OS is Fedora based"
echo "--------Redhat Version--------" echo "--Fedora/RHEL/CentOS Version--"
cat /etc/redhat-release cat /etc/redhat-release
echo "------------------------------" echo "------------------------------"
# use dnf on fedora # use dnf on fedora
# use yum on centos and redhat # use yum on centos and rhel
if [ -e /etc/fedora-release ] if [ -e /etc/fedora-release ]
then then
command -v dnf command -v dnf
@@ -191,7 +191,7 @@ then
redhatRelease=$(</etc/redhat-release) redhatRelease=$(</etc/redhat-release)
if [[ $redhatRelease == "CentOS release 6."* || $redhatRelease == "Red Hat Enterprise Linux Server release 6."* ]] if [[ $redhatRelease == "CentOS release 6."* || $redhatRelease == "Red Hat Enterprise Linux Server release 6."* ]]
then then
echo "The current OS is Red Hat Enterprise Linux 6 or Centos 6" echo "The current OS is Red Hat Enterprise Linux 6 or CentOS 6"
# Install known dependencies, as a best effort. # Install known dependencies, as a best effort.
# The remaining dependencies are covered by the GitHub doc that will be shown by `print_rhel6message` # The remaining dependencies are covered by the GitHub doc that will be shown by `print_rhel6message`

View File

@@ -0,0 +1,13 @@
const { spawn } = require('child_process');
// argv[0] = node
// argv[1] = macos-run-invoker.js
var shell = process.argv[2];
var args = process.argv.slice(3);
console.log(`::debug::macos-run-invoker: ${shell}`);
console.log(`::debug::macos-run-invoker: ${JSON.stringify(args)}`);
var launch = spawn(shell, args, { stdio: 'inherit' });
launch.on('exit', function (code) {
if (code !== 0) {
process.exit(code);
}
});

View File

@@ -1,6 +1,7 @@
#!/bin/bash #!/bin/bash
SVC_NAME="{{SvcNameVar}}" SVC_NAME="{{SvcNameVar}}"
SVC_NAME=${SVC_NAME// /_}
SVC_DESCRIPTION="{{SvcDescription}}" SVC_DESCRIPTION="{{SvcDescription}}"
SVC_CMD=$1 SVC_CMD=$1

View File

@@ -108,9 +108,9 @@ namespace GitHub.Runner.Common
CredentialData GetMigratedCredentials(); CredentialData GetMigratedCredentials();
RunnerSettings GetSettings(); RunnerSettings GetSettings();
void SaveCredential(CredentialData credential); void SaveCredential(CredentialData credential);
void SaveMigratedCredential(CredentialData credential);
void SaveSettings(RunnerSettings settings); void SaveSettings(RunnerSettings settings);
void DeleteCredential(); void DeleteCredential();
void DeleteMigratedCredential();
void DeleteSettings(); void DeleteSettings();
} }
@@ -232,21 +232,6 @@ namespace GitHub.Runner.Common
File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden); File.SetAttributes(_credFilePath, File.GetAttributes(_credFilePath) | FileAttributes.Hidden);
} }
public void SaveMigratedCredential(CredentialData credential)
{
Trace.Info("Saving {0} migrated credential @ {1}", credential.Scheme, _migratedCredFilePath);
if (File.Exists(_migratedCredFilePath))
{
// Delete existing credential file first, since the file is hidden and not able to overwrite.
Trace.Info("Delete exist runner migrated credential file.");
IOUtil.DeleteFile(_migratedCredFilePath);
}
IOUtil.SaveObject(credential, _migratedCredFilePath);
Trace.Info("Migrated Credentials Saved.");
File.SetAttributes(_migratedCredFilePath, File.GetAttributes(_migratedCredFilePath) | FileAttributes.Hidden);
}
public void SaveSettings(RunnerSettings settings) public void SaveSettings(RunnerSettings settings)
{ {
Trace.Info("Saving runner settings."); Trace.Info("Saving runner settings.");
@@ -268,6 +253,11 @@ namespace GitHub.Runner.Common
IOUtil.Delete(_migratedCredFilePath, default(CancellationToken)); IOUtil.Delete(_migratedCredFilePath, default(CancellationToken));
} }
public void DeleteMigratedCredential()
{
IOUtil.Delete(_migratedCredFilePath, default(CancellationToken));
}
public void DeleteSettings() public void DeleteSettings()
{ {
IOUtil.Delete(_configFilePath, default(CancellationToken)); IOUtil.Delete(_configFilePath, default(CancellationToken));

View File

@@ -87,6 +87,7 @@ namespace GitHub.Runner.Common
public static class Args public static class Args
{ {
public static readonly string Auth = "auth"; public static readonly string Auth = "auth";
public static readonly string Labels = "labels";
public static readonly string MonitorSocketAddress = "monitorsocketaddress"; public static readonly string MonitorSocketAddress = "monitorsocketaddress";
public static readonly string Name = "name"; public static readonly string Name = "name";
public static readonly string Pool = "pool"; public static readonly string Pool = "pool";
@@ -136,6 +137,9 @@ namespace GitHub.Runner.Common
public const int RunnerUpdating = 3; public const int RunnerUpdating = 3;
public const int RunOnceRunnerUpdating = 4; public const int RunOnceRunnerUpdating = 4;
} }
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
public static readonly string WorkerCrash = "WORKER_CRASH";
} }
public static class RunnerEvent public static class RunnerEvent

View File

@@ -1,19 +1,18 @@
using GitHub.Runner.Common.Util; using System;
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Diagnostics;
using System.Net.Http;
using System.Diagnostics.Tracing;
using GitHub.DistributedTask.Logging; using GitHub.DistributedTask.Logging;
using System.Net.Http.Headers;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common namespace GitHub.Runner.Common
@@ -24,7 +23,7 @@ namespace GitHub.Runner.Common
CancellationToken RunnerShutdownToken { get; } CancellationToken RunnerShutdownToken { get; }
ShutdownReason RunnerShutdownReason { get; } ShutdownReason RunnerShutdownReason { get; }
ISecretMasker SecretMasker { get; } ISecretMasker SecretMasker { get; }
ProductInfoHeaderValue UserAgent { get; } List<ProductInfoHeaderValue> UserAgents { get; }
RunnerWebProxy WebProxy { get; } RunnerWebProxy WebProxy { get; }
string GetDirectory(WellKnownDirectory directory); string GetDirectory(WellKnownDirectory directory);
string GetConfigFile(WellKnownConfigFile configFile); string GetConfigFile(WellKnownConfigFile configFile);
@@ -54,7 +53,7 @@ namespace GitHub.Runner.Common
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new ConcurrentDictionary<Type, object>(); private readonly ConcurrentDictionary<Type, object> _serviceInstances = new ConcurrentDictionary<Type, object>();
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new ConcurrentDictionary<Type, Type>(); private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new ConcurrentDictionary<Type, Type>();
private readonly ISecretMasker _secretMasker = new SecretMasker(); private readonly ISecretMasker _secretMasker = new SecretMasker();
private readonly ProductInfoHeaderValue _userAgent = new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version); private readonly List<ProductInfoHeaderValue> _userAgents = new List<ProductInfoHeaderValue>() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource(); private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
private object _perfLock = new object(); private object _perfLock = new object();
private Tracing _trace; private Tracing _trace;
@@ -72,7 +71,7 @@ namespace GitHub.Runner.Common
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token; public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
public ShutdownReason RunnerShutdownReason { get; private set; } public ShutdownReason RunnerShutdownReason { get; private set; }
public ISecretMasker SecretMasker => _secretMasker; public ISecretMasker SecretMasker => _secretMasker;
public ProductInfoHeaderValue UserAgent => _userAgent; public List<ProductInfoHeaderValue> UserAgents => _userAgents;
public RunnerWebProxy WebProxy => _webProxy; public RunnerWebProxy WebProxy => _webProxy;
public HostContext(string hostType, string logFile = null) public HostContext(string hostType, string logFile = null)
{ {
@@ -89,6 +88,7 @@ namespace GitHub.Runner.Common
this.SecretMasker.AddValueEncoder(ValueEncoders.JsonStringEscape); this.SecretMasker.AddValueEncoder(ValueEncoders.JsonStringEscape);
this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape); this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
this.SecretMasker.AddValueEncoder(ValueEncoders.XmlDataEscape); this.SecretMasker.AddValueEncoder(ValueEncoders.XmlDataEscape);
this.SecretMasker.AddValueEncoder(ValueEncoders.TrimDoubleQuotes);
// Create the trace manager. // Create the trace manager.
if (string.IsNullOrEmpty(logFile)) if (string.IsNullOrEmpty(logFile))
@@ -189,6 +189,17 @@ namespace GitHub.Runner.Common
{ {
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)"); _trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
} }
var credFile = GetConfigFile(WellKnownConfigFile.Credentials);
if (File.Exists(credFile))
{
var credData = IOUtil.LoadObject<CredentialData>(credFile);
if (credData != null &&
credData.Data.TryGetValue("clientId", out var clientId))
{
_userAgents.Add(new ProductInfoHeaderValue($"RunnerId", clientId));
}
}
} }
public string GetDirectory(WellKnownDirectory directory) public string GetDirectory(WellKnownDirectory directory)
@@ -603,9 +614,8 @@ namespace GitHub.Runner.Common
{ {
public static HttpClientHandler CreateHttpClientHandler(this IHostContext context) public static HttpClientHandler CreateHttpClientHandler(this IHostContext context)
{ {
HttpClientHandler clientHandler = new HttpClientHandler(); var handlerFactory = context.GetService<IHttpClientHandlerFactory>();
clientHandler.Proxy = context.WebProxy; return handlerFactory.CreateClientHandler(context.WebProxy);
return clientHandler;
} }
} }

View File

@@ -0,0 +1,19 @@
using System.Net.Http;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common
{
[ServiceLocator(Default = typeof(HttpClientHandlerFactory))]
public interface IHttpClientHandlerFactory : IRunnerService
{
HttpClientHandler CreateClientHandler(RunnerWebProxy webProxy);
}
public class HttpClientHandlerFactory : RunnerService, IHttpClientHandlerFactory
{
public HttpClientHandler CreateClientHandler(RunnerWebProxy webProxy)
{
return new HttpClientHandler() { Proxy = webProxy };
}
}
}

View File

@@ -41,7 +41,7 @@ namespace GitHub.Runner.Common
// job request // job request
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken); Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, CancellationToken cancellationToken); Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId, CancellationToken cancellationToken);
Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken); Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken);
// agent package // agent package
@@ -50,10 +50,6 @@ namespace GitHub.Runner.Common
// agent update // agent update
Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState); Task<TaskAgent> UpdateAgentUpdateStateAsync(int agentPoolId, int agentId, string currentState);
// runner authorization url
Task<string> GetRunnerAuthUrlAsync(int runnerPoolId, int runnerId);
Task ReportRunnerAuthUrlErrorAsync(int runnerPoolId, int runnerId, string error);
} }
public sealed class RunnerServer : RunnerService, IRunnerServer public sealed class RunnerServer : RunnerService, IRunnerServer
@@ -300,10 +296,10 @@ namespace GitHub.Runner.Common
// JobRequest // JobRequest
//----------------------------------------------------------------- //-----------------------------------------------------------------
public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, CancellationToken cancellationToken = default(CancellationToken)) public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId = null, CancellationToken cancellationToken = default(CancellationToken))
{ {
CheckConnection(RunnerConnectionType.JobRequest); CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, cancellationToken: cancellationToken); return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId: orchestrationId, cancellationToken: cancellationToken);
} }
public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken)) public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken))

View File

@@ -102,7 +102,8 @@ namespace GitHub.Runner.Common
Console.Write(message); Console.Write(message);
Console.ResetColor(); Console.ResetColor();
} }
else { else
{
Console.Write(message); Console.Write(message);
} }
} }
@@ -126,7 +127,8 @@ namespace GitHub.Runner.Common
Console.WriteLine(line); Console.WriteLine(line);
Console.ResetColor(); Console.ResetColor();
} }
else { else
{
Console.WriteLine(line); Console.WriteLine(line);
} }
} }

View File

@@ -39,6 +39,7 @@ namespace GitHub.Runner.Listener
private readonly string[] validArgs = private readonly string[] validArgs =
{ {
Constants.Runner.CommandLine.Args.Auth, Constants.Runner.CommandLine.Args.Auth,
Constants.Runner.CommandLine.Args.Labels,
Constants.Runner.CommandLine.Args.MonitorSocketAddress, Constants.Runner.CommandLine.Args.MonitorSocketAddress,
Constants.Runner.CommandLine.Args.Name, Constants.Runner.CommandLine.Args.Name,
Constants.Runner.CommandLine.Args.Pool, Constants.Runner.CommandLine.Args.Pool,
@@ -249,6 +250,24 @@ namespace GitHub.Runner.Listener
return GetArg(Constants.Runner.CommandLine.Args.StartupType); return GetArg(Constants.Runner.CommandLine.Args.StartupType);
} }
public ISet<string> GetLabels()
{
var labelSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
string labels = GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Labels,
description: $"This runner will have the following labels: 'self-hosted', '{VarUtil.OS}', '{VarUtil.OSArchitecture}' \nEnter any additional labels (ex. label-1,label-2):",
defaultValue: string.Empty,
validator: Validators.LabelsValidator,
isOptional: true);
if (!string.IsNullOrEmpty(labels))
{
labelSet = labels.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToHashSet<string>(StringComparer.OrdinalIgnoreCase);
}
return labelSet;
}
// //
// Private helpers. // Private helpers.
// //
@@ -280,7 +299,8 @@ namespace GitHub.Runner.Listener
string name, string name,
string description, string description,
string defaultValue, string defaultValue,
Func<string, bool> validator) Func<string, bool> validator,
bool isOptional = false)
{ {
// Check for the arg in the command line parser. // Check for the arg in the command line parser.
ArgUtil.NotNull(validator, nameof(validator)); ArgUtil.NotNull(validator, nameof(validator));
@@ -311,7 +331,8 @@ namespace GitHub.Runner.Listener
secret: Constants.Runner.CommandLine.Args.Secrets.Any(x => string.Equals(x, name, StringComparison.OrdinalIgnoreCase)), secret: Constants.Runner.CommandLine.Args.Secrets.Any(x => string.Equals(x, name, StringComparison.OrdinalIgnoreCase)),
defaultValue: defaultValue, defaultValue: defaultValue,
validator: validator, validator: validator,
unattended: Unattended); unattended: Unattended,
isOptional: isOptional);
} }
private string GetEnvArg(string name) private string GetEnvArg(string name)

View File

@@ -92,9 +92,11 @@ namespace GitHub.Runner.Listener.Configuration
_term.WriteSection("Authentication"); _term.WriteSection("Authentication");
while (true) while (true)
{ {
// Get the URL // When testing against a dev deployment of Actions Service, set this environment variable
var useDevActionsServiceUrl = Environment.GetEnvironmentVariable("USE_DEV_ACTIONS_SERVICE_URL");
var inputUrl = command.GetUrl(); var inputUrl = command.GetUrl();
if (inputUrl.Contains("codedev.ms", StringComparison.OrdinalIgnoreCase)) if (inputUrl.Contains("codedev.ms", StringComparison.OrdinalIgnoreCase)
|| useDevActionsServiceUrl != null)
{ {
runnerSettings.ServerUrl = inputUrl; runnerSettings.ServerUrl = inputUrl;
// Get the credentials // Get the credentials
@@ -117,6 +119,19 @@ namespace GitHub.Runner.Listener.Configuration
// Determine the service deployment type based on connection data. (Hosted/OnPremises) // Determine the service deployment type based on connection data. (Hosted/OnPremises)
runnerSettings.IsHostedServer = runnerSettings.GitHubUrl == null || IsHostedServer(new UriBuilder(runnerSettings.GitHubUrl)); runnerSettings.IsHostedServer = runnerSettings.GitHubUrl == null || IsHostedServer(new UriBuilder(runnerSettings.GitHubUrl));
// Warn if the Actions server url and GHES server url has different Host
if (!runnerSettings.IsHostedServer)
{
// Example actionsServerUrl is https://my-ghes/_services/pipelines/[...]
// Example githubServerUrl is https://my-ghes
var actionsServerUrl = new Uri(runnerSettings.ServerUrl);
var githubServerUrl = new Uri(runnerSettings.GitHubUrl);
if (!string.Equals(actionsServerUrl.Authority, githubServerUrl.Authority, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"GitHub Actions is not properly configured in GHES. GHES url: {runnerSettings.GitHubUrl}, Actions url: {runnerSettings.ServerUrl}.");
}
}
// Validate can connect. // Validate can connect.
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), creds); await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), creds);
@@ -166,6 +181,9 @@ namespace GitHub.Runner.Listener.Configuration
_term.WriteLine(); _term.WriteLine();
var userLabels = command.GetLabels();
_term.WriteLine();
var agents = await _runnerServer.GetAgentsAsync(runnerSettings.PoolId, runnerSettings.AgentName); var agents = await _runnerServer.GetAgentsAsync(runnerSettings.PoolId, runnerSettings.AgentName);
Trace.Verbose("Returns {0} agents", agents.Count); Trace.Verbose("Returns {0} agents", agents.Count);
agent = agents.FirstOrDefault(); agent = agents.FirstOrDefault();
@@ -175,7 +193,7 @@ namespace GitHub.Runner.Listener.Configuration
if (command.GetReplace()) if (command.GetReplace())
{ {
// Update existing agent with new PublicKey, agent version. // Update existing agent with new PublicKey, agent version.
agent = UpdateExistingAgent(agent, publicKey); agent = UpdateExistingAgent(agent, publicKey, userLabels);
try try
{ {
@@ -198,7 +216,7 @@ namespace GitHub.Runner.Listener.Configuration
else else
{ {
// Create a new agent. // Create a new agent.
agent = CreateNewAgent(runnerSettings.AgentName, publicKey); agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels);
try try
{ {
@@ -216,36 +234,11 @@ namespace GitHub.Runner.Listener.Configuration
// Add Agent Id to settings // Add Agent Id to settings
runnerSettings.AgentId = agent.Id; runnerSettings.AgentId = agent.Id;
// respect the serverUrl resolve by server.
// in case of agent configured using collection url instead of account url.
string agentServerUrl;
if (agent.Properties.TryGetValidatedValue<string>("ServerUrl", out agentServerUrl) &&
!string.IsNullOrEmpty(agentServerUrl))
{
Trace.Info($"Agent server url resolve by server: '{agentServerUrl}'.");
// we need make sure the Schema/Host/Port component of the url remain the same.
UriBuilder inputServerUrl = new UriBuilder(runnerSettings.ServerUrl);
UriBuilder serverReturnedServerUrl = new UriBuilder(agentServerUrl);
if (Uri.Compare(inputServerUrl.Uri, serverReturnedServerUrl.Uri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0)
{
inputServerUrl.Path = serverReturnedServerUrl.Path;
Trace.Info($"Replace server returned url's scheme://host:port component with user input server url's scheme://host:port: '{inputServerUrl.Uri.AbsoluteUri}'.");
runnerSettings.ServerUrl = inputServerUrl.Uri.AbsoluteUri;
}
else
{
runnerSettings.ServerUrl = agentServerUrl;
}
}
// See if the server supports our OAuth key exchange for credentials // See if the server supports our OAuth key exchange for credentials
if (agent.Authorization != null && if (agent.Authorization != null &&
agent.Authorization.ClientId != Guid.Empty && agent.Authorization.ClientId != Guid.Empty &&
agent.Authorization.AuthorizationUrl != null) agent.Authorization.AuthorizationUrl != null)
{ {
UriBuilder configServerUrl = new UriBuilder(runnerSettings.ServerUrl);
UriBuilder oauthEndpointUrlBuilder = new UriBuilder(agent.Authorization.AuthorizationUrl);
var credentialData = new CredentialData var credentialData = new CredentialData
{ {
Scheme = Constants.Configuration.OAuth, Scheme = Constants.Configuration.OAuth,
@@ -253,7 +246,6 @@ namespace GitHub.Runner.Listener.Configuration
{ {
{ "clientId", agent.Authorization.ClientId.ToString("D") }, { "clientId", agent.Authorization.ClientId.ToString("D") },
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri }, { "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
{ "oauthEndpointUrl", oauthEndpointUrlBuilder.Uri.AbsoluteUri },
}, },
}; };
@@ -448,7 +440,7 @@ namespace GitHub.Runner.Listener.Configuration
} }
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey) private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels)
{ {
ArgUtil.NotNull(agent, nameof(agent)); ArgUtil.NotNull(agent, nameof(agent));
agent.Authorization = new TaskAgentAuthorization agent.Authorization = new TaskAgentAuthorization
@@ -456,18 +448,25 @@ namespace GitHub.Runner.Listener.Configuration
PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus), PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus),
}; };
// update - update instead of delete so we don't lose labels etc... // update should replace the existing labels
agent.Version = BuildConstants.RunnerPackage.Version; agent.Version = BuildConstants.RunnerPackage.Version;
agent.OSDescription = RuntimeInformation.OSDescription; agent.OSDescription = RuntimeInformation.OSDescription;
agent.Labels.Add("self-hosted"); agent.Labels.Clear();
agent.Labels.Add(VarUtil.OS);
agent.Labels.Add(VarUtil.OSArchitecture); agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System));
foreach (var userLabel in userLabels)
{
agent.Labels.Add(new AgentLabel(userLabel, LabelType.User));
}
return agent; return agent;
} }
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey) private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels)
{ {
TaskAgent agent = new TaskAgent(agentName) TaskAgent agent = new TaskAgent(agentName)
{ {
@@ -480,9 +479,14 @@ namespace GitHub.Runner.Listener.Configuration
OSDescription = RuntimeInformation.OSDescription, OSDescription = RuntimeInformation.OSDescription,
}; };
agent.Labels.Add("self-hosted"); agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
agent.Labels.Add(VarUtil.OS); agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
agent.Labels.Add(VarUtil.OSArchitecture); agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System));
foreach (var userLabel in userLabels)
{
agent.Labels.Add(new AgentLabel(userLabel, LabelType.User));
}
return agent; return agent;
} }
@@ -511,7 +515,7 @@ namespace GitHub.Runner.Listener.Configuration
using (var httpClient = new HttpClient(httpClientHandler)) using (var httpClient = new HttpClient(httpClientHandler))
{ {
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("RemoteAuth", githubToken); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("RemoteAuth", githubToken);
httpClient.DefaultRequestHeaders.UserAgent.Add(HostContext.UserAgent); httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
var bodyObject = new Dictionary<string, string>() var bodyObject = new Dictionary<string, string>()
{ {

View File

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

View File

@@ -20,7 +20,8 @@ namespace GitHub.Runner.Listener.Configuration
bool secret, bool secret,
string defaultValue, string defaultValue,
Func<String, bool> validator, Func<String, bool> validator,
bool unattended); bool unattended,
bool isOptional = false);
} }
public sealed class PromptManager : RunnerService, IPromptManager public sealed class PromptManager : RunnerService, IPromptManager
@@ -56,7 +57,8 @@ namespace GitHub.Runner.Listener.Configuration
bool secret, bool secret,
string defaultValue, string defaultValue,
Func<string, bool> validator, Func<string, bool> validator,
bool unattended) bool unattended,
bool isOptional = false)
{ {
Trace.Info(nameof(ReadValue)); Trace.Info(nameof(ReadValue));
ArgUtil.NotNull(validator, nameof(validator)); ArgUtil.NotNull(validator, nameof(validator));
@@ -70,6 +72,10 @@ namespace GitHub.Runner.Listener.Configuration
{ {
return defaultValue; return defaultValue;
} }
else if (isOptional)
{
return string.Empty;
}
// Otherwise throw. // Otherwise throw.
throw new Exception($"Invalid configuration provided for {argName}. Terminating unattended configuration."); throw new Exception($"Invalid configuration provided for {argName}. Terminating unattended configuration.");
@@ -85,17 +91,27 @@ namespace GitHub.Runner.Listener.Configuration
{ {
_terminal.Write($"[press Enter for {defaultValue}] "); _terminal.Write($"[press Enter for {defaultValue}] ");
} }
else if (isOptional){
_terminal.Write($"[press Enter to skip] ");
}
// Read and trim the value. // Read and trim the value.
value = secret ? _terminal.ReadSecret() : _terminal.ReadLine(); value = secret ? _terminal.ReadSecret() : _terminal.ReadLine();
value = value?.Trim() ?? string.Empty; value = value?.Trim() ?? string.Empty;
// Return the default if not specified. // Return the default if not specified.
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(defaultValue)) if (string.IsNullOrEmpty(value))
{
if (!string.IsNullOrEmpty(defaultValue))
{ {
Trace.Info($"Falling back to the default: '{defaultValue}'"); Trace.Info($"Falling back to the default: '{defaultValue}'");
return defaultValue; return defaultValue;
} }
else if (isOptional)
{
return string.Empty;
}
}
// Return the value if it is not empty and it is valid. // Return the value if it is not empty and it is valid.
// Otherwise try the loop again. // Otherwise try the loop again.

View File

@@ -1,6 +1,7 @@
using GitHub.Runner.Common.Util; using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using System; using System;
using System.Linq;
using System.IO; using System.IO;
using System.Security.Principal; using System.Security.Principal;
@@ -46,6 +47,21 @@ namespace GitHub.Runner.Listener.Configuration
string.Equals(value, "N", StringComparison.CurrentCultureIgnoreCase); string.Equals(value, "N", StringComparison.CurrentCultureIgnoreCase);
} }
public static bool LabelsValidator(string labels)
{
if (!string.IsNullOrEmpty(labels))
{
var labelSet = labels.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToHashSet<string>(StringComparer.OrdinalIgnoreCase);
if (labelSet.Any(x => x.Length > 256))
{
return false;
}
}
return true;
}
public static bool NonEmptyValidator(string value) public static bool NonEmptyValidator(string value)
{ {
return !string.IsNullOrEmpty(value); return !string.IsNullOrEmpty(value);

View File

@@ -12,6 +12,7 @@ using System.Linq;
using GitHub.Services.Common; using GitHub.Services.Common;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Services.WebApi.Jwt;
namespace GitHub.Runner.Listener namespace GitHub.Runner.Listener
{ {
@@ -86,15 +87,30 @@ namespace GitHub.Runner.Listener
} }
} }
var orchestrationId = string.Empty;
var systemConnection = jobRequestMessage.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
if (systemConnection?.Authorization != null &&
systemConnection.Authorization.Parameters.TryGetValue("AccessToken", out var accessToken) &&
!string.IsNullOrEmpty(accessToken))
{
var jwt = JsonWebToken.Create(accessToken);
var claims = jwt.ExtractClaims();
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
if (!string.IsNullOrEmpty(orchestrationId))
{
Trace.Info($"Pull OrchestrationId {orchestrationId} from JWT claims");
}
}
WorkerDispatcher newDispatch = new WorkerDispatcher(jobRequestMessage.JobId, jobRequestMessage.RequestId); WorkerDispatcher newDispatch = new WorkerDispatcher(jobRequestMessage.JobId, jobRequestMessage.RequestId);
if (runOnce) if (runOnce)
{ {
Trace.Info("Start dispatcher for one time used runner."); Trace.Info("Start dispatcher for one time used runner.");
newDispatch.WorkerDispatch = RunOnceAsync(jobRequestMessage, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token); newDispatch.WorkerDispatch = RunOnceAsync(jobRequestMessage, orchestrationId, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token);
} }
else else
{ {
newDispatch.WorkerDispatch = RunAsync(jobRequestMessage, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token); newDispatch.WorkerDispatch = RunAsync(jobRequestMessage, orchestrationId, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token);
} }
_jobInfos.TryAdd(newDispatch.JobId, newDispatch); _jobInfos.TryAdd(newDispatch.JobId, newDispatch);
@@ -284,11 +300,11 @@ namespace GitHub.Runner.Listener
} }
} }
private async Task RunOnceAsync(Pipelines.AgentJobRequestMessage message, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken) private async Task RunOnceAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
{ {
try try
{ {
await RunAsync(message, previousJobDispatch, jobRequestCancellationToken, workerCancelTimeoutKillToken); await RunAsync(message, orchestrationId, previousJobDispatch, jobRequestCancellationToken, workerCancelTimeoutKillToken);
} }
finally finally
{ {
@@ -297,7 +313,7 @@ namespace GitHub.Runner.Listener
} }
} }
private async Task RunAsync(Pipelines.AgentJobRequestMessage message, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken) private async Task RunAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
{ {
Busy = true; Busy = true;
try try
@@ -328,7 +344,7 @@ namespace GitHub.Runner.Listener
// start renew job request // start renew job request
Trace.Info($"Start renew job request {requestId} for job {message.JobId}."); Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, firstJobRequestRenewed, lockRenewalTokenSource.Token); Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
// wait till first renew succeed or job request is canceled // wait till first renew succeed or job request is canceled
// not even start worker if the first renew fail // not even start worker if the first renew fail
@@ -607,7 +623,7 @@ namespace GitHub.Runner.Listener
} }
} }
public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token) public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
{ {
var runnerServer = HostContext.GetService<IRunnerServer>(); var runnerServer = HostContext.GetService<IRunnerServer>();
TaskAgentJobRequest request = null; TaskAgentJobRequest request = null;
@@ -620,7 +636,7 @@ namespace GitHub.Runner.Listener
{ {
try try
{ {
request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, token); request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId, token);
Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}"); Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}");
@@ -842,7 +858,6 @@ namespace GitHub.Runner.Listener
} }
} }
// 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) private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null)
{ {
Trace.Entering(); Trace.Entering();
@@ -936,8 +951,10 @@ namespace GitHub.Runner.Listener
ArgUtil.NotNull(timeline, nameof(timeline)); ArgUtil.NotNull(timeline, nameof(timeline));
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job"); TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
ArgUtil.NotNull(jobRecord, nameof(jobRecord)); ArgUtil.NotNull(jobRecord, nameof(jobRecord));
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
jobRecord.ErrorCount++; jobRecord.ErrorCount++;
jobRecord.Issues.Add(new Issue() { Type = IssueType.Error, Message = errorMessage }); jobRecord.Issues.Add(unhandledExceptionIssue);
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None); await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
} }
catch (Exception ex) catch (Exception ex)

View File

@@ -13,10 +13,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Services.WebApi;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Test")]
namespace GitHub.Runner.Listener namespace GitHub.Runner.Listener
{ {
[ServiceLocator(Default = typeof(MessageListener))] [ServiceLocator(Default = typeof(MessageListener))]
@@ -35,30 +32,18 @@ namespace GitHub.Runner.Listener
private ITerminal _term; private ITerminal _term;
private IRunnerServer _runnerServer; private IRunnerServer _runnerServer;
private TaskAgentSession _session; private TaskAgentSession _session;
private ICredentialManager _credMgr;
private IConfigurationStore _configStore;
private TimeSpan _getNextMessageRetryInterval; private TimeSpan _getNextMessageRetryInterval;
private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30); private readonly TimeSpan _sessionCreationRetryInterval = TimeSpan.FromSeconds(30);
private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4); private readonly TimeSpan _sessionConflictRetryLimit = TimeSpan.FromMinutes(4);
private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30); private readonly TimeSpan _clockSkewRetryLimit = TimeSpan.FromMinutes(30);
private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>(); private readonly Dictionary<string, int> _sessionCreationExceptionTracker = new Dictionary<string, int>();
// Whether load credentials from .credentials_migrated file
internal bool _useMigratedCredentials;
// need to check auth url if there is only .credentials and auth schema is OAuth
internal bool _needToCheckAuthorizationUrlUpdate;
internal Task<VssCredentials> _authorizationUrlMigrationBackgroundTask;
internal Task _authorizationUrlRollbackReattemptDelayBackgroundTask;
public override void Initialize(IHostContext hostContext) public override void Initialize(IHostContext hostContext)
{ {
base.Initialize(hostContext); base.Initialize(hostContext);
_term = HostContext.GetService<ITerminal>(); _term = HostContext.GetService<ITerminal>();
_runnerServer = HostContext.GetService<IRunnerServer>(); _runnerServer = HostContext.GetService<IRunnerServer>();
_credMgr = HostContext.GetService<ICredentialManager>();
_configStore = HostContext.GetService<IConfigurationStore>();
} }
public async Task<Boolean> CreateSessionAsync(CancellationToken token) public async Task<Boolean> CreateSessionAsync(CancellationToken token)
@@ -73,8 +58,8 @@ namespace GitHub.Runner.Listener
// Create connection. // Create connection.
Trace.Info("Loading Credentials"); Trace.Info("Loading Credentials");
_useMigratedCredentials = !StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_SPSAUTHURL")); var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials creds = _credMgr.LoadCredentials(_useMigratedCredentials); VssCredentials creds = credMgr.LoadCredentials();
var agent = new TaskAgentReference var agent = new TaskAgentReference
{ {
@@ -89,17 +74,6 @@ namespace GitHub.Runner.Listener
string errorMessage = string.Empty; string errorMessage = string.Empty;
bool encounteringError = false; bool encounteringError = false;
var originalCreds = _configStore.GetCredentials();
var migratedCreds = _configStore.GetMigratedCredentials();
if (migratedCreds == null)
{
_useMigratedCredentials = false;
if (originalCreds.Scheme == Constants.Configuration.OAuth)
{
_needToCheckAuthorizationUrlUpdate = true;
}
}
while (true) while (true)
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@@ -127,12 +101,6 @@ namespace GitHub.Runner.Listener
encounteringError = false; encounteringError = false;
} }
if (_needToCheckAuthorizationUrlUpdate)
{
// start background task try to get new authorization url
_authorizationUrlMigrationBackgroundTask = GetNewOAuthAuthorizationSetting(token);
}
return true; return true;
} }
catch (OperationCanceledException) when (token.IsCancellationRequested) catch (OperationCanceledException) when (token.IsCancellationRequested)
@@ -150,24 +118,25 @@ namespace GitHub.Runner.Listener
Trace.Error("Catch exception during create session."); Trace.Error("Catch exception during create session.");
Trace.Error(ex); Trace.Error(ex);
if (!IsSessionCreationExceptionRetriable(ex)) if (ex is VssOAuthTokenRequestException && creds.Federated is VssOAuthCredential vssOAuthCred)
{ {
if (_useMigratedCredentials) // Check whether we get 401 because the runner registration already removed by the service.
// If the runner registration get deleted, we can't exchange oauth token.
Trace.Error("Test oauth app registration.");
var oauthTokenProvider = new VssOAuthTokenProvider(vssOAuthCred, new Uri(serverUrl));
var authError = await oauthTokenProvider.ValidateCredentialAsync(token);
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
{ {
// migrated credentials might cause lose permission during permission check, _term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure.");
// we will force to use original credential and try again return false;
_useMigratedCredentials = false;
var reattemptBackoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromHours(24), TimeSpan.FromHours(36));
_authorizationUrlRollbackReattemptDelayBackgroundTask = HostContext.Delay(reattemptBackoff, token); // retry migrated creds in 24-36 hours.
creds = _credMgr.LoadCredentials(false);
Trace.Error("Fallback to original credentials and try again.");
} }
else }
if (!IsSessionCreationExceptionRetriable(ex))
{ {
_term.WriteError($"Failed to create session. {ex.Message}"); _term.WriteError($"Failed to create session. {ex.Message}");
return false; return false;
} }
}
if (!encounteringError) //print the message only on the first error if (!encounteringError) //print the message only on the first error
{ {
@@ -227,51 +196,6 @@ namespace GitHub.Runner.Listener
encounteringError = false; encounteringError = false;
continuousError = 0; continuousError = 0;
} }
if (_needToCheckAuthorizationUrlUpdate &&
_authorizationUrlMigrationBackgroundTask?.IsCompleted == true)
{
if (HostContext.GetService<IJobDispatcher>().Busy ||
HostContext.GetService<ISelfUpdater>().Busy)
{
Trace.Info("Job or runner updates in progress, update credentials next time.");
}
else
{
try
{
var newCred = await _authorizationUrlMigrationBackgroundTask;
await _runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), newCred);
Trace.Info("Updated connection to use migrated credential for next GetMessage call.");
_useMigratedCredentials = true;
_authorizationUrlMigrationBackgroundTask = null;
_needToCheckAuthorizationUrlUpdate = false;
}
catch (Exception ex)
{
Trace.Error("Fail to refresh connection with new authorization url.");
Trace.Error(ex);
}
}
}
if (_authorizationUrlRollbackReattemptDelayBackgroundTask?.IsCompleted == true)
{
try
{
// we rolled back to use original creds about 2 days before, now it's a good time to try migrated creds again.
Trace.Info("Re-attempt to use migrated credential");
var migratedCreds = _credMgr.LoadCredentials();
await _runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), migratedCreds);
_useMigratedCredentials = true;
_authorizationUrlRollbackReattemptDelayBackgroundTask = null;
}
catch (Exception ex)
{
Trace.Error("Fail to refresh connection with new authorization url on rollback reattempt.");
Trace.Error(ex);
}
}
} }
catch (OperationCanceledException) when (token.IsCancellationRequested) catch (OperationCanceledException) when (token.IsCancellationRequested)
{ {
@@ -294,23 +218,9 @@ namespace GitHub.Runner.Listener
Trace.Info($"{nameof(TaskAgentSessionExpiredException)} received, recovered by recreate session."); Trace.Info($"{nameof(TaskAgentSessionExpiredException)} received, recovered by recreate session.");
} }
else if (!IsGetNextMessageExceptionRetriable(ex)) else if (!IsGetNextMessageExceptionRetriable(ex))
{
if (_useMigratedCredentials)
{
// migrated credentials might cause lose permission during permission check,
// we will force to use original credential and try again
_useMigratedCredentials = false;
var reattemptBackoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromHours(24), TimeSpan.FromHours(36));
_authorizationUrlRollbackReattemptDelayBackgroundTask = HostContext.Delay(reattemptBackoff, token); // retry migrated creds in 24-36 hours.
var originalCreds = _credMgr.LoadCredentials(false);
await _runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), originalCreds);
Trace.Error("Fallback to original credentials and try again.");
}
else
{ {
throw; throw;
} }
}
else else
{ {
continuousError++; continuousError++;
@@ -501,80 +411,5 @@ namespace GitHub.Runner.Listener
return true; return true;
} }
} }
private async Task<VssCredentials> GetNewOAuthAuthorizationSetting(CancellationToken token)
{
Trace.Info("Start checking oauth authorization url update.");
while (true)
{
var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(45));
await HostContext.Delay(backoff, token);
try
{
var migratedAuthorizationUrl = await _runnerServer.GetRunnerAuthUrlAsync(_settings.PoolId, _settings.AgentId);
if (!string.IsNullOrEmpty(migratedAuthorizationUrl))
{
var credData = _configStore.GetCredentials();
var clientId = credData.Data.GetValueOrDefault("clientId", null);
var currentAuthorizationUrl = credData.Data.GetValueOrDefault("authorizationUrl", null);
Trace.Info($"Current authorization url: {currentAuthorizationUrl}, new authorization url: {migratedAuthorizationUrl}");
if (string.Equals(currentAuthorizationUrl, migratedAuthorizationUrl, StringComparison.OrdinalIgnoreCase))
{
// We don't need to update credentials.
Trace.Info("No needs to update authorization url");
await Task.Delay(TimeSpan.FromMilliseconds(-1), token);
}
var keyManager = HostContext.GetService<IRSAKeyManager>();
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
var migratedClientCredential = new VssOAuthJwtBearerClientCredential(clientId, migratedAuthorizationUrl, signingCredentials);
var migratedRunnerCredential = new VssOAuthCredential(new Uri(migratedAuthorizationUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, migratedClientCredential);
Trace.Info("Try connect service with Token Service OAuth endpoint.");
var runnerServer = HostContext.CreateService<IRunnerServer>();
await runnerServer.ConnectAsync(new Uri(_settings.ServerUrl), migratedRunnerCredential);
await runnerServer.GetAgentPoolsAsync();
Trace.Info($"Successfully connected service with new authorization url.");
var migratedCredData = new CredentialData
{
Scheme = Constants.Configuration.OAuth,
Data =
{
{ "clientId", clientId },
{ "authorizationUrl", migratedAuthorizationUrl },
{ "oauthEndpointUrl", migratedAuthorizationUrl },
},
};
_configStore.SaveMigratedCredential(migratedCredData);
return migratedRunnerCredential;
}
else
{
Trace.Verbose("No authorization url updates");
}
}
catch (Exception ex)
{
Trace.Error("Fail to get/test new authorization url.");
Trace.Error(ex);
try
{
await _runnerServer.ReportRunnerAuthUrlErrorAsync(_settings.PoolId, _settings.AgentId, ex.ToString());
}
catch (Exception e)
{
// best effort
Trace.Error("Fail to report the migration error");
Trace.Error(e);
}
}
}
}
} }
} }

View File

@@ -102,7 +102,9 @@ namespace GitHub.Runner.Listener
IRunner runner = context.GetService<IRunner>(); IRunner runner = context.GetService<IRunner>();
try try
{ {
return await runner.ExecuteCommand(command); var returnCode = await runner.ExecuteCommand(command);
trace.Info($"Runner execution has finished with return code {returnCode}");
return returnCode;
} }
catch (OperationCanceledException) when (context.RunnerShutdownToken.IsCancellationRequested) catch (OperationCanceledException) when (context.RunnerShutdownToken.IsCancellationRequested)
{ {

View File

@@ -37,7 +37,7 @@ namespace GitHub.Runner.Listener
{ {
try try
{ {
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy); VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
_inConfigStage = true; _inConfigStage = true;
_completedCommand.Reset(); _completedCommand.Reset();
@@ -466,6 +466,7 @@ Config Options:
--url string Repository to add the runner to. Required if unattended --url string Repository to add the runner to. Required if unattended
--token string Registration token. Required if unattended --token string Registration token. Required if unattended
--name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"}) --name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"})
--labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
--work string Relative runner work directory (default {Constants.Path.WorkDirectory}) --work string Relative runner work directory (default {Constants.Path.WorkDirectory})
--replace Replace any existing runner with the same name (default false)"); --replace Replace any existing runner with the same name (default false)");
#if OS_WINDOWS #if OS_WINDOWS
@@ -478,7 +479,9 @@ Examples:
Configure a runner non-interactively: Configure a runner non-interactively:
.{separator}config.{ext} --unattended --url <url> --token <token> .{separator}config.{ext} --unattended --url <url> --token <token>
Configure a runner non-interactively, replacing any existing runner with the same name: Configure a runner non-interactively, replacing any existing runner with the same name:
.{separator}config.{ext} --unattended --url <url> --token <token> --replace [--name <name>]"); .{separator}config.{ext} --unattended --url <url> --token <token> --replace [--name <name>]
Configure a runner non-interactively with three extra labels:
.{separator}config.{ext} --unattended --url <url> --token <token> --labels L1,L2,L3");
#if OS_WINDOWS #if OS_WINDOWS
_term.WriteLine($@" Configure a runner to run as a service:"); _term.WriteLine($@" Configure a runner to run as a service:");
_term.WriteLine($@" .{separator}config.{ext} --url <url> --token <token> --runasservice"); _term.WriteLine($@" .{separator}config.{ext} --url <url> --token <token> --runasservice");

View File

@@ -80,7 +80,12 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
// Validate args. // Validate args.
ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(executionContext, nameof(executionContext));
executionContext.Output($"Syncing repository: {repoFullName}"); executionContext.Output($"Syncing repository: {repoFullName}");
Uri repositoryUrl = new Uri($"https://github.com/{repoFullName}");
// Repository URL
var githubUrl = executionContext.GetGitHubContext("server_url");
var githubUri = new Uri(!string.IsNullOrEmpty(githubUrl) ? githubUrl : "https://github.com");
var portInfo = githubUri.IsDefaultPort ? string.Empty : $":{githubUri.Port}";
Uri repositoryUrl = new Uri($"{githubUri.Scheme}://{githubUri.Host}{portInfo}/{repoFullName}");
if (!repositoryUrl.IsAbsoluteUri) if (!repositoryUrl.IsAbsoluteUri)
{ {
throw new InvalidOperationException("Repository url need to be an absolute uri."); throw new InvalidOperationException("Repository url need to be an absolute uri.");

View File

@@ -318,7 +318,12 @@ namespace GitHub.Runner.Sdk
} }
} }
using (var registration = cancellationToken.Register(async () => await CancelAndKillProcessTree(killProcessOnCancel))) var cancellationFinished = new TaskCompletionSource<bool>();
using (var registration = cancellationToken.Register(async () =>
{
await CancelAndKillProcessTree(killProcessOnCancel);
cancellationFinished.TrySetResult(true);
}))
{ {
Trace.Info($"Process started with process id {_proc.Id}, waiting for process exit."); Trace.Info($"Process started with process id {_proc.Id}, waiting for process exit.");
while (true) while (true)
@@ -344,6 +349,13 @@ namespace GitHub.Runner.Sdk
Trace.Info($"Finished process {_proc.Id} with exit code {_proc.ExitCode}, and elapsed time {_stopWatch.Elapsed}."); Trace.Info($"Finished process {_proc.Id} with exit code {_proc.ExitCode}, and elapsed time {_stopWatch.Elapsed}.");
} }
if (cancellationToken.IsCancellationRequested)
{
// Ensure cancellation also finish on the cancellationToken.Register thread.
await cancellationFinished.Task;
Trace.Info($"Process Cancellation finished.");
}
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Wait for process to finish. // Wait for process to finish.

View File

@@ -14,10 +14,10 @@ namespace GitHub.Runner.Sdk
{ {
public static class VssUtil public static class VssUtil
{ {
public static void InitializeVssClientSettings(ProductInfoHeaderValue additionalUserAgent, IWebProxy proxy) public static void InitializeVssClientSettings(List<ProductInfoHeaderValue> additionalUserAgents, IWebProxy proxy)
{ {
var headerValues = new List<ProductInfoHeaderValue>(); var headerValues = new List<ProductInfoHeaderValue>();
headerValues.Add(additionalUserAgent); headerValues.AddRange(additionalUserAgents);
headerValues.Add(new ProductInfoHeaderValue($"({RuntimeInformation.OSDescription.Trim()})")); headerValues.Add(new ProductInfoHeaderValue($"({RuntimeInformation.OSDescription.Trim()})"));
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0) if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)

View File

@@ -485,9 +485,12 @@ namespace GitHub.Runner.Worker
} }
foreach (var property in command.Properties) foreach (var property in command.Properties)
{
if (!string.Equals(property.Key, Constants.Runner.InternalTelemetryIssueDataKey, StringComparison.OrdinalIgnoreCase))
{ {
issue.Data[property.Key] = property.Value; issue.Data[property.Key] = property.Value;
} }
}
context.AddIssue(issue); context.AddIssue(issue);
} }

View File

@@ -1,31 +1,42 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Threading; using System.Net;
using System.Threading.Tasks;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Container;
using GitHub.Services.Common; using GitHub.Services.Common;
using Newtonsoft.Json;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants; using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
namespace GitHub.Runner.Worker namespace GitHub.Runner.Worker
{ {
public class PrepareResult
{
public PrepareResult(List<JobExtensionRunner> containerSetupSteps, Dictionary<Guid, IActionRunner> preStepTracker)
{
this.ContainerSetupSteps = containerSetupSteps;
this.PreStepTracker = preStepTracker;
}
public List<JobExtensionRunner> ContainerSetupSteps { get; set; }
public Dictionary<Guid, IActionRunner> PreStepTracker { get; set; }
}
[ServiceLocator(Default = typeof(ActionManager))] [ServiceLocator(Default = typeof(ActionManager))]
public interface IActionManager : IRunnerService public interface IActionManager : IRunnerService
{ {
Dictionary<Guid, ContainerInfo> CachedActionContainers { get; } Dictionary<Guid, ContainerInfo> CachedActionContainers { get; }
Task<List<JobExtensionRunner>> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps); Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps);
Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action); Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action);
} }
@@ -35,11 +46,11 @@ namespace GitHub.Runner.Worker
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k). //81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
private const int _defaultCopyBufferSize = 81920; private const int _defaultCopyBufferSize = 81920;
private const string _dotcomApiUrl = "https://api.github.com";
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>(); private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers; public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
public async Task<List<JobExtensionRunner>> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps) public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
{ {
ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(executionContext, nameof(executionContext));
ArgUtil.NotNull(steps, nameof(steps)); ArgUtil.NotNull(steps, nameof(steps));
@@ -49,6 +60,7 @@ namespace GitHub.Runner.Worker
Dictionary<string, List<Guid>> imagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase); Dictionary<string, List<Guid>> imagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, ActionContainer> imagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase); Dictionary<string, ActionContainer> imagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase);
List<JobExtensionRunner> containerSetupSteps = new List<JobExtensionRunner>(); List<JobExtensionRunner> containerSetupSteps = new List<JobExtensionRunner>();
Dictionary<Guid, IActionRunner> preStepTracker = new Dictionary<Guid, IActionRunner>();
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>(); IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
// TODO: Deprecate the PREVIEW_ACTION_TOKEN // TODO: Deprecate the PREVIEW_ACTION_TOKEN
@@ -59,13 +71,12 @@ namespace GitHub.Runner.Worker
} }
// Clear the cache (for self-hosted runners) // Clear the cache (for self-hosted runners)
// Note, temporarily avoid this step for the on-premises product, to avoid rate limiting.
var configurationStore = HostContext.GetService<IConfigurationStore>();
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
if (isHostedServer)
{
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken); IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
}
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
var newActionMetadata = executionContext.Variables.GetBoolean("DistributedTask.NewActionMetadata") ?? false;
var repositoryActions = new List<Pipelines.ActionStep>();
foreach (var action in actions) foreach (var action in actions)
{ {
@@ -84,7 +95,8 @@ namespace GitHub.Runner.Worker
Trace.Info($"Action {action.Name} ({action.Id}) needs to pull image '{containerReference.Image}'"); Trace.Info($"Action {action.Name} ({action.Id}) needs to pull image '{containerReference.Image}'");
imagesToPull[containerReference.Image].Add(action.Id); imagesToPull[containerReference.Image].Add(action.Id);
} }
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository) // todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository && !newActionMetadata)
{ {
// only download the repository archive // only download the repository archive
await DownloadRepositoryActionAsync(executionContext, action); await DownloadRepositoryActionAsync(executionContext, action);
@@ -117,6 +129,97 @@ namespace GitHub.Runner.Worker
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo; imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
} }
} }
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
{
var definition = LoadAction(executionContext, action);
if (definition.Data.Execution.HasPre)
{
var actionRunner = HostContext.CreateService<IActionRunner>();
actionRunner.Action = action;
actionRunner.Stage = ActionRunStage.Pre;
actionRunner.Condition = definition.Data.Execution.InitCondition;
Trace.Info($"Add 'pre' execution for {action.Id}");
preStepTracker[action.Id] = actionRunner;
}
}
}
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository && newActionMetadata)
{
repositoryActions.Add(action);
}
}
if (repositoryActions.Count > 0)
{
// Get the download info
var downloadInfos = await GetDownloadInfoAsync(executionContext, repositoryActions);
// Download each action
foreach (var action in repositoryActions)
{
var lookupKey = GetDownloadInfoLookupKey(action);
if (string.IsNullOrEmpty(lookupKey))
{
continue;
}
if (!downloadInfos.TryGetValue(lookupKey, out var downloadInfo))
{
throw new Exception($"Missing download info for {lookupKey}");
}
await DownloadRepositoryActionAsync(executionContext, downloadInfo);
}
// More preparation based on content in the repository (action.yml)
foreach (var action in repositoryActions)
{
var setupInfo = PrepareRepositoryActionAsync(executionContext, action);
if (setupInfo != null)
{
if (!string.IsNullOrEmpty(setupInfo.Image))
{
if (!imagesToPull.ContainsKey(setupInfo.Image))
{
imagesToPull[setupInfo.Image] = new List<Guid>();
}
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to pull image '{setupInfo.Image}'");
imagesToPull[setupInfo.Image].Add(action.Id);
}
else
{
ArgUtil.NotNullOrEmpty(setupInfo.ActionRepository, nameof(setupInfo.ActionRepository));
if (!imagesToBuild.ContainsKey(setupInfo.ActionRepository))
{
imagesToBuild[setupInfo.ActionRepository] = new List<Guid>();
}
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to build image '{setupInfo.Dockerfile}'");
imagesToBuild[setupInfo.ActionRepository].Add(action.Id);
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
}
}
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
{
var definition = LoadAction(executionContext, action);
if (definition.Data.Execution.HasPre)
{
var actionRunner = HostContext.CreateService<IActionRunner>();
actionRunner.Action = action;
actionRunner.Stage = ActionRunStage.Pre;
actionRunner.Condition = definition.Data.Execution.InitCondition;
Trace.Info($"Add 'pre' execution for {action.Id}");
preStepTracker[action.Id] = actionRunner;
}
}
} }
} }
@@ -153,7 +256,7 @@ namespace GitHub.Runner.Worker
} }
#endif #endif
return containerSetupSteps; return new PrepareResult(containerSetupSteps, preStepTracker);
} }
public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action) public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action)
@@ -245,14 +348,19 @@ namespace GitHub.Runner.Worker
Trace.Info($"Action container env: {StringUtil.ConvertToJson(containerAction.Environment)}."); Trace.Info($"Action container env: {StringUtil.ConvertToJson(containerAction.Environment)}.");
} }
if (!string.IsNullOrEmpty(containerAction.Pre))
{
Trace.Info($"Action container pre entrypoint: {containerAction.Pre}.");
}
if (!string.IsNullOrEmpty(containerAction.EntryPoint)) if (!string.IsNullOrEmpty(containerAction.EntryPoint))
{ {
Trace.Info($"Action container entrypoint: {containerAction.EntryPoint}."); Trace.Info($"Action container entrypoint: {containerAction.EntryPoint}.");
} }
if (!string.IsNullOrEmpty(containerAction.Cleanup)) if (!string.IsNullOrEmpty(containerAction.Post))
{ {
Trace.Info($"Action container cleanup entrypoint: {containerAction.Cleanup}."); Trace.Info($"Action container post entrypoint: {containerAction.Post}.");
} }
if (CachedActionContainers.TryGetValue(action.Id, out var container)) if (CachedActionContainers.TryGetValue(action.Id, out var container))
@@ -264,8 +372,9 @@ namespace GitHub.Runner.Worker
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.NodeJS) else if (definition.Data.Execution.ExecutionType == ActionExecutionType.NodeJS)
{ {
var nodeAction = definition.Data.Execution as NodeJSActionExecutionData; var nodeAction = definition.Data.Execution as NodeJSActionExecutionData;
Trace.Info($"Action pre node.js file: {nodeAction.Pre ?? "N/A"}.");
Trace.Info($"Action node.js file: {nodeAction.Script}."); Trace.Info($"Action node.js file: {nodeAction.Script}.");
Trace.Info($"Action cleanup node.js file: {nodeAction.Cleanup ?? "N/A"}."); Trace.Info($"Action post node.js file: {nodeAction.Post ?? "N/A"}.");
} }
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Plugin) else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Plugin)
{ {
@@ -281,7 +390,7 @@ namespace GitHub.Runner.Worker
if (!string.IsNullOrEmpty(plugin.PostPluginTypeName)) if (!string.IsNullOrEmpty(plugin.PostPluginTypeName))
{ {
pluginAction.Cleanup = plugin.PostPluginTypeName; pluginAction.Post = plugin.PostPluginTypeName;
Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}."); Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}.");
} }
} }
@@ -402,7 +511,12 @@ namespace GitHub.Runner.Worker
var imageName = $"{dockerManger.DockerInstanceLabel}:{Guid.NewGuid().ToString("N")}"; var imageName = $"{dockerManger.DockerInstanceLabel}:{Guid.NewGuid().ToString("N")}";
while (retryCount < 3) while (retryCount < 3)
{ {
buildExitCode = await dockerManger.DockerBuild(executionContext, setupInfo.Container.WorkingDirectory, Directory.GetParent(setupInfo.Container.Dockerfile).FullName, imageName); buildExitCode = await dockerManger.DockerBuild(
executionContext,
setupInfo.Container.WorkingDirectory,
setupInfo.Container.Dockerfile,
Directory.GetParent(setupInfo.Container.Dockerfile).FullName,
imageName);
if (buildExitCode == 0) if (buildExitCode == 0)
{ {
break; break;
@@ -431,6 +545,127 @@ namespace GitHub.Runner.Worker
} }
} }
// This implementation is temporary and will be removed when we switch to a REST API call to the service to resolve the download info
private async Task<bool> RepoExistsAsync(IExecutionContext executionContext, Pipelines.RepositoryPathReference repositoryReference, string authorization)
{
var apiUrl = GetApiUrl(executionContext);
var repoUrl = $"{apiUrl}/repos/{repositoryReference.Name}";
for (var attempt = 1; attempt <= 3; attempt++)
{
executionContext.Debug($"Checking whether repo exists: {repoUrl}");
try
{
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
using (var httpClient = new HttpClient(httpClientHandler))
{
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(authorization);
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
using (var response = await httpClient.GetAsync(repoUrl))
{
if (response.IsSuccessStatusCode)
{
return true;
}
else if (response.StatusCode == HttpStatusCode.NotFound)
{
return false;
}
else
{
// Throw
response.EnsureSuccessStatusCode();
}
}
}
}
catch (Exception ex)
{
if (attempt < 3)
{
executionContext.Debug($"Failed checking whether repo '{repositoryReference.Name}' exists: {ex.Message}");
}
else
{
executionContext.Error($"Failed checking whether repo '{repositoryReference.Name}' exists: {ex.Message}");
throw;
}
}
}
return false; // Never reaches here
}
// This implementation is temporary and will be replaced with a REST API call to the service to resolve
private async Task<Dictionary<string, ActionDownloadInfo>> GetDownloadInfoAsync(IExecutionContext executionContext, List<Pipelines.ActionStep> actions)
{
var result = new Dictionary<string, ActionDownloadInfo>(StringComparer.OrdinalIgnoreCase);
var configurationStore = HostContext.GetService<IConfigurationStore>();
var runnerSettings = configurationStore.GetSettings();
var apiUrl = GetApiUrl(executionContext);
var accessToken = executionContext.GetGitHubContext("token");
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
var authorization = $"Basic {base64EncodingToken}";
foreach (var action in actions)
{
var lookupKey = GetDownloadInfoLookupKey(action);
if (string.IsNullOrEmpty(lookupKey) || result.ContainsKey(lookupKey))
{
continue;
}
var repositoryReference = action.Reference as Pipelines.RepositoryPathReference;
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
var downloadInfo = default(ActionDownloadInfo);
if (runnerSettings.IsHostedServer)
{
downloadInfo = new ActionDownloadInfo
{
NameWithOwner = repositoryReference.Name,
Ref = repositoryReference.Ref,
ArchiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
Authorization = authorization,
};
}
// Test whether the repo exists in the instance
else if (await RepoExistsAsync(executionContext, repositoryReference, authorization))
{
downloadInfo = new ActionDownloadInfo
{
NameWithOwner = repositoryReference.Name,
Ref = repositoryReference.Ref,
ArchiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
Authorization = authorization,
};
}
// Fallback to dotcom
else
{
downloadInfo = new ActionDownloadInfo
{
NameWithOwner = repositoryReference.Name,
Ref = repositoryReference.Ref,
ArchiveLink = BuildLinkToActionArchive(_dotcomApiUrl, repositoryReference.Name, repositoryReference.Ref),
Authorization = null,
};
}
result.Add(lookupKey, downloadInfo);
}
// Register secrets
foreach (var downloadInfo in result.Values)
{
HostContext.SecretMasker.AddValue(downloadInfo.Authorization);
}
return result;
}
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction) private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
{ {
Trace.Entering(); Trace.Entering();
@@ -454,7 +689,7 @@ namespace GitHub.Runner.Worker
ArgUtil.NotNullOrEmpty(repositoryReference.Ref, nameof(repositoryReference.Ref)); ArgUtil.NotNullOrEmpty(repositoryReference.Ref, nameof(repositoryReference.Ref));
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref); string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref);
string watermarkFile = destDirectory + ".completed"; string watermarkFile = GetWatermarkFilePath(destDirectory);
if (File.Exists(watermarkFile)) if (File.Exists(watermarkFile))
{ {
executionContext.Debug($"Action '{repositoryReference.Name}@{repositoryReference.Ref}' already downloaded at '{destDirectory}'."); executionContext.Debug($"Action '{repositoryReference.Name}@{repositoryReference.Ref}' already downloaded at '{destDirectory}'.");
@@ -468,27 +703,118 @@ namespace GitHub.Runner.Worker
executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'"); executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'");
} }
#if OS_WINDOWS var configurationStore = HostContext.GetService<IConfigurationStore>();
string archiveLink = $"https://api.github.com/repos/{repositoryReference.Name}/zipball/{repositoryReference.Ref}"; var isHostedServer = configurationStore.GetSettings().IsHostedServer;
#else if (isHostedServer)
string archiveLink = $"https://api.github.com/repos/{repositoryReference.Name}/tarball/{repositoryReference.Ref}"; {
#endif string apiUrl = GetApiUrl(executionContext);
string archiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref);
Trace.Info($"Download archive '{archiveLink}' to '{destDirectory}'."); Trace.Info($"Download archive '{archiveLink}' to '{destDirectory}'.");
var downloadDetails = new ActionDownloadDetails(archiveLink, ConfigureAuthorizationFromContext);
await DownloadRepositoryActionAsync(executionContext, downloadDetails, null, destDirectory);
return;
}
else
{
string apiUrl = GetApiUrl(executionContext);
// URLs to try:
var downloadAttempts = new List<ActionDownloadDetails> {
// A built-in action or an action the user has created, on their GHES instance
// Example: https://my-ghes/api/v3/repos/my-org/my-action/tarball/v1
new ActionDownloadDetails(
BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
ConfigureAuthorizationFromContext),
// The same action, on GitHub.com
// Example: https://api.github.com/repos/my-org/my-action/tarball/v1
new ActionDownloadDetails(
BuildLinkToActionArchive(_dotcomApiUrl, repositoryReference.Name, repositoryReference.Ref),
configureAuthorization: (e,h) => { /* no authorization for dotcom */ })
};
foreach (var downloadAttempt in downloadAttempts)
{
Trace.Info($"Download archive '{downloadAttempt.ArchiveLink}' to '{destDirectory}'.");
try
{
await DownloadRepositoryActionAsync(executionContext, downloadAttempt, null, destDirectory);
return;
}
catch (ActionNotFoundException)
{
Trace.Info($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}' at {downloadAttempt.ArchiveLink}");
continue;
}
}
throw new ActionNotFoundException($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}'. Paths attempted: {string.Join(", ", downloadAttempts.Select(d => d.ArchiveLink))}");
}
}
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadInfo downloadInfo)
{
Trace.Entering();
ArgUtil.NotNull(executionContext, nameof(executionContext));
ArgUtil.NotNull(downloadInfo, nameof(downloadInfo));
ArgUtil.NotNullOrEmpty(downloadInfo.NameWithOwner, nameof(downloadInfo.NameWithOwner));
ArgUtil.NotNullOrEmpty(downloadInfo.Ref, nameof(downloadInfo.Ref));
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), downloadInfo.NameWithOwner.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), downloadInfo.Ref);
string watermarkFile = GetWatermarkFilePath(destDirectory);
if (File.Exists(watermarkFile))
{
executionContext.Debug($"Action '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}' already downloaded at '{destDirectory}'.");
return;
}
else
{
// make sure we get a clean folder ready to use.
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
Directory.CreateDirectory(destDirectory);
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
}
Trace.Info($"Download archive '{downloadInfo.ArchiveLink}' to '{destDirectory}'.");
await DownloadRepositoryActionAsync(executionContext, null, downloadInfo, destDirectory);
}
private string GetApiUrl(IExecutionContext executionContext)
{
string apiUrl = executionContext.GetGitHubContext("api_url");
if (!string.IsNullOrEmpty(apiUrl))
{
return apiUrl;
}
// Once the api_url is set for hosted, we can remove this fallback (it doesn't make sense for GHES)
return _dotcomApiUrl;
}
private static string BuildLinkToActionArchive(string apiUrl, string repository, string @ref)
{
#if OS_WINDOWS
return $"{apiUrl}/repos/{repository}/zipball/{@ref}";
#else
return $"{apiUrl}/repos/{repository}/tarball/{@ref}";
#endif
}
// todo: Remove the parameter "actionDownloadDetails" when feature flag DistributedTask.NewActionMetadata is removed
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadDetails actionDownloadDetails, ActionDownloadInfo downloadInfo, string destDirectory)
{
//download and extract action in a temp folder and rename it on success //download and extract action in a temp folder and rename it on success
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid()); string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
Directory.CreateDirectory(tempDirectory); Directory.CreateDirectory(tempDirectory);
#if OS_WINDOWS #if OS_WINDOWS
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip"); string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip");
#else #else
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz"); string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
#endif #endif
Trace.Info($"Save archive '{archiveLink}' into {archiveFile}.");
string link = downloadInfo != null ? downloadInfo.ArchiveLink : actionDownloadDetails.ArchiveLink;
Trace.Info($"Save archive '{link}' into {archiveFile}.");
try try
{ {
int retryCount = 0; int retryCount = 0;
// Allow up to 20 * 60s for any action to be downloaded from github graph. // Allow up to 20 * 60s for any action to be downloaded from github graph.
@@ -505,37 +831,23 @@ namespace GitHub.Runner.Worker
using (var httpClientHandler = HostContext.CreateHttpClientHandler()) using (var httpClientHandler = HostContext.CreateHttpClientHandler())
using (var httpClient = new HttpClient(httpClientHandler)) using (var httpClient = new HttpClient(httpClientHandler))
{ {
var configurationStore = HostContext.GetService<IConfigurationStore>(); // Legacy
var isHostedServer = configurationStore.GetSettings().IsHostedServer; if (downloadInfo == null)
if (isHostedServer)
{ {
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN"); actionDownloadDetails.ConfigureAuthorization(executionContext, httpClient);
if (string.IsNullOrEmpty(authToken))
{
// TODO: Deprecate the PREVIEW_ACTION_TOKEN
authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN");
}
if (!string.IsNullOrEmpty(authToken))
{
HostContext.SecretMasker.AddValue(authToken);
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
} }
// FF DistributedTask.NewActionMetadata
else else
{ {
var accessToken = executionContext.GetGitHubContext("token"); httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authorization);
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
}
}
else
{
// Intentionally empty. Temporary for GHES alpha release, download from dotcom unauthenticated.
} }
httpClient.DefaultRequestHeaders.UserAgent.Add(HostContext.UserAgent); httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
using (var result = await httpClient.GetStreamAsync(archiveLink)) using (var response = await httpClient.GetAsync(link))
{
if (response.IsSuccessStatusCode)
{
using (var result = await response.Content.ReadAsStreamAsync())
{ {
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token); await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
await fs.FlushAsync(actionDownloadCancellation.Token); await fs.FlushAsync(actionDownloadCancellation.Token);
@@ -544,25 +856,42 @@ namespace GitHub.Runner.Worker
break; break;
} }
} }
else if (response.StatusCode == HttpStatusCode.NotFound)
{
// It doesn't make sense to retry in this case, so just stop
throw new ActionNotFoundException(new Uri(link));
}
else
{
// Something else bad happened, let's go to our retry logic
response.EnsureSuccessStatusCode();
}
}
}
} }
catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested) catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
{ {
Trace.Info($"Action download has been cancelled."); Trace.Info("Action download has been cancelled.");
throw;
}
catch (ActionNotFoundException)
{
Trace.Info($"The action at '{link}' does not exist");
throw; throw;
} }
catch (Exception ex) when (retryCount < 2) catch (Exception ex) when (retryCount < 2)
{ {
retryCount++; retryCount++;
Trace.Error($"Fail to download archive '{archiveLink}' -- Attempt: {retryCount}"); Trace.Error($"Fail to download archive '{link}' -- Attempt: {retryCount}");
Trace.Error(ex); Trace.Error(ex);
if (actionDownloadTimeout.Token.IsCancellationRequested) if (actionDownloadTimeout.Token.IsCancellationRequested)
{ {
// action download didn't finish within timeout // action download didn't finish within timeout
executionContext.Warning($"Action '{archiveLink}' didn't finish download within {timeoutSeconds} seconds."); executionContext.Warning($"Action '{link}' didn't finish download within {timeoutSeconds} seconds.");
} }
else else
{ {
executionContext.Warning($"Failed to download action '{archiveLink}'. Error {ex.Message}"); executionContext.Warning($"Failed to download action '{link}'. Error: {ex.Message}");
} }
} }
} }
@@ -576,7 +905,7 @@ namespace GitHub.Runner.Worker
} }
ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile)); ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile));
executionContext.Debug($"Download '{archiveLink}' to '{archiveFile}'"); executionContext.Debug($"Download '{link}' to '{archiveFile}'");
var stagingDirectory = Path.Combine(tempDirectory, "_staging"); var stagingDirectory = Path.Combine(tempDirectory, "_staging");
Directory.CreateDirectory(stagingDirectory); Directory.CreateDirectory(stagingDirectory);
@@ -626,6 +955,7 @@ namespace GitHub.Runner.Worker
} }
Trace.Verbose("Create watermark file indicate action download succeed."); Trace.Verbose("Create watermark file indicate action download succeed.");
string watermarkFile = GetWatermarkFilePath(destDirectory);
File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString()); File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString());
executionContext.Debug($"Archive '{archiveFile}' has been unzipped into '{destDirectory}'."); executionContext.Debug($"Archive '{archiveFile}' has been unzipped into '{destDirectory}'.");
@@ -650,6 +980,32 @@ namespace GitHub.Runner.Worker
} }
} }
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
private void ConfigureAuthorizationFromContext(IExecutionContext executionContext, HttpClient httpClient)
{
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
if (string.IsNullOrEmpty(authToken))
{
// TODO: Deprecate the PREVIEW_ACTION_TOKEN
authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN");
}
if (!string.IsNullOrEmpty(authToken))
{
HostContext.SecretMasker.AddValue(authToken);
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
}
else
{
var accessToken = executionContext.GetGitHubContext("token");
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
}
}
private string GetWatermarkFilePath(string directory) => directory + ".completed";
private ActionContainer PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction) private ActionContainer PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
{ {
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference; var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
@@ -755,6 +1111,72 @@ namespace GitHub.Runner.Worker
throw new InvalidOperationException($"Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '{fullPath}'. Did you forget to run actions/checkout before running your local action?"); throw new InvalidOperationException($"Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '{fullPath}'. Did you forget to run actions/checkout before running your local action?");
} }
} }
private static string GetDownloadInfoLookupKey(Pipelines.ActionStep action)
{
if (action.Reference.Type != Pipelines.ActionSourceType.Repository)
{
return null;
}
var repositoryReference = action.Reference as Pipelines.RepositoryPathReference;
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
if (string.Equals(repositoryReference.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
{
return null;
}
if (!string.Equals(repositoryReference.RepositoryType, Pipelines.RepositoryTypes.GitHub, StringComparison.OrdinalIgnoreCase))
{
throw new NotSupportedException(repositoryReference.RepositoryType);
}
ArgUtil.NotNullOrEmpty(repositoryReference.Name, nameof(repositoryReference.Name));
ArgUtil.NotNullOrEmpty(repositoryReference.Ref, nameof(repositoryReference.Ref));
return $"{repositoryReference.Name}@{repositoryReference.Ref}";
}
private static AuthenticationHeaderValue CreateAuthHeader(string authorization)
{
if (string.IsNullOrEmpty(authorization))
{
return null;
}
var split = authorization.Split(new char[] { ' ' }, 2);
if (split.Length != 2 || string.IsNullOrWhiteSpace(split[0]) || string.IsNullOrWhiteSpace(split[1]))
{
throw new Exception("Unexpected authorization header format");
}
return new AuthenticationHeaderValue(split[0].Trim(), split[1].Trim());
}
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
private class ActionDownloadDetails
{
public string ArchiveLink { get; }
public Action<IExecutionContext, HttpClient> ConfigureAuthorization { get; }
public ActionDownloadDetails(string archiveLink, Action<IExecutionContext, HttpClient> configureAuthorization)
{
ArchiveLink = archiveLink;
ConfigureAuthorization = configureAuthorization;
}
}
private class ActionDownloadInfo
{
public string NameWithOwner { get; set; }
public string Ref { get; set; }
public string ArchiveLink { get; set; }
public string Authorization { get; set; }
}
} }
public sealed class Definition public sealed class Definition
@@ -788,7 +1210,8 @@ namespace GitHub.Runner.Worker
{ {
public override ActionExecutionType ExecutionType => ActionExecutionType.Container; public override ActionExecutionType ExecutionType => ActionExecutionType.Container;
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup); public override bool HasPre => !string.IsNullOrEmpty(Pre);
public override bool HasPost => !string.IsNullOrEmpty(Post);
public string Image { get; set; } public string Image { get; set; }
@@ -798,51 +1221,66 @@ namespace GitHub.Runner.Worker
public MappingToken Environment { get; set; } public MappingToken Environment { get; set; }
public string Cleanup { get; set; } public string Pre { get; set; }
public string Post { get; set; }
} }
public sealed class NodeJSActionExecutionData : ActionExecutionData public sealed class NodeJSActionExecutionData : ActionExecutionData
{ {
public override ActionExecutionType ExecutionType => ActionExecutionType.NodeJS; public override ActionExecutionType ExecutionType => ActionExecutionType.NodeJS;
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup); public override bool HasPre => !string.IsNullOrEmpty(Pre);
public override bool HasPost => !string.IsNullOrEmpty(Post);
public string Script { get; set; } public string Script { get; set; }
public string Cleanup { get; set; } public string Pre { get; set; }
public string Post { get; set; }
} }
public sealed class PluginActionExecutionData : ActionExecutionData public sealed class PluginActionExecutionData : ActionExecutionData
{ {
public override ActionExecutionType ExecutionType => ActionExecutionType.Plugin; public override ActionExecutionType ExecutionType => ActionExecutionType.Plugin;
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup); public override bool HasPre => false;
public override bool HasPost => !string.IsNullOrEmpty(Post);
public string Plugin { get; set; } public string Plugin { get; set; }
public string Cleanup { get; set; } public string Post { get; set; }
} }
public sealed class ScriptActionExecutionData : ActionExecutionData public sealed class ScriptActionExecutionData : ActionExecutionData
{ {
public override ActionExecutionType ExecutionType => ActionExecutionType.Script; public override ActionExecutionType ExecutionType => ActionExecutionType.Script;
public override bool HasPre => false;
public override bool HasCleanup => false; public override bool HasPost => false;
} }
public abstract class ActionExecutionData public abstract class ActionExecutionData
{ {
private string _initCondition = $"{Constants.Expressions.Always}()";
private string _cleanupCondition = $"{Constants.Expressions.Always}()"; private string _cleanupCondition = $"{Constants.Expressions.Always}()";
public abstract ActionExecutionType ExecutionType { get; } public abstract ActionExecutionType ExecutionType { get; }
public abstract bool HasCleanup { get; } public abstract bool HasPre { get; }
public abstract bool HasPost { get; }
public string CleanupCondition public string CleanupCondition
{ {
get { return _cleanupCondition; } get { return _cleanupCondition; }
set { _cleanupCondition = value; } set { _cleanupCondition = value; }
} }
public string InitCondition
{
get { return _initCondition; }
set { _initCondition = value; }
}
} }
public class ContainerSetupInfo public class ContainerSetupInfo
@@ -879,4 +1317,3 @@ namespace GitHub.Runner.Worker
public string ActionRepository { get; set; } public string ActionRepository { get; set; }
} }
} }

View File

@@ -305,6 +305,9 @@ namespace GitHub.Runner.Worker
var envToken = default(MappingToken); var envToken = default(MappingToken);
var mainToken = default(StringToken); var mainToken = default(StringToken);
var pluginToken = default(StringToken); var pluginToken = default(StringToken);
var preToken = default(StringToken);
var preEntrypointToken = default(StringToken);
var preIfToken = default(StringToken);
var postToken = default(StringToken); var postToken = default(StringToken);
var postEntrypointToken = default(StringToken); var postEntrypointToken = default(StringToken);
var postIfToken = default(StringToken); var postIfToken = default(StringToken);
@@ -343,6 +346,15 @@ namespace GitHub.Runner.Worker
case "post-if": case "post-if":
postIfToken = run.Value.AssertString("post-if"); postIfToken = run.Value.AssertString("post-if");
break; break;
case "pre":
preToken = run.Value.AssertString("pre");
break;
case "pre-entrypoint":
preEntrypointToken = run.Value.AssertString("pre-entrypoint");
break;
case "pre-if":
preIfToken = run.Value.AssertString("pre-if");
break;
default: default:
Trace.Info($"Ignore run property {runsKey}."); Trace.Info($"Ignore run property {runsKey}.");
break; break;
@@ -365,7 +377,9 @@ namespace GitHub.Runner.Worker
Arguments = argsToken, Arguments = argsToken,
EntryPoint = entrypointToken?.Value, EntryPoint = entrypointToken?.Value,
Environment = envToken, Environment = envToken,
Cleanup = postEntrypointToken?.Value, Pre = preEntrypointToken?.Value,
InitCondition = preIfToken?.Value ?? "always()",
Post = postEntrypointToken?.Value,
CleanupCondition = postIfToken?.Value ?? "always()" CleanupCondition = postIfToken?.Value ?? "always()"
}; };
} }
@@ -374,14 +388,16 @@ namespace GitHub.Runner.Worker
{ {
if (string.IsNullOrEmpty(mainToken?.Value)) if (string.IsNullOrEmpty(mainToken?.Value))
{ {
throw new ArgumentNullException($"Entry javascript fils is not provided."); throw new ArgumentNullException($"Entry javascript file is not provided.");
} }
else else
{ {
return new NodeJSActionExecutionData() return new NodeJSActionExecutionData()
{ {
Script = mainToken.Value, Script = mainToken.Value,
Cleanup = postToken?.Value, Pre = preToken?.Value,
InitCondition = preIfToken?.Value ?? "always()",
Post = postToken?.Value,
CleanupCondition = postIfToken?.Value ?? "always()" CleanupCondition = postIfToken?.Value ?? "always()"
}; };
} }

View File

@@ -0,0 +1,33 @@
using System;
using System.Runtime.Serialization;
namespace GitHub.Runner.Worker
{
public class ActionNotFoundException : Exception
{
public ActionNotFoundException(Uri actionUri)
: base(FormatMessage(actionUri))
{
}
public ActionNotFoundException(string message)
: base(message)
{
}
public ActionNotFoundException(string message, System.Exception inner)
: base(message, inner)
{
}
protected ActionNotFoundException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
private static string FormatMessage(Uri actionUri)
{
return $"An action could not be found at the URI '{actionUri}'";
}
}
}

View File

@@ -18,6 +18,7 @@ namespace GitHub.Runner.Worker
{ {
public enum ActionRunStage public enum ActionRunStage
{ {
Pre,
Main, Main,
Post, Post,
} }
@@ -81,20 +82,25 @@ namespace GitHub.Runner.Worker
ActionExecutionData handlerData = definition.Data?.Execution; ActionExecutionData handlerData = definition.Data?.Execution;
ArgUtil.NotNull(handlerData, nameof(handlerData)); ArgUtil.NotNull(handlerData, nameof(handlerData));
// The action has post cleanup defined. if (handlerData.HasPre &&
// we need to create timeline record for them and add them to the step list that StepRunner is using Action.Reference is Pipelines.RepositoryPathReference repoAction &&
if (handlerData.HasCleanup && Stage == ActionRunStage.Main) string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
{ {
string postDisplayName = null; ExecutionContext.Warning($"`pre` execution is not supported for local action from '{repoAction.Path}'");
if (this.DisplayName.StartsWith(PipelineTemplateConstants.RunDisplayPrefix))
{
postDisplayName = $"Post {this.DisplayName.Substring(PipelineTemplateConstants.RunDisplayPrefix.Length)}";
}
else
{
postDisplayName = $"Post {this.DisplayName}";
} }
// The action has post cleanup defined.
// we need to create timeline record for them and add them to the step list that StepRunner is using
if (handlerData.HasPost && (Stage == ActionRunStage.Pre || Stage == ActionRunStage.Main))
{
string postDisplayName = $"Post {this.DisplayName}";
if (Stage == ActionRunStage.Pre &&
this.DisplayName.StartsWith("Pre ", StringComparison.OrdinalIgnoreCase))
{
// Trim the leading `Pre ` from the display name.
// Otherwise, we will get `Post Pre xxx` as DisplayName for the Post step.
postDisplayName = $"Post {this.DisplayName.Substring("Pre ".Length)}";
}
var repositoryReference = Action.Reference as RepositoryPathReference; var repositoryReference = Action.Reference as RepositoryPathReference;
var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}"; var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}";
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" : var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
@@ -108,7 +114,7 @@ namespace GitHub.Runner.Worker
actionRunner.Condition = handlerData.CleanupCondition; actionRunner.Condition = handlerData.CleanupCondition;
actionRunner.DisplayName = postDisplayName; actionRunner.DisplayName = postDisplayName;
ExecutionContext.RegisterPostJobStep($"{actionRunner.Action.Name}_post", actionRunner); ExecutionContext.RegisterPostJobStep(actionRunner);
} }
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>(); IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
@@ -144,8 +150,10 @@ namespace GitHub.Runner.Worker
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator(); var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions); var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
var userInputs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, string> input in inputs) foreach (KeyValuePair<string, string> input in inputs)
{ {
userInputs.Add(input.Key);
string message = ""; string message = "";
if (definition.Data?.Deprecated?.TryGetValue(input.Key, out message) == true) if (definition.Data?.Deprecated?.TryGetValue(input.Key, out message) == true)
{ {
@@ -153,13 +161,22 @@ namespace GitHub.Runner.Worker
} }
} }
var validInputs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (handlerData.ExecutionType == ActionExecutionType.Container)
{
// container action always accept 'entryPoint' and 'args' as inputs
// https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswithargs
validInputs.Add("entryPoint");
validInputs.Add("args");
}
// Merge the default inputs from the definition // Merge the default inputs from the definition
if (definition.Data?.Inputs != null) if (definition.Data?.Inputs != null)
{ {
var manifestManager = HostContext.GetService<IActionManifestManager>(); var manifestManager = HostContext.GetService<IActionManifestManager>();
foreach (var input in (definition.Data?.Inputs)) foreach (var input in definition.Data.Inputs)
{ {
string key = input.Key.AssertString("action input name").Value; string key = input.Key.AssertString("action input name").Value;
validInputs.Add(key);
if (!inputs.ContainsKey(key)) if (!inputs.ContainsKey(key))
{ {
inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value); inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value);
@@ -167,6 +184,24 @@ namespace GitHub.Runner.Worker
} }
} }
// Validate inputs only for actions with action.yml
if (Action.Reference.Type == Pipelines.ActionSourceType.Repository)
{
var unexpectedInputs = new List<string>();
foreach (var input in userInputs)
{
if (!validInputs.Contains(input))
{
unexpectedInputs.Add(input);
}
}
if (unexpectedInputs.Count > 0)
{
ExecutionContext.Warning($"Unexpected input(s) '{string.Join("', '", unexpectedInputs)}', valid inputs are ['{string.Join("', '", validInputs)}']");
}
}
// Load the action environment. // Load the action environment.
ExecutionContext.Debug("Loading env"); ExecutionContext.Debug("Loading env");
var environment = new Dictionary<String, String>(VarUtil.EnvironmentVariableKeyComparer); var environment = new Dictionary<String, String>(VarUtil.EnvironmentVariableKeyComparer);

View File

@@ -17,7 +17,7 @@ namespace GitHub.Runner.Worker.Container
string DockerInstanceLabel { get; } string DockerInstanceLabel { get; }
Task<DockerVersion> DockerVersion(IExecutionContext context); Task<DockerVersion> DockerVersion(IExecutionContext context);
Task<int> DockerPull(IExecutionContext context, string image); Task<int> DockerPull(IExecutionContext context, string image);
Task<int> DockerBuild(IExecutionContext context, string workingDirectory, string dockerFile, string tag); Task<int> DockerBuild(IExecutionContext context, string workingDirectory, string dockerFile, string dockerContext, string tag);
Task<string> DockerCreate(IExecutionContext context, ContainerInfo container); Task<string> DockerCreate(IExecutionContext context, ContainerInfo container);
Task<int> DockerRun(IExecutionContext context, ContainerInfo container, EventHandler<ProcessDataReceivedEventArgs> stdoutDataReceived, EventHandler<ProcessDataReceivedEventArgs> stderrDataReceived); Task<int> DockerRun(IExecutionContext context, ContainerInfo container, EventHandler<ProcessDataReceivedEventArgs> stdoutDataReceived, EventHandler<ProcessDataReceivedEventArgs> stderrDataReceived);
Task<int> DockerStart(IExecutionContext context, string containerId); Task<int> DockerStart(IExecutionContext context, string containerId);
@@ -87,9 +87,9 @@ namespace GitHub.Runner.Worker.Container
return await ExecuteDockerCommandAsync(context, "pull", image, context.CancellationToken); return await ExecuteDockerCommandAsync(context, "pull", image, context.CancellationToken);
} }
public async Task<int> DockerBuild(IExecutionContext context, string workingDirectory, string dockerFile, string tag) public async Task<int> DockerBuild(IExecutionContext context, string workingDirectory, string dockerFile, string dockerContext, string tag)
{ {
return await ExecuteDockerCommandAsync(context, "build", $"-t {tag} \"{dockerFile}\"", workingDirectory, context.CancellationToken); return await ExecuteDockerCommandAsync(context, "build", $"-t {tag} -f \"{dockerFile}\" \"{dockerContext}\"", workingDirectory, context.CancellationToken);
} }
public async Task<string> DockerCreate(IExecutionContext context, ContainerInfo container) public async Task<string> DockerCreate(IExecutionContext context, ContainerInfo container)

View File

@@ -49,7 +49,7 @@ namespace GitHub.Runner.Worker
data: data); data: data);
executionContext.Debug($"Register post job cleanup for stopping/deleting containers."); executionContext.Debug($"Register post job cleanup for stopping/deleting containers.");
executionContext.RegisterPostJobStep(nameof(StopContainersAsync), postJobStep); executionContext.RegisterPostJobStep(postJobStep);
// Check whether we are inside a container. // Check whether we are inside a container.
// Our container feature requires to map working directory from host to the container. // Our container feature requires to map working directory from host to the container.

View File

@@ -103,12 +103,13 @@ namespace GitHub.Runner.Worker
// others // others
void ForceTaskComplete(); void ForceTaskComplete();
void RegisterPostJobStep(string refName, IStep step); void RegisterPostJobStep(IStep step);
} }
public sealed class ExecutionContext : RunnerService, IExecutionContext public sealed class ExecutionContext : RunnerService, IExecutionContext
{ {
private const int _maxIssueCount = 10; private const int _maxIssueCount = 10;
private const int _throttlingDelayReportThreshold = 10 * 1000; // Don't report throttling with less than 10 seconds delay
private readonly TimelineRecord _record = new TimelineRecord(); private readonly TimelineRecord _record = new TimelineRecord();
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new Dictionary<Guid, TimelineRecord>(); private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new Dictionary<Guid, TimelineRecord>();
@@ -161,6 +162,9 @@ namespace GitHub.Runner.Worker
// Only job level ExecutionContext has PostJobSteps // Only job level ExecutionContext has PostJobSteps
public Stack<IStep> PostJobSteps { get; private set; } public Stack<IStep> PostJobSteps { get; private set; }
// Only job level ExecutionContext has StepsWithPostRegistered
public HashSet<Guid> StepsWithPostRegistered { get; private set; }
public bool EchoOnActionCommand { get; set; } public bool EchoOnActionCommand { get; set; }
@@ -248,9 +252,15 @@ namespace GitHub.Runner.Worker
}); });
} }
public void RegisterPostJobStep(string refName, IStep step) public void RegisterPostJobStep(IStep step)
{ {
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, refName, IntraActionState); if (step is IActionRunner actionRunner && !Root.StepsWithPostRegistered.Add(actionRunner.Action.Id))
{
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
return;
}
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, IntraActionState);
Root.PostJobSteps.Push(step); Root.PostJobSteps.Push(step);
} }
@@ -326,7 +336,7 @@ namespace GitHub.Runner.Worker
} }
// report total delay caused by server throttling. // report total delay caused by server throttling.
if (_totalThrottlingDelayInMilliseconds > 0) if (_totalThrottlingDelayInMilliseconds > _throttlingDelayReportThreshold)
{ {
this.Warning($"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling."); this.Warning($"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling.");
} }
@@ -354,14 +364,18 @@ namespace GitHub.Runner.Worker
} }
} }
if (Root != this)
{
// only dispose TokenSource for step level ExecutionContext
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
}
_logger.End(); _logger.End();
if (!string.IsNullOrEmpty(ContextName)) if (!string.IsNullOrEmpty(ContextName))
{ {
StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult().ToString()); StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult());
StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult().ToString()); StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult());
} }
return Result.Value; return Result.Value;
@@ -647,6 +661,9 @@ namespace GitHub.Runner.Worker
// PostJobSteps for job ExecutionContext // PostJobSteps for job ExecutionContext
PostJobSteps = new Stack<IStep>(); PostJobSteps = new Stack<IStep>();
// StepsWithPostRegistered for job ExecutionContext
StepsWithPostRegistered = new HashSet<Guid>();
// Job timeline record. // Job timeline record.
InitializeTimelineRecord( InitializeTimelineRecord(
timelineId: message.Timeline.Id, timelineId: message.Timeline.Id,
@@ -839,7 +856,8 @@ namespace GitHub.Runner.Worker
{ {
Interlocked.Add(ref _totalThrottlingDelayInMilliseconds, Convert.ToInt64(data.Delay.TotalMilliseconds)); Interlocked.Add(ref _totalThrottlingDelayInMilliseconds, Convert.ToInt64(data.Delay.TotalMilliseconds));
if (!_throttlingReported) if (!_throttlingReported &&
_totalThrottlingDelayInMilliseconds > _throttlingDelayReportThreshold)
{ {
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.")); 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."));
@@ -847,7 +865,7 @@ namespace GitHub.Runner.Worker
} }
} }
private IExecutionContext CreatePostChild(string displayName, string refName, Dictionary<string, string> intraActionState) private IExecutionContext CreatePostChild(string displayName, Dictionary<string, string> intraActionState)
{ {
if (!_expandedForPostJob) if (!_expandedForPostJob)
{ {
@@ -856,7 +874,8 @@ namespace GitHub.Runner.Worker
_childTimelineRecordOrder = _childTimelineRecordOrder * 2; _childTimelineRecordOrder = _childTimelineRecordOrder * 2;
} }
return CreateChild(Guid.NewGuid(), displayName, refName, null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count); var newGuid = Guid.NewGuid();
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count);
} }
} }

View File

@@ -12,6 +12,8 @@ namespace GitHub.Runner.Worker.Expressions
{ {
public sealed class HashFilesFunction : Function public sealed class HashFilesFunction : Function
{ {
private const int _hashFileTimeoutSeconds = 120;
protected sealed override Object EvaluateCore( protected sealed override Object EvaluateCore(
EvaluationContext context, EvaluationContext context,
out ResultMemory resultMemory) out ResultMemory resultMemory)
@@ -89,20 +91,30 @@ namespace GitHub.Runner.Worker.Expressions
} }
env["patterns"] = string.Join(Environment.NewLine, patterns); env["patterns"] = string.Join(Environment.NewLine, patterns);
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(_hashFileTimeoutSeconds)))
{
try
{
int exitCode = p.ExecuteAsync(workingDirectory: githubWorkspace, int exitCode = p.ExecuteAsync(workingDirectory: githubWorkspace,
fileName: node, fileName: node,
arguments: $"\"{hashFilesScript.Replace("\"", "\\\"")}\"", arguments: $"\"{hashFilesScript.Replace("\"", "\\\"")}\"",
environment: env, environment: env,
requireExitCodeZero: false, requireExitCodeZero: false,
cancellationToken: new CancellationTokenSource(TimeSpan.FromSeconds(120)).Token).GetAwaiter().GetResult(); cancellationToken: tokenSource.Token).GetAwaiter().GetResult();
if (exitCode != 0) if (exitCode != 0)
{ {
throw new InvalidOperationException($"hashFiles('{ExpressionUtility.StringEscape(string.Join(", ", patterns))}') failed. Fail to hash files under directory '{githubWorkspace}'"); throw new InvalidOperationException($"hashFiles('{ExpressionUtility.StringEscape(string.Join(", ", patterns))}') failed. Fail to hash files under directory '{githubWorkspace}'");
} }
}
catch (OperationCanceledException) when (tokenSource.IsCancellationRequested)
{
throw new TimeoutException($"hashFiles('{ExpressionUtility.StringEscape(string.Join(", ", patterns))}') couldn't finish within {_hashFileTimeoutSeconds} seconds.");
}
return hashResult; return hashResult;
} }
}
private sealed class HashFilesTrace : ITraceWriter private sealed class HashFilesTrace : ITraceWriter
{ {

View File

@@ -10,10 +10,11 @@ namespace GitHub.Runner.Worker
{ {
"action", "action",
"actor", "actor",
"api_url", // temp for GHES alpha release "api_url",
"base_ref", "base_ref",
"event_name", "event_name",
"event_path", "event_path",
"graphql_url",
"head_ref", "head_ref",
"job", "job",
"ref", "ref",
@@ -21,8 +22,8 @@ namespace GitHub.Runner.Worker
"repository_owner", "repository_owner",
"run_id", "run_id",
"run_number", "run_number",
"server_url",
"sha", "sha",
"url", // temp for GHES alpha release
"workflow", "workflow",
"workspace", "workspace",
}; };

View File

@@ -52,7 +52,12 @@ namespace GitHub.Runner.Worker.Handlers
ExecutionContext.Output($"Dockerfile for action: '{dockerFile}'."); ExecutionContext.Output($"Dockerfile for action: '{dockerFile}'.");
var imageName = $"{dockerManger.DockerInstanceLabel}:{ExecutionContext.Id.ToString("N")}"; var imageName = $"{dockerManger.DockerInstanceLabel}:{ExecutionContext.Id.ToString("N")}";
var buildExitCode = await dockerManger.DockerBuild(ExecutionContext, ExecutionContext.GetGitHubContext("workspace"), Directory.GetParent(dockerFile).FullName, imageName); var buildExitCode = await dockerManger.DockerBuild(
ExecutionContext,
ExecutionContext.GetGitHubContext("workspace"),
dockerFile,
Directory.GetParent(dockerFile).FullName,
imageName);
if (buildExitCode != 0) if (buildExitCode != 0)
{ {
throw new InvalidOperationException($"Docker build failed with exit code {buildExitCode}"); throw new InvalidOperationException($"Docker build failed with exit code {buildExitCode}");
@@ -82,9 +87,13 @@ namespace GitHub.Runner.Worker.Handlers
container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint"); container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint");
} }
} }
else if (stage == ActionRunStage.Pre)
{
container.ContainerEntryPoint = Data.Pre;
}
else if (stage == ActionRunStage.Post) else if (stage == ActionRunStage.Post)
{ {
container.ContainerEntryPoint = Data.Cleanup; container.ContainerEntryPoint = Data.Post;
} }
// create inputs context for template evaluation // create inputs context for template evaluation

View File

@@ -60,9 +60,13 @@ namespace GitHub.Runner.Worker.Handlers
{ {
target = Data.Script; target = Data.Script;
} }
else if (stage == ActionRunStage.Pre)
{
target = Data.Pre;
}
else if (stage == ActionRunStage.Post) else if (stage == ActionRunStage.Post)
{ {
target = Data.Cleanup; target = Data.Post;
} }
ArgUtil.NotNullOrEmpty(target, nameof(target)); ArgUtil.NotNullOrEmpty(target, nameof(target));

View File

@@ -352,18 +352,27 @@ namespace GitHub.Runner.Worker.Handlers
if (File.Exists(gitConfigPath)) if (File.Exists(gitConfigPath))
{ {
// Check if the config contains the workflow repository url // Check if the config contains the workflow repository url
var qualifiedRepository = _executionContext.GetGitHubContext("repository"); var serverUrl = _executionContext.GetGitHubContext("server_url");
var configMatch = $"url = https://github.com/{qualifiedRepository}"; serverUrl = !string.IsNullOrEmpty(serverUrl) ? serverUrl : "https://github.com";
var host = new Uri(serverUrl, UriKind.Absolute).Host;
var nameWithOwner = _executionContext.GetGitHubContext("repository");
var patterns = new[] {
$"url = {serverUrl}/{nameWithOwner}",
$"url = git@{host}:{nameWithOwner}.git",
};
var content = File.ReadAllText(gitConfigPath); var content = File.ReadAllText(gitConfigPath);
foreach (var line in content.Split("\n").Select(x => x.Trim())) foreach (var line in content.Split("\n").Select(x => x.Trim()))
{ {
if (String.Equals(line, configMatch, StringComparison.OrdinalIgnoreCase)) foreach (var pattern in patterns)
{
if (String.Equals(line, pattern, StringComparison.OrdinalIgnoreCase))
{ {
repositoryPath = directoryPath; repositoryPath = directoryPath;
break; break;
} }
} }
} }
}
else else
{ {
// Recursive call // Recursive call

View File

@@ -31,7 +31,7 @@ namespace GitHub.Runner.Worker.Handlers
} }
else if (stage == ActionRunStage.Post) else if (stage == ActionRunStage.Post)
{ {
plugin = Data.Cleanup; plugin = Data.Post;
} }
ArgUtil.NotNullOrEmpty(plugin, nameof(plugin)); ArgUtil.NotNullOrEmpty(plugin, nameof(plugin));

View File

@@ -259,6 +259,16 @@ namespace GitHub.Runner.Worker.Handlers
// dump out the command // dump out the command
var fileName = isContainerStepHost ? shellCommand : commandPath; var fileName = isContainerStepHost ? shellCommand : commandPath;
#if OS_OSX
if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS
{
// launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process
string node12 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}");
string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js");
arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}";
fileName = node12;
}
#endif
ExecutionContext.Debug($"{fileName} {arguments}"); ExecutionContext.Debug($"{fileName} {arguments}");
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager)) using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))

View File

@@ -110,9 +110,9 @@ namespace GitHub.Runner.Worker.Handlers
// try to resolve path inside container if the request path is part of the mount volume // try to resolve path inside container if the request path is part of the mount volume
#if OS_WINDOWS #if OS_WINDOWS
if (Container.MountVolumes.Exists(x => path.StartsWith(x.SourceVolumePath, StringComparison.OrdinalIgnoreCase))) if (Container.MountVolumes.Exists(x => !string.IsNullOrEmpty(x.SourceVolumePath) && path.StartsWith(x.SourceVolumePath, StringComparison.OrdinalIgnoreCase)))
#else #else
if (Container.MountVolumes.Exists(x => path.StartsWith(x.SourceVolumePath))) if (Container.MountVolumes.Exists(x => !string.IsNullOrEmpty(x.SourceVolumePath) && path.StartsWith(x.SourceVolumePath)))
#endif #endif
{ {
return Container.TranslateToContainerPath(path); return Container.TranslateToContainerPath(path);
@@ -149,14 +149,14 @@ namespace GitHub.Runner.Worker.Handlers
throw new NotSupportedException(msg); throw new NotSupportedException(msg);
} }
nodeExternal = "node12_alpine"; nodeExternal = "node12_alpine";
executionContext.Output($"Container distribution is alpine. Running JavaScript Action with external tool: {nodeExternal}"); executionContext.Debug($"Container distribution is alpine. Running JavaScript Action with external tool: {nodeExternal}");
return nodeExternal; return nodeExternal;
} }
} }
} }
// Optimistically use the default // Optimistically use the default
nodeExternal = "node12"; nodeExternal = "node12";
executionContext.Output($"Running JavaScript Action with default external tool: {nodeExternal}"); executionContext.Debug($"Running JavaScript Action with default external tool: {nodeExternal}");
return nodeExternal; return nodeExternal;
} }

View File

@@ -21,7 +21,7 @@ namespace GitHub.Runner.Worker
} }
set set
{ {
this["status"] = new StringContextData(value.ToString()); this["status"] = new StringContextData(value.ToString().ToLowerInvariant());
} }
} }

View File

@@ -131,12 +131,13 @@ namespace GitHub.Runner.Worker
// Temporary hack for GHES alpha // Temporary hack for GHES alpha
var configurationStore = HostContext.GetService<IConfigurationStore>(); var configurationStore = HostContext.GetService<IConfigurationStore>();
var runnerSettings = configurationStore.GetSettings(); var runnerSettings = configurationStore.GetSettings();
if (!runnerSettings.IsHostedServer && !string.IsNullOrEmpty(runnerSettings.GitHubUrl)) if (string.IsNullOrEmpty(context.GetGitHubContext("server_url")) && !runnerSettings.IsHostedServer && !string.IsNullOrEmpty(runnerSettings.GitHubUrl))
{ {
var url = new Uri(runnerSettings.GitHubUrl); var url = new Uri(runnerSettings.GitHubUrl);
var portInfo = url.IsDefaultPort ? string.Empty : $":{url.Port.ToString(CultureInfo.InvariantCulture)}"; var portInfo = url.IsDefaultPort ? string.Empty : $":{url.Port.ToString(CultureInfo.InvariantCulture)}";
context.SetGitHubContext("url", $"{url.Scheme}://{url.Host}{portInfo}"); context.SetGitHubContext("server_url", $"{url.Scheme}://{url.Host}{portInfo}");
context.SetGitHubContext("api_url", $"{url.Scheme}://{url.Host}{portInfo}/api/v3"); context.SetGitHubContext("api_url", $"{url.Scheme}://{url.Host}{portInfo}/api/v3");
context.SetGitHubContext("graphql_url", $"{url.Scheme}://{url.Host}{portInfo}/api/graphql");
} }
// Evaluate the job-level environment variables // Evaluate the job-level environment variables
@@ -197,8 +198,8 @@ namespace GitHub.Runner.Worker
// Download actions not already in the cache // Download actions not already in the cache
Trace.Info("Downloading actions"); Trace.Info("Downloading actions");
var actionManager = HostContext.GetService<IActionManager>(); var actionManager = HostContext.GetService<IActionManager>();
var prepareSteps = await actionManager.PrepareActionsAsync(context, message.Steps); var prepareResult = await actionManager.PrepareActionsAsync(context, message.Steps);
preJobSteps.AddRange(prepareSteps); preJobSteps.AddRange(prepareResult.ContainerSetupSteps);
// Add start-container steps, record and stop-container steps // Add start-container steps, record and stop-container steps
if (jobContext.Container != null || jobContext.ServiceContainers.Count > 0) if (jobContext.Container != null || jobContext.ServiceContainers.Count > 0)
@@ -239,8 +240,22 @@ namespace GitHub.Runner.Worker
actionRunner.TryEvaluateDisplayName(contextData, context); actionRunner.TryEvaluateDisplayName(contextData, context);
jobSteps.Add(actionRunner); jobSteps.Add(actionRunner);
if (prepareResult.PreStepTracker.TryGetValue(step.Id, out var preStep))
{
Trace.Info($"Adding pre-{action.DisplayName}.");
preStep.TryEvaluateDisplayName(contextData, context);
preStep.DisplayName = $"Pre {preStep.DisplayName}";
preJobSteps.Add(preStep);
} }
} }
}
var intraActionStates = new Dictionary<Guid, Dictionary<string, string>>();
foreach (var preStep in prepareResult.PreStepTracker)
{
intraActionStates[preStep.Key] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
// Create execution context for pre-job steps // Create execution context for pre-job steps
foreach (var step in preJobSteps) foreach (var step in preJobSteps)
@@ -252,6 +267,12 @@ namespace GitHub.Runner.Worker
Guid stepId = Guid.NewGuid(); Guid stepId = Guid.NewGuid();
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N")); extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"));
} }
else if (step is IActionRunner actionStep)
{
ArgUtil.NotNull(actionStep, step.DisplayName);
Guid stepId = Guid.NewGuid();
actionStep.ExecutionContext = jobContext.CreateChild(stepId, actionStep.DisplayName, stepId.ToString("N"), null, null, intraActionStates[actionStep.Action.Id]);
}
} }
// Create execution context for job steps // Create execution context for job steps
@@ -260,7 +281,8 @@ namespace GitHub.Runner.Worker
if (step is IActionRunner actionStep) if (step is IActionRunner actionStep)
{ {
ArgUtil.NotNull(actionStep, step.DisplayName); ArgUtil.NotNull(actionStep, step.DisplayName);
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName); intraActionStates.TryGetValue(actionStep.Action.Id, out var intraActionState);
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName, intraActionState);
} }
} }

View File

@@ -5,21 +5,13 @@ using GitHub.Services.Common;
using GitHub.Services.WebApi; using GitHub.Services.WebApi;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Net.Http; using System.Net.Http;
using System.Text;
using System.IO.Compression;
using System.Diagnostics;
using Newtonsoft.Json.Linq;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.ObjectTemplating;
namespace GitHub.Runner.Worker namespace GitHub.Runner.Worker
{ {
@@ -122,13 +114,6 @@ namespace GitHub.Runner.Worker
_tempDirectoryManager = HostContext.GetService<ITempDirectoryManager>(); _tempDirectoryManager = HostContext.GetService<ITempDirectoryManager>();
_tempDirectoryManager.InitializeTempDirectory(jobContext); _tempDirectoryManager.InitializeTempDirectory(jobContext);
// // Expand container properties
// jobContext.Container?.ExpandProperties(jobContext.Variables);
// foreach (var sidecar in jobContext.SidecarContainers)
// {
// sidecar.ExpandProperties(jobContext.Variables);
// }
// Get the job extension. // Get the job extension.
Trace.Info("Getting job extension."); Trace.Info("Getting job extension.");
IJobExtension jobExtension = HostContext.CreateService<IJobExtension>(); IJobExtension jobExtension = HostContext.CreateService<IJobExtension>();
@@ -254,6 +239,12 @@ namespace GitHub.Runner.Worker
Trace.Error(ex); Trace.Error(ex);
return TaskResult.Failed; return TaskResult.Failed;
} }
catch (TaskOrchestrationPlanTerminatedException ex)
{
Trace.Error($"TaskOrchestrationPlanTerminatedException received, while attempting to raise JobCompletedEvent for job {message.JobId}.");
Trace.Error(ex);
return TaskResult.Failed;
}
catch (Exception ex) catch (Exception ex)
{ {
Trace.Error($"Catch exception while attempting to raise JobCompletedEvent for job {message.JobId}, job request {message.RequestId}."); Trace.Error($"Catch exception while attempting to raise JobCompletedEvent for job {message.JobId}, job request {message.RequestId}.");

View File

@@ -59,19 +59,19 @@ namespace GitHub.Runner.Worker
public void SetConclusion( public void SetConclusion(
string scopeName, string scopeName,
string stepName, string stepName,
string conclusion) ActionResult conclusion)
{ {
var step = GetStep(scopeName, stepName); var step = GetStep(scopeName, stepName);
step["conclusion"] = new StringContextData(conclusion); step["conclusion"] = new StringContextData(conclusion.ToString().ToLowerInvariant());
} }
public void SetOutcome( public void SetOutcome(
string scopeName, string scopeName,
string stepName, string stepName,
string outcome) ActionResult outcome)
{ {
var step = GetStep(scopeName, stepName); var step = GetStep(scopeName, stepName);
step["outcome"] = new StringContextData(outcome); step["outcome"] = new StringContextData(outcome.ToString().ToLowerInvariant());
} }
private DictionaryContextData GetStep(string scopeName, string stepName) private DictionaryContextData GetStep(string scopeName, string stepName)

View File

@@ -98,11 +98,14 @@ namespace GitHub.Runner.Worker
envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty); envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
} }
bool evaluateStepEnvFailed = false;
if (step is IActionRunner actionStep) if (step is IActionRunner actionStep)
{ {
// Set GITHUB_ACTION // Set GITHUB_ACTION
step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name); step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name);
try
{
// Evaluate and merge action's env block to env context // Evaluate and merge action's env block to env context
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(); var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer); var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
@@ -111,7 +114,18 @@ namespace GitHub.Runner.Worker
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty); envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
} }
} }
catch (Exception ex)
{
// fail the step since there is an evaluate error.
Trace.Info("Caught exception from expression for step.env");
evaluateStepEnvFailed = true;
step.ExecutionContext.Error(ex);
CompleteStep(step, nextStep, TaskResult.Failed);
}
}
if (!evaluateStepEnvFailed)
{
try try
{ {
// Register job cancellation call back only if job cancellation token not been fire before each step run // Register job cancellation call back only if job cancellation token not been fire before each step run
@@ -219,6 +233,7 @@ namespace GitHub.Runner.Worker
} }
} }
} }
}
// Update the job result. // Update the job result.
if (step.ExecutionContext.Result == TaskResult.Failed) if (step.ExecutionContext.Result == TaskResult.Failed)

View File

@@ -40,7 +40,7 @@ namespace GitHub.Runner.Worker
// Validate args. // Validate args.
ArgUtil.NotNullOrEmpty(pipeIn, nameof(pipeIn)); ArgUtil.NotNullOrEmpty(pipeIn, nameof(pipeIn));
ArgUtil.NotNullOrEmpty(pipeOut, nameof(pipeOut)); ArgUtil.NotNullOrEmpty(pipeOut, nameof(pipeOut));
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy); VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
var jobRunner = HostContext.CreateService<IJobRunner>(); var jobRunner = HostContext.CreateService<IJobRunner>();
using (var channel = HostContext.CreateService<IProcessChannel>()) using (var channel = HostContext.CreateService<IProcessChannel>())

View File

@@ -43,6 +43,8 @@
"entrypoint": "non-empty-string", "entrypoint": "non-empty-string",
"args": "container-runs-args", "args": "container-runs-args",
"env": "container-runs-env", "env": "container-runs-env",
"pre-entrypoint": "non-empty-string",
"pre-if": "non-empty-string",
"post-entrypoint": "non-empty-string", "post-entrypoint": "non-empty-string",
"post-if": "non-empty-string" "post-if": "non-empty-string"
} }
@@ -67,6 +69,8 @@
"properties": { "properties": {
"using": "non-empty-string", "using": "non-empty-string",
"main": "non-empty-string", "main": "non-empty-string",
"pre": "non-empty-string",
"pre-if": "non-empty-string",
"post": "non-empty-string", "post": "non-empty-string",
"post-if": "non-empty-string" "post-if": "non-empty-string"
} }

View File

@@ -82,7 +82,7 @@ namespace GitHub.DistributedTask.WebApi
httpMethod, httpMethod,
locationId, locationId,
routeValues: routeValues, routeValues: routeValues,
version: new ApiResourceVersion(5.1, 1), version: new ApiResourceVersion(6.0, 2),
userState: userState, userState: userState,
cancellationToken: cancellationToken, cancellationToken: cancellationToken,
content: content); content: content);
@@ -109,7 +109,7 @@ namespace GitHub.DistributedTask.WebApi
httpMethod, httpMethod,
locationId, locationId,
routeValues: routeValues, routeValues: routeValues,
version: new ApiResourceVersion(5.1, 1), version: new ApiResourceVersion(6.0, 2),
userState: userState, userState: userState,
cancellationToken: cancellationToken).ConfigureAwait(false)) cancellationToken: cancellationToken).ConfigureAwait(false))
{ {
@@ -164,7 +164,7 @@ namespace GitHub.DistributedTask.WebApi
httpMethod, httpMethod,
locationId, locationId,
routeValues: routeValues, routeValues: routeValues,
version: new ApiResourceVersion(5.1, 1), version: new ApiResourceVersion(6.0, 2),
queryParameters: queryParams, queryParameters: queryParams,
userState: userState, userState: userState,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
@@ -227,7 +227,7 @@ namespace GitHub.DistributedTask.WebApi
httpMethod, httpMethod,
locationId, locationId,
routeValues: routeValues, routeValues: routeValues,
version: new ApiResourceVersion(5.1, 1), version: new ApiResourceVersion(6.0, 2),
queryParameters: queryParams, queryParameters: queryParams,
userState: userState, userState: userState,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
@@ -257,7 +257,7 @@ namespace GitHub.DistributedTask.WebApi
httpMethod, httpMethod,
locationId, locationId,
routeValues: routeValues, routeValues: routeValues,
version: new ApiResourceVersion(5.1, 1), version: new ApiResourceVersion(6.0, 2),
userState: userState, userState: userState,
cancellationToken: cancellationToken, cancellationToken: cancellationToken,
content: content); content: content);
@@ -287,7 +287,7 @@ namespace GitHub.DistributedTask.WebApi
httpMethod, httpMethod,
locationId, locationId,
routeValues: routeValues, routeValues: routeValues,
version: new ApiResourceVersion(5.1, 1), version: new ApiResourceVersion(6.0, 2),
userState: userState, userState: userState,
cancellationToken: cancellationToken, cancellationToken: cancellationToken,
content: content); content: content);

View File

@@ -60,6 +60,20 @@ namespace GitHub.DistributedTask.Logging
return SecurityElement.Escape(value); return SecurityElement.Escape(value);
} }
public static String TrimDoubleQuotes(String value)
{
var trimmed = string.Empty;
if (!string.IsNullOrEmpty(value) &&
value.Length > 8 &&
value.StartsWith('"') &&
value.EndsWith('"'))
{
trimmed = value.Substring(1, value.Length - 2);
}
return trimmed;
}
private static string Base64StringEscapeShift(String value, int shift) private static string Base64StringEscapeShift(String value, int shift)
{ {
var bytes = Encoding.UTF8.GetBytes(value); var bytes = Encoding.UTF8.GetBytes(value);

View File

@@ -0,0 +1,59 @@
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace GitHub.DistributedTask.WebApi
{
[DataContract]
public class AgentLabel
{
[JsonConstructor]
public AgentLabel()
{
}
public AgentLabel(string name)
{
this.Name = name;
this.Type = LabelType.System;
}
public AgentLabel(string name, LabelType type)
{
this.Name = name;
this.Type = type;
}
private AgentLabel(AgentLabel labelToBeCloned)
{
this.Id = labelToBeCloned.Id;
this.Name = labelToBeCloned.Name;
this.Type = labelToBeCloned.Type;
}
[DataMember]
public int Id
{
get;
set;
}
[DataMember]
public string Name
{
get;
set;
}
[DataMember]
public LabelType Type
{
get;
set;
}
public AgentLabel Clone()
{
return new AgentLabel(this);
}
}
}

View File

@@ -0,0 +1,14 @@
using System.Runtime.Serialization;
namespace GitHub.DistributedTask.WebApi
{
[DataContract]
public enum LabelType
{
[EnumMember]
System = 0,
[EnumMember]
User = 1
}
}

View File

@@ -51,7 +51,7 @@ namespace GitHub.DistributedTask.WebApi
if (agentToBeCloned.m_labels != null && agentToBeCloned.m_labels.Count > 0) if (agentToBeCloned.m_labels != null && agentToBeCloned.m_labels.Count > 0)
{ {
m_labels = new HashSet<string>(agentToBeCloned.m_labels, StringComparer.OrdinalIgnoreCase); m_labels = new HashSet<AgentLabel>(agentToBeCloned.m_labels);
} }
} }
@@ -118,13 +118,13 @@ namespace GitHub.DistributedTask.WebApi
/// <summary> /// <summary>
/// The labels of the runner /// The labels of the runner
/// </summary> /// </summary>
public ISet<string> Labels public ISet<AgentLabel> Labels
{ {
get get
{ {
if (m_labels == null) if (m_labels == null)
{ {
m_labels = new HashSet<string>(StringComparer.OrdinalIgnoreCase); m_labels = new HashSet<AgentLabel>();
} }
return m_labels; return m_labels;
} }
@@ -164,6 +164,6 @@ namespace GitHub.DistributedTask.WebApi
private PropertiesCollection m_properties; private PropertiesCollection m_properties;
[DataMember(IsRequired = false, EmitDefaultValue = false, Name = "Labels")] [DataMember(IsRequired = false, EmitDefaultValue = false, Name = "Labels")]
private HashSet<string> m_labels; private HashSet<AgentLabel> m_labels;
} }
} }

View File

@@ -95,6 +95,7 @@ namespace GitHub.DistributedTask.WebApi
Int64 requestId, Int64 requestId,
Guid lockToken, Guid lockToken,
DateTime? expiresOn = null, DateTime? expiresOn = null,
string orchestrationId = null,
Object userState = null, Object userState = null,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
@@ -104,7 +105,30 @@ namespace GitHub.DistributedTask.WebApi
LockedUntil = expiresOn, LockedUntil = expiresOn,
}; };
return UpdateAgentRequestAsync(poolId, requestId, lockToken, request, userState, cancellationToken); var additionalHeaders = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(orchestrationId))
{
additionalHeaders["X-VSS-OrchestrationId"] = orchestrationId;
}
HttpMethod httpMethod = new HttpMethod("PATCH");
Guid locationId = new Guid("fc825784-c92a-4299-9221-998a02d1b54f");
object routeValues = new { poolId = poolId, requestId = requestId };
HttpContent content = new ObjectContent<TaskAgentJobRequest>(request, new VssJsonMediaTypeFormatter(true));
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
queryParams.Add("lockToken", lockToken.ToString());
return SendAsync<TaskAgentJobRequest>(
httpMethod,
additionalHeaders,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(5.1, 1),
queryParameters: queryParams,
userState: userState,
cancellationToken: cancellationToken,
content: content);
} }
public Task<TaskAgent> ReplaceAgentAsync( public Task<TaskAgent> ReplaceAgentAsync(

View File

@@ -96,7 +96,7 @@ namespace GitHub.Services.WebApi.Jwt
return ret; return ret;
} }
internal static IEnumerable<Claim> ExtractClaims(this JsonWebToken token) public static IEnumerable<Claim> ExtractClaims(this JsonWebToken token)
{ {
ArgumentUtility.CheckForNull(token, nameof(token)); ArgumentUtility.CheckForNull(token, nameof(token));

View File

@@ -119,6 +119,15 @@ namespace GitHub.Services.OAuth
} }
} }
public async Task<string> ValidateCredentialAsync(CancellationToken cancellationToken)
{
var tokenHttpClient = new VssOAuthTokenHttpClient(this.SignInUrl);
var tokenResponse = await tokenHttpClient.GetTokenAsync(this.Grant, this.ClientCredential, this.TokenParameters, cancellationToken);
// return the underlying authentication error
return tokenResponse.Error;
}
/// <summary> /// <summary>
/// Issues a token request to the configured secure token service. On success, the access token issued by the /// Issues a token request to the configured secure token service. On success, the access token issued by the
/// token service is returned to the caller /// token service is returned to the caller

View File

@@ -85,6 +85,8 @@ namespace GitHub.Runner.Common.Tests
_hc.SecretMasker.AddValue("Pass word 123!"); _hc.SecretMasker.AddValue("Pass word 123!");
_hc.SecretMasker.AddValue("Pass<word>123!"); _hc.SecretMasker.AddValue("Pass<word>123!");
_hc.SecretMasker.AddValue("Pass'word'123!"); _hc.SecretMasker.AddValue("Pass'word'123!");
_hc.SecretMasker.AddValue("\"Password123!!\"");
_hc.SecretMasker.AddValue("\"short\"");
// Assert. // Assert.
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Password123!123")); Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Password123!123"));
@@ -99,6 +101,9 @@ namespace GitHub.Runner.Common.Tests
Assert.Equal("YWJjOlBh***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abc:Password123!")))); Assert.Equal("YWJjOlBh***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abc:Password123!"))));
Assert.Equal("YWJjZDpQ***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abcd:Password123!")))); Assert.Equal("YWJjZDpQ***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abcd:Password123!"))));
Assert.Equal("YWJjZGU6***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abcde:Password123!")))); Assert.Equal("YWJjZGU6***", _hc.SecretMasker.MaskSecrets(Convert.ToBase64String(Encoding.UTF8.GetBytes($"abcde:Password123!"))));
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123Password123!!123"));
Assert.Equal("123short123", _hc.SecretMasker.MaskSecrets("123short123"));
Assert.Equal("123***123", _hc.SecretMasker.MaskSecrets("123\"short\"123"));
} }
finally finally
{ {

View File

@@ -317,7 +317,8 @@ namespace GitHub.Runner.Common.Tests
false, // secret false, // secret
Environment.MachineName, // defaultValue Environment.MachineName, // defaultValue
Validators.NonEmptyValidator, // validator Validators.NonEmptyValidator, // validator
true)) // unattended true, // unattended
false)) // isOptional
.Returns("some runner"); .Returns("some runner");
// Act. // Act.
@@ -344,7 +345,8 @@ namespace GitHub.Runner.Common.Tests
false, // secret false, // secret
Environment.MachineName, // defaultValue Environment.MachineName, // defaultValue
Validators.NonEmptyValidator, // validator Validators.NonEmptyValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some runner"); .Returns("some runner");
// Act. // Act.
@@ -371,7 +373,8 @@ namespace GitHub.Runner.Common.Tests
false, // secret false, // secret
"some default auth", // defaultValue "some default auth", // defaultValue
Validators.AuthSchemeValidator, // validator Validators.AuthSchemeValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some auth"); .Returns("some auth");
// Act. // Act.
@@ -398,7 +401,8 @@ namespace GitHub.Runner.Common.Tests
true, // secret true, // secret
string.Empty, // defaultValue string.Empty, // defaultValue
Validators.NonEmptyValidator, // validator Validators.NonEmptyValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some token"); .Returns("some token");
// Act. // Act.
@@ -475,7 +479,8 @@ namespace GitHub.Runner.Common.Tests
true, // secret true, // secret
string.Empty, // defaultValue string.Empty, // defaultValue
Validators.NonEmptyValidator, // validator Validators.NonEmptyValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some token"); .Returns("some token");
// Act. // Act.
@@ -502,7 +507,8 @@ namespace GitHub.Runner.Common.Tests
true, // secret true, // secret
string.Empty, // defaultValue string.Empty, // defaultValue
Validators.NonEmptyValidator, // validator Validators.NonEmptyValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some token"); .Returns("some token");
// Act. // Act.
@@ -529,7 +535,8 @@ namespace GitHub.Runner.Common.Tests
false, // secret false, // secret
string.Empty, // defaultValue string.Empty, // defaultValue
Validators.ServerUrlValidator, // validator Validators.ServerUrlValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some url"); .Returns("some url");
// Act. // Act.
@@ -556,7 +563,8 @@ namespace GitHub.Runner.Common.Tests
false, // secret false, // secret
"some default account", // defaultValue "some default account", // defaultValue
Validators.NTAccountValidator, // validator Validators.NTAccountValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some windows logon account"); .Returns("some windows logon account");
// Act. // Act.
@@ -584,7 +592,8 @@ namespace GitHub.Runner.Common.Tests
true, // secret true, // secret
string.Empty, // defaultValue string.Empty, // defaultValue
Validators.NonEmptyValidator, // validator Validators.NonEmptyValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some windows logon password"); .Returns("some windows logon password");
// Act. // Act.
@@ -611,7 +620,8 @@ namespace GitHub.Runner.Common.Tests
false, // secret false, // secret
"_work", // defaultValue "_work", // defaultValue
Validators.NonEmptyValidator, // validator Validators.NonEmptyValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some work"); .Returns("some work");
// Act. // Act.
@@ -640,7 +650,8 @@ namespace GitHub.Runner.Common.Tests
false, // secret false, // secret
string.Empty, // defaultValue string.Empty, // defaultValue
Validators.ServerUrlValidator, // validator Validators.ServerUrlValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some url"); .Returns("some url");
// Act. // Act.
@@ -669,7 +680,8 @@ namespace GitHub.Runner.Common.Tests
false, // secret false, // secret
string.Empty, // defaultValue string.Empty, // defaultValue
Validators.ServerUrlValidator, // validator Validators.ServerUrlValidator, // validator
false)) // unattended false, // unattended
false)) // isOptional
.Returns("some url"); .Returns("some url");
// Act. // Act.

View File

@@ -145,6 +145,8 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
IConfigurationManager configManager = new ConfigurationManager(); IConfigurationManager configManager = new ConfigurationManager();
configManager.Initialize(tc); configManager.Initialize(tc);
var userLabels = "userlabel1,userlabel2";
trace.Info("Preparing command line arguments"); trace.Info("Preparing command line arguments");
var command = new CommandSettings( var command = new CommandSettings(
tc, tc,
@@ -156,7 +158,8 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
"--pool", _expectedPoolName, "--pool", _expectedPoolName,
"--work", _expectedWorkFolder, "--work", _expectedWorkFolder,
"--auth", _expectedAuthType, "--auth", _expectedAuthType,
"--token", _expectedToken "--token", _expectedToken,
"--labels", userLabels
}); });
trace.Info("Constructed."); trace.Info("Constructed.");
_store.Setup(x => x.IsConfigured()).Returns(false); _store.Setup(x => x.IsConfigured()).Returns(false);
@@ -178,7 +181,10 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
// validate GetAgentPoolsAsync gets called twice with automation pool type // validate GetAgentPoolsAsync gets called twice with automation pool type
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2)); _runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2));
_runnerServer.Verify(x => x.AddAgentAsync(It.IsAny<int>(), It.Is<TaskAgent>(a => a.Labels.Contains("self-hosted") && a.Labels.Contains(VarUtil.OS) && a.Labels.Contains(VarUtil.OSArchitecture))), Times.Once); var expectedLabels = new List<string>() { "self-hosted", VarUtil.OS, VarUtil.OSArchitecture};
expectedLabels.AddRange(userLabels.Split(",").ToList());
_runnerServer.Verify(x => x.AddAgentAsync(It.IsAny<int>(), It.Is<TaskAgent>(a => a.Labels.Select(x => x.Name).ToHashSet().SetEquals(expectedLabels))), Times.Once);
} }
} }
} }

View File

@@ -73,7 +73,7 @@ namespace GitHub.Runner.Common.Tests.Listener
Assert.NotNull(sessionIdProperty); Assert.NotNull(sessionIdProperty);
sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5)); sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request)); _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request));
_runnerServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest())); _runnerServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest()));
@@ -112,7 +112,7 @@ namespace GitHub.Runner.Common.Tests.Listener
hc.SetSingleton<IRunnerServer>(_runnerServer.Object); hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object); hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 }); _configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())) _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => .Returns(() =>
{ {
count++; count++;
@@ -139,10 +139,10 @@ namespace GitHub.Runner.Common.Tests.Listener
var jobDispatcher = new JobDispatcher(); var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc); jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token); await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully); Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully);
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5)); _runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
} }
} }
@@ -170,7 +170,7 @@ namespace GitHub.Runner.Common.Tests.Listener
hc.SetSingleton<IRunnerServer>(_runnerServer.Object); hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object); hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 }); _configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())) _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => .Returns(() =>
{ {
count++; count++;
@@ -197,11 +197,11 @@ namespace GitHub.Runner.Common.Tests.Listener
var jobDispatcher = new JobDispatcher(); var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc); jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token); await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed."); Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
Assert.False(cancellationTokenSource.IsCancellationRequested); Assert.False(cancellationTokenSource.IsCancellationRequested);
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5)); _runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
} }
} }
@@ -229,7 +229,7 @@ namespace GitHub.Runner.Common.Tests.Listener
hc.SetSingleton<IRunnerServer>(_runnerServer.Object); hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object); hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 }); _configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())) _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => .Returns(() =>
{ {
count++; count++;
@@ -256,11 +256,11 @@ namespace GitHub.Runner.Common.Tests.Listener
var jobDispatcher = new JobDispatcher(); var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc); jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token); await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed."); Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
Assert.False(cancellationTokenSource.IsCancellationRequested); Assert.False(cancellationTokenSource.IsCancellationRequested);
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5)); _runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
} }
} }
@@ -288,7 +288,7 @@ namespace GitHub.Runner.Common.Tests.Listener
hc.SetSingleton<IRunnerServer>(_runnerServer.Object); hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object); hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 }); _configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())) _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => .Returns(() =>
{ {
count++; count++;
@@ -315,11 +315,11 @@ namespace GitHub.Runner.Common.Tests.Listener
var jobDispatcher = new JobDispatcher(); var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc); jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token); await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed."); Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
Assert.True(cancellationTokenSource.IsCancellationRequested); Assert.True(cancellationTokenSource.IsCancellationRequested);
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(8)); _runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(8));
_runnerServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3)); _runnerServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3));
_runnerServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Once); _runnerServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Once);
} }
@@ -349,7 +349,7 @@ namespace GitHub.Runner.Common.Tests.Listener
hc.SetSingleton<IRunnerServer>(_runnerServer.Object); hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object); hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 }); _configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())) _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => .Returns(() =>
{ {
count++; count++;
@@ -372,11 +372,11 @@ namespace GitHub.Runner.Common.Tests.Listener
var jobDispatcher = new JobDispatcher(); var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc); jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token); await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.False(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should failed."); Assert.False(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should failed.");
Assert.False(cancellationTokenSource.IsCancellationRequested); Assert.False(cancellationTokenSource.IsCancellationRequested);
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(6)); _runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(6));
} }
} }
@@ -404,7 +404,7 @@ namespace GitHub.Runner.Common.Tests.Listener
hc.SetSingleton<IRunnerServer>(_runnerServer.Object); hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object); hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 }); _configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())) _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(() => .Returns(() =>
{ {
count++; count++;
@@ -436,11 +436,11 @@ namespace GitHub.Runner.Common.Tests.Listener
var jobDispatcher = new JobDispatcher(); var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc); jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token); await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed."); Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
Assert.False(cancellationTokenSource.IsCancellationRequested); Assert.False(cancellationTokenSource.IsCancellationRequested);
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5)); _runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
_runnerServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3)); _runnerServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3));
_runnerServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Never); _runnerServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Never);
} }
@@ -481,7 +481,7 @@ namespace GitHub.Runner.Common.Tests.Listener
Assert.NotNull(sessionIdProperty); Assert.NotNull(sessionIdProperty);
sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5)); sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request)); _runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request));
_runnerServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest())); _runnerServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest()));

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,9 @@ namespace GitHub.Runner.Common.Tests
private static readonly List<string> SkippedFiles = new List<string>() private static readonly List<string> SkippedFiles = new List<string>()
{ {
"Runner.Common\\HostContext.cs", "Runner.Common\\HostContext.cs",
"Runner.Common/HostContext.cs" "Runner.Common/HostContext.cs",
"Runner.Common\\HttpClientHandlerFactory.cs",
"Runner.Common/HttpClientHandlerFactory.cs"
}; };
[Fact] [Fact]

View File

@@ -86,7 +86,7 @@ namespace GitHub.Runner.Common.Tests
} }
} }
public ProductInfoHeaderValue UserAgent => new ProductInfoHeaderValue("L0Test", "0.0"); public List<ProductInfoHeaderValue> UserAgents => new List<ProductInfoHeaderValue>() { new ProductInfoHeaderValue("L0Test", "0.0") };
public RunnerWebProxy WebProxy => new RunnerWebProxy(); public RunnerWebProxy WebProxy => new RunnerWebProxy();

File diff suppressed because it is too large Load Diff

View File

@@ -65,6 +65,52 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_ContainerAction_Dockerfile_Pre()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction_init.yml"));
//Assert
Assert.Equal("Hello World", result.Name);
Assert.Equal("Greet the world and record the time", result.Description);
Assert.Equal(2, result.Inputs.Count);
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
Assert.Equal(ActionExecutionType.Container, result.Execution.ExecutionType);
var containerAction = result.Execution as ContainerActionExecutionData;
Assert.Equal("Dockerfile", containerAction.Image);
Assert.Equal("main.sh", containerAction.EntryPoint);
Assert.Equal("init.sh", containerAction.Pre);
Assert.Equal("success()", containerAction.InitCondition);
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
Assert.Equal("foo", containerAction.Environment[0].Value.ToString());
Assert.Equal("Url", containerAction.Environment[1].Key.ToString());
Assert.Equal("bar", containerAction.Environment[1].Value.ToString());
}
finally
{
Teardown();
}
}
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [Trait("Category", "Worker")]
@@ -97,7 +143,7 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal("Dockerfile", containerAction.Image); Assert.Equal("Dockerfile", containerAction.Image);
Assert.Equal("main.sh", containerAction.EntryPoint); Assert.Equal("main.sh", containerAction.EntryPoint);
Assert.Equal("cleanup.sh", containerAction.Cleanup); Assert.Equal("cleanup.sh", containerAction.Post);
Assert.Equal("failure()", containerAction.CleanupCondition); Assert.Equal("failure()", containerAction.CleanupCondition);
Assert.Equal("bzz", containerAction.Arguments[0].ToString()); Assert.Equal("bzz", containerAction.Arguments[0].ToString());
Assert.Equal("Token", containerAction.Environment[0].Key.ToString()); Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
@@ -111,6 +157,52 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_ContainerAction_Dockerfile_Pre_DefaultCondition()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction_init_default.yml"));
//Assert
Assert.Equal("Hello World", result.Name);
Assert.Equal("Greet the world and record the time", result.Description);
Assert.Equal(2, result.Inputs.Count);
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
Assert.Equal(ActionExecutionType.Container, result.Execution.ExecutionType);
var containerAction = result.Execution as ContainerActionExecutionData;
Assert.Equal("Dockerfile", containerAction.Image);
Assert.Equal("main.sh", containerAction.EntryPoint);
Assert.Equal("init.sh", containerAction.Pre);
Assert.Equal("always()", containerAction.InitCondition);
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
Assert.Equal("foo", containerAction.Environment[0].Value.ToString());
Assert.Equal("Url", containerAction.Environment[1].Key.ToString());
Assert.Equal("bar", containerAction.Environment[1].Value.ToString());
}
finally
{
Teardown();
}
}
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [Trait("Category", "Worker")]
@@ -143,7 +235,7 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal("Dockerfile", containerAction.Image); Assert.Equal("Dockerfile", containerAction.Image);
Assert.Equal("main.sh", containerAction.EntryPoint); Assert.Equal("main.sh", containerAction.EntryPoint);
Assert.Equal("cleanup.sh", containerAction.Cleanup); Assert.Equal("cleanup.sh", containerAction.Post);
Assert.Equal("always()", containerAction.CleanupCondition); Assert.Equal("always()", containerAction.CleanupCondition);
Assert.Equal("bzz", containerAction.Arguments[0].ToString()); Assert.Equal("bzz", containerAction.Arguments[0].ToString());
Assert.Equal("Token", containerAction.Environment[0].Key.ToString()); Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
@@ -323,6 +415,94 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_NodeAction_Pre()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "nodeaction_init.yml"));
//Assert
Assert.Equal("Hello World", result.Name);
Assert.Equal("Greet the world and record the time", result.Description);
Assert.Equal(2, result.Inputs.Count);
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
Assert.Equal(1, result.Deprecated.Count);
Assert.True(result.Deprecated.ContainsKey("greeting"));
result.Deprecated.TryGetValue("greeting", out string value);
Assert.Equal("This property has been deprecated", value);
Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType);
var nodeAction = result.Execution as NodeJSActionExecutionData;
Assert.Equal("main.js", nodeAction.Script);
Assert.Equal("init.js", nodeAction.Pre);
Assert.Equal("cancelled()", nodeAction.InitCondition);
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_NodeAction_Init_DefaultCondition()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "nodeaction_init_default.yml"));
//Assert
Assert.Equal("Hello World", result.Name);
Assert.Equal("Greet the world and record the time", result.Description);
Assert.Equal(2, result.Inputs.Count);
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
Assert.Equal(1, result.Deprecated.Count);
Assert.True(result.Deprecated.ContainsKey("greeting"));
result.Deprecated.TryGetValue("greeting", out string value);
Assert.Equal("This property has been deprecated", value);
Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType);
var nodeAction = result.Execution as NodeJSActionExecutionData;
Assert.Equal("main.js", nodeAction.Script);
Assert.Equal("init.js", nodeAction.Pre);
Assert.Equal("always()", nodeAction.InitCondition);
}
finally
{
Teardown();
}
}
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
[Trait("Category", "Worker")] [Trait("Category", "Worker")]
@@ -358,7 +538,7 @@ namespace GitHub.Runner.Common.Tests.Worker
var nodeAction = result.Execution as NodeJSActionExecutionData; var nodeAction = result.Execution as NodeJSActionExecutionData;
Assert.Equal("main.js", nodeAction.Script); Assert.Equal("main.js", nodeAction.Script);
Assert.Equal("cleanup.js", nodeAction.Cleanup); Assert.Equal("cleanup.js", nodeAction.Post);
Assert.Equal("cancelled()", nodeAction.CleanupCondition); Assert.Equal("cancelled()", nodeAction.CleanupCondition);
} }
finally finally
@@ -402,7 +582,7 @@ namespace GitHub.Runner.Common.Tests.Worker
var nodeAction = result.Execution as NodeJSActionExecutionData; var nodeAction = result.Execution as NodeJSActionExecutionData;
Assert.Equal("main.js", nodeAction.Script); Assert.Equal("main.js", nodeAction.Script);
Assert.Equal("cleanup.js", nodeAction.Cleanup); Assert.Equal("cleanup.js", nodeAction.Post);
Assert.Equal("always()", nodeAction.CleanupCondition); Assert.Equal("always()", nodeAction.CleanupCondition);
} }
finally finally

View File

@@ -278,6 +278,59 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal("${{ matrix.node }}", _actionRunner.DisplayName); Assert.Equal("${{ matrix.node }}", _actionRunner.DisplayName);
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async void WarnInvalidInputs()
{
//Arrange
Setup();
var actionId = Guid.NewGuid();
var actionInputs = new MappingToken(null, null, null);
actionInputs.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "test1"));
actionInputs.Add(new StringToken(null, null, null, "input2"), new StringToken(null, null, null, "test2"));
actionInputs.Add(new StringToken(null, null, null, "invalid1"), new StringToken(null, null, null, "invalid1"));
actionInputs.Add(new StringToken(null, null, null, "invalid2"), new StringToken(null, null, null, "invalid2"));
var action = new Pipelines.ActionStep()
{
Name = "action",
Id = actionId,
Reference = new Pipelines.RepositoryPathReference()
{
Name = "actions/runner",
Ref = "v1"
},
Inputs = actionInputs
};
_actionRunner.Action = action;
Dictionary<string, string> finialInputs = new Dictionary<string, string>();
_handlerFactory.Setup(x => x.Create(It.IsAny<IExecutionContext>(), It.IsAny<ActionStepDefinitionReference>(), It.IsAny<IStepHost>(), It.IsAny<ActionExecutionData>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Variables>(), It.IsAny<string>()))
.Callback((IExecutionContext executionContext, Pipelines.ActionStepDefinitionReference actionReference, IStepHost stepHost, ActionExecutionData data, Dictionary<string, string> inputs, Dictionary<string, string> environment, Variables runtimeVariables, string taskDirectory) =>
{
finialInputs = inputs;
})
.Returns(new Mock<IHandler>().Object);
//Act
await _actionRunner.RunAsync();
foreach (var input in finialInputs)
{
_hc.GetTrace().Info($"Input: {input.Key}={input.Value}");
}
//Assert
Assert.Equal("test1", finialInputs["input1"]);
Assert.Equal("test2", finialInputs["input2"]);
Assert.Equal("github", finialInputs["input3"]);
Assert.Equal("invalid1", finialInputs["invalid1"]);
Assert.Equal("invalid2", finialInputs["invalid2"]);
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<string>()), Times.Once);
}
private void Setup([CallerMemberName] string name = "") private void Setup([CallerMemberName] string name = "")
{ {
_ecTokenSource?.Dispose(); _ecTokenSource?.Dispose();

View File

@@ -1,4 +1,5 @@
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker; using GitHub.Runner.Worker;
using Moq; using Moq;
using System; using System;
@@ -199,20 +200,20 @@ namespace GitHub.Runner.Common.Tests.Worker
var postRunner1 = hc.CreateService<IActionRunner>(); var postRunner1 = hc.CreateService<IActionRunner>();
postRunner1.Action = new Pipelines.ActionStep() { Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } }; postRunner1.Action = new Pipelines.ActionStep() { Id = Guid.NewGuid(), Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
postRunner1.Stage = ActionRunStage.Post; postRunner1.Stage = ActionRunStage.Post;
postRunner1.Condition = "always()"; postRunner1.Condition = "always()";
postRunner1.DisplayName = "post1"; postRunner1.DisplayName = "post1";
var postRunner2 = hc.CreateService<IActionRunner>(); var postRunner2 = hc.CreateService<IActionRunner>();
postRunner2.Action = new Pipelines.ActionStep() { Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } }; postRunner2.Action = new Pipelines.ActionStep() { Id = Guid.NewGuid(), Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
postRunner2.Stage = ActionRunStage.Post; postRunner2.Stage = ActionRunStage.Post;
postRunner2.Condition = "always()"; postRunner2.Condition = "always()";
postRunner2.DisplayName = "post2"; postRunner2.DisplayName = "post2";
action1.RegisterPostJobStep("post1", postRunner1); action1.RegisterPostJobStep(postRunner1);
action2.RegisterPostJobStep("post2", postRunner2); action2.RegisterPostJobStep(postRunner2);
Assert.NotNull(jobContext.JobSteps); Assert.NotNull(jobContext.JobSteps);
Assert.NotNull(jobContext.PostJobSteps); Assert.NotNull(jobContext.PostJobSteps);
@@ -238,6 +239,145 @@ namespace GitHub.Runner.Common.Tests.Worker
} }
} }
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RegisterPostJobAction_NotRegisterPostTwice()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange: Create a job request message.
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{
Alias = Pipelines.PipelineConstants.SelfAlias,
Id = "github",
Version = "sha1"
});
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true";
// Arrange: Setup the paging logger.
var pagingLogger1 = new Mock<IPagingLogger>();
var pagingLogger2 = new Mock<IPagingLogger>();
var pagingLogger3 = new Mock<IPagingLogger>();
var pagingLogger4 = new Mock<IPagingLogger>();
var pagingLogger5 = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.IsAny<string>())).Callback((Guid id, string msg) => { hc.GetTrace().Info(msg); });
var actionRunner1 = new ActionRunner();
actionRunner1.Initialize(hc);
var actionRunner2 = new ActionRunner();
actionRunner2.Initialize(hc);
hc.EnqueueInstance(pagingLogger1.Object);
hc.EnqueueInstance(pagingLogger2.Object);
hc.EnqueueInstance(pagingLogger3.Object);
hc.EnqueueInstance(pagingLogger4.Object);
hc.EnqueueInstance(pagingLogger5.Object);
hc.EnqueueInstance(actionRunner1 as IActionRunner);
hc.EnqueueInstance(actionRunner2 as IActionRunner);
hc.SetSingleton(jobServerQueue.Object);
var jobContext = new Runner.Worker.ExecutionContext();
jobContext.Initialize(hc);
// Act.
jobContext.InitializeJob(jobRequest, CancellationToken.None);
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null);
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_1_main", "action_1_main", null, null);
var actionId = Guid.NewGuid();
var postRunner1 = hc.CreateService<IActionRunner>();
postRunner1.Action = new Pipelines.ActionStep() { Id = actionId, Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
postRunner1.Stage = ActionRunStage.Post;
postRunner1.Condition = "always()";
postRunner1.DisplayName = "post1";
var postRunner2 = hc.CreateService<IActionRunner>();
postRunner2.Action = new Pipelines.ActionStep() { Id = actionId, Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
postRunner2.Stage = ActionRunStage.Post;
postRunner2.Condition = "always()";
postRunner2.DisplayName = "post2";
action1.RegisterPostJobStep(postRunner1);
action2.RegisterPostJobStep(postRunner2);
Assert.NotNull(jobContext.JobSteps);
Assert.NotNull(jobContext.PostJobSteps);
Assert.Equal(1, jobContext.PostJobSteps.Count);
var post1 = jobContext.PostJobSteps.Pop();
Assert.Equal("post1", (post1 as IActionRunner).Action.Name);
Assert.Equal(ActionRunStage.Post, (post1 as IActionRunner).Stage);
Assert.Equal("always()", (post1 as IActionRunner).Condition);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void ActionResult_Lowercase()
{
using (TestHostContext hc = CreateTestContext())
{
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
Guid jobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{
Alias = Pipelines.PipelineConstants.SelfAlias,
Id = "github",
Version = "sha1"
});
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true";
// Arrange: Setup the paging logger.
var pagingLogger1 = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
hc.EnqueueInstance(pagingLogger1.Object);
hc.SetSingleton(jobServerQueue.Object);
var jobContext = new Runner.Worker.ExecutionContext();
jobContext.Initialize(hc);
// Act.
jobContext.InitializeJob(jobRequest, CancellationToken.None);
jobContext.StepsContext.SetConclusion(null, "step1", ActionResult.Success);
var conclusion1 = (jobContext.StepsContext.GetScope(null)["step1"] as DictionaryContextData)["conclusion"].ToString();
Assert.Equal(conclusion1, conclusion1.ToLowerInvariant());
jobContext.StepsContext.SetOutcome(null, "step2", ActionResult.Cancelled);
var outcome1 = (jobContext.StepsContext.GetScope(null)["step2"] as DictionaryContextData)["outcome"].ToString();
Assert.Equal(outcome1, outcome1.ToLowerInvariant());
jobContext.StepsContext.SetConclusion(null, "step3", ActionResult.Failure);
var conclusion2 = (jobContext.StepsContext.GetScope(null)["step3"] as DictionaryContextData)["conclusion"].ToString();
Assert.Equal(conclusion2, conclusion2.ToLowerInvariant());
jobContext.StepsContext.SetOutcome(null, "step4", ActionResult.Skipped);
var outcome2 = (jobContext.StepsContext.GetScope(null)["step4"] as DictionaryContextData)["outcome"].ToString();
Assert.Equal(outcome2, outcome2.ToLowerInvariant());
jobContext.JobContext.Status = ActionResult.Success;
Assert.Equal(jobContext.JobContext["status"].ToString(), jobContext.JobContext["status"].ToString().ToLowerInvariant());
}
}
private TestHostContext CreateTestContext([CallerMemberName] String testName = "") private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
{ {
var hc = new TestHostContext(this, testName); var hc = new TestHostContext(this, testName);

View File

@@ -141,7 +141,7 @@ namespace GitHub.Runner.Common.Tests.Worker
jobExtension.Initialize(hc); jobExtension.Initialize(hc);
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>())) _actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
.Returns(Task.FromResult(new List<JobExtensionRunner>())); .Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>(), new Dictionary<Guid, IActionRunner>())));
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message); List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
@@ -176,7 +176,7 @@ namespace GitHub.Runner.Common.Tests.Worker
jobExtension.Initialize(hc); jobExtension.Initialize(hc);
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>())) _actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
.Returns(Task.FromResult(new List<JobExtensionRunner>() { new JobExtensionRunner(null, "", "prepare1", null), new JobExtensionRunner(null, "", "prepare2", null) })); .Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>() { new JobExtensionRunner(null, "", "prepare1", null), new JobExtensionRunner(null, "", "prepare2", null) }, new Dictionary<Guid, IActionRunner>())));
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message); List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);

View File

@@ -686,14 +686,17 @@ namespace GitHub.Runner.Common.Tests.Worker
// <WORKSPACE>/workflow-repo/nested-other-repo // <WORKSPACE>/workflow-repo/nested-other-repo
// <WORKSPACE>/other-repo // <WORKSPACE>/other-repo
// <WORKSPACE>/other-repo/nested-workflow-repo // <WORKSPACE>/other-repo/nested-workflow-repo
// <WORKSPACE>/workflow-repo-using-ssh
var workflowRepository = Path.Combine(workspaceDirectory, "workflow-repo"); var workflowRepository = Path.Combine(workspaceDirectory, "workflow-repo");
var nestedOtherRepository = Path.Combine(workspaceDirectory, "workflow-repo", "nested-other-repo"); var nestedOtherRepository = Path.Combine(workspaceDirectory, "workflow-repo", "nested-other-repo");
var otherRepository = Path.Combine(workspaceDirectory, workflowRepository, "nested-other-repo"); var otherRepository = Path.Combine(workspaceDirectory, workflowRepository, "nested-other-repo");
var nestedWorkflowRepository = Path.Combine(workspaceDirectory, "other-repo", "nested-workflow-repo"); var nestedWorkflowRepository = Path.Combine(workspaceDirectory, "other-repo", "nested-workflow-repo");
var workflowRepositoryUsingSsh = Path.Combine(workspaceDirectory, "workflow-repo-using-ssh");
await CreateRepository(hostContext, workflowRepository, "https://github.com/my-org/workflow-repo"); await CreateRepository(hostContext, workflowRepository, "https://github.com/my-org/workflow-repo");
await CreateRepository(hostContext, nestedOtherRepository, "https://github.com/my-org/other-repo"); await CreateRepository(hostContext, nestedOtherRepository, "https://github.com/my-org/other-repo");
await CreateRepository(hostContext, otherRepository, "https://github.com/my-org/other-repo"); await CreateRepository(hostContext, otherRepository, "https://github.com/my-org/other-repo");
await CreateRepository(hostContext, nestedWorkflowRepository, "https://github.com/my-org/workflow-repo"); await CreateRepository(hostContext, nestedWorkflowRepository, "https://github.com/my-org/workflow-repo");
await CreateRepository(hostContext, workflowRepositoryUsingSsh, "git@github.com:my-org/workflow-repo.git");
// Create test files // Create test files
var file_noRepository = Path.Combine(workspaceDirectory, "no-repo.txt"); var file_noRepository = Path.Combine(workspaceDirectory, "no-repo.txt");
@@ -703,7 +706,8 @@ namespace GitHub.Runner.Common.Tests.Worker
var file_nestedOtherRepository = Path.Combine(nestedOtherRepository, "nested-other-repo"); var file_nestedOtherRepository = Path.Combine(nestedOtherRepository, "nested-other-repo");
var file_otherRepository = Path.Combine(otherRepository, "other-repo.txt"); var file_otherRepository = Path.Combine(otherRepository, "other-repo.txt");
var file_nestedWorkflowRepository = Path.Combine(nestedWorkflowRepository, "nested-workflow-repo.txt"); var file_nestedWorkflowRepository = Path.Combine(nestedWorkflowRepository, "nested-workflow-repo.txt");
foreach (var file in new[] { file_noRepository, file_workflowRepository, file_workflowRepository_nestedDirectory, file_workflowRepository_failsafe, file_nestedOtherRepository, file_otherRepository, file_nestedWorkflowRepository }) var file_workflowRepositoryUsingSsh = Path.Combine(workflowRepositoryUsingSsh, "workflow-repo-using-ssh.txt");
foreach (var file in new[] { file_noRepository, file_workflowRepository, file_workflowRepository_nestedDirectory, file_workflowRepository_failsafe, file_nestedOtherRepository, file_otherRepository, file_nestedWorkflowRepository, file_workflowRepositoryUsingSsh })
{ {
Directory.CreateDirectory(Path.GetDirectoryName(file)); Directory.CreateDirectory(Path.GetDirectoryName(file));
File.WriteAllText(file, ""); File.WriteAllText(file, "");
@@ -718,8 +722,9 @@ namespace GitHub.Runner.Common.Tests.Worker
Process($"{file_nestedOtherRepository}: some error 6"); Process($"{file_nestedOtherRepository}: some error 6");
Process($"{file_otherRepository}: some error 7"); Process($"{file_otherRepository}: some error 7");
Process($"{file_nestedWorkflowRepository}: some error 8"); Process($"{file_nestedWorkflowRepository}: some error 8");
Process($"{file_workflowRepositoryUsingSsh}: some error 9");
Assert.Equal(8, _issues.Count); Assert.Equal(9, _issues.Count);
Assert.Equal("some error 1", _issues[0].Item1.Message); Assert.Equal("some error 1", _issues[0].Item1.Message);
Assert.False(_issues[0].Item1.Data.ContainsKey("file")); Assert.False(_issues[0].Item1.Data.ContainsKey("file"));
@@ -744,6 +749,9 @@ namespace GitHub.Runner.Common.Tests.Worker
Assert.Equal("some error 8", _issues[7].Item1.Message); Assert.Equal("some error 8", _issues[7].Item1.Message);
Assert.Equal(file_nestedWorkflowRepository.Substring(nestedWorkflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[7].Item1.Data["file"]); Assert.Equal(file_nestedWorkflowRepository.Substring(nestedWorkflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[7].Item1.Data["file"]);
Assert.Equal("some error 9", _issues[8].Item1.Message);
Assert.Equal(file_workflowRepositoryUsingSsh.Substring(workflowRepositoryUsingSsh.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[8].Item1.Data["file"]);
} }
Environment.SetEnvironmentVariable("RUNNER_TEST_GET_REPOSITORY_PATH_FAILSAFE", ""); Environment.SetEnvironmentVariable("RUNNER_TEST_GET_REPOSITORY_PATH_FAILSAFE", "");

View File

@@ -536,12 +536,12 @@ namespace GitHub.Runner.Common.Tests.Worker
step2.Verify(x => x.RunAsync(), Times.Once); step2.Verify(x => x.RunAsync(), Times.Once);
step3.Verify(x => x.RunAsync(), Times.Once); step3.Verify(x => x.RunAsync(), Times.Once);
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["outcome"].AssertString("")); Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["conclusion"].AssertString("")); Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["conclusion"].AssertString(""));
Assert.Equal(TaskResult.Failed.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["outcome"].AssertString("")); Assert.Equal(TaskResult.Failed.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["conclusion"].AssertString("")); Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["conclusion"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["outcome"].AssertString("")); Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["conclusion"].AssertString("")); Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["conclusion"].AssertString(""));
} }
} }
@@ -572,12 +572,12 @@ namespace GitHub.Runner.Common.Tests.Worker
step2.Verify(x => x.RunAsync(), Times.Once); step2.Verify(x => x.RunAsync(), Times.Once);
step3.Verify(x => x.RunAsync(), Times.Once); step3.Verify(x => x.RunAsync(), Times.Once);
Assert.Equal(TaskResult.Skipped.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["outcome"].AssertString("")); Assert.Equal(TaskResult.Skipped.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Skipped.ToActionResult().ToString(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["conclusion"].AssertString("")); Assert.Equal(TaskResult.Skipped.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step1"].AssertDictionary("")["conclusion"].AssertString(""));
Assert.Equal(TaskResult.Failed.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["outcome"].AssertString("")); Assert.Equal(TaskResult.Failed.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["conclusion"].AssertString("")); Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step2"].AssertDictionary("")["conclusion"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["outcome"].AssertString("")); Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["outcome"].AssertString(""));
Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["conclusion"].AssertString("")); Assert.Equal(TaskResult.Succeeded.ToActionResult().ToString().ToLowerInvariant(), _stepContext.GetScope(null)["step3"].AssertDictionary("")["conclusion"].AssertString(""));
} }
} }
@@ -615,8 +615,8 @@ namespace GitHub.Runner.Common.Tests.Worker
stepContext.Object.Result = r; stepContext.Object.Result = r;
} }
_stepContext.SetOutcome("", stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult().ToString()); _stepContext.SetOutcome("", stepContext.Object.ContextName, (stepContext.Object.Outcome ?? stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
_stepContext.SetConclusion("", stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult().ToString()); _stepContext.SetConclusion("", stepContext.Object.ContextName, (stepContext.Object.Result ?? TaskResult.Succeeded).ToActionResult());
}); });
var trace = hc.GetTrace(); var trace = hc.GetTrace();
stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); }); stepContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { trace.Info($"[{tag}]{message}"); });

View File

@@ -0,0 +1,27 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'docker'
image: 'Dockerfile'
args:
- 'bzz'
entrypoint: 'main.sh'
env:
Token: foo
Url: bar
pre-entrypoint: 'init.sh'
pre-if: 'success()'

View File

@@ -0,0 +1,26 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'docker'
image: 'Dockerfile'
args:
- 'bzz'
entrypoint: 'main.sh'
env:
Token: foo
Url: bar
pre-entrypoint: 'init.sh'

View File

@@ -0,0 +1,22 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
deprecationMessage: 'This property has been deprecated'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'node12'
main: 'main.js'
pre: 'init.js'
pre-if: 'cancelled()'

View File

@@ -0,0 +1,21 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
deprecationMessage: 'This property has been deprecated'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'node12'
main: 'main.js'
pre: 'init.js'

View File

@@ -173,6 +173,57 @@ function package ()
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"${window_path}\", \"${zip_name}\")" powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"${window_path}\", \"${zip_name}\")"
fi fi
runner_patch_pkg_name="actions-runner-${RUNTIME_ID}-${runner_ver}-patch"
heading "Packaging Patch Version ${runner_patch_pkg_name}"
echo "Downloading latest runner ..."
mkdir -p "_temp"
pushd "_temp" > /dev/null
latest_version_label=$(curl -s -X GET 'https://api.github.com/repos/actions/runner/releases/latest' | jq -r '.tag_name')
latest_version=$(echo ${latest_version_label:1})
latest_version_runner_file=""
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
latest_version_runner_file="actions-runner-${RUNTIME_ID}-${latest_version}.tar.gz"
elif [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
latest_version_runner_file="actions-runner-${RUNTIME_ID}-${latest_version}.zip"
fi
latest_version_download_url="https://github.com/actions/runner/releases/download/${latest_version_label}/${latest_version_runner_file}"
echo "Downloading ${latest_version_label} for ${RUNTIME_ID} ..."
echo $latest_version_download_url
curl -O -L ${latest_version_download_url}
mkdir -p "latest_runner"
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
tar xzf "./${latest_version_runner_file}" -C latest_runner
elif [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::ExtractToDirectory(\"./${latest_version_runner_file}\", \"latest_runner\")"
fi
mkdir -p "_patch"
diff -qrN "${LAYOUT_DIR}" "latest_runner" | cut -d " " -f 2 | xargs -t -I file cp file "_patch/"$(echo file | cut -c ${#LAYOUT_DIR}- | cut -c 3-)
popd > /dev/null
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
patch_tar_name="${runner_patch_pkg_name}.tar.gz"
echo "Creating $patch_tar_name in ${PACKAGE_DIR}/_temp/_patch"
tar -czf "${patch_tar_name}" -C "${PACKAGE_DIR}/_temp/_patch" .
elif [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
patch_zip_name="${runner_patch_pkg_name}.zip"
echo "Convert ${PACKAGE_DIR} to Windows style path"
window_path=${PACKAGE_DIR:1}
window_path=${window_path:0:1}:${window_path:1}
echo "Creating $patch_zip_name in ${window_path}/_temp/_patch"
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"${window_path}\", \"${patch_zip_name}\")"
fi
rm -Rf "${PACKAGE_DIR}/_temp"
popd > /dev/null popd > /dev/null
} }

View File

@@ -1 +1 @@
2.169.0 2.263.0