mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
46 Commits
v2.169.1
...
users/etha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9b28c7210 | ||
|
|
8aadbbdb8e | ||
|
|
9ec7047441 | ||
|
|
0d5e84b183 | ||
|
|
a254442dcc | ||
|
|
e4dfd0e8fd | ||
|
|
00a736d9cc | ||
|
|
5988076fcf | ||
|
|
9939cf527e | ||
|
|
941a24ee37 | ||
|
|
5843362bd4 | ||
|
|
180a687f30 | ||
|
|
038e5e2c2e | ||
|
|
5815819f24 | ||
|
|
1aea046932 | ||
|
|
eda463601c | ||
|
|
f994ae0542 | ||
|
|
3c5aef791c | ||
|
|
c4626d0c3a | ||
|
|
416a7ac4b8 | ||
|
|
11435857e4 | ||
|
|
6f260012a3 | ||
|
|
4fc87ddfc6 | ||
|
|
b45c1b9440 | ||
|
|
73307c0a30 | ||
|
|
cd8e4ddba1 | ||
|
|
abf59bdcb6 | ||
|
|
09cf59c1e0 | ||
|
|
7a65236022 | ||
|
|
462b5117c8 | ||
|
|
6922f3cb86 | ||
|
|
911135e66c | ||
|
|
01c9a8a8af | ||
|
|
33d2d2c328 | ||
|
|
a246b3b29d | ||
|
|
c7768d4a7b | ||
|
|
70729fb3c4 | ||
|
|
1470a3b6e2 | ||
|
|
2fadf430e4 | ||
|
|
f798f5606b | ||
|
|
3f7a01af93 | ||
|
|
d5c54f9819 | ||
|
|
9f78ad3b34 | ||
|
|
97883c8cd5 | ||
|
|
c5fa9fb062 | ||
|
|
b2dcdc21dc |
35
.github/workflows/codeql.yml
vendored
Normal file
35
.github/workflows/codeql.yml
vendored
Normal 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
57
docs/automate.md
Normal 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
|
||||||
|
```
|
||||||
@@ -23,7 +23,7 @@ An ADR is an Architectural Decision Record. This allows consensus on the direct
|
|||||||
|
|
||||||
### Required Dev Dependencies
|
### Required Dev Dependencies
|
||||||
|
|
||||||
 Git for Windows [Install Here](https://git-scm.com/downloads) (needed for dev sh script)
|
  Git for Windows and Linux [Install Here](https://git-scm.com/downloads) (needed for dev sh script)
|
||||||
|
|
||||||
### To Build, Test, Layout
|
### To Build, Test, Layout
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ Sample developer flow:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/actions/runner
|
git clone https://github.com/actions/runner
|
||||||
|
cd runner
|
||||||
cd ./src
|
cd ./src
|
||||||
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
|
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
|
||||||
<make code changes>
|
<make code changes>
|
||||||
@@ -53,7 +54,7 @@ cd ./src
|
|||||||
### Editors
|
### Editors
|
||||||
|
|
||||||
[Using Visual Studio Code](https://code.visualstudio.com/)
|
[Using Visual Studio Code](https://code.visualstudio.com/)
|
||||||
[Using Visual Studio 2019](https://www.visualstudio.com/vs/)
|
[Using Visual Studio](https://code.visualstudio.com/docs)
|
||||||
|
|
||||||
### Styling
|
### Styling
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.168.0
|
<Update to ./src/runnerversion when creating release>
|
||||||
|
|||||||
4
scripts/README.md
Normal file
4
scripts/README.md
Normal 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
147
scripts/create-latest-svc.sh
Executable 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
83
scripts/delete.sh
Executable 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
76
scripts/remove-svc.sh
Executable 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
|
||||||
193
src/Misc/dotnet-install.ps1
vendored
193
src/Misc/dotnet-install.ps1
vendored
@@ -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
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -137,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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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))
|
||||||
@@ -614,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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
src/Runner.Common/HttpClientHandlerFactory.cs
Normal file
19
src/Runner.Common/HttpClientHandlerFactory.cs
Normal 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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ namespace GitHub.Runner.Common
|
|||||||
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
||||||
Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent;
|
Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent;
|
||||||
Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
||||||
|
Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class JobServer : RunnerService, IJobServer
|
public sealed class JobServer : RunnerService, IJobServer
|
||||||
@@ -113,5 +114,14 @@ namespace GitHub.Runner.Common
|
|||||||
CheckConnection();
|
CheckConnection();
|
||||||
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
|
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
// Action download info
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
public Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
CheckConnection();
|
||||||
|
return _taskClient.ResolveActionDownloadInfoAsync(scopeIdentifier, hubName, planId, actions, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -96,13 +96,14 @@ namespace GitHub.Runner.Common
|
|||||||
Trace.Info($"WRITE: {message}");
|
Trace.Info($"WRITE: {message}");
|
||||||
if (!Silent)
|
if (!Silent)
|
||||||
{
|
{
|
||||||
if(colorCode != null)
|
if (colorCode != null)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = colorCode.Value;
|
Console.ForegroundColor = colorCode.Value;
|
||||||
Console.Write(message);
|
Console.Write(message);
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
Console.Write(message);
|
Console.Write(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,13 +121,14 @@ namespace GitHub.Runner.Common
|
|||||||
Trace.Info($"WRITE LINE: {line}");
|
Trace.Info($"WRITE LINE: {line}");
|
||||||
if (!Silent)
|
if (!Silent)
|
||||||
{
|
{
|
||||||
if(colorCode != null)
|
if (colorCode != null)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = colorCode.Value;
|
Console.ForegroundColor = colorCode.Value;
|
||||||
Console.WriteLine(line);
|
Console.WriteLine(line);
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
Console.WriteLine(line);
|
Console.WriteLine(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
@@ -219,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,
|
||||||
@@ -256,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 },
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -462,7 +451,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// update should replace the existing labels
|
// 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.Clear();
|
agent.Labels.Clear();
|
||||||
|
|
||||||
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
||||||
@@ -473,7 +462,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
agent.Labels.Add(new AgentLabel(userLabel, LabelType.User));
|
agent.Labels.Add(new AgentLabel(userLabel, LabelType.User));
|
||||||
}
|
}
|
||||||
|
|
||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
var migratedCred = store.GetMigratedCredentials();
|
||||||
if (preferMigrated)
|
if (migratedCred != null)
|
||||||
{
|
{
|
||||||
var migratedCred = store.GetMigratedCredentials();
|
credData = migratedCred;
|
||||||
if (migratedCred != null)
|
|
||||||
{
|
// Re-write .credentials with Token URL
|
||||||
credData = migratedCred;
|
store.SaveCredential(credData);
|
||||||
}
|
|
||||||
|
// Delete .credentials_migrated
|
||||||
|
store.DeleteMigratedCredential();
|
||||||
}
|
}
|
||||||
|
|
||||||
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
||||||
|
|||||||
@@ -858,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();
|
||||||
@@ -952,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)
|
||||||
|
|||||||
@@ -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,25 +118,26 @@ 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
|
|
||||||
_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
|
|
||||||
{
|
|
||||||
_term.WriteError($"Failed to create session. {ex.Message}");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!IsSessionCreationExceptionRetriable(ex))
|
||||||
|
{
|
||||||
|
_term.WriteError($"Failed to create session. {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!encounteringError) //print the message only on the first error
|
if (!encounteringError) //print the message only on the first error
|
||||||
{
|
{
|
||||||
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
|
_term.WriteError($"{DateTime.UtcNow:u}: Runner connect error: {ex.Message}. Retrying until reconnected.");
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
@@ -295,21 +219,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
else if (!IsGetNextMessageExceptionRetriable(ex))
|
else if (!IsGetNextMessageExceptionRetriable(ex))
|
||||||
{
|
{
|
||||||
if (_useMigratedCredentials)
|
throw;
|
||||||
{
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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.");
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -486,7 +486,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
foreach (var property in command.Properties)
|
foreach (var property in command.Properties)
|
||||||
{
|
{
|
||||||
issue.Data[property.Key] = property.Value;
|
if (!string.Equals(property.Key, Constants.Runner.InternalTelemetryIssueDataKey, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
issue.Data[property.Key] = property.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AddIssue(issue);
|
context.AddIssue(issue);
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
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 WebApi = GitHub.DistributedTask.WebApi;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ 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;
|
||||||
@@ -73,13 +72,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.
|
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
||||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
|
||||||
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
|
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
||||||
if (isHostedServer)
|
var newActionMetadata = executionContext.Variables.GetBoolean("DistributedTask.NewActionMetadata") ?? false;
|
||||||
{
|
|
||||||
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
var repositoryActions = new List<Pipelines.ActionStep>();
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
{
|
{
|
||||||
@@ -98,7 +96,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);
|
||||||
@@ -132,6 +131,81 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
||||||
{
|
{
|
||||||
@@ -321,6 +395,12 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}.");
|
Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Composite && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
||||||
|
{
|
||||||
|
var compositeAction = definition.Data.Execution as CompositeActionExecutionData;
|
||||||
|
Trace.Info($"Load {compositeAction.Steps.Count} action steps.");
|
||||||
|
Trace.Verbose($"Details: {StringUtil.ConvertToJson(compositeAction.Steps)}");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotSupportedException(definition.Data.Execution.ExecutionType.ToString());
|
throw new NotSupportedException(definition.Data.Execution.ExecutionType.ToString());
|
||||||
@@ -438,7 +518,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;
|
||||||
@@ -467,6 +552,143 @@ 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, WebApi.ActionDownloadInfo actionDownloadInfo, string token)
|
||||||
|
{
|
||||||
|
var apiUrl = GetApiUrl(executionContext);
|
||||||
|
var repoUrl = $"{apiUrl}/repos/{actionDownloadInfo.NameWithOwner}";
|
||||||
|
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(token);
|
||||||
|
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 '{actionDownloadInfo.NameWithOwner}' exists: {ex.Message}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
executionContext.Error($"Failed checking whether repo '{actionDownloadInfo.NameWithOwner}' 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<IDictionary<string, WebApi.ActionDownloadInfo>> GetDownloadInfoAsync(IExecutionContext executionContext, List<Pipelines.ActionStep> actions)
|
||||||
|
{
|
||||||
|
executionContext.Output("Getting action download info");
|
||||||
|
|
||||||
|
// Convert to action reference
|
||||||
|
var actionReferences = actions
|
||||||
|
.GroupBy(x => GetDownloadInfoLookupKey(x))
|
||||||
|
.Where(x => !string.IsNullOrEmpty(x.Key))
|
||||||
|
.Select(x =>
|
||||||
|
{
|
||||||
|
var action = x.First();
|
||||||
|
var repositoryReference = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
|
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
|
||||||
|
return new WebApi.ActionReference
|
||||||
|
{
|
||||||
|
NameWithOwner = repositoryReference.Name,
|
||||||
|
Ref = repositoryReference.Ref,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Nothing to resolve?
|
||||||
|
if (actionReferences.Count == 0)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, WebApi.ActionDownloadInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve download info
|
||||||
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
|
var actionDownloadInfos = default(WebApi.ActionDownloadInfoCollection);
|
||||||
|
for (var attempt = 1; attempt <= 3; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Plan.ScopeIdentifier, executionContext.Plan.PlanType, executionContext.Plan.PlanId, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (attempt < 3)
|
||||||
|
{
|
||||||
|
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
||||||
|
executionContext.Debug(ex.ToString());
|
||||||
|
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
|
||||||
|
{
|
||||||
|
var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
|
||||||
|
executionContext.Output($"Retrying in {backoff.TotalSeconds} seconds");
|
||||||
|
await Task.Delay(backoff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgUtil.NotNull(actionDownloadInfos, nameof(actionDownloadInfos));
|
||||||
|
ArgUtil.NotNull(actionDownloadInfos.Actions, nameof(actionDownloadInfos.Actions));
|
||||||
|
var apiUrl = GetApiUrl(executionContext);
|
||||||
|
var defaultAccessToken = executionContext.GetGitHubContext("token");
|
||||||
|
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||||
|
var runnerSettings = configurationStore.GetSettings();
|
||||||
|
|
||||||
|
foreach (var actionDownloadInfo in actionDownloadInfos.Actions.Values)
|
||||||
|
{
|
||||||
|
// Add secret
|
||||||
|
HostContext.SecretMasker.AddValue(actionDownloadInfo.Authentication?.Token);
|
||||||
|
|
||||||
|
// Temporary code: Fix token and download URL
|
||||||
|
if (runnerSettings.IsHostedServer)
|
||||||
|
{
|
||||||
|
actionDownloadInfo.Authentication = new WebApi.ActionDownloadAuthentication { Token = defaultAccessToken };
|
||||||
|
actionDownloadInfo.TarballUrl = actionDownloadInfo.TarballUrl.Replace("<GITHUB_API_URL>", apiUrl);
|
||||||
|
actionDownloadInfo.ZipballUrl = actionDownloadInfo.ZipballUrl.Replace("<GITHUB_API_URL>", apiUrl);
|
||||||
|
}
|
||||||
|
else if (await RepoExistsAsync(executionContext, actionDownloadInfo, defaultAccessToken))
|
||||||
|
{
|
||||||
|
actionDownloadInfo.Authentication = new WebApi.ActionDownloadAuthentication { Token = defaultAccessToken };
|
||||||
|
actionDownloadInfo.TarballUrl = actionDownloadInfo.TarballUrl.Replace("<GITHUB_API_URL>", apiUrl);
|
||||||
|
actionDownloadInfo.ZipballUrl = actionDownloadInfo.ZipballUrl.Replace("<GITHUB_API_URL>", apiUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actionDownloadInfo.TarballUrl = actionDownloadInfo.TarballUrl.Replace("<GITHUB_API_URL>", "https://api.github.com");
|
||||||
|
actionDownloadInfo.ZipballUrl = actionDownloadInfo.ZipballUrl.Replace("<GITHUB_API_URL>", "https://api.github.com");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionDownloadInfos.Actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
@@ -490,7 +712,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}'.");
|
||||||
@@ -504,27 +726,116 @@ 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);
|
||||||
Trace.Info($"Download archive '{archiveLink}' to '{destDirectory}'.");
|
string archiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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, WebApi.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}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
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, WebApi.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");
|
||||||
|
string link = downloadInfo?.ZipballUrl ?? actionDownloadDetails.ArchiveLink;
|
||||||
#else
|
#else
|
||||||
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
|
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
|
||||||
|
string link = downloadInfo?.TarballUrl ?? actionDownloadDetails.ArchiveLink;
|
||||||
#endif
|
#endif
|
||||||
Trace.Info($"Save archive '{archiveLink}' into {archiveFile}.");
|
|
||||||
|
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.
|
||||||
@@ -541,64 +852,67 @@ 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);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var accessToken = executionContext.GetGitHubContext("token");
|
|
||||||
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// FF DistributedTask.NewActionMetadata
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Intentionally empty. Temporary for GHES alpha release, download from dotcom unauthenticated.
|
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
using (var result = await httpClient.GetStreamAsync(archiveLink))
|
using (var response = await httpClient.GetAsync(link))
|
||||||
{
|
{
|
||||||
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
if (response.IsSuccessStatusCode)
|
||||||
await fs.FlushAsync(actionDownloadCancellation.Token);
|
{
|
||||||
|
using (var result = await response.Content.ReadAsStreamAsync())
|
||||||
|
{
|
||||||
|
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
||||||
|
await fs.FlushAsync(actionDownloadCancellation.Token);
|
||||||
|
|
||||||
// download succeed, break out the retry loop.
|
// download succeed, break out the retry loop.
|
||||||
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}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,7 +926,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);
|
||||||
@@ -662,6 +976,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}'.");
|
||||||
@@ -686,6 +1001,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;
|
||||||
@@ -766,6 +1107,11 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Action plugin: {(actionDefinitionData.Execution as PluginActionExecutionData).Plugin}, no more preparation.");
|
Trace.Info($"Action plugin: {(actionDefinitionData.Execution as PluginActionExecutionData).Plugin}, no more preparation.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
else if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Composite && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
||||||
|
{
|
||||||
|
Trace.Info($"Action composite: {(actionDefinitionData.Execution as CompositeActionExecutionData).Steps}, no more preparation.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotSupportedException(actionDefinitionData.Execution.ExecutionType.ToString());
|
throw new NotSupportedException(actionDefinitionData.Execution.ExecutionType.ToString());
|
||||||
@@ -791,6 +1137,64 @@ 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 string GetDownloadInfoLookupKey(WebApi.ActionDownloadInfo info)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNullOrEmpty(info.NameWithOwner, nameof(info.NameWithOwner));
|
||||||
|
ArgUtil.NotNullOrEmpty(info.Ref, nameof(info.Ref));
|
||||||
|
return $"{info.NameWithOwner}@{info.Ref}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthenticationHeaderValue CreateAuthHeader(string token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{token}"));
|
||||||
|
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
||||||
|
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Definition
|
public sealed class Definition
|
||||||
@@ -818,6 +1222,7 @@ namespace GitHub.Runner.Worker
|
|||||||
NodeJS,
|
NodeJS,
|
||||||
Plugin,
|
Plugin,
|
||||||
Script,
|
Script,
|
||||||
|
Composite,
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ContainerActionExecutionData : ActionExecutionData
|
public sealed class ContainerActionExecutionData : ActionExecutionData
|
||||||
@@ -874,6 +1279,14 @@ namespace GitHub.Runner.Worker
|
|||||||
public override bool HasPost => false;
|
public override bool HasPost => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class CompositeActionExecutionData : ActionExecutionData
|
||||||
|
{
|
||||||
|
public override ActionExecutionType ExecutionType => ActionExecutionType.Composite;
|
||||||
|
public override bool HasPre => false;
|
||||||
|
public override bool HasPost => false;
|
||||||
|
public List<Pipelines.ActionStep> Steps { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class ActionExecutionData
|
public abstract class ActionExecutionData
|
||||||
{
|
{
|
||||||
private string _initCondition = $"{Constants.Expressions.Always}()";
|
private string _initCondition = $"{Constants.Expressions.Always}()";
|
||||||
@@ -931,4 +1344,3 @@ namespace GitHub.Runner.Worker
|
|||||||
public string ActionRepository { get; set; }
|
public string ActionRepository { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using YamlDotNet.Core;
|
|||||||
using YamlDotNet.Core.Events;
|
using YamlDotNet.Core.Events;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -92,7 +93,7 @@ namespace GitHub.Runner.Worker
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "runs":
|
case "runs":
|
||||||
actionDefinition.Execution = ConvertRuns(context, actionPair.Value);
|
actionDefinition.Execution = ConvertRuns(executionContext, context, actionPair.Value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Trace.Info($"Ignore action property {propertyName}.");
|
Trace.Info($"Ignore action property {propertyName}.");
|
||||||
@@ -284,7 +285,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// Add the file table
|
// Add the file table
|
||||||
if (_fileTable?.Count > 0)
|
if (_fileTable?.Count > 0)
|
||||||
{
|
{
|
||||||
for (var i = 0 ; i < _fileTable.Count ; i++)
|
for (var i = 0; i < _fileTable.Count; i++)
|
||||||
{
|
{
|
||||||
result.GetFileId(_fileTable[i]);
|
result.GetFileId(_fileTable[i]);
|
||||||
}
|
}
|
||||||
@@ -294,6 +295,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ActionExecutionData ConvertRuns(
|
private ActionExecutionData ConvertRuns(
|
||||||
|
IExecutionContext executionContext,
|
||||||
TemplateContext context,
|
TemplateContext context,
|
||||||
TemplateToken inputsToken)
|
TemplateToken inputsToken)
|
||||||
{
|
{
|
||||||
@@ -311,6 +313,8 @@ namespace GitHub.Runner.Worker
|
|||||||
var postToken = default(StringToken);
|
var postToken = default(StringToken);
|
||||||
var postEntrypointToken = default(StringToken);
|
var postEntrypointToken = default(StringToken);
|
||||||
var postIfToken = default(StringToken);
|
var postIfToken = default(StringToken);
|
||||||
|
var stepsLoaded = default(List<Pipelines.ActionStep>);
|
||||||
|
|
||||||
foreach (var run in runsMapping)
|
foreach (var run in runsMapping)
|
||||||
{
|
{
|
||||||
var runsKey = run.Key.AssertString("runs key").Value;
|
var runsKey = run.Key.AssertString("runs key").Value;
|
||||||
@@ -355,6 +359,15 @@ namespace GitHub.Runner.Worker
|
|||||||
case "pre-if":
|
case "pre-if":
|
||||||
preIfToken = run.Value.AssertString("pre-if");
|
preIfToken = run.Value.AssertString("pre-if");
|
||||||
break;
|
break;
|
||||||
|
case "steps":
|
||||||
|
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
||||||
|
{
|
||||||
|
var steps = run.Value.AssertSequence("steps");
|
||||||
|
var evaluator = executionContext.ToPipelineTemplateEvaluator();
|
||||||
|
stepsLoaded = evaluator.LoadCompositeSteps(steps);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw new Exception("You aren't supposed to be using Composite Actions yet!");
|
||||||
default:
|
default:
|
||||||
Trace.Info($"Ignore run property {runsKey}.");
|
Trace.Info($"Ignore run property {runsKey}.");
|
||||||
break;
|
break;
|
||||||
@@ -402,6 +415,21 @@ namespace GitHub.Runner.Worker
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(usingToken.Value, "composite", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
||||||
|
{
|
||||||
|
if (stepsLoaded == null)
|
||||||
|
{
|
||||||
|
// TODO: Add a more helpful error message + including file name, etc. to show user that it's because of their yaml file
|
||||||
|
throw new ArgumentNullException($"No steps provided.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new CompositeActionExecutionData()
|
||||||
|
{
|
||||||
|
Steps = stepsLoaded,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker' or 'node12' instead.");
|
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker' or 'node12' instead.");
|
||||||
|
|||||||
33
src/Runner.Worker/ActionNotFoundException.cs
Normal file
33
src/Runner.Worker/ActionNotFoundException.cs
Normal 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}'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -94,6 +94,13 @@ namespace GitHub.Runner.Worker
|
|||||||
if (handlerData.HasPost && (Stage == ActionRunStage.Pre || Stage == ActionRunStage.Main))
|
if (handlerData.HasPost && (Stage == ActionRunStage.Pre || Stage == ActionRunStage.Main))
|
||||||
{
|
{
|
||||||
string postDisplayName = $"Post {this.DisplayName}";
|
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}" :
|
||||||
@@ -143,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)
|
||||||
{
|
{
|
||||||
@@ -152,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);
|
||||||
@@ -166,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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ namespace GitHub.Runner.Worker
|
|||||||
TaskResult? CommandResult { get; set; }
|
TaskResult? CommandResult { get; set; }
|
||||||
CancellationToken CancellationToken { get; }
|
CancellationToken CancellationToken { get; }
|
||||||
List<ServiceEndpoint> Endpoints { get; }
|
List<ServiceEndpoint> Endpoints { get; }
|
||||||
|
TaskOrchestrationPlanReference Plan { get; }
|
||||||
|
|
||||||
PlanFeatures Features { get; }
|
PlanFeatures Features { get; }
|
||||||
Variables Variables { get; }
|
Variables Variables { get; }
|
||||||
@@ -62,7 +63,7 @@ namespace GitHub.Runner.Worker
|
|||||||
JobContext JobContext { get; }
|
JobContext JobContext { get; }
|
||||||
|
|
||||||
// Only job level ExecutionContext has JobSteps
|
// Only job level ExecutionContext has JobSteps
|
||||||
Queue<IStep> JobSteps { get; }
|
List<IStep> JobSteps { get; }
|
||||||
|
|
||||||
// Only job level ExecutionContext has PostJobSteps
|
// Only job level ExecutionContext has PostJobSteps
|
||||||
Stack<IStep> PostJobSteps { get; }
|
Stack<IStep> PostJobSteps { get; }
|
||||||
@@ -104,11 +105,13 @@ namespace GitHub.Runner.Worker
|
|||||||
// others
|
// others
|
||||||
void ForceTaskComplete();
|
void ForceTaskComplete();
|
||||||
void RegisterPostJobStep(IStep step);
|
void RegisterPostJobStep(IStep step);
|
||||||
|
void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int location);
|
||||||
}
|
}
|
||||||
|
|
||||||
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>();
|
||||||
@@ -140,6 +143,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public Task ForceCompleted => _forceCompleted.Task;
|
public Task ForceCompleted => _forceCompleted.Task;
|
||||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||||
public List<ServiceEndpoint> Endpoints { get; private set; }
|
public List<ServiceEndpoint> Endpoints { get; private set; }
|
||||||
|
public TaskOrchestrationPlanReference Plan { get; private set; }
|
||||||
public Variables Variables { get; private set; }
|
public Variables Variables { get; private set; }
|
||||||
public Dictionary<string, string> IntraActionState { get; private set; }
|
public Dictionary<string, string> IntraActionState { get; private set; }
|
||||||
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; }
|
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; }
|
||||||
@@ -156,7 +160,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public List<ContainerInfo> ServiceContainers { get; private set; }
|
public List<ContainerInfo> ServiceContainers { get; private set; }
|
||||||
|
|
||||||
// Only job level ExecutionContext has JobSteps
|
// Only job level ExecutionContext has JobSteps
|
||||||
public Queue<IStep> JobSteps { get; private set; }
|
public List<IStep> JobSteps { get; private set; }
|
||||||
|
|
||||||
// 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; }
|
||||||
@@ -166,7 +170,6 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public bool EchoOnActionCommand { get; set; }
|
public bool EchoOnActionCommand { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public TaskResult? Result
|
public TaskResult? Result
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -263,6 +266,20 @@ namespace GitHub.Runner.Worker
|
|||||||
Root.PostJobSteps.Push(step);
|
Root.PostJobSteps.Push(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper function used in CompositeActionHandler::RunAsync to
|
||||||
|
/// add a child node, aka a step, to the current job to the Root.JobSteps based on the location.
|
||||||
|
/// </summary>
|
||||||
|
public void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int location)
|
||||||
|
{
|
||||||
|
// TODO: For UI purposes, look at figuring out how to condense steps in one node => maybe use the same previous GUID
|
||||||
|
var newGuid = Guid.NewGuid();
|
||||||
|
step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), null, null);
|
||||||
|
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
||||||
|
// TODO: confirm whether not copying message contexts is safe
|
||||||
|
Root.JobSteps.Insert(location, step);
|
||||||
|
}
|
||||||
|
|
||||||
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null)
|
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
@@ -274,6 +291,7 @@ namespace GitHub.Runner.Worker
|
|||||||
child.Features = Features;
|
child.Features = Features;
|
||||||
child.Variables = Variables;
|
child.Variables = Variables;
|
||||||
child.Endpoints = Endpoints;
|
child.Endpoints = Endpoints;
|
||||||
|
child.Plan = Plan;
|
||||||
if (intraActionState == null)
|
if (intraActionState == null)
|
||||||
{
|
{
|
||||||
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -335,7 +353,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.");
|
||||||
}
|
}
|
||||||
@@ -363,14 +381,18 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_cancellationTokenSource?.Dispose();
|
if (Root != this)
|
||||||
|
{
|
||||||
|
// only dispose TokenSource for step level ExecutionContext
|
||||||
|
_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;
|
||||||
@@ -571,7 +593,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||||
|
|
||||||
// Features
|
// Plan
|
||||||
|
Plan = message.Plan;
|
||||||
Features = PlanUtil.GetFeatures(message.Plan);
|
Features = PlanUtil.GetFeatures(message.Plan);
|
||||||
|
|
||||||
// Endpoints
|
// Endpoints
|
||||||
@@ -651,7 +674,7 @@ namespace GitHub.Runner.Worker
|
|||||||
PrependPath = new List<string>();
|
PrependPath = new List<string>();
|
||||||
|
|
||||||
// JobSteps for job ExecutionContext
|
// JobSteps for job ExecutionContext
|
||||||
JobSteps = new Queue<IStep>();
|
JobSteps = new List<IStep>();
|
||||||
|
|
||||||
// PostJobSteps for job ExecutionContext
|
// PostJobSteps for job ExecutionContext
|
||||||
PostJobSteps = new Stack<IStep>();
|
PostJobSteps = new Stack<IStep>();
|
||||||
@@ -851,7 +874,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."));
|
||||||
|
|
||||||
|
|||||||
@@ -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,19 +91,29 @@ namespace GitHub.Runner.Worker.Expressions
|
|||||||
}
|
}
|
||||||
env["patterns"] = string.Join(Environment.NewLine, patterns);
|
env["patterns"] = string.Join(Environment.NewLine, patterns);
|
||||||
|
|
||||||
int exitCode = p.ExecuteAsync(workingDirectory: githubWorkspace,
|
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(_hashFileTimeoutSeconds)))
|
||||||
fileName: node,
|
|
||||||
arguments: $"\"{hashFilesScript.Replace("\"", "\\\"")}\"",
|
|
||||||
environment: env,
|
|
||||||
requireExitCodeZero: false,
|
|
||||||
cancellationToken: new CancellationTokenSource(TimeSpan.FromSeconds(120)).Token).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
if (exitCode != 0)
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"hashFiles('{ExpressionUtility.StringEscape(string.Join(", ", patterns))}') failed. Fail to hash files under directory '{githubWorkspace}'");
|
try
|
||||||
}
|
{
|
||||||
|
int exitCode = p.ExecuteAsync(workingDirectory: githubWorkspace,
|
||||||
|
fileName: node,
|
||||||
|
arguments: $"\"{hashFilesScript.Replace("\"", "\\\"")}\"",
|
||||||
|
environment: env,
|
||||||
|
requireExitCodeZero: false,
|
||||||
|
cancellationToken: tokenSource.Token).GetAwaiter().GetResult();
|
||||||
|
|
||||||
return hashResult;
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class HashFilesTrace : ITraceWriter
|
private sealed class HashFilesTrace : ITraceWriter
|
||||||
|
|||||||
@@ -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",
|
||||||
};
|
};
|
||||||
|
|||||||
98
src/Runner.Worker/Handlers/CompositeActionHandler.cs
Normal file
98
src/Runner.Worker/Handlers/CompositeActionHandler.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Handlers
|
||||||
|
{
|
||||||
|
[ServiceLocator(Default = typeof(CompositeActionHandler))]
|
||||||
|
public interface ICompositeActionHandler : IHandler
|
||||||
|
{
|
||||||
|
CompositeActionExecutionData Data { get; set; }
|
||||||
|
}
|
||||||
|
public sealed class CompositeActionHandler : Handler, ICompositeActionHandler
|
||||||
|
{
|
||||||
|
public CompositeActionExecutionData Data { get; set; }
|
||||||
|
|
||||||
|
public Task RunAsync(ActionRunStage stage)
|
||||||
|
{
|
||||||
|
// Validate args.
|
||||||
|
Trace.Entering();
|
||||||
|
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
|
||||||
|
ArgUtil.NotNull(Inputs, nameof(Inputs));
|
||||||
|
|
||||||
|
var githubContext = ExecutionContext.ExpressionValues["github"] as GitHubContext;
|
||||||
|
ArgUtil.NotNull(githubContext, nameof(githubContext));
|
||||||
|
|
||||||
|
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
|
||||||
|
|
||||||
|
// Resolve action steps
|
||||||
|
var actionSteps = Data.Steps;
|
||||||
|
|
||||||
|
// Create Context Data to reuse for each composite action step
|
||||||
|
var inputsData = new DictionaryContextData();
|
||||||
|
foreach (var i in Inputs)
|
||||||
|
{
|
||||||
|
inputsData[i.Key] = new StringContextData(i.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add each composite action step to the front of the queue
|
||||||
|
int location = 0;
|
||||||
|
foreach (Pipelines.ActionStep aStep in actionSteps)
|
||||||
|
{
|
||||||
|
// Ex:
|
||||||
|
// runs:
|
||||||
|
// using: "composite"
|
||||||
|
// steps:
|
||||||
|
// - uses: example/test-composite@v2 (a)
|
||||||
|
// - run echo hello world (b)
|
||||||
|
// - run echo hello world 2 (c)
|
||||||
|
//
|
||||||
|
// ethanchewy/test-composite/action.yaml
|
||||||
|
// runs:
|
||||||
|
// using: "composite"
|
||||||
|
// steps:
|
||||||
|
// - run echo hello world 3 (d)
|
||||||
|
// - run echo hello world 4 (e)
|
||||||
|
//
|
||||||
|
// Steps processed as follow:
|
||||||
|
// | a |
|
||||||
|
// | a | => | d |
|
||||||
|
// (Run step d)
|
||||||
|
// | a |
|
||||||
|
// | a | => | e |
|
||||||
|
// (Run step e)
|
||||||
|
// | a |
|
||||||
|
// (Run step a)
|
||||||
|
// | b |
|
||||||
|
// (Run step b)
|
||||||
|
// | c |
|
||||||
|
// (Run step c)
|
||||||
|
// Done.
|
||||||
|
|
||||||
|
var actionRunner = HostContext.CreateService<IActionRunner>();
|
||||||
|
actionRunner.Action = aStep;
|
||||||
|
actionRunner.Stage = stage;
|
||||||
|
actionRunner.Condition = aStep.Condition;
|
||||||
|
actionRunner.DisplayName = aStep.DisplayName;
|
||||||
|
// TODO: Do we need to add any context data from the job message?
|
||||||
|
// (See JobExtension.cs ~line 236)
|
||||||
|
|
||||||
|
ExecutionContext.RegisterNestedStep(actionRunner, inputsData, location);
|
||||||
|
location++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}");
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
handler = HostContext.CreateService<IRunnerPluginHandler>();
|
handler = HostContext.CreateService<IRunnerPluginHandler>();
|
||||||
(handler as IRunnerPluginHandler).Data = data as PluginActionExecutionData;
|
(handler as IRunnerPluginHandler).Data = data as PluginActionExecutionData;
|
||||||
}
|
}
|
||||||
|
else if (data.ExecutionType == ActionExecutionType.Composite)
|
||||||
|
{
|
||||||
|
handler = HostContext.CreateService<ICompositeActionHandler>();
|
||||||
|
(handler as ICompositeActionHandler).Data = data as CompositeActionExecutionData;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
|
|||||||
@@ -352,15 +352,24 @@ 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)
|
||||||
{
|
{
|
||||||
repositoryPath = directoryPath;
|
if (String.Equals(line, pattern, StringComparison.OrdinalIgnoreCase))
|
||||||
break;
|
{
|
||||||
|
repositoryPath = directoryPath;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
this["status"] = new StringContextData(value.ToString());
|
this["status"] = new StringContextData(value.ToString().ToLowerInvariant());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>();
|
||||||
@@ -167,7 +152,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
foreach (var step in jobSteps)
|
foreach (var step in jobSteps)
|
||||||
{
|
{
|
||||||
jobContext.JobSteps.Enqueue(step);
|
jobContext.JobSteps.Add(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
await stepsRunner.RunAsync(jobContext);
|
await stepsRunner.RunAsync(jobContext);
|
||||||
@@ -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}.");
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -59,14 +59,15 @@ namespace GitHub.Runner.Worker
|
|||||||
checkPostJobActions = true;
|
checkPostJobActions = true;
|
||||||
while (jobContext.PostJobSteps.TryPop(out var postStep))
|
while (jobContext.PostJobSteps.TryPop(out var postStep))
|
||||||
{
|
{
|
||||||
jobContext.JobSteps.Enqueue(postStep);
|
jobContext.JobSteps.Add(postStep);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var step = jobContext.JobSteps.Dequeue();
|
var step = jobContext.JobSteps[0];
|
||||||
var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps.Peek() : null;
|
jobContext.JobSteps.RemoveAt(0);
|
||||||
|
var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps[0] : null;
|
||||||
|
|
||||||
Trace.Info($"Processing step: DisplayName='{step.DisplayName}'");
|
Trace.Info($"Processing step: DisplayName='{step.DisplayName}'");
|
||||||
ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext));
|
ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext));
|
||||||
@@ -98,124 +99,139 @@ 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);
|
||||||
|
|
||||||
// Evaluate and merge action's env block to env context
|
try
|
||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
|
||||||
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
|
|
||||||
foreach (var env in actionEnvironment)
|
|
||||||
{
|
{
|
||||||
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
// Evaluate and merge action's env block to env context
|
||||||
}
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
}
|
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
|
||||||
|
foreach (var env in actionEnvironment)
|
||||||
try
|
|
||||||
{
|
|
||||||
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
|
||||||
if (!jobContext.CancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// Test the condition again. The job was canceled after the condition was originally evaluated.
|
|
||||||
jobCancelRegister = jobContext.CancellationToken.Register(() =>
|
|
||||||
{
|
{
|
||||||
// mark job as cancelled
|
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
||||||
jobContext.Result = TaskResult.Canceled;
|
|
||||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
|
||||||
|
|
||||||
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
|
||||||
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
|
|
||||||
var conditionReTestResult = false;
|
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter);
|
|
||||||
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
|
||||||
conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Cancel the step since we get exception while re-evaluate step condition.
|
|
||||||
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
|
||||||
step.ExecutionContext.Error(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!conditionReTestResult)
|
|
||||||
{
|
|
||||||
// Cancel the step.
|
|
||||||
Trace.Info("Cancel current running step.");
|
|
||||||
step.ExecutionContext.CancelToken();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (jobContext.Result != TaskResult.Canceled)
|
|
||||||
{
|
|
||||||
// mark job as cancelled
|
|
||||||
jobContext.Result = TaskResult.Canceled;
|
|
||||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
// Evaluate condition.
|
|
||||||
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
|
||||||
var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext);
|
|
||||||
var conditionResult = false;
|
|
||||||
var conditionEvaluateError = default(Exception);
|
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
|
|
||||||
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
|
||||||
conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Info("Caught exception from expression.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
conditionEvaluateError = ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no evaluate error but condition is false
|
|
||||||
if (!conditionResult && conditionEvaluateError == null)
|
|
||||||
{
|
|
||||||
// Condition == false
|
|
||||||
Trace.Info("Skipping step due to condition evaluation.");
|
|
||||||
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace);
|
|
||||||
}
|
|
||||||
else if (conditionEvaluateError != null)
|
|
||||||
{
|
{
|
||||||
// fail the step since there is an evaluate error.
|
// fail the step since there is an evaluate error.
|
||||||
step.ExecutionContext.Error(conditionEvaluateError);
|
Trace.Info("Caught exception from expression for step.env");
|
||||||
|
evaluateStepEnvFailed = true;
|
||||||
|
step.ExecutionContext.Error(ex);
|
||||||
CompleteStep(step, nextStep, TaskResult.Failed);
|
CompleteStep(step, nextStep, TaskResult.Failed);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Run the step.
|
|
||||||
await RunStepAsync(step, jobContext.CancellationToken);
|
|
||||||
CompleteStep(step, nextStep);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
if (!evaluateStepEnvFailed)
|
||||||
{
|
{
|
||||||
if (jobCancelRegister != null)
|
try
|
||||||
{
|
{
|
||||||
jobCancelRegister?.Dispose();
|
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
||||||
jobCancelRegister = null;
|
if (!jobContext.CancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Test the condition again. The job was canceled after the condition was originally evaluated.
|
||||||
|
jobCancelRegister = jobContext.CancellationToken.Register(() =>
|
||||||
|
{
|
||||||
|
// mark job as cancelled
|
||||||
|
jobContext.Result = TaskResult.Canceled;
|
||||||
|
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||||
|
|
||||||
|
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
||||||
|
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
|
||||||
|
var conditionReTestResult = false;
|
||||||
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Cancel the step since we get exception while re-evaluate step condition.
|
||||||
|
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
||||||
|
step.ExecutionContext.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conditionReTestResult)
|
||||||
|
{
|
||||||
|
// Cancel the step.
|
||||||
|
Trace.Info("Cancel current running step.");
|
||||||
|
step.ExecutionContext.CancelToken();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (jobContext.Result != TaskResult.Canceled)
|
||||||
|
{
|
||||||
|
// mark job as cancelled
|
||||||
|
jobContext.Result = TaskResult.Canceled;
|
||||||
|
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate condition.
|
||||||
|
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
||||||
|
var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext);
|
||||||
|
var conditionResult = false;
|
||||||
|
var conditionEvaluateError = default(Exception);
|
||||||
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Info("Caught exception from expression.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
conditionEvaluateError = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no evaluate error but condition is false
|
||||||
|
if (!conditionResult && conditionEvaluateError == null)
|
||||||
|
{
|
||||||
|
// Condition == false
|
||||||
|
Trace.Info("Skipping step due to condition evaluation.");
|
||||||
|
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace);
|
||||||
|
}
|
||||||
|
else if (conditionEvaluateError != null)
|
||||||
|
{
|
||||||
|
// fail the step since there is an evaluate error.
|
||||||
|
step.ExecutionContext.Error(conditionEvaluateError);
|
||||||
|
CompleteStep(step, nextStep, TaskResult.Failed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Run the step.
|
||||||
|
await RunStepAsync(step, jobContext.CancellationToken);
|
||||||
|
CompleteStep(step, nextStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (jobCancelRegister != null)
|
||||||
|
{
|
||||||
|
jobCancelRegister?.Dispose();
|
||||||
|
jobCancelRegister = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -394,7 +410,11 @@ namespace GitHub.Runner.Worker
|
|||||||
scope = scopesToInitialize.Pop();
|
scope = scopesToInitialize.Pop();
|
||||||
executionContext.Debug($"Initializing scope '{scope.Name}'");
|
executionContext.Debug($"Initializing scope '{scope.Name}'");
|
||||||
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.ParentName);
|
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.ParentName);
|
||||||
executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null;
|
// TODO: Fix this temporary workaround for Composite Actions
|
||||||
|
if (!executionContext.ExpressionValues.ContainsKey("inputs") && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
||||||
|
{
|
||||||
|
executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null;
|
||||||
|
}
|
||||||
var templateEvaluator = executionContext.ToPipelineTemplateEvaluator();
|
var templateEvaluator = executionContext.ToPipelineTemplateEvaluator();
|
||||||
var inputs = default(DictionaryContextData);
|
var inputs = default(DictionaryContextData);
|
||||||
try
|
try
|
||||||
@@ -417,7 +437,11 @@ namespace GitHub.Runner.Worker
|
|||||||
// Setup expression values
|
// Setup expression values
|
||||||
var scopeName = executionContext.ScopeName;
|
var scopeName = executionContext.ScopeName;
|
||||||
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scopeName);
|
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scopeName);
|
||||||
executionContext.ExpressionValues["inputs"] = string.IsNullOrEmpty(scopeName) ? null : scopeInputs[scopeName];
|
// TODO: Fix this temporary workaround for Composite Actions
|
||||||
|
if (!executionContext.ExpressionValues.ContainsKey("inputs") && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
||||||
|
{
|
||||||
|
executionContext.ExpressionValues["inputs"] = string.IsNullOrEmpty(scopeName) ? null : scopeInputs[scopeName];
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,8 @@
|
|||||||
"one-of": [
|
"one-of": [
|
||||||
"container-runs",
|
"container-runs",
|
||||||
"node12-runs",
|
"node12-runs",
|
||||||
"plugin-runs"
|
"plugin-runs",
|
||||||
|
"composite-runs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"container-runs": {
|
"container-runs": {
|
||||||
@@ -83,6 +84,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"composite-runs": {
|
||||||
|
"mapping": {
|
||||||
|
"properties": {
|
||||||
|
"using": "non-empty-string",
|
||||||
|
"steps": "composite-steps"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"composite-steps": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"secrets",
|
||||||
|
"steps",
|
||||||
|
"inputs",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,0)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,0)",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"sequence": {
|
||||||
|
"item-type": "any"
|
||||||
|
}
|
||||||
|
},
|
||||||
"container-runs-context": {
|
"container-runs-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"inputs"
|
"inputs"
|
||||||
|
|||||||
@@ -317,5 +317,37 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [Preview API] Resolves information required to download actions (URL, token) defined in an orchestration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
||||||
|
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
||||||
|
/// <param name="planId"></param>
|
||||||
|
/// <param name="actionReferenceList"></param>
|
||||||
|
/// <param name="userState"></param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||||
|
public virtual Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(
|
||||||
|
Guid scopeIdentifier,
|
||||||
|
string hubName,
|
||||||
|
Guid planId,
|
||||||
|
ActionReferenceList actionReferenceList,
|
||||||
|
object userState = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
|
Guid locationId = new Guid("27d7f831-88c1-4719-8ca1-6a061dad90eb");
|
||||||
|
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId };
|
||||||
|
HttpContent content = new ObjectContent<ActionReferenceList>(actionReferenceList, new VssJsonMediaTypeFormatter(true));
|
||||||
|
|
||||||
|
return SendAsync<ActionDownloadInfoCollection>(
|
||||||
|
httpMethod,
|
||||||
|
locationId,
|
||||||
|
routeValues: routeValues,
|
||||||
|
version: new ApiResourceVersion(6.0, 1),
|
||||||
|
userState: userState,
|
||||||
|
cancellationToken: cancellationToken,
|
||||||
|
content: content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
public const String StepEnv = "step-env";
|
public const String StepEnv = "step-env";
|
||||||
public const String StepIfResult = "step-if-result";
|
public const String StepIfResult = "step-if-result";
|
||||||
public const String Steps = "steps";
|
public const String Steps = "steps";
|
||||||
|
public const String StepsInTemplate = "steps-in-template";
|
||||||
public const String StepsScopeInputs = "steps-scope-inputs";
|
public const String StepsScopeInputs = "steps-scope-inputs";
|
||||||
public const String StepsScopeOutputs = "steps-scope-outputs";
|
public const String StepsScopeOutputs = "steps-scope-outputs";
|
||||||
public const String StepsTemplateRoot = "steps-template-root";
|
public const String StepsTemplateRoot = "steps-template-root";
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
var evaluationResult = EvaluationResult.CreateIntermediateResult(null, ifResult);
|
var evaluationResult = EvaluationResult.CreateIntermediateResult(null, ifResult);
|
||||||
return evaluationResult.IsTruthy;
|
return evaluationResult.IsTruthy;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Boolean? ConvertToStepContinueOnError(
|
internal static Boolean? ConvertToStepContinueOnError(
|
||||||
TemplateContext context,
|
TemplateContext context,
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
@@ -264,5 +263,351 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Note: originally was List<Step> but we need to change to List<ActionStep> to use the "Inputs" attribute
|
||||||
|
internal static List<ActionStep> ConvertToSteps(
|
||||||
|
TemplateContext context,
|
||||||
|
TemplateToken steps)
|
||||||
|
{
|
||||||
|
var stepsSequence = steps.AssertSequence($"job {PipelineTemplateConstants.Steps}");
|
||||||
|
|
||||||
|
var result = new List<ActionStep>();
|
||||||
|
foreach (var stepsItem in stepsSequence)
|
||||||
|
{
|
||||||
|
var step = ConvertToStep(context, stepsItem);
|
||||||
|
if (step != null) // step = null means we are hitting error during step conversion, there should be an error in context.errors
|
||||||
|
{
|
||||||
|
if (step.Enabled)
|
||||||
|
{
|
||||||
|
result.Add(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionStep ConvertToStep(
|
||||||
|
TemplateContext context,
|
||||||
|
TemplateToken stepsItem)
|
||||||
|
{
|
||||||
|
var step = stepsItem.AssertMapping($"{PipelineTemplateConstants.Steps} item");
|
||||||
|
var continueOnError = default(ScalarToken);
|
||||||
|
var env = default(TemplateToken);
|
||||||
|
var id = default(StringToken);
|
||||||
|
var ifCondition = default(String);
|
||||||
|
var ifToken = default(ScalarToken);
|
||||||
|
var name = default(ScalarToken);
|
||||||
|
var run = default(ScalarToken);
|
||||||
|
var scope = default(StringToken);
|
||||||
|
var timeoutMinutes = default(ScalarToken);
|
||||||
|
var uses = default(StringToken);
|
||||||
|
var with = default(TemplateToken);
|
||||||
|
var workingDir = default(ScalarToken);
|
||||||
|
var path = default(ScalarToken);
|
||||||
|
var clean = default(ScalarToken);
|
||||||
|
var fetchDepth = default(ScalarToken);
|
||||||
|
var lfs = default(ScalarToken);
|
||||||
|
var submodules = default(ScalarToken);
|
||||||
|
var shell = default(ScalarToken);
|
||||||
|
|
||||||
|
foreach (var stepProperty in step)
|
||||||
|
{
|
||||||
|
var propertyName = stepProperty.Key.AssertString($"{PipelineTemplateConstants.Steps} item key");
|
||||||
|
|
||||||
|
switch (propertyName.Value)
|
||||||
|
{
|
||||||
|
case PipelineTemplateConstants.Clean:
|
||||||
|
clean = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Clean}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.ContinueOnError:
|
||||||
|
ConvertToStepContinueOnError(context, stepProperty.Value, allowExpressions: true); // Validate early if possible
|
||||||
|
continueOnError = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} {PipelineTemplateConstants.ContinueOnError}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Env:
|
||||||
|
ConvertToStepEnvironment(context, stepProperty.Value, StringComparer.Ordinal, allowExpressions: true); // Validate early if possible
|
||||||
|
env = stepProperty.Value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.FetchDepth:
|
||||||
|
fetchDepth = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.FetchDepth}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Id:
|
||||||
|
id = stepProperty.Value.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Id}");
|
||||||
|
if (!NameValidation.IsValid(id.Value, true))
|
||||||
|
{
|
||||||
|
context.Error(id, $"Step id {id.Value} is invalid. Ids must start with a letter or '_' and contain only alphanumeric characters, '-', or '_'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.If:
|
||||||
|
ifToken = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.If}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Lfs:
|
||||||
|
lfs = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Lfs}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Name:
|
||||||
|
name = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Name}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Path:
|
||||||
|
path = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Path}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Run:
|
||||||
|
run = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Run}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Shell:
|
||||||
|
shell = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Shell}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Scope:
|
||||||
|
scope = stepProperty.Value.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Scope}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Submodules:
|
||||||
|
submodules = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Submodules}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.TimeoutMinutes:
|
||||||
|
ConvertToStepTimeout(context, stepProperty.Value, allowExpressions: true); // Validate early if possible
|
||||||
|
timeoutMinutes = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.TimeoutMinutes}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Uses:
|
||||||
|
uses = stepProperty.Value.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Uses}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.With:
|
||||||
|
ConvertToStepInputs(context, stepProperty.Value, allowExpressions: true); // Validate early if possible
|
||||||
|
with = stepProperty.Value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.WorkingDirectory:
|
||||||
|
workingDir = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.WorkingDirectory}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
propertyName.AssertUnexpectedValue($"{PipelineTemplateConstants.Steps} item key"); // throws
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixup the if-condition
|
||||||
|
var isDefaultScope = String.IsNullOrEmpty(scope?.Value);
|
||||||
|
ifCondition = ConvertToIfCondition(context, ifToken, false, isDefaultScope);
|
||||||
|
|
||||||
|
if (run != null)
|
||||||
|
{
|
||||||
|
var result = new ActionStep
|
||||||
|
{
|
||||||
|
ScopeName = scope?.Value,
|
||||||
|
ContextName = id?.Value,
|
||||||
|
ContinueOnError = continueOnError,
|
||||||
|
DisplayNameToken = name,
|
||||||
|
Condition = ifCondition,
|
||||||
|
TimeoutInMinutes = timeoutMinutes,
|
||||||
|
Environment = env,
|
||||||
|
Reference = new ScriptReference(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var inputs = new MappingToken(null, null, null);
|
||||||
|
inputs.Add(new StringToken(null, null, null, PipelineConstants.ScriptStepInputs.Script), run);
|
||||||
|
|
||||||
|
if (workingDir != null)
|
||||||
|
{
|
||||||
|
inputs.Add(new StringToken(null, null, null, PipelineConstants.ScriptStepInputs.WorkingDirectory), workingDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shell != null)
|
||||||
|
{
|
||||||
|
inputs.Add(new StringToken(null, null, null, PipelineConstants.ScriptStepInputs.Shell), shell);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Inputs = inputs;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uses.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Uses}");
|
||||||
|
var result = new ActionStep
|
||||||
|
{
|
||||||
|
ScopeName = scope?.Value,
|
||||||
|
ContextName = id?.Value,
|
||||||
|
ContinueOnError = continueOnError,
|
||||||
|
DisplayNameToken = name,
|
||||||
|
Condition = ifCondition,
|
||||||
|
TimeoutInMinutes = timeoutMinutes,
|
||||||
|
Inputs = with,
|
||||||
|
Environment = env,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (uses.Value.StartsWith("docker://", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var image = uses.Value.Substring("docker://".Length);
|
||||||
|
result.Reference = new ContainerRegistryReference { Image = image };
|
||||||
|
}
|
||||||
|
else if (uses.Value.StartsWith("./") || uses.Value.StartsWith(".\\"))
|
||||||
|
{
|
||||||
|
result.Reference = new RepositoryPathReference
|
||||||
|
{
|
||||||
|
RepositoryType = PipelineConstants.SelfAlias,
|
||||||
|
Path = uses.Value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var usesSegments = uses.Value.Split('@');
|
||||||
|
var pathSegments = usesSegments[0].Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var gitRef = usesSegments.Length == 2 ? usesSegments[1] : String.Empty;
|
||||||
|
|
||||||
|
if (usesSegments.Length != 2 ||
|
||||||
|
pathSegments.Length < 2 ||
|
||||||
|
String.IsNullOrEmpty(pathSegments[0]) ||
|
||||||
|
String.IsNullOrEmpty(pathSegments[1]) ||
|
||||||
|
String.IsNullOrEmpty(gitRef))
|
||||||
|
{
|
||||||
|
// todo: loc
|
||||||
|
context.Error(uses, $"Expected format {{org}}/{{repo}}[/path]@ref. Actual '{uses.Value}'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var repositoryName = $"{pathSegments[0]}/{pathSegments[1]}";
|
||||||
|
var directoryPath = pathSegments.Length > 2 ? String.Join("/", pathSegments.Skip(2)) : String.Empty;
|
||||||
|
|
||||||
|
result.Reference = new RepositoryPathReference
|
||||||
|
{
|
||||||
|
RepositoryType = RepositoryTypes.GitHub,
|
||||||
|
Name = repositoryName,
|
||||||
|
Ref = gitRef,
|
||||||
|
Path = directoryPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When empty, default to "success()".
|
||||||
|
/// When a status function is not referenced, format as "success() && <CONDITION>".
|
||||||
|
/// </summary>
|
||||||
|
private static String ConvertToIfCondition(
|
||||||
|
TemplateContext context,
|
||||||
|
TemplateToken token,
|
||||||
|
Boolean isJob,
|
||||||
|
Boolean isDefaultScope)
|
||||||
|
{
|
||||||
|
String condition;
|
||||||
|
if (token is null)
|
||||||
|
{
|
||||||
|
condition = null;
|
||||||
|
}
|
||||||
|
else if (token is BasicExpressionToken expressionToken)
|
||||||
|
{
|
||||||
|
condition = expressionToken.Expression;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var stringToken = token.AssertString($"{(isJob ? "job" : "step")} {PipelineTemplateConstants.If}");
|
||||||
|
condition = stringToken.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(condition))
|
||||||
|
{
|
||||||
|
return $"{PipelineTemplateConstants.Success}()";
|
||||||
|
}
|
||||||
|
|
||||||
|
var expressionParser = new ExpressionParser();
|
||||||
|
var functions = default(IFunctionInfo[]);
|
||||||
|
var namedValues = default(INamedValueInfo[]);
|
||||||
|
if (isJob)
|
||||||
|
{
|
||||||
|
namedValues = s_jobIfNamedValues;
|
||||||
|
// TODO: refactor into seperate functions
|
||||||
|
// functions = PhaseCondition.FunctionInfo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
namedValues = isDefaultScope ? s_stepNamedValues : s_stepInTemplateNamedValues;
|
||||||
|
functions = s_stepConditionFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = default(ExpressionNode);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
node = expressionParser.CreateTree(condition, null, namedValues, functions) as ExpressionNode;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
context.Error(token, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
return $"{PipelineTemplateConstants.Success}()";
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasStatusFunction = node.Traverse().Any(x =>
|
||||||
|
{
|
||||||
|
if (x is Function function)
|
||||||
|
{
|
||||||
|
return String.Equals(function.Name, PipelineTemplateConstants.Always, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
String.Equals(function.Name, PipelineTemplateConstants.Cancelled, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
String.Equals(function.Name, PipelineTemplateConstants.Failure, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
String.Equals(function.Name, PipelineTemplateConstants.Success, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasStatusFunction ? condition : $"{PipelineTemplateConstants.Success}() && ({condition})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly INamedValueInfo[] s_jobIfNamedValues = new INamedValueInfo[]
|
||||||
|
{
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.GitHub),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Needs),
|
||||||
|
};
|
||||||
|
private static readonly INamedValueInfo[] s_stepNamedValues = new INamedValueInfo[]
|
||||||
|
{
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Strategy),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Matrix),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Steps),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.GitHub),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Job),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Runner),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Env),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Needs),
|
||||||
|
};
|
||||||
|
private static readonly INamedValueInfo[] s_stepInTemplateNamedValues = new INamedValueInfo[]
|
||||||
|
{
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Strategy),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Matrix),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Steps),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Inputs),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.GitHub),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Job),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Runner),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Env),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Needs),
|
||||||
|
};
|
||||||
|
private static readonly IFunctionInfo[] s_stepConditionFunctions = new IFunctionInfo[]
|
||||||
|
{
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.Always, 0, 0),
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.Cancelled, 0, 0),
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.Failure, 0, 0),
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.Success, 0, 0),
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.HashFiles, 1, Byte.MaxValue),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,32 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ActionStep> LoadCompositeSteps(
|
||||||
|
TemplateToken token
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var result = default(List<ActionStep>);
|
||||||
|
if (token != null && token.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
var context = CreateContext(null, null, setMissingContext: false);
|
||||||
|
// TODO: we might want to to have a bool to prevent it from filling in with missing context w/ dummy variables
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsInTemplate, token, 0, null, omitHeader: true);
|
||||||
|
context.Errors.Check();
|
||||||
|
result = PipelineTemplateConverter.ConvertToSteps(context, token);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||||
|
{
|
||||||
|
context.Errors.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Errors.Check();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Dictionary<String, String> EvaluateStepEnvironment(
|
public Dictionary<String, String> EvaluateStepEnvironment(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
@@ -400,7 +426,8 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
private TemplateContext CreateContext(
|
private TemplateContext CreateContext(
|
||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions,
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
IEnumerable<KeyValuePair<String, Object>> expressionState = null)
|
IEnumerable<KeyValuePair<String, Object>> expressionState = null,
|
||||||
|
bool setMissingContext = true)
|
||||||
{
|
{
|
||||||
var result = new TemplateContext
|
var result = new TemplateContext
|
||||||
{
|
{
|
||||||
@@ -449,18 +476,21 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
// - Evaluating early when all referenced contexts are available, even though all allowed
|
// - Evaluating early when all referenced contexts are available, even though all allowed
|
||||||
// contexts may not yet be available. For example, evaluating step display name can often
|
// contexts may not yet be available. For example, evaluating step display name can often
|
||||||
// be performed early.
|
// be performed early.
|
||||||
foreach (var name in s_expressionValueNames)
|
if (setMissingContext)
|
||||||
{
|
{
|
||||||
if (!result.ExpressionValues.ContainsKey(name))
|
foreach (var name in s_expressionValueNames)
|
||||||
{
|
{
|
||||||
result.ExpressionValues[name] = null;
|
if (!result.ExpressionValues.ContainsKey(name))
|
||||||
|
{
|
||||||
|
result.ExpressionValues[name] = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
foreach (var name in s_expressionFunctionNames)
|
||||||
foreach (var name in s_expressionFunctionNames)
|
|
||||||
{
|
|
||||||
if (!functionNames.Contains(name))
|
|
||||||
{
|
{
|
||||||
result.ExpressionFunctions.Add(new FunctionInfo<NoOperation>(name, 0, Int32.MaxValue));
|
if (!functionNames.Contains(name))
|
||||||
|
{
|
||||||
|
result.ExpressionFunctions.Add(new FunctionInfo<NoOperation>(name, 0, Int32.MaxValue));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,5 +94,12 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
public static readonly String Resources = "resources";
|
public static readonly String Resources = "resources";
|
||||||
public static readonly String All = "all";
|
public static readonly String All = "all";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ScriptStepInputs
|
||||||
|
{
|
||||||
|
public static readonly String Script = "script";
|
||||||
|
public static readonly String WorkingDirectory = "workingDirectory";
|
||||||
|
public static readonly String Shell = "shell";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/Sdk/DTWebApi/WebApi/ActionDownloadInfo.cs
Normal file
40
src/Sdk/DTWebApi/WebApi/ActionDownloadInfo.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
[DataContract]
|
||||||
|
public class ActionDownloadInfo
|
||||||
|
{
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public ActionDownloadAuthentication Authentication { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string NameWithOwner { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string ResolvedNameWithOwner { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string ResolvedSha { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string TarballUrl { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string Ref { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string ZipballUrl { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
public class ActionDownloadAuthentication
|
||||||
|
{
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public DateTime ExpiresAt { get; set; }
|
||||||
|
|
||||||
|
[DataMember(EmitDefaultValue = false)]
|
||||||
|
public string Token { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Sdk/DTWebApi/WebApi/ActionDownloadInfoCollection.cs
Normal file
16
src/Sdk/DTWebApi/WebApi/ActionDownloadInfoCollection.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
[DataContract]
|
||||||
|
public class ActionDownloadInfoCollection
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public IDictionary<string, ActionDownloadInfo> Actions
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/Sdk/DTWebApi/WebApi/ActionReference.cs
Normal file
22
src/Sdk/DTWebApi/WebApi/ActionReference.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
[DataContract]
|
||||||
|
public class ActionReference
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string NameWithOwner
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public string Ref
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Sdk/DTWebApi/WebApi/ActionReferenceList.cs
Normal file
16
src/Sdk/DTWebApi/WebApi/ActionReferenceList.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
[DataContract]
|
||||||
|
public class ActionReferenceList
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public IList<ActionReference> Actions
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -131,7 +140,7 @@ namespace GitHub.Services.OAuth
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (this.SignInUrl == null ||
|
if (this.SignInUrl == null ||
|
||||||
this.Grant == null ||
|
this.Grant == null ||
|
||||||
this.ClientCredential == null)
|
this.ClientCredential == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -323,6 +324,60 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
|||||||
@@ -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", "");
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(variableSet.Select(x => x.Object).ToList()));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(variableSet.Select(x => x.Object).ToList()));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -115,7 +115,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(variableSet.Select(x => x.Object).ToList()));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(variableSet.Select(x => x.Object).ToList()));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -154,7 +154,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(variableSet.Steps.Select(x => x.Object).ToList()));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(variableSet.Steps.Select(x => x.Object).ToList()));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -208,7 +208,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(variableSet.Steps.Select(x => x.Object).ToList()));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(variableSet.Steps.Select(x => x.Object).ToList()));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -287,7 +287,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(variableSet.Steps.Select(x => x.Object).ToList()));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(variableSet.Steps.Select(x => x.Object).ToList()));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -330,7 +330,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(variableSet.Step.Select(x => x.Object).ToList()));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(variableSet.Step.Select(x => x.Object).ToList()));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -361,7 +361,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(variableSet.Select(x => x.Object).ToList()));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(variableSet.Select(x => x.Object).ToList()));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -391,7 +391,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(variableSet.Select(x => x.Object).ToList()));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(variableSet.Select(x => x.Object).ToList()));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -417,7 +417,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new[] { step1.Object }));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(new[] { step1.Object }));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -455,7 +455,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new[] { step1.Object, step2.Object }));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(new[] { step1.Object, step2.Object }));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -493,7 +493,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new[] { step1.Object, step2.Object }));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(new[] { step1.Object, step2.Object }));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -524,7 +524,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new[] { step1.Object, step2.Object, step3.Object }));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(new[] { step1.Object, step2.Object, step3.Object }));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -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(""));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,7 +560,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
_ec.Object.Result = null;
|
_ec.Object.Result = null;
|
||||||
|
|
||||||
_ec.Setup(x => x.JobSteps).Returns(new Queue<IStep>(new[] { step1.Object, step2.Object, step3.Object }));
|
_ec.Setup(x => x.JobSteps).Returns(new List<IStep>(new[] { step1.Object, step2.Object, step3.Object }));
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
@@ -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}"); });
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.169.0
|
2.263.0
|
||||||
|
|||||||
Reference in New Issue
Block a user