mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
43 Commits
users/tihu
...
automate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6122975a2 | ||
|
|
21d5973243 | ||
|
|
f4c58235e2 | ||
|
|
e8581d562c | ||
|
|
1d88f86230 | ||
|
|
ce43e5043a | ||
|
|
2d97cef1d2 | ||
|
|
52ff9b4f59 | ||
|
|
055585d8cd | ||
|
|
7e7e6c6568 | ||
|
|
ccdf27e0d4 | ||
|
|
de317dba60 | ||
|
|
1410fe000e | ||
|
|
8e78c7ba11 | ||
|
|
74cc31524c | ||
|
|
0fdbfaf862 | ||
|
|
de914793e6 | ||
|
|
c126b52fe5 | ||
|
|
117ec1fff9 | ||
|
|
d5c7097d2c | ||
|
|
f9baec4b32 | ||
|
|
a20ad4e121 | ||
|
|
2bd0b1af0e | ||
|
|
baa6ded3bc | ||
|
|
7817e1a976 | ||
|
|
d90273a068 | ||
|
|
2cdde6cb16 | ||
|
|
1f52dfa636 | ||
|
|
83b5742278 | ||
|
|
ba69b5bc93 | ||
|
|
0e8777ebda | ||
|
|
a5f06b3ec2 | ||
|
|
be325f26a6 | ||
|
|
dec260920f | ||
|
|
b0a1294ef5 | ||
|
|
3d70ef2da1 | ||
|
|
e23d68f6e2 | ||
|
|
dff1024cd3 | ||
|
|
9fc0686dc2 | ||
|
|
ab001a7004 | ||
|
|
178a618e01 | ||
|
|
dfaf6e06ee | ||
|
|
b0a71481f0 |
75
docs/adrs/0361-wrapper-action.md
Normal file
75
docs/adrs/0361-wrapper-action.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# ADR 361: Wrapper Action
|
||||||
|
|
||||||
|
**Date**: 2020-03-06
|
||||||
|
|
||||||
|
**Status**: Pending
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
In addition to action's regular execution, action author may wants their action has a chance to participate in:
|
||||||
|
- Job initialize
|
||||||
|
My Action will collect machine resource usage (CPU/RAM/Disk) during a workflow job execution, we need to start perf recorder at the begin of the job.
|
||||||
|
- Job cleanup
|
||||||
|
My Action will dirty local workspace or machine environment during execution, we need to cleanup these changes at the end of the job.
|
||||||
|
Ex: `actions/checkout@v2` will write `github.token` into local `.git/config` during execution, it has post job cleanup defined to undo the changes.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### Add `pre` and `post` execution to action
|
||||||
|
|
||||||
|
Node Action Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'My action with pre'
|
||||||
|
description: 'My action with pre'
|
||||||
|
runs:
|
||||||
|
using: 'node12'
|
||||||
|
pre: 'setup.js'
|
||||||
|
pre-if: 'success()' // Optional
|
||||||
|
main: 'index.js'
|
||||||
|
post: 'cleanup.js'
|
||||||
|
post-if: 'success()' // Optional
|
||||||
|
```
|
||||||
|
|
||||||
|
Container Action Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: 'My action with pre'
|
||||||
|
description: 'My action with pre'
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'mycontainer:latest'
|
||||||
|
pre-entrypoint: 'setup.sh'
|
||||||
|
pre-if: 'success()' // Optional
|
||||||
|
entrypoint: 'entrypoint.sh'
|
||||||
|
post-entrypoint: 'cleanup.sh'
|
||||||
|
post-if: 'success()' // Optional
|
||||||
|
```
|
||||||
|
|
||||||
|
Both `pre` and `post` will has default `pre-if/post-if` sets to `always()`.
|
||||||
|
Setting `pre` to `always()` will make sure no matter what condition evaluate result the `main` gets at runtime, the `pre` has always run already.
|
||||||
|
`pre` executes in order of how the steps are defined.
|
||||||
|
`pre` will always be added to job steps list during job setup.
|
||||||
|
> Action referenced from local repository (`./my-action`) won't get `pre` setup correctly since the repository haven't checkout during job initialize.
|
||||||
|
> We can't use GitHub api to download the repository since there is a about 3 mins delay between `git push` and the new commit available to download using GitHub api.
|
||||||
|
|
||||||
|
`post` will be pushed into a `poststeps` stack lazily when the action's `pre` or `main` execution passed `if` condition check and about to run, you can't have an action that only contains a `post`, we will pop and run each `post` after all `pre` and `main` finished.
|
||||||
|
> Currently `post` works for both repository action (`org/repo@v1`) and local action (`./my-action`)
|
||||||
|
|
||||||
|
Valid action:
|
||||||
|
- only has `main`
|
||||||
|
- has `pre` and `main`
|
||||||
|
- has `main` and `post`
|
||||||
|
- has `pre`, `main` and `post`
|
||||||
|
|
||||||
|
Invalid action:
|
||||||
|
- only has `pre`
|
||||||
|
- only has `post`
|
||||||
|
- has `pre` and `post`
|
||||||
|
|
||||||
|
Potential downside of introducing `pre`:
|
||||||
|
|
||||||
|
- Extra magic wrt step order. Users should control the step order. Especially when we introduce templates.
|
||||||
|
- Eliminates the possibility to lazily download the action tarball, since `pre` always run by default, we have to download the tarball to check whether action defined a `pre`
|
||||||
|
- `pre` doesn't work with local action, we suggested customer use local action for testing their action changes, ex CI for their action, to avoid delay between `git push` and GitHub repo tarball download api.
|
||||||
|
- Condition on the `pre` can't be controlled using dynamic step outputs. `pre` executes too early.
|
||||||
56
docs/adrs/0397-runner-registration-labels.md
Normal file
56
docs/adrs/0397-runner-registration-labels.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# ADR 0397: Support adding custom labels during runner config
|
||||||
|
**Date**: 2020-03-30
|
||||||
|
|
||||||
|
**Status**: Approved
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Since configuring self-hosted runners is commonly automated via scripts, the labels need to be able to be created during configuration. The runner currently registers the built-in labels (os, arch) during registration but does not accept labels via command line args to extend the set registered.
|
||||||
|
|
||||||
|
See Issue: https://github.com/actions/runner/issues/262
|
||||||
|
|
||||||
|
This is another version of [ADR275](https://github.com/actions/runner/pull/275)
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
This ADR proposes that we add a `--labels` option to `config`, which could be used to add custom additional labels to the configured runner.
|
||||||
|
|
||||||
|
For example, to add a single extra label the operator could run:
|
||||||
|
```bash
|
||||||
|
./config.sh --labels mylabel
|
||||||
|
```
|
||||||
|
> Note: the current runner command line parsing and envvar override algorithm only supports a single argument (key).
|
||||||
|
|
||||||
|
This would add the label `mylabel` to the runner, and enable users to select the runner in their workflow using this label:
|
||||||
|
```yaml
|
||||||
|
runs-on: [self-hosted, mylabel]
|
||||||
|
```
|
||||||
|
|
||||||
|
To add multiple labels the operator could run:
|
||||||
|
```bash
|
||||||
|
./config.sh --labels mylabel,anotherlabel
|
||||||
|
```
|
||||||
|
> Note: the current runner command line parsing and envvar override algorithm only supports a single argument (key).
|
||||||
|
|
||||||
|
This would add the label `mylabel` and `anotherlabel` to the runner, and enable users to select the runner in their workflow using this label:
|
||||||
|
```yaml
|
||||||
|
runs-on: [self-hosted, mylabel, anotherlabel]
|
||||||
|
```
|
||||||
|
|
||||||
|
It would not be possible to remove labels from an existing runner using `config.sh`, instead labels would have to be removed using the GitHub UI.
|
||||||
|
|
||||||
|
The labels argument will split on commas, trim and discard empty strings. That effectively means don't use commans in unattended config label names. Alternatively we could choose to escape commans but it's a nice to have.
|
||||||
|
|
||||||
|
## Replace
|
||||||
|
|
||||||
|
If an existing runner exists and the option to replace is chosen (interactively of via unattend as in this scenario), then the labels will be replaced / overwritten (not merged).
|
||||||
|
|
||||||
|
## Overriding built-in labels
|
||||||
|
|
||||||
|
Note that it is possible to register "built-in" hosted labels like `ubuntu-latest` and is not considered an error. This is an effective way for the org / runner admin to dictate by policy through registration that this set of runners will be used without having to edit all the workflow files now and in the future.
|
||||||
|
|
||||||
|
We will also not make other restrictions such as limiting explicitly adding os / arch labels and validating. We will assume that explicit labels were added for a reason and not restricting offers the most flexibility and future proofing / compat.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
The ability to add custom labels to a self-hosted runner would enable most scenarios where job runner selection based on runner capabilities or characteristics are required.
|
||||||
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
|
||||||
|
```
|
||||||
@@ -1,27 +1,11 @@
|
|||||||
## Features
|
## Features
|
||||||
- Expose whether debug is on/off via RUNNER_DEBUG. (#253)
|
- Runner support for GHES Alpha (#381 #386 #390 #393 $401)
|
||||||
- Upload log on runner when worker get killed due to cancellation timeout. (#255)
|
- Allow secrets context in Container.env (#388)
|
||||||
- Update config.sh/cmd --help documentation (#282)
|
|
||||||
- Set http_proxy and related env vars for job/service containers (#304)
|
|
||||||
- Set both http_proxy and HTTP_PROXY env for runner/worker processes. (#298)
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- Verify runner Windows service hash started successfully after configuration (#236)
|
- Raise warning when volume mount root. (#413)
|
||||||
- Detect source file path in L0 without using env. (#257)
|
- Fix typo (#394)
|
||||||
- Handle escaped '%' in commands data section (#200)
|
|
||||||
- Allow container to be null/empty during matrix expansion (#266)
|
|
||||||
- Translate problem matcher file to host path (#272)
|
|
||||||
- Change hashFiles() expression function to use @actions/glob. (#268)
|
|
||||||
- Default post-job action's condition to always(). (#293)
|
|
||||||
- Support action.yaml file as action's entry file (#288)
|
|
||||||
- Trace javascript action exit code to debug instead of user logs (#290)
|
|
||||||
- Change prompt message when removing a runner to lines up with GitHub.com UI (#303)
|
|
||||||
- Include step.env as part of env context. (#300)
|
|
||||||
- Update Base64 Encoders to deal with suffixes (#284)
|
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
- Move .sln file under ./src (#238)
|
- N/A
|
||||||
- Treat warnings as errors during compile (#249)
|
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.164.0
|
2.168.0
|
||||||
|
|||||||
135
scripts/create-latest-svc.sh
Executable file
135
scripts/create-latest-svc.sh
Executable file
@@ -0,0 +1,135 @@
|
|||||||
|
#/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
|
||||||
|
# RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myorg
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# export RUNNER_CFG_PAT=<yourPAT>
|
||||||
|
# ./create-latest-svc scope [name] [user]
|
||||||
|
#
|
||||||
|
# scope required repo (:owner/:repo) or org (:organization)
|
||||||
|
# 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}
|
||||||
|
runner_name=${2:-$(hostname)}
|
||||||
|
svc_user=${3:-$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..."
|
||||||
|
|
||||||
|
# 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 RUNNER_TOKEN=$(curl -s -X POST ${base_api_url}/${runner_scope}/actions/runners/registration-token -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" | jq -r '.token')
|
||||||
|
|
||||||
|
if [ -z "$RUNNER_TOKEN" ]; then fatal "Failed to get a token"; fi
|
||||||
|
|
||||||
|
#---------------------------------------
|
||||||
|
# Download latest released and extract
|
||||||
|
#---------------------------------------
|
||||||
|
echo
|
||||||
|
echo "Downloading latest runner ..."
|
||||||
|
|
||||||
|
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}"
|
||||||
|
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
|
||||||
13
src/Misc/dotnet-install.sh
vendored
13
src/Misc/dotnet-install.sh
vendored
@@ -172,7 +172,7 @@ get_current_os_name() {
|
|||||||
return 0
|
return 0
|
||||||
elif [ "$uname" = "FreeBSD" ]; then
|
elif [ "$uname" = "FreeBSD" ]; then
|
||||||
echo "freebsd"
|
echo "freebsd"
|
||||||
return 0
|
return 0
|
||||||
elif [ "$uname" = "Linux" ]; then
|
elif [ "$uname" = "Linux" ]; then
|
||||||
local linux_platform_name
|
local linux_platform_name
|
||||||
linux_platform_name="$(get_linux_platform_name)" || { echo "linux" && return 0 ; }
|
linux_platform_name="$(get_linux_platform_name)" || { echo "linux" && return 0 ; }
|
||||||
@@ -728,11 +728,12 @@ downloadcurl() {
|
|||||||
# Append feed_credential as late as possible before calling curl to avoid logging feed_credential
|
# Append feed_credential as late as possible before calling curl to avoid logging feed_credential
|
||||||
remote_path="${remote_path}${feed_credential}"
|
remote_path="${remote_path}${feed_credential}"
|
||||||
|
|
||||||
|
local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs "
|
||||||
local failed=false
|
local failed=false
|
||||||
if [ -z "$out_path" ]; then
|
if [ -z "$out_path" ]; then
|
||||||
curl --retry 10 -sSL -f --create-dirs "$remote_path" || failed=true
|
curl $curl_options "$remote_path" || failed=true
|
||||||
else
|
else
|
||||||
curl --retry 10 -sSL -f --create-dirs -o "$out_path" "$remote_path" || failed=true
|
curl $curl_options -o "$out_path" "$remote_path" || failed=true
|
||||||
fi
|
fi
|
||||||
if [ "$failed" = true ]; then
|
if [ "$failed" = true ]; then
|
||||||
say_verbose "Curl download failed"
|
say_verbose "Curl download failed"
|
||||||
@@ -748,12 +749,12 @@ downloadwget() {
|
|||||||
|
|
||||||
# Append feed_credential as late as possible before calling wget to avoid logging feed_credential
|
# Append feed_credential as late as possible before calling wget to avoid logging feed_credential
|
||||||
remote_path="${remote_path}${feed_credential}"
|
remote_path="${remote_path}${feed_credential}"
|
||||||
|
local wget_options="--tries 20 --waitretry 2 --connect-timeout 15 "
|
||||||
local failed=false
|
local failed=false
|
||||||
if [ -z "$out_path" ]; then
|
if [ -z "$out_path" ]; then
|
||||||
wget -q --tries 10 -O - "$remote_path" || failed=true
|
wget -q $wget_options -O - "$remote_path" || failed=true
|
||||||
else
|
else
|
||||||
wget --tries 10 -O "$out_path" "$remote_path" || failed=true
|
wget $wget_options -O "$out_path" "$remote_path" || failed=true
|
||||||
fi
|
fi
|
||||||
if [ "$failed" = true ]; then
|
if [ "$failed" = true ]; then
|
||||||
say_verbose "Wget download failed"
|
say_verbose "Wget download failed"
|
||||||
|
|||||||
1077
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
1077
src/Misc/expressionFunc/hashFiles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@
|
|||||||
"@types/node": "^12.7.12",
|
"@types/node": "^12.7.12",
|
||||||
"@typescript-eslint/parser": "^2.8.0",
|
"@typescript-eslint/parser": "^2.8.0",
|
||||||
"@zeit/ncc": "^0.20.5",
|
"@zeit/ncc": "^0.20.5",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-github": "^2.0.0",
|
"eslint-plugin-github": "^2.0.0",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"typescript": "^3.6.4"
|
"typescript": "^3.6.4"
|
||||||
|
|||||||
13
src/Misc/layoutbin/macos-run-invoker.js
Normal file
13
src/Misc/layoutbin/macos-run-invoker.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const { spawn } = require('child_process');
|
||||||
|
// argv[0] = node
|
||||||
|
// argv[1] = macos-run-invoker.js
|
||||||
|
var shell = process.argv[2];
|
||||||
|
var args = process.argv.slice(3);
|
||||||
|
console.log(`::debug::macos-run-invoker: ${shell}`);
|
||||||
|
console.log(`::debug::macos-run-invoker: ${JSON.stringify(args)}`);
|
||||||
|
var launch = spawn(shell, args, { stdio: 'inherit' });
|
||||||
|
launch.on('exit', function (code) {
|
||||||
|
if (code !== 0) {
|
||||||
|
process.exit(code);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -15,6 +15,9 @@ namespace GitHub.Runner.Common
|
|||||||
[DataContract]
|
[DataContract]
|
||||||
public sealed class RunnerSettings
|
public sealed class RunnerSettings
|
||||||
{
|
{
|
||||||
|
[DataMember(Name = "IsHostedServer", EmitDefaultValue = false)]
|
||||||
|
private bool? _isHostedServer;
|
||||||
|
|
||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public int AgentId { get; set; }
|
public int AgentId { get; set; }
|
||||||
|
|
||||||
@@ -42,6 +45,21 @@ namespace GitHub.Runner.Common
|
|||||||
[DataMember(EmitDefaultValue = false)]
|
[DataMember(EmitDefaultValue = false)]
|
||||||
public string MonitorSocketAddress { get; set; }
|
public string MonitorSocketAddress { get; set; }
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public bool IsHostedServer
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// Old runners do not have this property. Hosted runners likely don't have this property either.
|
||||||
|
return _isHostedServer ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isHostedServer = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
// Computed property for convenience. Can either return:
|
// Computed property for convenience. Can either return:
|
||||||
// 1. If runner was configured at the repo level, returns something like: "myorg/myrepo"
|
// 1. If runner was configured at the repo level, returns something like: "myorg/myrepo"
|
||||||
@@ -69,6 +87,15 @@ namespace GitHub.Runner.Common
|
|||||||
return repoOrOrgName;
|
return repoOrOrgName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[OnSerializing]
|
||||||
|
private void OnSerializing(StreamingContext context)
|
||||||
|
{
|
||||||
|
if (_isHostedServer.HasValue && _isHostedServer.Value)
|
||||||
|
{
|
||||||
|
_isHostedServer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(ConfigurationStore))]
|
[ServiceLocator(Default = typeof(ConfigurationStore))]
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ namespace GitHub.Runner.Common
|
|||||||
public static class Args
|
public static class Args
|
||||||
{
|
{
|
||||||
public static readonly string Auth = "auth";
|
public static readonly string Auth = "auth";
|
||||||
|
public static readonly string Labels = "labels";
|
||||||
public static readonly string MonitorSocketAddress = "monitorsocketaddress";
|
public static readonly string MonitorSocketAddress = "monitorsocketaddress";
|
||||||
public static readonly string Name = "name";
|
public static readonly string Name = "name";
|
||||||
public static readonly string Pool = "pool";
|
public static readonly string Pool = "pool";
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace GitHub.Runner.Common
|
|||||||
CancellationToken RunnerShutdownToken { get; }
|
CancellationToken RunnerShutdownToken { get; }
|
||||||
ShutdownReason RunnerShutdownReason { get; }
|
ShutdownReason RunnerShutdownReason { get; }
|
||||||
ISecretMasker SecretMasker { get; }
|
ISecretMasker SecretMasker { get; }
|
||||||
ProductInfoHeaderValue UserAgent { get; }
|
List<ProductInfoHeaderValue> UserAgents { get; }
|
||||||
RunnerWebProxy WebProxy { get; }
|
RunnerWebProxy WebProxy { get; }
|
||||||
string GetDirectory(WellKnownDirectory directory);
|
string GetDirectory(WellKnownDirectory directory);
|
||||||
string GetConfigFile(WellKnownConfigFile configFile);
|
string GetConfigFile(WellKnownConfigFile configFile);
|
||||||
@@ -54,7 +54,7 @@ namespace GitHub.Runner.Common
|
|||||||
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new ConcurrentDictionary<Type, object>();
|
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new ConcurrentDictionary<Type, object>();
|
||||||
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new ConcurrentDictionary<Type, Type>();
|
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new ConcurrentDictionary<Type, Type>();
|
||||||
private readonly ISecretMasker _secretMasker = new SecretMasker();
|
private readonly ISecretMasker _secretMasker = new SecretMasker();
|
||||||
private readonly ProductInfoHeaderValue _userAgent = new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version);
|
private readonly List<ProductInfoHeaderValue> _userAgents = new List<ProductInfoHeaderValue>() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
|
||||||
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
|
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
|
||||||
private object _perfLock = new object();
|
private object _perfLock = new object();
|
||||||
private Tracing _trace;
|
private Tracing _trace;
|
||||||
@@ -72,7 +72,7 @@ namespace GitHub.Runner.Common
|
|||||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||||
public ShutdownReason RunnerShutdownReason { get; private set; }
|
public ShutdownReason RunnerShutdownReason { get; private set; }
|
||||||
public ISecretMasker SecretMasker => _secretMasker;
|
public ISecretMasker SecretMasker => _secretMasker;
|
||||||
public ProductInfoHeaderValue UserAgent => _userAgent;
|
public List<ProductInfoHeaderValue> UserAgents => _userAgents;
|
||||||
public RunnerWebProxy WebProxy => _webProxy;
|
public RunnerWebProxy WebProxy => _webProxy;
|
||||||
public HostContext(string hostType, string logFile = null)
|
public HostContext(string hostType, string logFile = null)
|
||||||
{
|
{
|
||||||
@@ -189,6 +189,17 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
|
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var credFile = GetConfigFile(WellKnownConfigFile.Credentials);
|
||||||
|
if (File.Exists(credFile))
|
||||||
|
{
|
||||||
|
var credData = IOUtil.LoadObject<CredentialData>(credFile);
|
||||||
|
if (credData != null &&
|
||||||
|
credData.Data.TryGetValue("clientId", out var clientId))
|
||||||
|
{
|
||||||
|
_userAgents.Add(new ProductInfoHeaderValue($"RunnerId", clientId));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetDirectory(WellKnownDirectory directory)
|
public string GetDirectory(WellKnownDirectory directory)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
// job request
|
// job request
|
||||||
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
|
Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken);
|
||||||
Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, CancellationToken cancellationToken);
|
Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId, CancellationToken cancellationToken);
|
||||||
Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken);
|
Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken);
|
||||||
|
|
||||||
// agent package
|
// agent package
|
||||||
@@ -300,10 +300,10 @@ namespace GitHub.Runner.Common
|
|||||||
// JobRequest
|
// JobRequest
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
|
|
||||||
public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, CancellationToken cancellationToken = default(CancellationToken))
|
public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
CheckConnection(RunnerConnectionType.JobRequest);
|
CheckConnection(RunnerConnectionType.JobRequest);
|
||||||
return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, cancellationToken: cancellationToken);
|
return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId: orchestrationId, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken))
|
public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ namespace GitHub.Runner.Listener
|
|||||||
private readonly string[] validArgs =
|
private readonly string[] validArgs =
|
||||||
{
|
{
|
||||||
Constants.Runner.CommandLine.Args.Auth,
|
Constants.Runner.CommandLine.Args.Auth,
|
||||||
|
Constants.Runner.CommandLine.Args.Labels,
|
||||||
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
|
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
|
||||||
Constants.Runner.CommandLine.Args.Name,
|
Constants.Runner.CommandLine.Args.Name,
|
||||||
Constants.Runner.CommandLine.Args.Pool,
|
Constants.Runner.CommandLine.Args.Pool,
|
||||||
@@ -249,6 +250,24 @@ namespace GitHub.Runner.Listener
|
|||||||
return GetArg(Constants.Runner.CommandLine.Args.StartupType);
|
return GetArg(Constants.Runner.CommandLine.Args.StartupType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ISet<string> GetLabels()
|
||||||
|
{
|
||||||
|
var labelSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
string labels = GetArgOrPrompt(
|
||||||
|
name: Constants.Runner.CommandLine.Args.Labels,
|
||||||
|
description: $"This runner will have the following labels: 'self-hosted', '{VarUtil.OS}', '{VarUtil.OSArchitecture}' \nEnter any additional labels (ex. label-1,label-2):",
|
||||||
|
defaultValue: string.Empty,
|
||||||
|
validator: Validators.LabelsValidator,
|
||||||
|
isOptional: true);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(labels))
|
||||||
|
{
|
||||||
|
labelSet = labels.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToHashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return labelSet;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Private helpers.
|
// Private helpers.
|
||||||
//
|
//
|
||||||
@@ -280,7 +299,8 @@ namespace GitHub.Runner.Listener
|
|||||||
string name,
|
string name,
|
||||||
string description,
|
string description,
|
||||||
string defaultValue,
|
string defaultValue,
|
||||||
Func<string, bool> validator)
|
Func<string, bool> validator,
|
||||||
|
bool isOptional = false)
|
||||||
{
|
{
|
||||||
// Check for the arg in the command line parser.
|
// Check for the arg in the command line parser.
|
||||||
ArgUtil.NotNull(validator, nameof(validator));
|
ArgUtil.NotNull(validator, nameof(validator));
|
||||||
@@ -311,7 +331,8 @@ namespace GitHub.Runner.Listener
|
|||||||
secret: Constants.Runner.CommandLine.Args.Secrets.Any(x => string.Equals(x, name, StringComparison.OrdinalIgnoreCase)),
|
secret: Constants.Runner.CommandLine.Args.Secrets.Any(x => string.Equals(x, name, StringComparison.OrdinalIgnoreCase)),
|
||||||
defaultValue: defaultValue,
|
defaultValue: defaultValue,
|
||||||
validator: validator,
|
validator: validator,
|
||||||
unattended: Unattended);
|
unattended: Unattended,
|
||||||
|
isOptional: isOptional);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetEnvArg(string name)
|
private string GetEnvArg(string name)
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
RunnerSettings runnerSettings = new RunnerSettings();
|
RunnerSettings runnerSettings = new RunnerSettings();
|
||||||
|
|
||||||
bool isHostedServer = false;
|
|
||||||
// Loop getting url and creds until you can connect
|
// Loop getting url and creds until you can connect
|
||||||
ICredentialProvider credProvider = null;
|
ICredentialProvider credProvider = null;
|
||||||
VssCredentials creds = null;
|
VssCredentials creds = null;
|
||||||
@@ -95,8 +94,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
// Get the URL
|
// Get the URL
|
||||||
var inputUrl = command.GetUrl();
|
var inputUrl = command.GetUrl();
|
||||||
if (!inputUrl.Contains("github.com", StringComparison.OrdinalIgnoreCase) &&
|
if (inputUrl.Contains("codedev.ms", StringComparison.OrdinalIgnoreCase))
|
||||||
!inputUrl.Contains("github.localhost", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
runnerSettings.ServerUrl = inputUrl;
|
runnerSettings.ServerUrl = inputUrl;
|
||||||
// Get the credentials
|
// Get the credentials
|
||||||
@@ -117,7 +115,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
||||||
isHostedServer = await IsHostedServer(runnerSettings.ServerUrl, creds);
|
runnerSettings.IsHostedServer = runnerSettings.GitHubUrl == null || IsHostedServer(new UriBuilder(runnerSettings.GitHubUrl));
|
||||||
|
|
||||||
// Validate can connect.
|
// Validate can connect.
|
||||||
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), creds);
|
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), creds);
|
||||||
@@ -168,6 +166,9 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
|
|
||||||
|
var userLabels = command.GetLabels();
|
||||||
|
_term.WriteLine();
|
||||||
|
|
||||||
var agents = await _runnerServer.GetAgentsAsync(runnerSettings.PoolId, runnerSettings.AgentName);
|
var agents = await _runnerServer.GetAgentsAsync(runnerSettings.PoolId, runnerSettings.AgentName);
|
||||||
Trace.Verbose("Returns {0} agents", agents.Count);
|
Trace.Verbose("Returns {0} agents", agents.Count);
|
||||||
agent = agents.FirstOrDefault();
|
agent = agents.FirstOrDefault();
|
||||||
@@ -177,7 +178,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (command.GetReplace())
|
if (command.GetReplace())
|
||||||
{
|
{
|
||||||
// Update existing agent with new PublicKey, agent version.
|
// Update existing agent with new PublicKey, agent version.
|
||||||
agent = UpdateExistingAgent(agent, publicKey);
|
agent = UpdateExistingAgent(agent, publicKey, userLabels);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -199,8 +200,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create a new agent.
|
// Create a new agent.
|
||||||
agent = CreateNewAgent(runnerSettings.AgentName, publicKey);
|
agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -248,14 +249,6 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
UriBuilder configServerUrl = new UriBuilder(runnerSettings.ServerUrl);
|
UriBuilder configServerUrl = new UriBuilder(runnerSettings.ServerUrl);
|
||||||
UriBuilder oauthEndpointUrlBuilder = new UriBuilder(agent.Authorization.AuthorizationUrl);
|
UriBuilder oauthEndpointUrlBuilder = new UriBuilder(agent.Authorization.AuthorizationUrl);
|
||||||
if (!isHostedServer && Uri.Compare(configServerUrl.Uri, oauthEndpointUrlBuilder.Uri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0)
|
|
||||||
{
|
|
||||||
oauthEndpointUrlBuilder.Scheme = configServerUrl.Scheme;
|
|
||||||
oauthEndpointUrlBuilder.Host = configServerUrl.Host;
|
|
||||||
oauthEndpointUrlBuilder.Port = configServerUrl.Port;
|
|
||||||
Trace.Info($"Set oauth endpoint url's scheme://host:port component to match runner configure url's scheme://host:port: '{oauthEndpointUrlBuilder.Uri.AbsoluteUri}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var credentialData = new CredentialData
|
var credentialData = new CredentialData
|
||||||
{
|
{
|
||||||
Scheme = Constants.Configuration.OAuth,
|
Scheme = Constants.Configuration.OAuth,
|
||||||
@@ -291,7 +284,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
// there are two exception messages server send that indicate clock skew.
|
// there are two exception messages server send that indicate clock skew.
|
||||||
// 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}.
|
// 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}.
|
||||||
// 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}.
|
// 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}.
|
||||||
Trace.Error("Catch exception during test agent connection.");
|
Trace.Error("Catch exception during test agent connection.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
throw new Exception("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again.");
|
throw new Exception("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again.");
|
||||||
@@ -381,7 +374,6 @@ 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)
|
||||||
bool isHostedServer = await IsHostedServer(settings.ServerUrl, creds);
|
|
||||||
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds);
|
||||||
|
|
||||||
var agents = await _runnerServer.GetAgentsAsync(settings.PoolId, settings.AgentName);
|
var agents = await _runnerServer.GetAgentsAsync(settings.PoolId, settings.AgentName);
|
||||||
@@ -404,7 +396,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
_term.WriteLine("Cannot connect to server, because config files are missing. Skipping removing runner from the server.");
|
_term.WriteLine("Cannot connect to server, because config files are missing. Skipping removing runner from the server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
//delete credential config files
|
//delete credential config files
|
||||||
currentAction = "Removing .credentials";
|
currentAction = "Removing .credentials";
|
||||||
if (hasCredentials)
|
if (hasCredentials)
|
||||||
{
|
{
|
||||||
@@ -418,7 +410,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
_term.WriteLine("Does not exist. Skipping " + currentAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
//delete settings config file
|
//delete settings config file
|
||||||
currentAction = "Removing .runner";
|
currentAction = "Removing .runner";
|
||||||
if (isConfigured)
|
if (isConfigured)
|
||||||
{
|
{
|
||||||
@@ -459,7 +451,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey)
|
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(agent, nameof(agent));
|
ArgUtil.NotNull(agent, nameof(agent));
|
||||||
agent.Authorization = new TaskAgentAuthorization
|
agent.Authorization = new TaskAgentAuthorization
|
||||||
@@ -467,18 +459,25 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus),
|
PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus),
|
||||||
};
|
};
|
||||||
|
|
||||||
// update - update instead of delete so we don't lose labels etc...
|
// update should replace the existing labels
|
||||||
agent.Version = BuildConstants.RunnerPackage.Version;
|
agent.Version = BuildConstants.RunnerPackage.Version;
|
||||||
agent.OSDescription = RuntimeInformation.OSDescription;
|
agent.OSDescription = RuntimeInformation.OSDescription;
|
||||||
|
|
||||||
|
agent.Labels.Clear();
|
||||||
|
|
||||||
agent.Labels.Add("self-hosted");
|
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
||||||
agent.Labels.Add(VarUtil.OS);
|
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
||||||
agent.Labels.Add(VarUtil.OSArchitecture);
|
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System));
|
||||||
|
|
||||||
|
foreach (var userLabel in userLabels)
|
||||||
|
{
|
||||||
|
agent.Labels.Add(new AgentLabel(userLabel, LabelType.User));
|
||||||
|
}
|
||||||
|
|
||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey)
|
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels)
|
||||||
{
|
{
|
||||||
TaskAgent agent = new TaskAgent(agentName)
|
TaskAgent agent = new TaskAgent(agentName)
|
||||||
{
|
{
|
||||||
@@ -491,43 +490,43 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
OSDescription = RuntimeInformation.OSDescription,
|
OSDescription = RuntimeInformation.OSDescription,
|
||||||
};
|
};
|
||||||
|
|
||||||
agent.Labels.Add("self-hosted");
|
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
||||||
agent.Labels.Add(VarUtil.OS);
|
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
||||||
agent.Labels.Add(VarUtil.OSArchitecture);
|
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System));
|
||||||
|
|
||||||
|
foreach (var userLabel in userLabels)
|
||||||
|
{
|
||||||
|
agent.Labels.Add(new AgentLabel(userLabel, LabelType.User));
|
||||||
|
}
|
||||||
|
|
||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> IsHostedServer(string serverUrl, VssCredentials credentials)
|
private bool IsHostedServer(UriBuilder gitHubUrl)
|
||||||
{
|
{
|
||||||
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
return string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
var locationServer = HostContext.GetService<ILocationServer>();
|
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
VssConnection connection = VssUtil.CreateConnection(new Uri(serverUrl), credentials);
|
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase);
|
||||||
await locationServer.ConnectAsync(connection);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var connectionData = await locationServer.GetConnectionDataAsync();
|
|
||||||
Trace.Info($"Server deployment type: {connectionData.DeploymentType}");
|
|
||||||
return connectionData.DeploymentType.HasFlag(DeploymentFlags.Hosted);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Since the DeploymentType is Enum, deserialization exception means there is a new Enum member been added.
|
|
||||||
// It's more likely to be Hosted since OnPremises is always behind and customer can update their agent if are on-prem
|
|
||||||
Trace.Error(ex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, string githubToken, string runnerEvent)
|
private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, string githubToken, string runnerEvent)
|
||||||
{
|
{
|
||||||
|
var githubApiUrl = "";
|
||||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||||
var githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/actions/runner-registration";
|
if (IsHostedServer(gitHubUrlBuilder))
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/actions/runner-registration";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/actions/runner-registration";
|
||||||
|
}
|
||||||
|
|
||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
{
|
{
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("RemoteAuth", githubToken);
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("RemoteAuth", githubToken);
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.Add(HostContext.UserAgent);
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
|
|
||||||
var bodyObject = new Dictionary<string, string>()
|
var bodyObject = new Dictionary<string, string>()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
bool secret,
|
bool secret,
|
||||||
string defaultValue,
|
string defaultValue,
|
||||||
Func<String, bool> validator,
|
Func<String, bool> validator,
|
||||||
bool unattended);
|
bool unattended,
|
||||||
|
bool isOptional = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PromptManager : RunnerService, IPromptManager
|
public sealed class PromptManager : RunnerService, IPromptManager
|
||||||
@@ -56,7 +57,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
bool secret,
|
bool secret,
|
||||||
string defaultValue,
|
string defaultValue,
|
||||||
Func<string, bool> validator,
|
Func<string, bool> validator,
|
||||||
bool unattended)
|
bool unattended,
|
||||||
|
bool isOptional = false)
|
||||||
{
|
{
|
||||||
Trace.Info(nameof(ReadValue));
|
Trace.Info(nameof(ReadValue));
|
||||||
ArgUtil.NotNull(validator, nameof(validator));
|
ArgUtil.NotNull(validator, nameof(validator));
|
||||||
@@ -70,6 +72,10 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
else if (isOptional)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise throw.
|
// Otherwise throw.
|
||||||
throw new Exception($"Invalid configuration provided for {argName}. Terminating unattended configuration.");
|
throw new Exception($"Invalid configuration provided for {argName}. Terminating unattended configuration.");
|
||||||
@@ -85,18 +91,28 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
_terminal.Write($"[press Enter for {defaultValue}] ");
|
_terminal.Write($"[press Enter for {defaultValue}] ");
|
||||||
}
|
}
|
||||||
|
else if (isOptional){
|
||||||
|
_terminal.Write($"[press Enter to skip] ");
|
||||||
|
}
|
||||||
|
|
||||||
// Read and trim the value.
|
// Read and trim the value.
|
||||||
value = secret ? _terminal.ReadSecret() : _terminal.ReadLine();
|
value = secret ? _terminal.ReadSecret() : _terminal.ReadLine();
|
||||||
value = value?.Trim() ?? string.Empty;
|
value = value?.Trim() ?? string.Empty;
|
||||||
|
|
||||||
// Return the default if not specified.
|
// Return the default if not specified.
|
||||||
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(defaultValue))
|
if (string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
Trace.Info($"Falling back to the default: '{defaultValue}'");
|
if (!string.IsNullOrEmpty(defaultValue))
|
||||||
return defaultValue;
|
{
|
||||||
|
Trace.Info($"Falling back to the default: '{defaultValue}'");
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
else if (isOptional)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the value if it is not empty and it is valid.
|
// Return the value if it is not empty and it is valid.
|
||||||
// Otherwise try the loop again.
|
// Otherwise try the loop again.
|
||||||
if (!string.IsNullOrEmpty(value))
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
|
|
||||||
@@ -46,6 +47,21 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
string.Equals(value, "N", StringComparison.CurrentCultureIgnoreCase);
|
string.Equals(value, "N", StringComparison.CurrentCultureIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool LabelsValidator(string labels)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(labels))
|
||||||
|
{
|
||||||
|
var labelSet = labels.Split(',').Where(x => !string.IsNullOrEmpty(x)).ToHashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (labelSet.Any(x => x.Length > 256))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool NonEmptyValidator(string value)
|
public static bool NonEmptyValidator(string value)
|
||||||
{
|
{
|
||||||
return !string.IsNullOrEmpty(value);
|
return !string.IsNullOrEmpty(value);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using System.Linq;
|
|||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.WebApi.Jwt;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
namespace GitHub.Runner.Listener
|
||||||
{
|
{
|
||||||
@@ -86,15 +87,30 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var orchestrationId = string.Empty;
|
||||||
|
var systemConnection = jobRequestMessage.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (systemConnection?.Authorization != null &&
|
||||||
|
systemConnection.Authorization.Parameters.TryGetValue("AccessToken", out var accessToken) &&
|
||||||
|
!string.IsNullOrEmpty(accessToken))
|
||||||
|
{
|
||||||
|
var jwt = JsonWebToken.Create(accessToken);
|
||||||
|
var claims = jwt.ExtractClaims();
|
||||||
|
orchestrationId = claims.FirstOrDefault(x => string.Equals(x.Type, "orchid", StringComparison.OrdinalIgnoreCase))?.Value;
|
||||||
|
if (!string.IsNullOrEmpty(orchestrationId))
|
||||||
|
{
|
||||||
|
Trace.Info($"Pull OrchestrationId {orchestrationId} from JWT claims");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WorkerDispatcher newDispatch = new WorkerDispatcher(jobRequestMessage.JobId, jobRequestMessage.RequestId);
|
WorkerDispatcher newDispatch = new WorkerDispatcher(jobRequestMessage.JobId, jobRequestMessage.RequestId);
|
||||||
if (runOnce)
|
if (runOnce)
|
||||||
{
|
{
|
||||||
Trace.Info("Start dispatcher for one time used runner.");
|
Trace.Info("Start dispatcher for one time used runner.");
|
||||||
newDispatch.WorkerDispatch = RunOnceAsync(jobRequestMessage, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token);
|
newDispatch.WorkerDispatch = RunOnceAsync(jobRequestMessage, orchestrationId, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
newDispatch.WorkerDispatch = RunAsync(jobRequestMessage, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token);
|
newDispatch.WorkerDispatch = RunAsync(jobRequestMessage, orchestrationId, currentDispatch, newDispatch.WorkerCancellationTokenSource.Token, newDispatch.WorkerCancelTimeoutKillTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
_jobInfos.TryAdd(newDispatch.JobId, newDispatch);
|
_jobInfos.TryAdd(newDispatch.JobId, newDispatch);
|
||||||
@@ -284,11 +300,11 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunOnceAsync(Pipelines.AgentJobRequestMessage message, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
private async Task RunOnceAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RunAsync(message, previousJobDispatch, jobRequestCancellationToken, workerCancelTimeoutKillToken);
|
await RunAsync(message, orchestrationId, previousJobDispatch, jobRequestCancellationToken, workerCancelTimeoutKillToken);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -297,7 +313,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunAsync(Pipelines.AgentJobRequestMessage message, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
private async Task RunAsync(Pipelines.AgentJobRequestMessage message, string orchestrationId, WorkerDispatcher previousJobDispatch, CancellationToken jobRequestCancellationToken, CancellationToken workerCancelTimeoutKillToken)
|
||||||
{
|
{
|
||||||
Busy = true;
|
Busy = true;
|
||||||
try
|
try
|
||||||
@@ -328,7 +344,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
// start renew job request
|
// start renew job request
|
||||||
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
|
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
|
||||||
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
||||||
|
|
||||||
// wait till first renew succeed or job request is canceled
|
// wait till first renew succeed or job request is canceled
|
||||||
// not even start worker if the first renew fail
|
// not even start worker if the first renew fail
|
||||||
@@ -607,7 +623,7 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
public async Task RenewJobRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId, TaskCompletionSource<int> firstJobRequestRenewed, CancellationToken token)
|
||||||
{
|
{
|
||||||
var runnerServer = HostContext.GetService<IRunnerServer>();
|
var runnerServer = HostContext.GetService<IRunnerServer>();
|
||||||
TaskAgentJobRequest request = null;
|
TaskAgentJobRequest request = null;
|
||||||
@@ -620,7 +636,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, token);
|
request = await runnerServer.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId, token);
|
||||||
|
|
||||||
Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}");
|
Trace.Info($"Successfully renew job request {requestId}, job is valid till {request.LockedUntil.Value}");
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy);
|
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||||
|
|
||||||
_inConfigStage = true;
|
_inConfigStage = true;
|
||||||
_completedCommand.Reset();
|
_completedCommand.Reset();
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
public static class VssUtil
|
public static class VssUtil
|
||||||
{
|
{
|
||||||
public static void InitializeVssClientSettings(ProductInfoHeaderValue additionalUserAgent, IWebProxy proxy)
|
public static void InitializeVssClientSettings(List<ProductInfoHeaderValue> additionalUserAgents, IWebProxy proxy)
|
||||||
{
|
{
|
||||||
var headerValues = new List<ProductInfoHeaderValue>();
|
var headerValues = new List<ProductInfoHeaderValue>();
|
||||||
headerValues.Add(additionalUserAgent);
|
headerValues.AddRange(additionalUserAgents);
|
||||||
headerValues.Add(new ProductInfoHeaderValue($"({RuntimeInformation.OSDescription.Trim()})"));
|
headerValues.Add(new ProductInfoHeaderValue($"({RuntimeInformation.OSDescription.Trim()})"));
|
||||||
|
|
||||||
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
|
if (VssClientHttpRequestSettings.Default.UserAgent != null && VssClientHttpRequestSettings.Default.UserAgent.Count > 0)
|
||||||
|
|||||||
@@ -21,11 +21,24 @@ using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplat
|
|||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
|
public class PrepareResult
|
||||||
|
{
|
||||||
|
public PrepareResult(List<JobExtensionRunner> containerSetupSteps, Dictionary<Guid, IActionRunner> preStepTracker)
|
||||||
|
{
|
||||||
|
this.ContainerSetupSteps = containerSetupSteps;
|
||||||
|
this.PreStepTracker = preStepTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JobExtensionRunner> ContainerSetupSteps { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<Guid, IActionRunner> PreStepTracker { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(ActionManager))]
|
[ServiceLocator(Default = typeof(ActionManager))]
|
||||||
public interface IActionManager : IRunnerService
|
public interface IActionManager : IRunnerService
|
||||||
{
|
{
|
||||||
Dictionary<Guid, ContainerInfo> CachedActionContainers { get; }
|
Dictionary<Guid, ContainerInfo> CachedActionContainers { get; }
|
||||||
Task<List<JobExtensionRunner>> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps);
|
Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps);
|
||||||
Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action);
|
Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +52,7 @@ namespace GitHub.Runner.Worker
|
|||||||
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
||||||
|
|
||||||
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
||||||
public async Task<List<JobExtensionRunner>> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
ArgUtil.NotNull(steps, nameof(steps));
|
ArgUtil.NotNull(steps, nameof(steps));
|
||||||
@@ -49,17 +62,24 @@ namespace GitHub.Runner.Worker
|
|||||||
Dictionary<string, List<Guid>> imagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase);
|
Dictionary<string, List<Guid>> imagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase);
|
||||||
Dictionary<string, ActionContainer> imagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase);
|
Dictionary<string, ActionContainer> imagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase);
|
||||||
List<JobExtensionRunner> containerSetupSteps = new List<JobExtensionRunner>();
|
List<JobExtensionRunner> containerSetupSteps = new List<JobExtensionRunner>();
|
||||||
|
Dictionary<Guid, IActionRunner> preStepTracker = new Dictionary<Guid, IActionRunner>();
|
||||||
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
||||||
|
|
||||||
// TODO: Depreciate the PREVIEW_ACTION_TOKEN
|
// TODO: Deprecate the PREVIEW_ACTION_TOKEN
|
||||||
// Log even if we aren't using it to ensure users know.
|
// Log even if we aren't using it to ensure users know.
|
||||||
if (!string.IsNullOrEmpty(executionContext.Variables.Get("PREVIEW_ACTION_TOKEN")))
|
if (!string.IsNullOrEmpty(executionContext.Variables.Get("PREVIEW_ACTION_TOKEN")))
|
||||||
{
|
{
|
||||||
executionContext.Warning("The 'PREVIEW_ACTION_TOKEN' secret is depreciated. Please remove it from the repository's secrets");
|
executionContext.Warning("The 'PREVIEW_ACTION_TOKEN' secret is deprecated. Please remove it from the repository's secrets");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the cache (local runner)
|
// Clear the cache (for self-hosted runners)
|
||||||
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
// Note, temporarily avoid this step for the on-premises product, to avoid rate limiting.
|
||||||
|
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||||
|
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
|
||||||
|
if (isHostedServer)
|
||||||
|
{
|
||||||
|
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
{
|
{
|
||||||
@@ -111,6 +131,22 @@ namespace GitHub.Runner.Worker
|
|||||||
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
|
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
||||||
|
{
|
||||||
|
var definition = LoadAction(executionContext, action);
|
||||||
|
if (definition.Data.Execution.HasPre)
|
||||||
|
{
|
||||||
|
var actionRunner = HostContext.CreateService<IActionRunner>();
|
||||||
|
actionRunner.Action = action;
|
||||||
|
actionRunner.Stage = ActionRunStage.Pre;
|
||||||
|
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
||||||
|
|
||||||
|
Trace.Info($"Add 'pre' execution for {action.Id}");
|
||||||
|
preStepTracker[action.Id] = actionRunner;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +183,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return containerSetupSteps;
|
return new PrepareResult(containerSetupSteps, preStepTracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action)
|
public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action)
|
||||||
@@ -239,14 +275,19 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Action container env: {StringUtil.ConvertToJson(containerAction.Environment)}.");
|
Trace.Info($"Action container env: {StringUtil.ConvertToJson(containerAction.Environment)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(containerAction.Pre))
|
||||||
|
{
|
||||||
|
Trace.Info($"Action container pre entrypoint: {containerAction.Pre}.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(containerAction.EntryPoint))
|
if (!string.IsNullOrEmpty(containerAction.EntryPoint))
|
||||||
{
|
{
|
||||||
Trace.Info($"Action container entrypoint: {containerAction.EntryPoint}.");
|
Trace.Info($"Action container entrypoint: {containerAction.EntryPoint}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(containerAction.Cleanup))
|
if (!string.IsNullOrEmpty(containerAction.Post))
|
||||||
{
|
{
|
||||||
Trace.Info($"Action container cleanup entrypoint: {containerAction.Cleanup}.");
|
Trace.Info($"Action container post entrypoint: {containerAction.Post}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CachedActionContainers.TryGetValue(action.Id, out var container))
|
if (CachedActionContainers.TryGetValue(action.Id, out var container))
|
||||||
@@ -258,8 +299,9 @@ namespace GitHub.Runner.Worker
|
|||||||
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.NodeJS)
|
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.NodeJS)
|
||||||
{
|
{
|
||||||
var nodeAction = definition.Data.Execution as NodeJSActionExecutionData;
|
var nodeAction = definition.Data.Execution as NodeJSActionExecutionData;
|
||||||
|
Trace.Info($"Action pre node.js file: {nodeAction.Pre ?? "N/A"}.");
|
||||||
Trace.Info($"Action node.js file: {nodeAction.Script}.");
|
Trace.Info($"Action node.js file: {nodeAction.Script}.");
|
||||||
Trace.Info($"Action cleanup node.js file: {nodeAction.Cleanup ?? "N/A"}.");
|
Trace.Info($"Action post node.js file: {nodeAction.Post ?? "N/A"}.");
|
||||||
}
|
}
|
||||||
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Plugin)
|
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Plugin)
|
||||||
{
|
{
|
||||||
@@ -275,7 +317,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(plugin.PostPluginTypeName))
|
if (!string.IsNullOrEmpty(plugin.PostPluginTypeName))
|
||||||
{
|
{
|
||||||
pluginAction.Cleanup = plugin.PostPluginTypeName;
|
pluginAction.Post = plugin.PostPluginTypeName;
|
||||||
Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}.");
|
Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,7 +490,8 @@ 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);
|
||||||
if (File.Exists(destDirectory + ".completed"))
|
string watermarkFile = destDirectory + ".completed";
|
||||||
|
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}'.");
|
||||||
return;
|
return;
|
||||||
@@ -498,27 +541,36 @@ 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 authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
|
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||||
if (string.IsNullOrEmpty(authToken))
|
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
|
||||||
|
if (isHostedServer)
|
||||||
{
|
{
|
||||||
// TODO: Depreciate the PREVIEW_ACTION_TOKEN
|
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
|
||||||
authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN");
|
if (string.IsNullOrEmpty(authToken))
|
||||||
}
|
{
|
||||||
|
// TODO: Deprecate the PREVIEW_ACTION_TOKEN
|
||||||
|
authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN");
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(authToken))
|
if (!string.IsNullOrEmpty(authToken))
|
||||||
{
|
{
|
||||||
HostContext.SecretMasker.AddValue(authToken);
|
HostContext.SecretMasker.AddValue(authToken);
|
||||||
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
|
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var accessToken = executionContext.GetGitHubContext("token");
|
// Intentionally empty. Temporary for GHES alpha release, download from dotcom unauthenticated.
|
||||||
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.Add(HostContext.UserAgent);
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
using (var result = await httpClient.GetStreamAsync(archiveLink))
|
using (var result = await httpClient.GetStreamAsync(archiveLink))
|
||||||
{
|
{
|
||||||
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
||||||
@@ -610,7 +662,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
Trace.Verbose("Create watermark file indicate action download succeed.");
|
Trace.Verbose("Create watermark file indicate action download succeed.");
|
||||||
File.WriteAllText(destDirectory + ".completed", 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}'.");
|
||||||
Trace.Info("Finished getting action repository.");
|
Trace.Info("Finished getting action repository.");
|
||||||
@@ -772,7 +824,8 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
public override ActionExecutionType ExecutionType => ActionExecutionType.Container;
|
public override ActionExecutionType ExecutionType => ActionExecutionType.Container;
|
||||||
|
|
||||||
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup);
|
public override bool HasPre => !string.IsNullOrEmpty(Pre);
|
||||||
|
public override bool HasPost => !string.IsNullOrEmpty(Post);
|
||||||
|
|
||||||
public string Image { get; set; }
|
public string Image { get; set; }
|
||||||
|
|
||||||
@@ -782,51 +835,66 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public MappingToken Environment { get; set; }
|
public MappingToken Environment { get; set; }
|
||||||
|
|
||||||
public string Cleanup { get; set; }
|
public string Pre { get; set; }
|
||||||
|
|
||||||
|
public string Post { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class NodeJSActionExecutionData : ActionExecutionData
|
public sealed class NodeJSActionExecutionData : ActionExecutionData
|
||||||
{
|
{
|
||||||
public override ActionExecutionType ExecutionType => ActionExecutionType.NodeJS;
|
public override ActionExecutionType ExecutionType => ActionExecutionType.NodeJS;
|
||||||
|
|
||||||
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup);
|
public override bool HasPre => !string.IsNullOrEmpty(Pre);
|
||||||
|
public override bool HasPost => !string.IsNullOrEmpty(Post);
|
||||||
|
|
||||||
public string Script { get; set; }
|
public string Script { get; set; }
|
||||||
|
|
||||||
public string Cleanup { get; set; }
|
public string Pre { get; set; }
|
||||||
|
|
||||||
|
public string Post { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PluginActionExecutionData : ActionExecutionData
|
public sealed class PluginActionExecutionData : ActionExecutionData
|
||||||
{
|
{
|
||||||
public override ActionExecutionType ExecutionType => ActionExecutionType.Plugin;
|
public override ActionExecutionType ExecutionType => ActionExecutionType.Plugin;
|
||||||
|
|
||||||
public override bool HasCleanup => !string.IsNullOrEmpty(Cleanup);
|
public override bool HasPre => false;
|
||||||
|
|
||||||
|
public override bool HasPost => !string.IsNullOrEmpty(Post);
|
||||||
|
|
||||||
public string Plugin { get; set; }
|
public string Plugin { get; set; }
|
||||||
|
|
||||||
public string Cleanup { get; set; }
|
public string Post { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ScriptActionExecutionData : ActionExecutionData
|
public sealed class ScriptActionExecutionData : ActionExecutionData
|
||||||
{
|
{
|
||||||
public override ActionExecutionType ExecutionType => ActionExecutionType.Script;
|
public override ActionExecutionType ExecutionType => ActionExecutionType.Script;
|
||||||
|
public override bool HasPre => false;
|
||||||
public override bool HasCleanup => false;
|
public override bool HasPost => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class ActionExecutionData
|
public abstract class ActionExecutionData
|
||||||
{
|
{
|
||||||
|
private string _initCondition = $"{Constants.Expressions.Always}()";
|
||||||
private string _cleanupCondition = $"{Constants.Expressions.Always}()";
|
private string _cleanupCondition = $"{Constants.Expressions.Always}()";
|
||||||
|
|
||||||
public abstract ActionExecutionType ExecutionType { get; }
|
public abstract ActionExecutionType ExecutionType { get; }
|
||||||
|
|
||||||
public abstract bool HasCleanup { get; }
|
public abstract bool HasPre { get; }
|
||||||
|
public abstract bool HasPost { get; }
|
||||||
|
|
||||||
public string CleanupCondition
|
public string CleanupCondition
|
||||||
{
|
{
|
||||||
get { return _cleanupCondition; }
|
get { return _cleanupCondition; }
|
||||||
set { _cleanupCondition = value; }
|
set { _cleanupCondition = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string InitCondition
|
||||||
|
{
|
||||||
|
get { return _initCondition; }
|
||||||
|
set { _initCondition = value; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ContainerSetupInfo
|
public class ContainerSetupInfo
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile);
|
ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile);
|
||||||
|
|
||||||
List<string> EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary<string, PipelineContextData> contextData);
|
List<string> EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
|
||||||
|
|
||||||
Dictionary<string, string> EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary<string, PipelineContextData> contextData);
|
Dictionary<string, string> EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
|
||||||
|
|
||||||
string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token, IDictionary<string, PipelineContextData> contextData);
|
string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ActionManifestManager : RunnerService, IActionManifestManager
|
public sealed class ActionManifestManager : RunnerService, IActionManifestManager
|
||||||
@@ -54,7 +54,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
||||||
{
|
{
|
||||||
var context = CreateContext(executionContext, null);
|
var context = CreateContext(executionContext);
|
||||||
ActionDefinitionData actionDefinition = new ActionDefinitionData();
|
ActionDefinitionData actionDefinition = new ActionDefinitionData();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -133,13 +133,13 @@ namespace GitHub.Runner.Worker
|
|||||||
public List<string> EvaluateContainerArguments(
|
public List<string> EvaluateContainerArguments(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
SequenceToken token,
|
SequenceToken token,
|
||||||
IDictionary<string, PipelineContextData> contextData)
|
IDictionary<string, PipelineContextData> extraExpressionValues)
|
||||||
{
|
{
|
||||||
var result = new List<string>();
|
var result = new List<string>();
|
||||||
|
|
||||||
if (token != null)
|
if (token != null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(executionContext, contextData);
|
var context = CreateContext(executionContext, extraExpressionValues);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-args", token, 0, null, omitHeader: true);
|
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-args", token, 0, null, omitHeader: true);
|
||||||
@@ -172,13 +172,13 @@ namespace GitHub.Runner.Worker
|
|||||||
public Dictionary<string, string> EvaluateContainerEnvironment(
|
public Dictionary<string, string> EvaluateContainerEnvironment(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
MappingToken token,
|
MappingToken token,
|
||||||
IDictionary<string, PipelineContextData> contextData)
|
IDictionary<string, PipelineContextData> extraExpressionValues)
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (token != null)
|
if (token != null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(executionContext, contextData);
|
var context = CreateContext(executionContext, extraExpressionValues);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true);
|
var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true);
|
||||||
@@ -216,13 +216,12 @@ namespace GitHub.Runner.Worker
|
|||||||
public string EvaluateDefaultInput(
|
public string EvaluateDefaultInput(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
string inputName,
|
string inputName,
|
||||||
TemplateToken token,
|
TemplateToken token)
|
||||||
IDictionary<string, PipelineContextData> contextData)
|
|
||||||
{
|
{
|
||||||
string result = "";
|
string result = "";
|
||||||
if (token != null)
|
if (token != null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(executionContext, contextData);
|
var context = CreateContext(executionContext);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var evaluateResult = TemplateEvaluator.Evaluate(context, "input-default-context", token, 0, null, omitHeader: true);
|
var evaluateResult = TemplateEvaluator.Evaluate(context, "input-default-context", token, 0, null, omitHeader: true);
|
||||||
@@ -247,7 +246,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private TemplateContext CreateContext(
|
private TemplateContext CreateContext(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
IDictionary<string, PipelineContextData> contextData)
|
IDictionary<string, PipelineContextData> extraExpressionValues = null)
|
||||||
{
|
{
|
||||||
var result = new TemplateContext
|
var result = new TemplateContext
|
||||||
{
|
{
|
||||||
@@ -261,14 +260,27 @@ namespace GitHub.Runner.Worker
|
|||||||
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
TraceWriter = executionContext.ToTemplateTraceWriter(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (contextData?.Count > 0)
|
// Expression values from execution context
|
||||||
|
foreach (var pair in executionContext.ExpressionValues)
|
||||||
{
|
{
|
||||||
foreach (var pair in contextData)
|
result.ExpressionValues[pair.Key] = pair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra expression values
|
||||||
|
if (extraExpressionValues?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var pair in extraExpressionValues)
|
||||||
{
|
{
|
||||||
result.ExpressionValues[pair.Key] = pair.Value;
|
result.ExpressionValues[pair.Key] = pair.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expression functions from execution context
|
||||||
|
foreach (var item in executionContext.ExpressionFunctions)
|
||||||
|
{
|
||||||
|
result.ExpressionFunctions.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
// Add the file table
|
// Add the file table
|
||||||
if (_fileTable?.Count > 0)
|
if (_fileTable?.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -293,6 +305,9 @@ namespace GitHub.Runner.Worker
|
|||||||
var envToken = default(MappingToken);
|
var envToken = default(MappingToken);
|
||||||
var mainToken = default(StringToken);
|
var mainToken = default(StringToken);
|
||||||
var pluginToken = default(StringToken);
|
var pluginToken = default(StringToken);
|
||||||
|
var preToken = default(StringToken);
|
||||||
|
var preEntrypointToken = default(StringToken);
|
||||||
|
var preIfToken = default(StringToken);
|
||||||
var postToken = default(StringToken);
|
var postToken = default(StringToken);
|
||||||
var postEntrypointToken = default(StringToken);
|
var postEntrypointToken = default(StringToken);
|
||||||
var postIfToken = default(StringToken);
|
var postIfToken = default(StringToken);
|
||||||
@@ -331,6 +346,15 @@ namespace GitHub.Runner.Worker
|
|||||||
case "post-if":
|
case "post-if":
|
||||||
postIfToken = run.Value.AssertString("post-if");
|
postIfToken = run.Value.AssertString("post-if");
|
||||||
break;
|
break;
|
||||||
|
case "pre":
|
||||||
|
preToken = run.Value.AssertString("pre");
|
||||||
|
break;
|
||||||
|
case "pre-entrypoint":
|
||||||
|
preEntrypointToken = run.Value.AssertString("pre-entrypoint");
|
||||||
|
break;
|
||||||
|
case "pre-if":
|
||||||
|
preIfToken = run.Value.AssertString("pre-if");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Trace.Info($"Ignore run property {runsKey}.");
|
Trace.Info($"Ignore run property {runsKey}.");
|
||||||
break;
|
break;
|
||||||
@@ -353,7 +377,9 @@ namespace GitHub.Runner.Worker
|
|||||||
Arguments = argsToken,
|
Arguments = argsToken,
|
||||||
EntryPoint = entrypointToken?.Value,
|
EntryPoint = entrypointToken?.Value,
|
||||||
Environment = envToken,
|
Environment = envToken,
|
||||||
Cleanup = postEntrypointToken?.Value,
|
Pre = preEntrypointToken?.Value,
|
||||||
|
InitCondition = preIfToken?.Value ?? "always()",
|
||||||
|
Post = postEntrypointToken?.Value,
|
||||||
CleanupCondition = postIfToken?.Value ?? "always()"
|
CleanupCondition = postIfToken?.Value ?? "always()"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -362,14 +388,16 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(mainToken?.Value))
|
if (string.IsNullOrEmpty(mainToken?.Value))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException($"Entry javascript fils is not provided.");
|
throw new ArgumentNullException($"Entry javascript file is not provided.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new NodeJSActionExecutionData()
|
return new NodeJSActionExecutionData()
|
||||||
{
|
{
|
||||||
Script = mainToken.Value,
|
Script = mainToken.Value,
|
||||||
Cleanup = postToken?.Value,
|
Pre = preToken?.Value,
|
||||||
|
InitCondition = preIfToken?.Value ?? "always()",
|
||||||
|
Post = postToken?.Value,
|
||||||
CleanupCondition = postIfToken?.Value ?? "always()"
|
CleanupCondition = postIfToken?.Value ?? "always()"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
public enum ActionRunStage
|
public enum ActionRunStage
|
||||||
{
|
{
|
||||||
|
Pre,
|
||||||
Main,
|
Main,
|
||||||
Post,
|
Post,
|
||||||
}
|
}
|
||||||
@@ -26,7 +27,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public interface IActionRunner : IStep, IRunnerService
|
public interface IActionRunner : IStep, IRunnerService
|
||||||
{
|
{
|
||||||
ActionRunStage Stage { get; set; }
|
ActionRunStage Stage { get; set; }
|
||||||
Boolean TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context);
|
bool TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context);
|
||||||
Pipelines.ActionStep Action { get; set; }
|
Pipelines.ActionStep Action { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,20 +82,18 @@ namespace GitHub.Runner.Worker
|
|||||||
ActionExecutionData handlerData = definition.Data?.Execution;
|
ActionExecutionData handlerData = definition.Data?.Execution;
|
||||||
ArgUtil.NotNull(handlerData, nameof(handlerData));
|
ArgUtil.NotNull(handlerData, nameof(handlerData));
|
||||||
|
|
||||||
|
if (handlerData.HasPre &&
|
||||||
|
Action.Reference is Pipelines.RepositoryPathReference repoAction &&
|
||||||
|
string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ExecutionContext.Warning($"`pre` execution is not supported for local action from '{repoAction.Path}'");
|
||||||
|
}
|
||||||
|
|
||||||
// The action has post cleanup defined.
|
// The action has post cleanup defined.
|
||||||
// we need to create timeline record for them and add them to the step list that StepRunner is using
|
// we need to create timeline record for them and add them to the step list that StepRunner is using
|
||||||
if (handlerData.HasCleanup && Stage == ActionRunStage.Main)
|
if (handlerData.HasPost && (Stage == ActionRunStage.Pre || Stage == ActionRunStage.Main))
|
||||||
{
|
{
|
||||||
string postDisplayName = null;
|
string postDisplayName = $"Post {this.DisplayName}";
|
||||||
if (this.DisplayName.StartsWith(PipelineTemplateConstants.RunDisplayPrefix))
|
|
||||||
{
|
|
||||||
postDisplayName = $"Post {this.DisplayName.Substring(PipelineTemplateConstants.RunDisplayPrefix.Length)}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
postDisplayName = $"Post {this.DisplayName}";
|
|
||||||
}
|
|
||||||
|
|
||||||
var repositoryReference = Action.Reference as RepositoryPathReference;
|
var repositoryReference = Action.Reference as RepositoryPathReference;
|
||||||
var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}";
|
var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}";
|
||||||
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
|
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
|
||||||
@@ -108,7 +107,7 @@ namespace GitHub.Runner.Worker
|
|||||||
actionRunner.Condition = handlerData.CleanupCondition;
|
actionRunner.Condition = handlerData.CleanupCondition;
|
||||||
actionRunner.DisplayName = postDisplayName;
|
actionRunner.DisplayName = postDisplayName;
|
||||||
|
|
||||||
ExecutionContext.RegisterPostJobStep($"{actionRunner.Action.Name}_post", actionRunner);
|
ExecutionContext.RegisterPostJobStep(actionRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
|
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
|
||||||
@@ -142,7 +141,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// Load the inputs.
|
// Load the inputs.
|
||||||
ExecutionContext.Debug("Loading inputs");
|
ExecutionContext.Debug("Loading inputs");
|
||||||
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
|
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues);
|
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
|
||||||
|
|
||||||
foreach (KeyValuePair<string, string> input in inputs)
|
foreach (KeyValuePair<string, string> input in inputs)
|
||||||
{
|
{
|
||||||
@@ -162,13 +161,7 @@ namespace GitHub.Runner.Worker
|
|||||||
string key = input.Key.AssertString("action input name").Value;
|
string key = input.Key.AssertString("action input name").Value;
|
||||||
if (!inputs.ContainsKey(key))
|
if (!inputs.ContainsKey(key))
|
||||||
{
|
{
|
||||||
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value);
|
||||||
foreach (var data in ExecutionContext.ExpressionValues)
|
|
||||||
{
|
|
||||||
evaluateContext[data.Key] = data.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value, evaluateContext);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,10 +286,14 @@ namespace GitHub.Runner.Worker
|
|||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
// Try evaluating fully
|
// Try evaluating fully
|
||||||
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
didFullyEvaluate = templateEvaluator.TryEvaluateStepDisplayName(tokenToParse, contextData, out displayName);
|
if (tokenToParse.CheckHasRequiredContext(contextData, context.ExpressionFunctions))
|
||||||
|
{
|
||||||
|
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
||||||
|
displayName = templateEvaluator.EvaluateStepDisplayName(tokenToParse, contextData, context.ExpressionFunctions);
|
||||||
|
didFullyEvaluate = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (TemplateValidationException e)
|
catch (TemplateValidationException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
foreach (var volume in container.Volumes)
|
foreach (var volume in container.Volumes)
|
||||||
{
|
{
|
||||||
UserMountVolumes[volume] = volume;
|
UserMountVolumes[volume] = volume;
|
||||||
|
MountVolumes.Add(new MountVolume(volume));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,13 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
// Watermark for GitHub Action environment
|
// Watermark for GitHub Action environment
|
||||||
dockerOptions.Add("-e GITHUB_ACTIONS=true");
|
dockerOptions.Add("-e GITHUB_ACTIONS=true");
|
||||||
|
|
||||||
|
// Set CI=true when no one else already set it.
|
||||||
|
// CI=true is common set in most CI provider in GitHub
|
||||||
|
if (!container.ContainerEnvironmentVariables.ContainsKey("CI"))
|
||||||
|
{
|
||||||
|
dockerOptions.Add("-e CI=true");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var volume in container.MountVolumes)
|
foreach (var volume in container.MountVolumes)
|
||||||
{
|
{
|
||||||
// replace `"` with `\"` and add `"{0}"` to all path.
|
// replace `"` with `\"` and add `"{0}"` to all path.
|
||||||
@@ -189,6 +196,13 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
// Watermark for GitHub Action environment
|
// Watermark for GitHub Action environment
|
||||||
dockerOptions.Add("-e GITHUB_ACTIONS=true");
|
dockerOptions.Add("-e GITHUB_ACTIONS=true");
|
||||||
|
|
||||||
|
// Set CI=true when no one else already set it.
|
||||||
|
// CI=true is common set in most CI provider in GitHub
|
||||||
|
if (!container.ContainerEnvironmentVariables.ContainsKey("CI"))
|
||||||
|
{
|
||||||
|
dockerOptions.Add("-e CI=true");
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(container.ContainerEntryPoint))
|
if (!string.IsNullOrEmpty(container.ContainerEntryPoint))
|
||||||
{
|
{
|
||||||
dockerOptions.Add($"--entrypoint \"{container.ContainerEntryPoint}\"");
|
dockerOptions.Add($"--entrypoint \"{container.ContainerEntryPoint}\"");
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ namespace GitHub.Runner.Worker
|
|||||||
condition: $"{PipelineTemplateConstants.Always}()",
|
condition: $"{PipelineTemplateConstants.Always}()",
|
||||||
displayName: "Stop containers",
|
displayName: "Stop containers",
|
||||||
data: data);
|
data: data);
|
||||||
|
|
||||||
executionContext.Debug($"Register post job cleanup for stopping/deleting containers.");
|
executionContext.Debug($"Register post job cleanup for stopping/deleting containers.");
|
||||||
executionContext.RegisterPostJobStep(nameof(StopContainersAsync), postJobStep);
|
executionContext.RegisterPostJobStep(postJobStep);
|
||||||
|
|
||||||
// Check whether we are inside a container.
|
// Check whether we are inside a container.
|
||||||
// Our container feature requires to map working directory from host to the container.
|
// Our container feature requires to map working directory from host to the container.
|
||||||
@@ -180,6 +180,11 @@ namespace GitHub.Runner.Worker
|
|||||||
foreach (var volume in container.UserMountVolumes)
|
foreach (var volume in container.UserMountVolumes)
|
||||||
{
|
{
|
||||||
Trace.Info($"User provided volume: {volume.Value}");
|
Trace.Info($"User provided volume: {volume.Value}");
|
||||||
|
var mount = new MountVolume(volume.Value);
|
||||||
|
if (string.Equals(mount.SourceVolumePath, "/", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
executionContext.Warning($"Volume mount {volume.Value} is going to mount '/' into the container which may cause file ownership change in the entire file system and cause Actions Runner to lose permission to access the disk.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull down docker image with retry up to 3 times
|
// Pull down docker image with retry up to 3 times
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
@@ -16,12 +17,11 @@ using GitHub.DistributedTask.WebApi;
|
|||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker.Container;
|
||||||
|
using GitHub.Services.WebApi;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Text;
|
|
||||||
using System.Collections;
|
|
||||||
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -55,6 +55,7 @@ namespace GitHub.Runner.Worker
|
|||||||
IList<String> FileTable { get; }
|
IList<String> FileTable { get; }
|
||||||
StepsContext StepsContext { get; }
|
StepsContext StepsContext { get; }
|
||||||
DictionaryContextData ExpressionValues { get; }
|
DictionaryContextData ExpressionValues { get; }
|
||||||
|
IList<IFunctionInfo> ExpressionFunctions { get; }
|
||||||
List<string> PrependPath { get; }
|
List<string> PrependPath { get; }
|
||||||
ContainerInfo Container { get; set; }
|
ContainerInfo Container { get; set; }
|
||||||
List<ContainerInfo> ServiceContainers { get; }
|
List<ContainerInfo> ServiceContainers { get; }
|
||||||
@@ -102,7 +103,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// others
|
// others
|
||||||
void ForceTaskComplete();
|
void ForceTaskComplete();
|
||||||
void RegisterPostJobStep(string refName, IStep step);
|
void RegisterPostJobStep(IStep step);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
||||||
@@ -148,6 +149,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public IList<String> FileTable { get; private set; }
|
public IList<String> FileTable { get; private set; }
|
||||||
public StepsContext StepsContext { get; private set; }
|
public StepsContext StepsContext { get; private set; }
|
||||||
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
||||||
|
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
|
||||||
public bool WriteDebug { get; private set; }
|
public bool WriteDebug { get; private set; }
|
||||||
public List<string> PrependPath { get; private set; }
|
public List<string> PrependPath { get; private set; }
|
||||||
public ContainerInfo Container { get; set; }
|
public ContainerInfo Container { get; set; }
|
||||||
@@ -159,6 +161,9 @@ namespace GitHub.Runner.Worker
|
|||||||
// Only job level ExecutionContext has PostJobSteps
|
// Only job level ExecutionContext has PostJobSteps
|
||||||
public Stack<IStep> PostJobSteps { get; private set; }
|
public Stack<IStep> PostJobSteps { get; private set; }
|
||||||
|
|
||||||
|
// Only job level ExecutionContext has StepsWithPostRegistered
|
||||||
|
public HashSet<Guid> StepsWithPostRegistered { get; private set; }
|
||||||
|
|
||||||
public bool EchoOnActionCommand { get; set; }
|
public bool EchoOnActionCommand { get; set; }
|
||||||
|
|
||||||
|
|
||||||
@@ -246,9 +251,15 @@ namespace GitHub.Runner.Worker
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterPostJobStep(string refName, IStep step)
|
public void RegisterPostJobStep(IStep step)
|
||||||
{
|
{
|
||||||
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, refName, IntraActionState);
|
if (step is IActionRunner actionRunner && !Root.StepsWithPostRegistered.Add(actionRunner.Action.Id))
|
||||||
|
{
|
||||||
|
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, IntraActionState);
|
||||||
Root.PostJobSteps.Push(step);
|
Root.PostJobSteps.Push(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,6 +291,10 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
child.ExpressionValues[pair.Key] = pair.Value;
|
child.ExpressionValues[pair.Key] = pair.Value;
|
||||||
}
|
}
|
||||||
|
foreach (var item in ExpressionFunctions)
|
||||||
|
{
|
||||||
|
child.ExpressionFunctions.Add(item);
|
||||||
|
}
|
||||||
child._cancellationTokenSource = new CancellationTokenSource();
|
child._cancellationTokenSource = new CancellationTokenSource();
|
||||||
child.WriteDebug = WriteDebug;
|
child.WriteDebug = WriteDebug;
|
||||||
child._parentExecutionContext = this;
|
child._parentExecutionContext = this;
|
||||||
@@ -593,12 +608,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// File table
|
// File table
|
||||||
FileTable = new List<String>(message.FileTable ?? new string[0]);
|
FileTable = new List<String>(message.FileTable ?? new string[0]);
|
||||||
|
|
||||||
// Expression functions
|
|
||||||
if (Variables.GetBoolean("System.HashFilesV2") == true)
|
|
||||||
{
|
|
||||||
ExpressionConstants.UpdateFunction<Handlers.HashFiles>("hashFiles", 1, byte.MaxValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expression values
|
// Expression values
|
||||||
if (message.ContextData?.Count > 0)
|
if (message.ContextData?.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -647,6 +656,9 @@ namespace GitHub.Runner.Worker
|
|||||||
// PostJobSteps for job ExecutionContext
|
// PostJobSteps for job ExecutionContext
|
||||||
PostJobSteps = new Stack<IStep>();
|
PostJobSteps = new Stack<IStep>();
|
||||||
|
|
||||||
|
// StepsWithPostRegistered for job ExecutionContext
|
||||||
|
StepsWithPostRegistered = new HashSet<Guid>();
|
||||||
|
|
||||||
// Job timeline record.
|
// Job timeline record.
|
||||||
InitializeTimelineRecord(
|
InitializeTimelineRecord(
|
||||||
timelineId: message.Timeline.Id,
|
timelineId: message.Timeline.Id,
|
||||||
@@ -847,7 +859,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IExecutionContext CreatePostChild(string displayName, string refName, Dictionary<string, string> intraActionState)
|
private IExecutionContext CreatePostChild(string displayName, Dictionary<string, string> intraActionState)
|
||||||
{
|
{
|
||||||
if (!_expandedForPostJob)
|
if (!_expandedForPostJob)
|
||||||
{
|
{
|
||||||
@@ -856,7 +868,8 @@ namespace GitHub.Runner.Worker
|
|||||||
_childTimelineRecordOrder = _childTimelineRecordOrder * 2;
|
_childTimelineRecordOrder = _childTimelineRecordOrder * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateChild(Guid.NewGuid(), displayName, refName, null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count);
|
var newGuid = Guid.NewGuid();
|
||||||
|
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -915,11 +928,19 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context)
|
public static IEnumerable<KeyValuePair<string, object>> ToExpressionState(this IExecutionContext context)
|
||||||
{
|
{
|
||||||
var templateTrace = context.ToTemplateTraceWriter();
|
return new[] { new KeyValuePair<string, object>(nameof(IExecutionContext), context) };
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
}
|
||||||
return new PipelineTemplateEvaluator(templateTrace, schema, context.FileTable);
|
|
||||||
|
public static PipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context, ObjectTemplating.ITraceWriter traceWriter = null)
|
||||||
|
{
|
||||||
|
if (traceWriter == null)
|
||||||
|
{
|
||||||
|
traceWriter = context.ToTemplateTraceWriter();
|
||||||
|
}
|
||||||
|
var schema = PipelineTemplateSchemaFactory.GetSchema();
|
||||||
|
return new PipelineTemplateEvaluator(traceWriter, schema, context.FileTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ObjectTemplating.ITraceWriter ToTemplateTraceWriter(this IExecutionContext context)
|
public static ObjectTemplating.ITraceWriter ToTemplateTraceWriter(this IExecutionContext context)
|
||||||
@@ -934,6 +955,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
internal TemplateTraceWriter(IExecutionContext executionContext)
|
internal TemplateTraceWriter(IExecutionContext executionContext)
|
||||||
{
|
{
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
_executionContext = executionContext;
|
_executionContext = executionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using GitHub.DistributedTask.Expressions2;
|
|
||||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
|
||||||
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
|
||||||
{
|
|
||||||
[ServiceLocator(Default = typeof(ExpressionManager))]
|
|
||||||
public interface IExpressionManager : IRunnerService
|
|
||||||
{
|
|
||||||
ConditionResult Evaluate(IExecutionContext context, string condition, bool hostTracingOnly = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ExpressionManager : RunnerService, IExpressionManager
|
|
||||||
{
|
|
||||||
public ConditionResult Evaluate(IExecutionContext executionContext, string condition, bool hostTracingOnly = false)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
|
|
||||||
ConditionResult result = new ConditionResult();
|
|
||||||
var expressionTrace = new TraceWriter(Trace, hostTracingOnly ? null : executionContext);
|
|
||||||
var tree = Parse(executionContext, expressionTrace, condition);
|
|
||||||
var expressionResult = tree.Evaluate(expressionTrace, HostContext.SecretMasker, state: executionContext, options: null);
|
|
||||||
result.Value = expressionResult.IsTruthy;
|
|
||||||
result.Trace = expressionTrace.Trace;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IExpressionNode Parse(IExecutionContext executionContext, TraceWriter expressionTrace, string condition)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(condition))
|
|
||||||
{
|
|
||||||
condition = $"{PipelineTemplateConstants.Success}()";
|
|
||||||
}
|
|
||||||
|
|
||||||
var parser = new ExpressionParser();
|
|
||||||
var namedValues = executionContext.ExpressionValues.Keys.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
|
|
||||||
var functions = new IFunctionInfo[]
|
|
||||||
{
|
|
||||||
new FunctionInfo<AlwaysNode>(name: Constants.Expressions.Always, minParameters: 0, maxParameters: 0),
|
|
||||||
new FunctionInfo<CancelledNode>(name: Constants.Expressions.Cancelled, minParameters: 0, maxParameters: 0),
|
|
||||||
new FunctionInfo<FailureNode>(name: Constants.Expressions.Failure, minParameters: 0, maxParameters: 0),
|
|
||||||
new FunctionInfo<SuccessNode>(name: Constants.Expressions.Success, minParameters: 0, maxParameters: 0),
|
|
||||||
};
|
|
||||||
return parser.CreateTree(condition, expressionTrace, namedValues, functions) ?? new SuccessNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class TraceWriter : DistributedTask.Expressions2.ITraceWriter
|
|
||||||
{
|
|
||||||
private readonly IExecutionContext _executionContext;
|
|
||||||
private readonly Tracing _trace;
|
|
||||||
private readonly StringBuilder _traceBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
public string Trace => _traceBuilder.ToString();
|
|
||||||
|
|
||||||
public TraceWriter(Tracing trace, IExecutionContext executionContext)
|
|
||||||
{
|
|
||||||
ArgUtil.NotNull(trace, nameof(trace));
|
|
||||||
_trace = trace;
|
|
||||||
_executionContext = executionContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Info(string message)
|
|
||||||
{
|
|
||||||
_trace.Info(message);
|
|
||||||
_executionContext?.Debug(message);
|
|
||||||
_traceBuilder.AppendLine(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Verbose(string message)
|
|
||||||
{
|
|
||||||
_trace.Verbose(message);
|
|
||||||
_executionContext?.Debug(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class AlwaysNode : Function
|
|
||||||
{
|
|
||||||
protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class CancelledNode : Function
|
|
||||||
{
|
|
||||||
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
var executionContext = evaluationContext.State as IExecutionContext;
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
|
||||||
return jobStatus == ActionResult.Cancelled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class FailureNode : Function
|
|
||||||
{
|
|
||||||
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
var executionContext = evaluationContext.State as IExecutionContext;
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
|
||||||
return jobStatus == ActionResult.Failure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class SuccessNode : Function
|
|
||||||
{
|
|
||||||
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
var executionContext = evaluationContext.State as IExecutionContext;
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
|
||||||
return jobStatus == ActionResult.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class ContextValueNode : NamedValue
|
|
||||||
{
|
|
||||||
protected override Object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
var jobContext = evaluationContext.State as IExecutionContext;
|
|
||||||
ArgUtil.NotNull(jobContext, nameof(jobContext));
|
|
||||||
return jobContext.ExpressionValues[Name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ConditionResult
|
|
||||||
{
|
|
||||||
public ConditionResult(bool value = false, string trace = null)
|
|
||||||
{
|
|
||||||
this.Value = value;
|
|
||||||
this.Trace = trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Value { get; set; }
|
|
||||||
public string Trace { get; set; }
|
|
||||||
|
|
||||||
public static implicit operator ConditionResult(bool value)
|
|
||||||
{
|
|
||||||
return new ConditionResult(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
src/Runner.Worker/Expressions/AlwaysFunction.cs
Normal file
25
src/Runner.Worker/Expressions/AlwaysFunction.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
|
{
|
||||||
|
public sealed class AlwaysFunction : Function
|
||||||
|
{
|
||||||
|
protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/Runner.Worker/Expressions/CancelledFunction.cs
Normal file
31
src/Runner.Worker/Expressions/CancelledFunction.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
|
{
|
||||||
|
public sealed class CancelledFunction : Function
|
||||||
|
{
|
||||||
|
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
var templateContext = evaluationContext.State as TemplateContext;
|
||||||
|
ArgUtil.NotNull(templateContext, nameof(templateContext));
|
||||||
|
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
|
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||||
|
return jobStatus == ActionResult.Cancelled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/Runner.Worker/Expressions/FailureFunction.cs
Normal file
31
src/Runner.Worker/Expressions/FailureFunction.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
|
{
|
||||||
|
public sealed class FailureFunction : Function
|
||||||
|
{
|
||||||
|
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
var templateContext = evaluationContext.State as TemplateContext;
|
||||||
|
ArgUtil.NotNull(templateContext, nameof(templateContext));
|
||||||
|
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
|
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||||
|
return jobStatus == ActionResult.Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,28 +8,9 @@ using System.Reflection;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker.Handlers
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
{
|
{
|
||||||
public class FunctionTrace : ITraceWriter
|
public sealed class HashFilesFunction : Function
|
||||||
{
|
|
||||||
private GitHub.DistributedTask.Expressions2.ITraceWriter _trace;
|
|
||||||
|
|
||||||
public FunctionTrace(GitHub.DistributedTask.Expressions2.ITraceWriter trace)
|
|
||||||
{
|
|
||||||
_trace = trace;
|
|
||||||
}
|
|
||||||
public void Info(string message)
|
|
||||||
{
|
|
||||||
_trace.Info(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Verbose(string message)
|
|
||||||
{
|
|
||||||
_trace.Info(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class HashFiles : Function
|
|
||||||
{
|
{
|
||||||
protected sealed override Object EvaluateCore(
|
protected sealed override Object EvaluateCore(
|
||||||
EvaluationContext context,
|
EvaluationContext context,
|
||||||
@@ -82,7 +63,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
string node = Path.Combine(runnerRoot, "externals", "node12", "bin", $"node{IOUtil.ExeExtension}");
|
string node = Path.Combine(runnerRoot, "externals", "node12", "bin", $"node{IOUtil.ExeExtension}");
|
||||||
string hashFilesScript = Path.Combine(binDir, "hashFiles");
|
string hashFilesScript = Path.Combine(binDir, "hashFiles");
|
||||||
var hashResult = string.Empty;
|
var hashResult = string.Empty;
|
||||||
var p = new ProcessInvoker(new FunctionTrace(context.Trace));
|
var p = new ProcessInvoker(new HashFilesTrace(context.Trace));
|
||||||
p.ErrorDataReceived += ((_, data) =>
|
p.ErrorDataReceived += ((_, data) =>
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(data.Data) && data.Data.StartsWith("__OUTPUT__") && data.Data.EndsWith("__OUTPUT__"))
|
if (!string.IsNullOrEmpty(data.Data) && data.Data.StartsWith("__OUTPUT__") && data.Data.EndsWith("__OUTPUT__"))
|
||||||
@@ -122,5 +103,24 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
return hashResult;
|
return hashResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class HashFilesTrace : ITraceWriter
|
||||||
|
{
|
||||||
|
private GitHub.DistributedTask.Expressions2.ITraceWriter _trace;
|
||||||
|
|
||||||
|
public HashFilesTrace(GitHub.DistributedTask.Expressions2.ITraceWriter trace)
|
||||||
|
{
|
||||||
|
_trace = trace;
|
||||||
|
}
|
||||||
|
public void Info(string message)
|
||||||
|
{
|
||||||
|
_trace.Info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Verbose(string message)
|
||||||
|
{
|
||||||
|
_trace.Info(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
31
src/Runner.Worker/Expressions/SuccessFunction.cs
Normal file
31
src/Runner.Worker/Expressions/SuccessFunction.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Expressions
|
||||||
|
{
|
||||||
|
public sealed class SuccessFunction : Function
|
||||||
|
{
|
||||||
|
protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
var templateContext = evaluationContext.State as TemplateContext;
|
||||||
|
ArgUtil.NotNull(templateContext, nameof(templateContext));
|
||||||
|
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
|
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
|
||||||
|
return jobStatus == ActionResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
"action",
|
"action",
|
||||||
"actor",
|
"actor",
|
||||||
|
"api_url", // temp for GHES alpha release
|
||||||
"base_ref",
|
"base_ref",
|
||||||
"event_name",
|
"event_name",
|
||||||
"event_path",
|
"event_path",
|
||||||
@@ -17,9 +18,11 @@ namespace GitHub.Runner.Worker
|
|||||||
"job",
|
"job",
|
||||||
"ref",
|
"ref",
|
||||||
"repository",
|
"repository",
|
||||||
|
"repository_owner",
|
||||||
"run_id",
|
"run_id",
|
||||||
"run_number",
|
"run_number",
|
||||||
"sha",
|
"sha",
|
||||||
|
"url", // temp for GHES alpha release
|
||||||
"workflow",
|
"workflow",
|
||||||
"workspace",
|
"workspace",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -82,9 +82,13 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint");
|
container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (stage == ActionRunStage.Pre)
|
||||||
|
{
|
||||||
|
container.ContainerEntryPoint = Data.Pre;
|
||||||
|
}
|
||||||
else if (stage == ActionRunStage.Post)
|
else if (stage == ActionRunStage.Post)
|
||||||
{
|
{
|
||||||
container.ContainerEntryPoint = Data.Cleanup;
|
container.ContainerEntryPoint = Data.Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create inputs context for template evaluation
|
// create inputs context for template evaluation
|
||||||
@@ -97,14 +101,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
var extraExpressionValues = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
||||||
evaluateContext["inputs"] = inputsContext;
|
extraExpressionValues["inputs"] = inputsContext;
|
||||||
|
|
||||||
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
||||||
if (Data.Arguments != null)
|
if (Data.Arguments != null)
|
||||||
{
|
{
|
||||||
container.ContainerEntryPointArgs = "";
|
container.ContainerEntryPointArgs = "";
|
||||||
var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, evaluateContext);
|
var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, extraExpressionValues);
|
||||||
foreach (var arg in evaluatedArgs)
|
foreach (var arg in evaluatedArgs)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(arg))
|
if (!string.IsNullOrEmpty(arg))
|
||||||
@@ -124,7 +128,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
if (Data.Environment != null)
|
if (Data.Environment != null)
|
||||||
{
|
{
|
||||||
var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, evaluateContext);
|
var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, extraExpressionValues);
|
||||||
foreach (var env in evaluatedEnv)
|
foreach (var env in evaluatedEnv)
|
||||||
{
|
{
|
||||||
if (!this.Environment.ContainsKey(env.Key))
|
if (!this.Environment.ContainsKey(env.Key))
|
||||||
|
|||||||
@@ -60,9 +60,13 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
target = Data.Script;
|
target = Data.Script;
|
||||||
}
|
}
|
||||||
|
else if (stage == ActionRunStage.Pre)
|
||||||
|
{
|
||||||
|
target = Data.Pre;
|
||||||
|
}
|
||||||
else if (stage == ActionRunStage.Post)
|
else if (stage == ActionRunStage.Post)
|
||||||
{
|
{
|
||||||
target = Data.Cleanup;
|
target = Data.Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(target, nameof(target));
|
ArgUtil.NotNullOrEmpty(target, nameof(target));
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
else if (stage == ActionRunStage.Post)
|
else if (stage == ActionRunStage.Post)
|
||||||
{
|
{
|
||||||
plugin = Data.Cleanup;
|
plugin = Data.Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(plugin, nameof(plugin));
|
ArgUtil.NotNullOrEmpty(plugin, nameof(plugin));
|
||||||
|
|||||||
@@ -259,6 +259,16 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
// dump out the command
|
// dump out the command
|
||||||
var fileName = isContainerStepHost ? shellCommand : commandPath;
|
var fileName = isContainerStepHost ? shellCommand : commandPath;
|
||||||
|
#if OS_OSX
|
||||||
|
if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS
|
||||||
|
{
|
||||||
|
// launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process
|
||||||
|
string node12 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}");
|
||||||
|
string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js");
|
||||||
|
arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}";
|
||||||
|
fileName = node12;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
ExecutionContext.Debug($"{fileName} {arguments}");
|
ExecutionContext.Debug($"{fileName} {arguments}");
|
||||||
|
|
||||||
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
// try to resolve path inside container if the request path is part of the mount volume
|
// try to resolve path inside container if the request path is part of the mount volume
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
if (Container.MountVolumes.Exists(x => path.StartsWith(x.SourceVolumePath, StringComparison.OrdinalIgnoreCase)))
|
if (Container.MountVolumes.Exists(x => !string.IsNullOrEmpty(x.SourceVolumePath) && path.StartsWith(x.SourceVolumePath, StringComparison.OrdinalIgnoreCase)))
|
||||||
#else
|
#else
|
||||||
if (Container.MountVolumes.Exists(x => path.StartsWith(x.SourceVolumePath)))
|
if (Container.MountVolumes.Exists(x => !string.IsNullOrEmpty(x.SourceVolumePath) && path.StartsWith(x.SourceVolumePath)))
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
return Container.TranslateToContainerPath(path);
|
return Container.TranslateToContainerPath(path);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
@@ -127,12 +128,23 @@ namespace GitHub.Runner.Worker
|
|||||||
context.SetRunnerContext("workspace", Path.Combine(_workDirectory, trackingConfig.PipelineDirectory));
|
context.SetRunnerContext("workspace", Path.Combine(_workDirectory, trackingConfig.PipelineDirectory));
|
||||||
context.SetGitHubContext("workspace", Path.Combine(_workDirectory, trackingConfig.WorkspaceDirectory));
|
context.SetGitHubContext("workspace", Path.Combine(_workDirectory, trackingConfig.WorkspaceDirectory));
|
||||||
|
|
||||||
|
// Temporary hack for GHES alpha
|
||||||
|
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||||
|
var runnerSettings = configurationStore.GetSettings();
|
||||||
|
if (!runnerSettings.IsHostedServer && !string.IsNullOrEmpty(runnerSettings.GitHubUrl))
|
||||||
|
{
|
||||||
|
var url = new Uri(runnerSettings.GitHubUrl);
|
||||||
|
var portInfo = url.IsDefaultPort ? string.Empty : $":{url.Port.ToString(CultureInfo.InvariantCulture)}";
|
||||||
|
context.SetGitHubContext("url", $"{url.Scheme}://{url.Host}{portInfo}");
|
||||||
|
context.SetGitHubContext("api_url", $"{url.Scheme}://{url.Host}{portInfo}/api/v3");
|
||||||
|
}
|
||||||
|
|
||||||
// Evaluate the job-level environment variables
|
// Evaluate the job-level environment variables
|
||||||
context.Debug("Evaluating job-level environment variables");
|
context.Debug("Evaluating job-level environment variables");
|
||||||
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
||||||
foreach (var token in message.EnvironmentVariables)
|
foreach (var token in message.EnvironmentVariables)
|
||||||
{
|
{
|
||||||
var environmentVariables = templateEvaluator.EvaluateStepEnvironment(token, jobContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer);
|
var environmentVariables = templateEvaluator.EvaluateStepEnvironment(token, jobContext.ExpressionValues, jobContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
|
||||||
foreach (var pair in environmentVariables)
|
foreach (var pair in environmentVariables)
|
||||||
{
|
{
|
||||||
context.EnvironmentVariables[pair.Key] = pair.Value ?? string.Empty;
|
context.EnvironmentVariables[pair.Key] = pair.Value ?? string.Empty;
|
||||||
@@ -142,7 +154,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Evaluate the job container
|
// Evaluate the job container
|
||||||
context.Debug("Evaluating job container");
|
context.Debug("Evaluating job container");
|
||||||
var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues);
|
var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||||
if (container != null)
|
if (container != null)
|
||||||
{
|
{
|
||||||
jobContext.Container = new Container.ContainerInfo(HostContext, container);
|
jobContext.Container = new Container.ContainerInfo(HostContext, container);
|
||||||
@@ -150,7 +162,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Evaluate the job service containers
|
// Evaluate the job service containers
|
||||||
context.Debug("Evaluating job service containers");
|
context.Debug("Evaluating job service containers");
|
||||||
var serviceContainers = templateEvaluator.EvaluateJobServiceContainers(message.JobServiceContainers, jobContext.ExpressionValues);
|
var serviceContainers = templateEvaluator.EvaluateJobServiceContainers(message.JobServiceContainers, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||||
if (serviceContainers?.Count > 0)
|
if (serviceContainers?.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var pair in serviceContainers)
|
foreach (var pair in serviceContainers)
|
||||||
@@ -170,7 +182,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
context.JobDefaults["run"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
context.JobDefaults["run"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
var defaultsRun = defaults.First(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase));
|
var defaultsRun = defaults.First(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase));
|
||||||
var jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues);
|
var jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||||
foreach (var pair in jobDefaults)
|
foreach (var pair in jobDefaults)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(pair.Value))
|
if (!string.IsNullOrEmpty(pair.Value))
|
||||||
@@ -185,8 +197,8 @@ namespace GitHub.Runner.Worker
|
|||||||
// Download actions not already in the cache
|
// Download actions not already in the cache
|
||||||
Trace.Info("Downloading actions");
|
Trace.Info("Downloading actions");
|
||||||
var actionManager = HostContext.GetService<IActionManager>();
|
var actionManager = HostContext.GetService<IActionManager>();
|
||||||
var prepareSteps = await actionManager.PrepareActionsAsync(context, message.Steps);
|
var prepareResult = await actionManager.PrepareActionsAsync(context, message.Steps);
|
||||||
preJobSteps.AddRange(prepareSteps);
|
preJobSteps.AddRange(prepareResult.ContainerSetupSteps);
|
||||||
|
|
||||||
// Add start-container steps, record and stop-container steps
|
// Add start-container steps, record and stop-container steps
|
||||||
if (jobContext.Container != null || jobContext.ServiceContainers.Count > 0)
|
if (jobContext.Container != null || jobContext.ServiceContainers.Count > 0)
|
||||||
@@ -227,9 +239,23 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
actionRunner.TryEvaluateDisplayName(contextData, context);
|
actionRunner.TryEvaluateDisplayName(contextData, context);
|
||||||
jobSteps.Add(actionRunner);
|
jobSteps.Add(actionRunner);
|
||||||
|
|
||||||
|
if (prepareResult.PreStepTracker.TryGetValue(step.Id, out var preStep))
|
||||||
|
{
|
||||||
|
Trace.Info($"Adding pre-{action.DisplayName}.");
|
||||||
|
preStep.TryEvaluateDisplayName(contextData, context);
|
||||||
|
preStep.DisplayName = $"Pre {preStep.DisplayName}";
|
||||||
|
preJobSteps.Add(preStep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var intraActionStates = new Dictionary<Guid, Dictionary<string, string>>();
|
||||||
|
foreach (var preStep in prepareResult.PreStepTracker)
|
||||||
|
{
|
||||||
|
intraActionStates[preStep.Key] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
// Create execution context for pre-job steps
|
// Create execution context for pre-job steps
|
||||||
foreach (var step in preJobSteps)
|
foreach (var step in preJobSteps)
|
||||||
{
|
{
|
||||||
@@ -240,6 +266,12 @@ namespace GitHub.Runner.Worker
|
|||||||
Guid stepId = Guid.NewGuid();
|
Guid stepId = Guid.NewGuid();
|
||||||
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"));
|
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"));
|
||||||
}
|
}
|
||||||
|
else if (step is IActionRunner actionStep)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(actionStep, step.DisplayName);
|
||||||
|
Guid stepId = Guid.NewGuid();
|
||||||
|
actionStep.ExecutionContext = jobContext.CreateChild(stepId, actionStep.DisplayName, stepId.ToString("N"), null, null, intraActionStates[actionStep.Action.Id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create execution context for job steps
|
// Create execution context for job steps
|
||||||
@@ -248,7 +280,8 @@ namespace GitHub.Runner.Worker
|
|||||||
if (step is IActionRunner actionStep)
|
if (step is IActionRunner actionStep)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(actionStep, step.DisplayName);
|
ArgUtil.NotNull(actionStep, step.DisplayName);
|
||||||
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName);
|
intraActionStates.TryGetValue(actionStep.Action.Id, out var intraActionState);
|
||||||
|
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName, intraActionState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,7 +370,7 @@ namespace GitHub.Runner.Worker
|
|||||||
context.ExpressionValues["steps"] = context.StepsContext.GetScope(context.ScopeName);
|
context.ExpressionValues["steps"] = context.StepsContext.GetScope(context.ScopeName);
|
||||||
|
|
||||||
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
||||||
var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues);
|
var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues, context.ExpressionFunctions);
|
||||||
foreach (var output in outputs)
|
foreach (var output in outputs)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(output.Value))
|
if (string.IsNullOrEmpty(output.Value))
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
@@ -10,8 +8,13 @@ using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
|||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
|
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.Expressions;
|
||||||
|
using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -63,11 +66,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
var step = jobContext.JobSteps.Dequeue();
|
var step = jobContext.JobSteps.Dequeue();
|
||||||
IStep nextStep = null;
|
var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps.Peek() : null;
|
||||||
if (jobContext.JobSteps.Count > 0)
|
|
||||||
{
|
|
||||||
nextStep = jobContext.JobSteps.Peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
||||||
@@ -76,6 +75,13 @@ namespace GitHub.Runner.Worker
|
|||||||
// Start
|
// Start
|
||||||
step.ExecutionContext.Start();
|
step.ExecutionContext.Start();
|
||||||
|
|
||||||
|
// Expression functions
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<FailureFunction>(PipelineTemplateConstants.Failure, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0));
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue));
|
||||||
|
|
||||||
// Initialize scope
|
// Initialize scope
|
||||||
if (InitializeScope(step, scopeInputs))
|
if (InitializeScope(step, scopeInputs))
|
||||||
{
|
{
|
||||||
@@ -99,14 +105,13 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Evaluate and merge action's env block to env context
|
// Evaluate and merge action's env block to env context
|
||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer);
|
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
|
||||||
foreach (var env in actionEnvironment)
|
foreach (var env in actionEnvironment)
|
||||||
{
|
{
|
||||||
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var expressionManager = HostContext.GetService<IExpressionManager>();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
||||||
@@ -120,28 +125,29 @@ namespace GitHub.Runner.Worker
|
|||||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||||
|
|
||||||
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
||||||
ConditionResult conditionReTestResult;
|
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
|
||||||
|
var conditionReTestResult = false;
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
||||||
conditionReTestResult = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
conditionReTestResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition, hostTracingOnly: true);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Cancel the step since we get exception while re-evaluate step condition.
|
// 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.");
|
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
||||||
step.ExecutionContext.Error(ex);
|
step.ExecutionContext.Error(ex);
|
||||||
conditionReTestResult = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!conditionReTestResult.Value)
|
if (!conditionReTestResult)
|
||||||
{
|
{
|
||||||
// Cancel the step.
|
// Cancel the step.
|
||||||
Trace.Info("Cancel current running step.");
|
Trace.Info("Cancel current running step.");
|
||||||
@@ -161,34 +167,35 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Evaluate condition.
|
// Evaluate condition.
|
||||||
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
||||||
Exception conditionEvaluateError = null;
|
var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext);
|
||||||
ConditionResult conditionResult;
|
var conditionResult = false;
|
||||||
|
var conditionEvaluateError = default(Exception);
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
||||||
conditionResult = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
conditionResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Info("Caught exception from expression.");
|
Trace.Info("Caught exception from expression.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
conditionResult = false;
|
|
||||||
conditionEvaluateError = ex;
|
conditionEvaluateError = ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// no evaluate error but condition is false
|
// no evaluate error but condition is false
|
||||||
if (!conditionResult.Value && conditionEvaluateError == null)
|
if (!conditionResult && conditionEvaluateError == null)
|
||||||
{
|
{
|
||||||
// Condition == false
|
// Condition == false
|
||||||
Trace.Info("Skipping step due to condition evaluation.");
|
Trace.Info("Skipping step due to condition evaluation.");
|
||||||
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionResult.Trace);
|
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace);
|
||||||
}
|
}
|
||||||
else if (conditionEvaluateError != null)
|
else if (conditionEvaluateError != null)
|
||||||
{
|
{
|
||||||
@@ -248,7 +255,7 @@ namespace GitHub.Runner.Worker
|
|||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
timeoutMinutes = templateEvaluator.EvaluateStepTimeout(step.Timeout, step.ExecutionContext.ExpressionValues);
|
timeoutMinutes = templateEvaluator.EvaluateStepTimeout(step.Timeout, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -339,7 +346,7 @@ namespace GitHub.Runner.Worker
|
|||||||
var continueOnError = false;
|
var continueOnError = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues);
|
continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -392,7 +399,7 @@ namespace GitHub.Runner.Worker
|
|||||||
var inputs = default(DictionaryContextData);
|
var inputs = default(DictionaryContextData);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
inputs = templateEvaluator.EvaluateStepScopeInputs(scope.Inputs, executionContext.ExpressionValues);
|
inputs = templateEvaluator.EvaluateStepScopeInputs(scope.Inputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -448,7 +455,7 @@ namespace GitHub.Runner.Worker
|
|||||||
var outputs = default(DictionaryContextData);
|
var outputs = default(DictionaryContextData);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
outputs = templateEvaluator.EvaluateStepScopeOutputs(scope.Outputs, executionContext.ExpressionValues);
|
outputs = templateEvaluator.EvaluateStepScopeOutputs(scope.Outputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -476,5 +483,43 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
executionContext.Complete(result, resultCode: resultCode);
|
executionContext.Complete(result, resultCode: resultCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class ConditionTraceWriter : ObjectTemplating::ITraceWriter
|
||||||
|
{
|
||||||
|
private readonly IExecutionContext _executionContext;
|
||||||
|
private readonly Tracing _trace;
|
||||||
|
private readonly StringBuilder _traceBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
public string Trace => _traceBuilder.ToString();
|
||||||
|
|
||||||
|
public ConditionTraceWriter(Tracing trace, IExecutionContext executionContext)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(trace, nameof(trace));
|
||||||
|
_trace = trace;
|
||||||
|
_executionContext = executionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Error(string format, params Object[] args)
|
||||||
|
{
|
||||||
|
var message = StringUtil.Format(format, args);
|
||||||
|
_trace.Error(message);
|
||||||
|
_executionContext?.Debug(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Info(string format, params Object[] args)
|
||||||
|
{
|
||||||
|
var message = StringUtil.Format(format, args);
|
||||||
|
_trace.Info(message);
|
||||||
|
_executionContext?.Debug(message);
|
||||||
|
_traceBuilder.AppendLine(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Verbose(string format, params Object[] args)
|
||||||
|
{
|
||||||
|
var message = StringUtil.Format(format, args);
|
||||||
|
_trace.Verbose(message);
|
||||||
|
_executionContext?.Debug(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// Validate args.
|
// Validate args.
|
||||||
ArgUtil.NotNullOrEmpty(pipeIn, nameof(pipeIn));
|
ArgUtil.NotNullOrEmpty(pipeIn, nameof(pipeIn));
|
||||||
ArgUtil.NotNullOrEmpty(pipeOut, nameof(pipeOut));
|
ArgUtil.NotNullOrEmpty(pipeOut, nameof(pipeOut));
|
||||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy);
|
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||||
var jobRunner = HostContext.CreateService<IJobRunner>();
|
var jobRunner = HostContext.CreateService<IJobRunner>();
|
||||||
|
|
||||||
using (var channel = HostContext.CreateService<IProcessChannel>())
|
using (var channel = HostContext.CreateService<IProcessChannel>())
|
||||||
|
|||||||
@@ -43,6 +43,8 @@
|
|||||||
"entrypoint": "non-empty-string",
|
"entrypoint": "non-empty-string",
|
||||||
"args": "container-runs-args",
|
"args": "container-runs-args",
|
||||||
"env": "container-runs-env",
|
"env": "container-runs-env",
|
||||||
|
"pre-entrypoint": "non-empty-string",
|
||||||
|
"pre-if": "non-empty-string",
|
||||||
"post-entrypoint": "non-empty-string",
|
"post-entrypoint": "non-empty-string",
|
||||||
"post-if": "non-empty-string"
|
"post-if": "non-empty-string"
|
||||||
}
|
}
|
||||||
@@ -67,6 +69,8 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"using": "non-empty-string",
|
"using": "non-empty-string",
|
||||||
"main": "non-empty-string",
|
"main": "non-empty-string",
|
||||||
|
"pre": "non-empty-string",
|
||||||
|
"pre-if": "non-empty-string",
|
||||||
"post": "non-empty-string",
|
"post": "non-empty-string",
|
||||||
"post-if": "non-empty-string"
|
"post-if": "non-empty-string"
|
||||||
}
|
}
|
||||||
@@ -91,7 +95,8 @@
|
|||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"job",
|
"job",
|
||||||
"runner"
|
"runner",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using GitHub.DistributedTask.Expressions2.Sdk.Functions;
|
|||||||
|
|
||||||
namespace GitHub.DistributedTask.Expressions2
|
namespace GitHub.DistributedTask.Expressions2
|
||||||
{
|
{
|
||||||
public static class ExpressionConstants
|
internal static class ExpressionConstants
|
||||||
{
|
{
|
||||||
static ExpressionConstants()
|
static ExpressionConstants()
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,6 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
AddFunction<StartsWith>("startsWith", 2, 2);
|
AddFunction<StartsWith>("startsWith", 2, 2);
|
||||||
AddFunction<ToJson>("toJson", 1, 1);
|
AddFunction<ToJson>("toJson", 1, 1);
|
||||||
AddFunction<FromJson>("fromJson", 1, 1);
|
AddFunction<FromJson>("fromJson", 1, 1);
|
||||||
AddFunction<HashFiles>("hashFiles", 1, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
|
private static void AddFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
|
||||||
@@ -25,12 +24,6 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
WellKnownFunctions.Add(name, new FunctionInfo<T>(name, minParameters, maxParameters));
|
WellKnownFunctions.Add(name, new FunctionInfo<T>(name, minParameters, maxParameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UpdateFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
|
|
||||||
where T : Function, new()
|
|
||||||
{
|
|
||||||
WellKnownFunctions[name] = new FunctionInfo<T>(name, minParameters, maxParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static readonly String False = "false";
|
internal static readonly String False = "false";
|
||||||
internal static readonly String Infinity = "Infinity";
|
internal static readonly String Infinity = "Infinity";
|
||||||
internal static readonly Int32 MaxDepth = 50;
|
internal static readonly Int32 MaxDepth = 50;
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Minimatch;
|
|
||||||
using System.IO;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
|
||||||
namespace GitHub.DistributedTask.Expressions2.Sdk.Functions
|
|
||||||
{
|
|
||||||
internal sealed class HashFiles : Function
|
|
||||||
{
|
|
||||||
protected sealed override Object EvaluateCore(
|
|
||||||
EvaluationContext context,
|
|
||||||
out ResultMemory resultMemory)
|
|
||||||
{
|
|
||||||
resultMemory = null;
|
|
||||||
|
|
||||||
// hashFiles() only works on the runner and only works with files under GITHUB_WORKSPACE
|
|
||||||
// Since GITHUB_WORKSPACE is set by runner, I am using that as the fact of this code runs on server or runner.
|
|
||||||
if (context.State is ObjectTemplating.TemplateContext templateContext &&
|
|
||||||
templateContext.ExpressionValues.TryGetValue(PipelineTemplateConstants.GitHub, out var githubContextData) &&
|
|
||||||
githubContextData is DictionaryContextData githubContext &&
|
|
||||||
githubContext.TryGetValue(PipelineTemplateConstants.Workspace, out var workspace) == true &&
|
|
||||||
workspace is StringContextData workspaceData)
|
|
||||||
{
|
|
||||||
string searchRoot = workspaceData.Value;
|
|
||||||
string pattern = Parameters[0].Evaluate(context).ConvertToString();
|
|
||||||
|
|
||||||
// Convert slashes on Windows
|
|
||||||
if (s_isWindows)
|
|
||||||
{
|
|
||||||
pattern = pattern.Replace('\\', '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root the pattern
|
|
||||||
if (!Path.IsPathRooted(pattern))
|
|
||||||
{
|
|
||||||
var patternRoot = s_isWindows ? searchRoot.Replace('\\', '/').TrimEnd('/') : searchRoot.TrimEnd('/');
|
|
||||||
pattern = string.Concat(patternRoot, "/", pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all files
|
|
||||||
context.Trace.Info($"Search root directory: '{searchRoot}'");
|
|
||||||
context.Trace.Info($"Search pattern: '{pattern}'");
|
|
||||||
var files = Directory.GetFiles(searchRoot, "*", SearchOption.AllDirectories)
|
|
||||||
.Select(x => s_isWindows ? x.Replace('\\', '/') : x)
|
|
||||||
.OrderBy(x => x, StringComparer.Ordinal)
|
|
||||||
.ToList();
|
|
||||||
if (files.Count == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"hashFiles('{ExpressionUtility.StringEscape(pattern)}') failed. Directory '{searchRoot}' is empty");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Trace.Info($"Found {files.Count} files");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match
|
|
||||||
var matcher = new Minimatcher(pattern, s_minimatchOptions);
|
|
||||||
files = matcher.Filter(files)
|
|
||||||
.Select(x => s_isWindows ? x.Replace('/', '\\') : x)
|
|
||||||
.ToList();
|
|
||||||
if (files.Count == 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"hashFiles('{ExpressionUtility.StringEscape(pattern)}') failed. Search pattern '{pattern}' doesn't match any file under '{searchRoot}'");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Trace.Info($"{files.Count} matches to hash");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash each file
|
|
||||||
List<byte> filesSha256 = new List<byte>();
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
|
||||||
context.Trace.Info($"Hash {file}");
|
|
||||||
using (SHA256 sha256hash = SHA256.Create())
|
|
||||||
{
|
|
||||||
using (var fileStream = File.OpenRead(file))
|
|
||||||
{
|
|
||||||
filesSha256.AddRange(sha256hash.ComputeHash(fileStream));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash the hashes
|
|
||||||
using (SHA256 sha256hash = SHA256.Create())
|
|
||||||
{
|
|
||||||
var hashBytes = sha256hash.ComputeHash(filesSha256.ToArray());
|
|
||||||
StringBuilder hashString = new StringBuilder();
|
|
||||||
for (int i = 0; i < hashBytes.Length; i++)
|
|
||||||
{
|
|
||||||
hashString.Append(hashBytes[i].ToString("x2"));
|
|
||||||
}
|
|
||||||
var result = hashString.ToString();
|
|
||||||
context.Trace.Info($"Final hash result: '{result}'");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("'hashfiles' expression function is only supported under runner context.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly bool s_isWindows = Environment.OSVersion.Platform != PlatformID.Unix && Environment.OSVersion.Platform != PlatformID.MacOSX;
|
|
||||||
|
|
||||||
// Only support basic globbing (* ? and []) and globstar (**)
|
|
||||||
private static readonly Options s_minimatchOptions = new Options
|
|
||||||
{
|
|
||||||
Dot = true,
|
|
||||||
NoBrace = true,
|
|
||||||
NoCase = s_isWindows,
|
|
||||||
NoComment = true,
|
|
||||||
NoExt = true,
|
|
||||||
NoNegate = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -82,7 +82,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
httpMethod,
|
httpMethod,
|
||||||
locationId,
|
locationId,
|
||||||
routeValues: routeValues,
|
routeValues: routeValues,
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
version: new ApiResourceVersion(6.0, 2),
|
||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken,
|
cancellationToken: cancellationToken,
|
||||||
content: content);
|
content: content);
|
||||||
@@ -109,7 +109,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
httpMethod,
|
httpMethod,
|
||||||
locationId,
|
locationId,
|
||||||
routeValues: routeValues,
|
routeValues: routeValues,
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
version: new ApiResourceVersion(6.0, 2),
|
||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken).ConfigureAwait(false))
|
cancellationToken: cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
@@ -164,7 +164,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
httpMethod,
|
httpMethod,
|
||||||
locationId,
|
locationId,
|
||||||
routeValues: routeValues,
|
routeValues: routeValues,
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
version: new ApiResourceVersion(6.0, 2),
|
||||||
queryParameters: queryParams,
|
queryParameters: queryParams,
|
||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
@@ -227,7 +227,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
httpMethod,
|
httpMethod,
|
||||||
locationId,
|
locationId,
|
||||||
routeValues: routeValues,
|
routeValues: routeValues,
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
version: new ApiResourceVersion(6.0, 2),
|
||||||
queryParameters: queryParams,
|
queryParameters: queryParams,
|
||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
@@ -257,7 +257,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
httpMethod,
|
httpMethod,
|
||||||
locationId,
|
locationId,
|
||||||
routeValues: routeValues,
|
routeValues: routeValues,
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
version: new ApiResourceVersion(6.0, 2),
|
||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken,
|
cancellationToken: cancellationToken,
|
||||||
content: content);
|
content: content);
|
||||||
@@ -287,7 +287,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
httpMethod,
|
httpMethod,
|
||||||
locationId,
|
locationId,
|
||||||
routeValues: routeValues,
|
routeValues: routeValues,
|
||||||
version: new ApiResourceVersion(5.1, 1),
|
version: new ApiResourceVersion(6.0, 2),
|
||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken,
|
cancellationToken: cancellationToken,
|
||||||
content: content);
|
content: content);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
|
||||||
@@ -22,10 +23,27 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
{
|
{
|
||||||
var context = definition[i].Value.AssertSequence($"{TemplateConstants.Context}");
|
var context = definition[i].Value.AssertSequence($"{TemplateConstants.Context}");
|
||||||
definition.RemoveAt(i);
|
definition.RemoveAt(i);
|
||||||
Context = context
|
var readerContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
.Select(x => x.AssertString($"{TemplateConstants.Context} item").Value)
|
var evaluatorContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
.Distinct()
|
foreach (TemplateToken item in context)
|
||||||
.ToArray();
|
{
|
||||||
|
var itemStr = item.AssertString($"{TemplateConstants.Context} item").Value;
|
||||||
|
readerContext.Add(itemStr);
|
||||||
|
|
||||||
|
// Remove min/max parameter info
|
||||||
|
var paramIndex = itemStr.IndexOf('(');
|
||||||
|
if (paramIndex > 0)
|
||||||
|
{
|
||||||
|
evaluatorContext.Add(String.Concat(itemStr.Substring(0, paramIndex + 1), ")"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
evaluatorContext.Add(itemStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReaderContext = readerContext.ToArray();
|
||||||
|
EvaluatorContext = evaluatorContext.ToArray();
|
||||||
}
|
}
|
||||||
else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal))
|
else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
@@ -40,7 +58,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
internal abstract DefinitionType DefinitionType { get; }
|
internal abstract DefinitionType DefinitionType { get; }
|
||||||
|
|
||||||
internal String[] Context { get; private set; } = new String[0];
|
/// <summary>
|
||||||
|
/// Used by the template reader to determine allowed expression values and functions.
|
||||||
|
/// Also used by the template reader to validate function min/max parameters.
|
||||||
|
/// </summary>
|
||||||
|
internal String[] ReaderContext { get; private set; } = new String[0];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by the template evaluator to determine allowed expression values and functions.
|
||||||
|
/// The min/max parameter info is omitted.
|
||||||
|
/// </summary>
|
||||||
|
internal String[] EvaluatorContext { get; private set; } = new String[0];
|
||||||
|
|
||||||
internal abstract void Validate(
|
internal abstract void Validate(
|
||||||
TemplateSchema schema,
|
TemplateSchema schema,
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
{
|
{
|
||||||
var inherited = schema.GetDefinition(Inherits);
|
var inherited = schema.GetDefinition(Inherits);
|
||||||
|
|
||||||
if (inherited.Context.Length > 0)
|
if (inherited.ReaderContext.Length > 0)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException($"Property '{TemplateConstants.Context}' is not supported on inhertied definitions");
|
throw new NotSupportedException($"Property '{TemplateConstants.Context}' is not supported on inhertied definitions");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
{
|
{
|
||||||
var nestedDefinition = schema.GetDefinition(nestedType);
|
var nestedDefinition = schema.GetDefinition(nestedType);
|
||||||
|
|
||||||
if (nestedDefinition.Context.Length > 0)
|
if (nestedDefinition.ReaderContext.Length > 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"'{name}' is a one-of definition and references another definition that defines context. This is currently not supported.");
|
throw new ArgumentException($"'{name}' is a one-of definition and references another definition that defines context. This is currently not supported.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,16 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
var evaluator = new TemplateEvaluator(context, template, removeBytes);
|
var evaluator = new TemplateEvaluator(context, template, removeBytes);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var availableContext = new HashSet<String>(context.ExpressionValues.Keys.Concat(context.ExpressionFunctions.Select(x => $"{x.Name}({x.MinParameters},{x.MaxParameters})")));
|
var availableContext = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var key in context.ExpressionValues.Keys)
|
||||||
|
{
|
||||||
|
availableContext.Add(key);
|
||||||
|
}
|
||||||
|
foreach (var function in context.ExpressionFunctions)
|
||||||
|
{
|
||||||
|
availableContext.Add($"{function.Name}()");
|
||||||
|
}
|
||||||
|
|
||||||
var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext);
|
var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext);
|
||||||
result = evaluator.Evaluate(definitionInfo);
|
result = evaluator.Evaluate(definitionInfo);
|
||||||
|
|
||||||
@@ -393,14 +402,13 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
Definition = m_schema.GetDefinition(name);
|
Definition = m_schema.GetDefinition(name);
|
||||||
|
|
||||||
// Determine whether to expand
|
// Determine whether to expand
|
||||||
if (Definition.Context.Length > 0)
|
m_allowedContext = Definition.EvaluatorContext;
|
||||||
|
if (Definition.EvaluatorContext.Length > 0)
|
||||||
{
|
{
|
||||||
m_allowedContext = Definition.Context;
|
|
||||||
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_allowedContext = new String[0];
|
|
||||||
Expand = false;
|
Expand = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,9 +424,9 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
Definition = m_schema.GetDefinition(name);
|
Definition = m_schema.GetDefinition(name);
|
||||||
|
|
||||||
// Determine whether to expand
|
// Determine whether to expand
|
||||||
if (Definition.Context.Length > 0)
|
if (Definition.EvaluatorContext.Length > 0)
|
||||||
{
|
{
|
||||||
m_allowedContext = new HashSet<String>(parent.m_allowedContext.Concat(Definition.Context)).ToArray();
|
m_allowedContext = new HashSet<String>(parent.m_allowedContext.Concat(Definition.EvaluatorContext), StringComparer.OrdinalIgnoreCase).ToArray();
|
||||||
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
Expand = m_availableContext.IsSupersetOf(m_allowedContext);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
|
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TemplateValidationException(
|
||||||
|
String message,
|
||||||
|
IEnumerable<TemplateValidationError> errors)
|
||||||
|
: this(message)
|
||||||
|
{
|
||||||
|
m_errors = new List<TemplateValidationError>(errors ?? Enumerable.Empty<TemplateValidationError>());
|
||||||
|
}
|
||||||
|
|
||||||
public TemplateValidationException(String message)
|
public TemplateValidationException(String message)
|
||||||
: base(message)
|
: base(message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -780,15 +780,8 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
// Lookup the definition
|
// Lookup the definition
|
||||||
Definition = m_schema.GetDefinition(name);
|
Definition = m_schema.GetDefinition(name);
|
||||||
|
|
||||||
// Determine whether to expand
|
// Record allowed context
|
||||||
if (Definition.Context.Length > 0)
|
AllowedContext = Definition.ReaderContext;
|
||||||
{
|
|
||||||
AllowedContext = Definition.Context;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AllowedContext = new String[0];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefinitionInfo(
|
public DefinitionInfo(
|
||||||
@@ -800,10 +793,10 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
// Lookup the definition
|
// Lookup the definition
|
||||||
Definition = m_schema.GetDefinition(name);
|
Definition = m_schema.GetDefinition(name);
|
||||||
|
|
||||||
// Determine whether to expand
|
// Record allowed context
|
||||||
if (Definition.Context.Length > 0)
|
if (Definition.ReaderContext.Length > 0)
|
||||||
{
|
{
|
||||||
AllowedContext = new HashSet<String>(parent.AllowedContext.Concat(Definition.Context)).ToArray();
|
AllowedContext = new HashSet<String>(parent.AllowedContext.Concat(Definition.ReaderContext), StringComparer.OrdinalIgnoreCase).ToArray();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.ObjectTemplating
|
namespace GitHub.DistributedTask.ObjectTemplating
|
||||||
@@ -41,7 +42,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < 50; i++)
|
for (int i = 0; i < 50; i++)
|
||||||
{
|
{
|
||||||
String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.Message;
|
String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.ToString();
|
||||||
Add(new TemplateValidationError(message));
|
Add(new TemplateValidationError(message));
|
||||||
if (ex.InnerException == null)
|
if (ex.InnerException == null)
|
||||||
{
|
{
|
||||||
@@ -88,6 +89,23 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws <c ref="TemplateValidationException" /> if any errors.
|
||||||
|
/// <param name="prefix">The error message prefix</param>
|
||||||
|
/// </summary>
|
||||||
|
public void Check(String prefix)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrEmpty(prefix))
|
||||||
|
{
|
||||||
|
this.Check();
|
||||||
|
}
|
||||||
|
else if (m_errors.Count > 0)
|
||||||
|
{
|
||||||
|
var message = $"{prefix.Trim()} {String.Join(",", m_errors.Select(e => e.Message))}";
|
||||||
|
throw new TemplateValidationException(message, m_errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
m_errors.Clear();
|
m_errors.Clear();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Globalization;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
using GitHub.Services.WebApi.Internal;
|
using GitHub.Services.WebApi.Internal;
|
||||||
@@ -35,11 +37,29 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
String[] allowedContext,
|
String[] allowedContext,
|
||||||
out Exception ex)
|
out Exception ex)
|
||||||
{
|
{
|
||||||
// Create dummy allowed contexts
|
// Create dummy named values and functions
|
||||||
INamedValueInfo[] namedValues = null;
|
var namedValues = new List<INamedValueInfo>();
|
||||||
|
var functions = new List<IFunctionInfo>();
|
||||||
if (allowedContext?.Length > 0)
|
if (allowedContext?.Length > 0)
|
||||||
{
|
{
|
||||||
namedValues = allowedContext.Select(x => new NamedValueInfo<ContextValueNode>(x)).ToArray();
|
foreach (var contextItem in allowedContext)
|
||||||
|
{
|
||||||
|
var match = s_function.Match(contextItem);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
var functionName = match.Groups[1].Value;
|
||||||
|
var minParameters = Int32.Parse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture);
|
||||||
|
var maxParametersRaw = match.Groups[3].Value;
|
||||||
|
var maxParameters = String.Equals(maxParametersRaw, TemplateConstants.MaxConstant, StringComparison.Ordinal)
|
||||||
|
? Int32.MaxValue
|
||||||
|
: Int32.Parse(maxParametersRaw, NumberStyles.None, CultureInfo.InvariantCulture);
|
||||||
|
functions.Add(new FunctionInfo<DummyFunction>(functionName, minParameters, maxParameters));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
namedValues.Add(new NamedValueInfo<ContextValueNode>(contextItem));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
@@ -47,7 +67,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
ExpressionNode root = null;
|
ExpressionNode root = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
root = new ExpressionParser().CreateTree(expression, null, namedValues, null) as ExpressionNode;
|
root = new ExpressionParser().CreateTree(expression, null, namedValues, functions) as ExpressionNode;
|
||||||
|
|
||||||
result = true;
|
result = true;
|
||||||
ex = null;
|
ex = null;
|
||||||
@@ -60,5 +80,18 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class DummyFunction : Function
|
||||||
|
{
|
||||||
|
protected override Object EvaluateCore(
|
||||||
|
EvaluationContext context,
|
||||||
|
out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Regex s_function = new Regex(@"^([a-zA-Z0-9_]+)\(([0-9]+),([0-9]+|MAX)\)$", RegexOptions.Compiled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.Expressions2.Sdk;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
||||||
{
|
{
|
||||||
@@ -106,6 +109,43 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens
|
|||||||
throw new ArgumentException($"Error while reading '{objectDescription}'. Unexpected value '{literal.ToString()}'");
|
throw new ArgumentException($"Error while reading '{objectDescription}'. Unexpected value '{literal.ToString()}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Traverses the token and checks whether all required expression values
|
||||||
|
/// and functions are provided.
|
||||||
|
/// </summary>
|
||||||
|
public static bool CheckHasRequiredContext(
|
||||||
|
this TemplateToken token,
|
||||||
|
IReadOnlyObject expressionValues,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
|
{
|
||||||
|
var expressionTokens = token.Traverse()
|
||||||
|
.OfType<BasicExpressionToken>()
|
||||||
|
.ToArray();
|
||||||
|
var parser = new ExpressionParser();
|
||||||
|
foreach (var expressionToken in expressionTokens)
|
||||||
|
{
|
||||||
|
var tree = parser.ValidateSyntax(expressionToken.Expression, null);
|
||||||
|
foreach (var node in tree.Traverse())
|
||||||
|
{
|
||||||
|
if (node is NamedValue namedValue)
|
||||||
|
{
|
||||||
|
if (expressionValues?.Keys.Any(x => string.Equals(x, namedValue.Name, StringComparison.OrdinalIgnoreCase)) != true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (node is Function function &&
|
||||||
|
!ExpressionConstants.WellKnownFunctions.ContainsKey(function.Name) &&
|
||||||
|
expressionFunctions?.Any(x => string.Equals(x.Name, function.Name, StringComparison.OrdinalIgnoreCase)) != true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all tokens (depth first)
|
/// Returns all tokens (depth first)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
{
|
{
|
||||||
public const String Always = "always";
|
public const String Always = "always";
|
||||||
public const String BooleanStepsContext = "boolean-steps-context";
|
public const String BooleanStepsContext = "boolean-steps-context";
|
||||||
|
public const String BooleanStrategyContext = "boolean-strategy-context";
|
||||||
public const String CancelTimeoutMinutes = "cancel-timeout-minutes";
|
public const String CancelTimeoutMinutes = "cancel-timeout-minutes";
|
||||||
public const String Cancelled = "cancelled";
|
public const String Cancelled = "cancelled";
|
||||||
public const String Checkout = "checkout";
|
public const String Checkout = "checkout";
|
||||||
@@ -24,6 +25,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
public const String FetchDepth = "fetch-depth";
|
public const String FetchDepth = "fetch-depth";
|
||||||
public const String GeneratedId = "generated-id";
|
public const String GeneratedId = "generated-id";
|
||||||
public const String GitHub = "github";
|
public const String GitHub = "github";
|
||||||
|
public const String HashFiles = "hashFiles";
|
||||||
public const String Id = "id";
|
public const String Id = "id";
|
||||||
public const String If = "if";
|
public const String If = "if";
|
||||||
public const String Image = "image";
|
public const String Image = "image";
|
||||||
@@ -31,6 +33,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
public const String Inputs = "inputs";
|
public const String Inputs = "inputs";
|
||||||
public const String Job = "job";
|
public const String Job = "job";
|
||||||
public const String JobDefaultsRun = "job-defaults-run";
|
public const String JobDefaultsRun = "job-defaults-run";
|
||||||
|
public const String JobIfResult = "job-if-result";
|
||||||
public const String JobOutputs = "job-outputs";
|
public const String JobOutputs = "job-outputs";
|
||||||
public const String Jobs = "jobs";
|
public const String Jobs = "jobs";
|
||||||
public const String Labels = "labels";
|
public const String Labels = "labels";
|
||||||
@@ -60,6 +63,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
public const String Shell = "shell";
|
public const String Shell = "shell";
|
||||||
public const String Skipped = "skipped";
|
public const String Skipped = "skipped";
|
||||||
public const String StepEnv = "step-env";
|
public const String StepEnv = "step-env";
|
||||||
|
public const String StepIfResult = "step-if-result";
|
||||||
public const String Steps = "steps";
|
public const String Steps = "steps";
|
||||||
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";
|
||||||
|
|||||||
@@ -16,6 +16,20 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
{
|
{
|
||||||
internal static class PipelineTemplateConverter
|
internal static class PipelineTemplateConverter
|
||||||
{
|
{
|
||||||
|
internal static Boolean ConvertToIfResult(
|
||||||
|
TemplateContext context,
|
||||||
|
TemplateToken ifResult)
|
||||||
|
{
|
||||||
|
var expression = ifResult.Traverse().FirstOrDefault(x => x is ExpressionToken);
|
||||||
|
if (expression != null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Unexpected type '{expression.GetType().Name}' encountered while reading 'if'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var evaluationResult = EvaluationResult.CreateIntermediateResult(null, ifResult);
|
||||||
|
return evaluationResult.IsTruthy;
|
||||||
|
}
|
||||||
|
|
||||||
internal static Boolean? ConvertToStepContinueOnError(
|
internal static Boolean? ConvertToStepContinueOnError(
|
||||||
TemplateContext context,
|
TemplateContext context,
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.Expressions2.Sdk;
|
using GitHub.DistributedTask.Expressions2.Sdk.Functions;
|
||||||
using GitHub.DistributedTask.ObjectTemplating;
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
@@ -14,6 +14,9 @@ using ITraceWriter = GitHub.DistributedTask.ObjectTemplating.ITraceWriter;
|
|||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates parts of the workflow DOM. For example, a job strategy or step inputs.
|
||||||
|
/// </summary>
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public class PipelineTemplateEvaluator
|
public class PipelineTemplateEvaluator
|
||||||
{
|
{
|
||||||
@@ -50,13 +53,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public DictionaryContextData EvaluateStepScopeInputs(
|
public DictionaryContextData EvaluateStepScopeInputs(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(DictionaryContextData);
|
var result = default(DictionaryContextData);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeInputs, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeInputs, token, 0, null, omitHeader: true);
|
||||||
@@ -76,13 +80,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public DictionaryContextData EvaluateStepScopeOutputs(
|
public DictionaryContextData EvaluateStepScopeOutputs(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(DictionaryContextData);
|
var result = default(DictionaryContextData);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeOutputs, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeOutputs, token, 0, null, omitHeader: true);
|
||||||
@@ -102,13 +107,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public Boolean EvaluateStepContinueOnError(
|
public Boolean EvaluateStepContinueOnError(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(Boolean?);
|
var result = default(Boolean?);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.BooleanStepsContext, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.BooleanStepsContext, token, 0, null, omitHeader: true);
|
||||||
@@ -126,16 +132,44 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
return result ?? false;
|
return result ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String EvaluateStepDisplayName(
|
||||||
|
TemplateToken token,
|
||||||
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
|
{
|
||||||
|
var result = default(String);
|
||||||
|
|
||||||
|
if (token != null && token.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StringStepsContext, token, 0, null, omitHeader: true);
|
||||||
|
context.Errors.Check();
|
||||||
|
result = PipelineTemplateConverter.ConvertToStepDisplayName(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,
|
||||||
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
StringComparer keyComparer)
|
StringComparer keyComparer)
|
||||||
{
|
{
|
||||||
var result = default(Dictionary<String, String>);
|
var result = default(Dictionary<String, String>);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepEnv, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepEnv, token, 0, null, omitHeader: true);
|
||||||
@@ -153,15 +187,44 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
return result ?? new Dictionary<String, String>(keyComparer);
|
return result ?? new Dictionary<String, String>(keyComparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean EvaluateStepIf(
|
||||||
|
TemplateToken token,
|
||||||
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
|
IEnumerable<KeyValuePair<String, Object>> expressionState)
|
||||||
|
{
|
||||||
|
var result = default(Boolean?);
|
||||||
|
|
||||||
|
if (token != null && token.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
var context = CreateContext(contextData, expressionFunctions, expressionState);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepIfResult, token, 0, null, omitHeader: true);
|
||||||
|
context.Errors.Check();
|
||||||
|
result = PipelineTemplateConverter.ConvertToIfResult(context, token);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||||
|
{
|
||||||
|
context.Errors.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Errors.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result ?? throw new InvalidOperationException("Step if cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
public Dictionary<String, String> EvaluateStepInputs(
|
public Dictionary<String, String> EvaluateStepInputs(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(Dictionary<String, String>);
|
var result = default(Dictionary<String, String>);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepWith, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepWith, token, 0, null, omitHeader: true);
|
||||||
@@ -181,13 +244,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public Int32 EvaluateStepTimeout(
|
public Int32 EvaluateStepTimeout(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(Int32?);
|
var result = default(Int32?);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.NumberStepsContext, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.NumberStepsContext, token, 0, null, omitHeader: true);
|
||||||
@@ -207,13 +271,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public JobContainer EvaluateJobContainer(
|
public JobContainer EvaluateJobContainer(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(JobContainer);
|
var result = default(JobContainer);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Container, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Container, token, 0, null, omitHeader: true);
|
||||||
@@ -233,13 +298,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public Dictionary<String, String> EvaluateJobOutput(
|
public Dictionary<String, String> EvaluateJobOutput(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(Dictionary<String, String>);
|
var result = default(Dictionary<String, String>);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobOutputs, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobOutputs, token, 0, null, omitHeader: true);
|
||||||
@@ -269,13 +335,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public Dictionary<String, String> EvaluateJobDefaultsRun(
|
public Dictionary<String, String> EvaluateJobDefaultsRun(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(Dictionary<String, String>);
|
var result = default(Dictionary<String, String>);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobDefaultsRun, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobDefaultsRun, token, 0, null, omitHeader: true);
|
||||||
@@ -305,13 +372,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
public IList<KeyValuePair<String, JobContainer>> EvaluateJobServiceContainers(
|
public IList<KeyValuePair<String, JobContainer>> EvaluateJobServiceContainers(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData)
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
var result = default(List<KeyValuePair<String, JobContainer>>);
|
var result = default(List<KeyValuePair<String, JobContainer>>);
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
if (token != null && token.Type != TokenType.Null)
|
||||||
{
|
{
|
||||||
var context = CreateContext(contextData);
|
var context = CreateContext(contextData, expressionFunctions);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Services, token, 0, null, omitHeader: true);
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Services, token, 0, null, omitHeader: true);
|
||||||
@@ -329,62 +397,10 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean TryEvaluateStepDisplayName(
|
private TemplateContext CreateContext(
|
||||||
TemplateToken token,
|
|
||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
out String stepName)
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
{
|
IEnumerable<KeyValuePair<String, Object>> expressionState = null)
|
||||||
stepName = default(String);
|
|
||||||
var context = CreateContext(contextData);
|
|
||||||
|
|
||||||
if (token != null && token.Type != TokenType.Null)
|
|
||||||
{
|
|
||||||
// We should only evaluate basic expressions if we are sure we have context on all the Named Values and functions
|
|
||||||
// Otherwise return and use a default name
|
|
||||||
if (token is BasicExpressionToken expressionToken)
|
|
||||||
{
|
|
||||||
ExpressionNode root = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
root = new ExpressionParser().ValidateSyntax(expressionToken.Expression, null) as ExpressionNode;
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
context.Errors.Add(exception);
|
|
||||||
context.Errors.Check();
|
|
||||||
}
|
|
||||||
foreach (var node in root.Traverse())
|
|
||||||
{
|
|
||||||
if (node is NamedValue namedValue && !contextData.ContainsKey(namedValue.Name))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (node is Function function &&
|
|
||||||
!context.ExpressionFunctions.Any(item => String.Equals(item.Name, function.Name)) &&
|
|
||||||
!ExpressionConstants.WellKnownFunctions.ContainsKey(function.Name))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StringStepsContext, token, 0, null, omitHeader: true);
|
|
||||||
context.Errors.Check();
|
|
||||||
stepName = PipelineTemplateConverter.ConvertToStepDisplayName(context, token);
|
|
||||||
}
|
|
||||||
catch (Exception ex) when (!(ex is TemplateValidationException))
|
|
||||||
{
|
|
||||||
context.Errors.Add(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Errors.Check();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TemplateContext CreateContext(DictionaryContextData contextData)
|
|
||||||
{
|
{
|
||||||
var result = new TemplateContext
|
var result = new TemplateContext
|
||||||
{
|
{
|
||||||
@@ -407,7 +423,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add named context
|
// Add named values
|
||||||
if (contextData != null)
|
if (contextData != null)
|
||||||
{
|
{
|
||||||
foreach (var pair in contextData)
|
foreach (var pair in contextData)
|
||||||
@@ -416,14 +432,46 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compat for new agent against old server
|
// Add functions
|
||||||
foreach (var name in s_contextNames)
|
var functionNames = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (expressionFunctions?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var function in expressionFunctions)
|
||||||
|
{
|
||||||
|
result.ExpressionFunctions.Add(function);
|
||||||
|
functionNames.Add(function.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add missing expression values and expression functions.
|
||||||
|
// This solves the following problems:
|
||||||
|
// - Compat for new agent against old server (new contexts not sent down in job message)
|
||||||
|
// - 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
|
||||||
|
// be performed early.
|
||||||
|
foreach (var name in s_expressionValueNames)
|
||||||
{
|
{
|
||||||
if (!result.ExpressionValues.ContainsKey(name))
|
if (!result.ExpressionValues.ContainsKey(name))
|
||||||
{
|
{
|
||||||
result.ExpressionValues[name] = null;
|
result.ExpressionValues[name] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
foreach (var name in s_expressionFunctionNames)
|
||||||
|
{
|
||||||
|
if (!functionNames.Contains(name))
|
||||||
|
{
|
||||||
|
result.ExpressionFunctions.Add(new FunctionInfo<NoOperation>(name, 0, Int32.MaxValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add state
|
||||||
|
if (expressionState != null)
|
||||||
|
{
|
||||||
|
foreach (var pair in expressionState)
|
||||||
|
{
|
||||||
|
result.State[pair.Key] = pair.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -431,9 +479,10 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
private readonly ITraceWriter m_trace;
|
private readonly ITraceWriter m_trace;
|
||||||
private readonly TemplateSchema m_schema;
|
private readonly TemplateSchema m_schema;
|
||||||
private readonly IList<String> m_fileTable;
|
private readonly IList<String> m_fileTable;
|
||||||
private readonly String[] s_contextNames = new[]
|
private readonly String[] s_expressionValueNames = new[]
|
||||||
{
|
{
|
||||||
PipelineTemplateConstants.GitHub,
|
PipelineTemplateConstants.GitHub,
|
||||||
|
PipelineTemplateConstants.Needs,
|
||||||
PipelineTemplateConstants.Strategy,
|
PipelineTemplateConstants.Strategy,
|
||||||
PipelineTemplateConstants.Matrix,
|
PipelineTemplateConstants.Matrix,
|
||||||
PipelineTemplateConstants.Needs,
|
PipelineTemplateConstants.Needs,
|
||||||
@@ -444,5 +493,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
PipelineTemplateConstants.Runner,
|
PipelineTemplateConstants.Runner,
|
||||||
PipelineTemplateConstants.Env,
|
PipelineTemplateConstants.Env,
|
||||||
};
|
};
|
||||||
|
private readonly String[] s_expressionFunctionNames = new[]
|
||||||
|
{
|
||||||
|
PipelineTemplateConstants.Always,
|
||||||
|
PipelineTemplateConstants.Cancelled,
|
||||||
|
PipelineTemplateConstants.Failure,
|
||||||
|
PipelineTemplateConstants.HashFiles,
|
||||||
|
PipelineTemplateConstants.Success,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,25 +2,35 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||||
{
|
{
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
public sealed class PipelineTemplateSchemaFactory
|
public static class PipelineTemplateSchemaFactory
|
||||||
{
|
{
|
||||||
public TemplateSchema CreateSchema()
|
public static TemplateSchema GetSchema()
|
||||||
{
|
{
|
||||||
var assembly = Assembly.GetExecutingAssembly();
|
if (s_schema == null)
|
||||||
var json = default(String);
|
|
||||||
using (var stream = assembly.GetManifestResourceStream("GitHub.DistributedTask.Pipelines.ObjectTemplating.workflow-v1.0.json"))
|
|
||||||
using (var streamReader = new StreamReader(stream))
|
|
||||||
{
|
{
|
||||||
json = streamReader.ReadToEnd();
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
|
var json = default(String);
|
||||||
|
using (var stream = assembly.GetManifestResourceStream("GitHub.DistributedTask.Pipelines.ObjectTemplating.workflow-v1.0.json"))
|
||||||
|
using (var streamReader = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
json = streamReader.ReadToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
var objectReader = new JsonObjectReader(null, json);
|
||||||
|
var schema = TemplateSchema.Load(objectReader);
|
||||||
|
Interlocked.CompareExchange(ref s_schema, schema, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var objectReader = new JsonObjectReader(null, json);
|
return s_schema;
|
||||||
return TemplateSchema.Load(objectReader);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TemplateSchema s_schema;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -12,7 +12,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a YAML file into a TemplateToken
|
/// Converts a YAML file into a TemplateToken
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class YamlObjectReader : IObjectReader
|
internal sealed class YamlObjectReader : IObjectReader
|
||||||
{
|
{
|
||||||
internal YamlObjectReader(
|
internal YamlObjectReader(
|
||||||
Int32? fileId,
|
Int32? fileId,
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
"steps-scope-input-value": {
|
"steps-scope-input-value": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
"strategy",
|
|
||||||
"needs",
|
"needs",
|
||||||
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
@@ -66,9 +66,9 @@
|
|||||||
"steps-scope-output-value": {
|
"steps-scope-output-value": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"inputs",
|
"inputs",
|
||||||
@@ -91,9 +91,9 @@
|
|||||||
"description": "Default input values for a steps template",
|
"description": "Default input values for a steps template",
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix"
|
||||||
"needs"
|
|
||||||
],
|
],
|
||||||
"one-of": [
|
"one-of": [
|
||||||
"string",
|
"string",
|
||||||
@@ -114,9 +114,9 @@
|
|||||||
"description": "Output values for a steps template",
|
"description": "Output values for a steps template",
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
@@ -204,6 +204,25 @@
|
|||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"job-if-result": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,MAX)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,MAX)"
|
||||||
|
],
|
||||||
|
"one-of": [
|
||||||
|
"null",
|
||||||
|
"boolean",
|
||||||
|
"number",
|
||||||
|
"string",
|
||||||
|
"sequence",
|
||||||
|
"mapping"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
@@ -272,9 +291,9 @@
|
|||||||
"runs-on": {
|
"runs-on": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix"
|
||||||
"needs"
|
|
||||||
],
|
],
|
||||||
"one-of": [
|
"one-of": [
|
||||||
"non-empty-string",
|
"non-empty-string",
|
||||||
@@ -297,10 +316,10 @@
|
|||||||
"job-env": {
|
"job-env": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
"secrets",
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs"
|
"secrets"
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
@@ -444,9 +463,9 @@
|
|||||||
"step-if": {
|
"step-if": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
@@ -454,7 +473,8 @@
|
|||||||
"always(0,0)",
|
"always(0,0)",
|
||||||
"failure(0,0)",
|
"failure(0,0)",
|
||||||
"cancelled(0,0)",
|
"cancelled(0,0)",
|
||||||
"success(0,0)"
|
"success(0,0)",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
@@ -462,9 +482,9 @@
|
|||||||
"step-if-in-template": {
|
"step-if-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"steps",
|
"steps",
|
||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
@@ -473,11 +493,63 @@
|
|||||||
"always(0,0)",
|
"always(0,0)",
|
||||||
"failure(0,0)",
|
"failure(0,0)",
|
||||||
"cancelled(0,0)",
|
"cancelled(0,0)",
|
||||||
"success(0,0)"
|
"success(0,0)",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"step-if-result": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"steps",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,0)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,0)",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"one-of": [
|
||||||
|
"null",
|
||||||
|
"boolean",
|
||||||
|
"number",
|
||||||
|
"string",
|
||||||
|
"sequence",
|
||||||
|
"mapping"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"step-if-result-in-template": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"steps",
|
||||||
|
"inputs",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"always(0,0)",
|
||||||
|
"failure(0,0)",
|
||||||
|
"cancelled(0,0)",
|
||||||
|
"success(0,0)",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"one-of": [
|
||||||
|
"null",
|
||||||
|
"boolean",
|
||||||
|
"number",
|
||||||
|
"string",
|
||||||
|
"sequence",
|
||||||
|
"mapping"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
"steps-template-reference": {
|
"steps-template-reference": {
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -501,9 +573,9 @@
|
|||||||
"steps-template-reference-inputs": {
|
"steps-template-reference-inputs": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
@@ -519,9 +591,9 @@
|
|||||||
"steps-template-reference-inputs-in-template": {
|
"steps-template-reference-inputs-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"inputs",
|
"inputs",
|
||||||
@@ -538,14 +610,15 @@
|
|||||||
"step-env": {
|
"step-env": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
@@ -556,15 +629,16 @@
|
|||||||
"step-env-in-template": {
|
"step-env-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
@@ -575,14 +649,35 @@
|
|||||||
"step-with": {
|
"step-with": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"mapping": {
|
||||||
|
"loose-key-type": "non-empty-string",
|
||||||
|
"loose-value-type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"step-with-in-template": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"needs",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"secrets",
|
||||||
|
"steps",
|
||||||
|
"inputs",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
@@ -593,9 +688,9 @@
|
|||||||
"container": {
|
"container": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix"
|
||||||
"needs"
|
|
||||||
],
|
],
|
||||||
"one-of": [
|
"one-of": [
|
||||||
"string",
|
"string",
|
||||||
@@ -618,9 +713,9 @@
|
|||||||
"services": {
|
"services": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix"
|
||||||
"needs"
|
|
||||||
],
|
],
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
@@ -631,9 +726,9 @@
|
|||||||
"services-container": {
|
"services-container": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix"
|
||||||
"needs"
|
|
||||||
],
|
],
|
||||||
"one-of": [
|
"one-of": [
|
||||||
"non-empty-string",
|
"non-empty-string",
|
||||||
@@ -644,29 +739,10 @@
|
|||||||
"container-env": {
|
"container-env": {
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
"loose-value-type": "string"
|
"loose-value-type": "string-runner-context"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"step-with-in-template": {
|
|
||||||
"context": [
|
|
||||||
"github",
|
|
||||||
"strategy",
|
|
||||||
"matrix",
|
|
||||||
"needs",
|
|
||||||
"secrets",
|
|
||||||
"steps",
|
|
||||||
"inputs",
|
|
||||||
"job",
|
|
||||||
"runner",
|
|
||||||
"env"
|
|
||||||
],
|
|
||||||
"mapping": {
|
|
||||||
"loose-key-type": "non-empty-string",
|
|
||||||
"loose-value-type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"non-empty-string": {
|
"non-empty-string": {
|
||||||
"string": {
|
"string": {
|
||||||
"require-non-empty": true
|
"require-non-empty": true
|
||||||
@@ -682,9 +758,9 @@
|
|||||||
"boolean-strategy-context": {
|
"boolean-strategy-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix"
|
||||||
"needs"
|
|
||||||
],
|
],
|
||||||
"boolean": {}
|
"boolean": {}
|
||||||
},
|
},
|
||||||
@@ -692,9 +768,9 @@
|
|||||||
"number-strategy-context": {
|
"number-strategy-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix"
|
||||||
"needs"
|
|
||||||
],
|
],
|
||||||
"number": {}
|
"number": {}
|
||||||
},
|
},
|
||||||
@@ -702,9 +778,9 @@
|
|||||||
"string-strategy-context": {
|
"string-strategy-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix"
|
||||||
"needs"
|
|
||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
@@ -712,14 +788,15 @@
|
|||||||
"boolean-steps-context": {
|
"boolean-steps-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"boolean": {}
|
"boolean": {}
|
||||||
},
|
},
|
||||||
@@ -727,15 +804,16 @@
|
|||||||
"boolean-steps-context-in-template": {
|
"boolean-steps-context-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"boolean": {}
|
"boolean": {}
|
||||||
},
|
},
|
||||||
@@ -743,14 +821,15 @@
|
|||||||
"number-steps-context": {
|
"number-steps-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"number": {}
|
"number": {}
|
||||||
},
|
},
|
||||||
@@ -758,15 +837,16 @@
|
|||||||
"number-steps-context-in-template": {
|
"number-steps-context-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"number": {}
|
"number": {}
|
||||||
},
|
},
|
||||||
@@ -774,9 +854,9 @@
|
|||||||
"string-runner-context": {
|
"string-runner-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
@@ -789,14 +869,15 @@
|
|||||||
"string-steps-context": {
|
"string-steps-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
@@ -804,15 +885,16 @@
|
|||||||
"string-steps-context-in-template": {
|
"string-steps-context-in-template": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
"needs",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"needs",
|
|
||||||
"secrets",
|
"secrets",
|
||||||
"steps",
|
"steps",
|
||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
}
|
}
|
||||||
|
|||||||
59
src/Sdk/DTWebApi/WebApi/AgentLabel.cs
Normal file
59
src/Sdk/DTWebApi/WebApi/AgentLabel.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
[DataContract]
|
||||||
|
public class AgentLabel
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
public AgentLabel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AgentLabel(string name)
|
||||||
|
{
|
||||||
|
this.Name = name;
|
||||||
|
this.Type = LabelType.System;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AgentLabel(string name, LabelType type)
|
||||||
|
{
|
||||||
|
this.Name = name;
|
||||||
|
this.Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AgentLabel(AgentLabel labelToBeCloned)
|
||||||
|
{
|
||||||
|
this.Id = labelToBeCloned.Id;
|
||||||
|
this.Name = labelToBeCloned.Name;
|
||||||
|
this.Type = labelToBeCloned.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public int Id
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public LabelType Type
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AgentLabel Clone()
|
||||||
|
{
|
||||||
|
return new AgentLabel(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/Sdk/DTWebApi/WebApi/LabelType.cs
Normal file
14
src/Sdk/DTWebApi/WebApi/LabelType.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.WebApi
|
||||||
|
{
|
||||||
|
[DataContract]
|
||||||
|
public enum LabelType
|
||||||
|
{
|
||||||
|
[EnumMember]
|
||||||
|
System = 0,
|
||||||
|
|
||||||
|
[EnumMember]
|
||||||
|
User = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,7 +51,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
|
|
||||||
if (agentToBeCloned.m_labels != null && agentToBeCloned.m_labels.Count > 0)
|
if (agentToBeCloned.m_labels != null && agentToBeCloned.m_labels.Count > 0)
|
||||||
{
|
{
|
||||||
m_labels = new HashSet<string>(agentToBeCloned.m_labels, StringComparer.OrdinalIgnoreCase);
|
m_labels = new HashSet<AgentLabel>(agentToBeCloned.m_labels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,13 +118,13 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The labels of the runner
|
/// The labels of the runner
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ISet<string> Labels
|
public ISet<AgentLabel> Labels
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (m_labels == null)
|
if (m_labels == null)
|
||||||
{
|
{
|
||||||
m_labels = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
m_labels = new HashSet<AgentLabel>();
|
||||||
}
|
}
|
||||||
return m_labels;
|
return m_labels;
|
||||||
}
|
}
|
||||||
@@ -164,6 +164,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
private PropertiesCollection m_properties;
|
private PropertiesCollection m_properties;
|
||||||
|
|
||||||
[DataMember(IsRequired = false, EmitDefaultValue = false, Name = "Labels")]
|
[DataMember(IsRequired = false, EmitDefaultValue = false, Name = "Labels")]
|
||||||
private HashSet<string> m_labels;
|
private HashSet<AgentLabel> m_labels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
Int64 requestId,
|
Int64 requestId,
|
||||||
Guid lockToken,
|
Guid lockToken,
|
||||||
DateTime? expiresOn = null,
|
DateTime? expiresOn = null,
|
||||||
|
string orchestrationId = null,
|
||||||
Object userState = null,
|
Object userState = null,
|
||||||
CancellationToken cancellationToken = default(CancellationToken))
|
CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
@@ -104,7 +105,30 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
LockedUntil = expiresOn,
|
LockedUntil = expiresOn,
|
||||||
};
|
};
|
||||||
|
|
||||||
return UpdateAgentRequestAsync(poolId, requestId, lockToken, request, userState, cancellationToken);
|
var additionalHeaders = new Dictionary<string, string>();
|
||||||
|
if (!string.IsNullOrEmpty(orchestrationId))
|
||||||
|
{
|
||||||
|
additionalHeaders["X-VSS-OrchestrationId"] = orchestrationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpMethod httpMethod = new HttpMethod("PATCH");
|
||||||
|
Guid locationId = new Guid("fc825784-c92a-4299-9221-998a02d1b54f");
|
||||||
|
object routeValues = new { poolId = poolId, requestId = requestId };
|
||||||
|
HttpContent content = new ObjectContent<TaskAgentJobRequest>(request, new VssJsonMediaTypeFormatter(true));
|
||||||
|
|
||||||
|
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>();
|
||||||
|
queryParams.Add("lockToken", lockToken.ToString());
|
||||||
|
|
||||||
|
return SendAsync<TaskAgentJobRequest>(
|
||||||
|
httpMethod,
|
||||||
|
additionalHeaders,
|
||||||
|
locationId,
|
||||||
|
routeValues: routeValues,
|
||||||
|
version: new ApiResourceVersion(5.1, 1),
|
||||||
|
queryParameters: queryParams,
|
||||||
|
userState: userState,
|
||||||
|
cancellationToken: cancellationToken,
|
||||||
|
content: content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TaskAgent> ReplaceAgentAsync(
|
public Task<TaskAgent> ReplaceAgentAsync(
|
||||||
@@ -171,5 +195,5 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly ApiResourceVersion m_currentApiVersion = new ApiResourceVersion(3.0, 1);
|
private readonly ApiResourceVersion m_currentApiVersion = new ApiResourceVersion(3.0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ namespace GitHub.Services.WebApi.Jwt
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IEnumerable<Claim> ExtractClaims(this JsonWebToken token)
|
public static IEnumerable<Claim> ExtractClaims(this JsonWebToken token)
|
||||||
{
|
{
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
ArgumentUtility.CheckForNull(token, nameof(token));
|
||||||
|
|
||||||
|
|||||||
@@ -317,7 +317,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
false, // secret
|
false, // secret
|
||||||
Environment.MachineName, // defaultValue
|
Environment.MachineName, // defaultValue
|
||||||
Validators.NonEmptyValidator, // validator
|
Validators.NonEmptyValidator, // validator
|
||||||
true)) // unattended
|
true, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some runner");
|
.Returns("some runner");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -344,7 +345,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
false, // secret
|
false, // secret
|
||||||
Environment.MachineName, // defaultValue
|
Environment.MachineName, // defaultValue
|
||||||
Validators.NonEmptyValidator, // validator
|
Validators.NonEmptyValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some runner");
|
.Returns("some runner");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -371,7 +373,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
false, // secret
|
false, // secret
|
||||||
"some default auth", // defaultValue
|
"some default auth", // defaultValue
|
||||||
Validators.AuthSchemeValidator, // validator
|
Validators.AuthSchemeValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some auth");
|
.Returns("some auth");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -398,7 +401,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
true, // secret
|
true, // secret
|
||||||
string.Empty, // defaultValue
|
string.Empty, // defaultValue
|
||||||
Validators.NonEmptyValidator, // validator
|
Validators.NonEmptyValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some token");
|
.Returns("some token");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -475,7 +479,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
true, // secret
|
true, // secret
|
||||||
string.Empty, // defaultValue
|
string.Empty, // defaultValue
|
||||||
Validators.NonEmptyValidator, // validator
|
Validators.NonEmptyValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some token");
|
.Returns("some token");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -502,7 +507,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
true, // secret
|
true, // secret
|
||||||
string.Empty, // defaultValue
|
string.Empty, // defaultValue
|
||||||
Validators.NonEmptyValidator, // validator
|
Validators.NonEmptyValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some token");
|
.Returns("some token");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -529,7 +535,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
false, // secret
|
false, // secret
|
||||||
string.Empty, // defaultValue
|
string.Empty, // defaultValue
|
||||||
Validators.ServerUrlValidator, // validator
|
Validators.ServerUrlValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some url");
|
.Returns("some url");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -556,7 +563,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
false, // secret
|
false, // secret
|
||||||
"some default account", // defaultValue
|
"some default account", // defaultValue
|
||||||
Validators.NTAccountValidator, // validator
|
Validators.NTAccountValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some windows logon account");
|
.Returns("some windows logon account");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -584,7 +592,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
true, // secret
|
true, // secret
|
||||||
string.Empty, // defaultValue
|
string.Empty, // defaultValue
|
||||||
Validators.NonEmptyValidator, // validator
|
Validators.NonEmptyValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some windows logon password");
|
.Returns("some windows logon password");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -611,7 +620,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
false, // secret
|
false, // secret
|
||||||
"_work", // defaultValue
|
"_work", // defaultValue
|
||||||
Validators.NonEmptyValidator, // validator
|
Validators.NonEmptyValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some work");
|
.Returns("some work");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -640,7 +650,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
false, // secret
|
false, // secret
|
||||||
string.Empty, // defaultValue
|
string.Empty, // defaultValue
|
||||||
Validators.ServerUrlValidator, // validator
|
Validators.ServerUrlValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some url");
|
.Returns("some url");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
@@ -669,7 +680,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
false, // secret
|
false, // secret
|
||||||
string.Empty, // defaultValue
|
string.Empty, // defaultValue
|
||||||
Validators.ServerUrlValidator, // validator
|
Validators.ServerUrlValidator, // validator
|
||||||
false)) // unattended
|
false, // unattended
|
||||||
|
false)) // isOptional
|
||||||
.Returns("some url");
|
.Returns("some url");
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
|
|
||||||
private Mock<IRSAKeyManager> _rsaKeyManager;
|
private Mock<IRSAKeyManager> _rsaKeyManager;
|
||||||
private string _expectedToken = "expectedToken";
|
private string _expectedToken = "expectedToken";
|
||||||
private string _expectedServerUrl = "https://localhost";
|
private string _expectedServerUrl = "https://codedev.ms";
|
||||||
private string _expectedAgentName = "expectedAgentName";
|
private string _expectedAgentName = "expectedAgentName";
|
||||||
private string _expectedPoolName = "poolName";
|
private string _expectedPoolName = "poolName";
|
||||||
private string _expectedAuthType = "pat";
|
private string _expectedAuthType = "pat";
|
||||||
@@ -145,6 +145,8 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
IConfigurationManager configManager = new ConfigurationManager();
|
IConfigurationManager configManager = new ConfigurationManager();
|
||||||
configManager.Initialize(tc);
|
configManager.Initialize(tc);
|
||||||
|
|
||||||
|
var userLabels = "userlabel1,userlabel2";
|
||||||
|
|
||||||
trace.Info("Preparing command line arguments");
|
trace.Info("Preparing command line arguments");
|
||||||
var command = new CommandSettings(
|
var command = new CommandSettings(
|
||||||
tc,
|
tc,
|
||||||
@@ -156,7 +158,8 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
"--pool", _expectedPoolName,
|
"--pool", _expectedPoolName,
|
||||||
"--work", _expectedWorkFolder,
|
"--work", _expectedWorkFolder,
|
||||||
"--auth", _expectedAuthType,
|
"--auth", _expectedAuthType,
|
||||||
"--token", _expectedToken
|
"--token", _expectedToken,
|
||||||
|
"--labels", userLabels
|
||||||
});
|
});
|
||||||
trace.Info("Constructed.");
|
trace.Info("Constructed.");
|
||||||
_store.Setup(x => x.IsConfigured()).Returns(false);
|
_store.Setup(x => x.IsConfigured()).Returns(false);
|
||||||
@@ -178,7 +181,10 @@ namespace GitHub.Runner.Common.Tests.Listener.Configuration
|
|||||||
// validate GetAgentPoolsAsync gets called twice with automation pool type
|
// validate GetAgentPoolsAsync gets called twice with automation pool type
|
||||||
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2));
|
_runnerServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Exactly(2));
|
||||||
|
|
||||||
_runnerServer.Verify(x => x.AddAgentAsync(It.IsAny<int>(), It.Is<TaskAgent>(a => a.Labels.Contains("self-hosted") && a.Labels.Contains(VarUtil.OS) && a.Labels.Contains(VarUtil.OSArchitecture))), Times.Once);
|
var expectedLabels = new List<string>() { "self-hosted", VarUtil.OS, VarUtil.OSArchitecture};
|
||||||
|
expectedLabels.AddRange(userLabels.Split(",").ToList());
|
||||||
|
|
||||||
|
_runnerServer.Verify(x => x.AddAgentAsync(It.IsAny<int>(), It.Is<TaskAgent>(a => a.Labels.Select(x => x.Name).ToHashSet().SetEquals(expectedLabels))), Times.Once);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
Assert.NotNull(sessionIdProperty);
|
Assert.NotNull(sessionIdProperty);
|
||||||
sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request));
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request));
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest()));
|
_runnerServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest()));
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
||||||
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns(() =>
|
.Returns(() =>
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
@@ -139,10 +139,10 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully);
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully);
|
||||||
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
||||||
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns(() =>
|
.Returns(() =>
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
@@ -197,11 +197,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
||||||
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
||||||
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
||||||
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns(() =>
|
.Returns(() =>
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
@@ -256,11 +256,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
||||||
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
||||||
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +288,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
||||||
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns(() =>
|
.Returns(() =>
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
@@ -315,11 +315,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
||||||
Assert.True(cancellationTokenSource.IsCancellationRequested);
|
Assert.True(cancellationTokenSource.IsCancellationRequested);
|
||||||
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(8));
|
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(8));
|
||||||
_runnerServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3));
|
_runnerServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3));
|
||||||
_runnerServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Once);
|
_runnerServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Once);
|
||||||
}
|
}
|
||||||
@@ -349,7 +349,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
||||||
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns(() =>
|
.Returns(() =>
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
@@ -372,11 +372,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.False(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should failed.");
|
Assert.False(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should failed.");
|
||||||
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
||||||
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(6));
|
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,7 +404,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
hc.SetSingleton<IRunnerServer>(_runnerServer.Object);
|
||||||
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
|
||||||
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
|
||||||
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||||
.Returns(() =>
|
.Returns(() =>
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
@@ -436,11 +436,11 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
var jobDispatcher = new JobDispatcher();
|
var jobDispatcher = new JobDispatcher();
|
||||||
jobDispatcher.Initialize(hc);
|
jobDispatcher.Initialize(hc);
|
||||||
|
|
||||||
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
|
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, Guid.NewGuid().ToString(), firstJobRequestRenewed, cancellationTokenSource.Token);
|
||||||
|
|
||||||
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
|
||||||
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
Assert.False(cancellationTokenSource.IsCancellationRequested);
|
||||||
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
_runnerServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
|
||||||
_runnerServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3));
|
_runnerServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3));
|
||||||
_runnerServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Never);
|
_runnerServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Never);
|
||||||
}
|
}
|
||||||
@@ -481,7 +481,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
Assert.NotNull(sessionIdProperty);
|
Assert.NotNull(sessionIdProperty);
|
||||||
sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request));
|
_runnerServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request));
|
||||||
|
|
||||||
_runnerServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest()));
|
_runnerServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest()));
|
||||||
|
|
||||||
|
|||||||
@@ -660,7 +660,7 @@ namespace GitHub.Runner.Common.Tests.Listener
|
|||||||
_settings.AgentId))
|
_settings.AgentId))
|
||||||
.Returns(async () =>
|
.Returns(async () =>
|
||||||
{
|
{
|
||||||
await Task.Delay(10);
|
await Task.Delay(100);
|
||||||
return "https://t.server";
|
return "https://t.server";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProductInfoHeaderValue UserAgent => new ProductInfoHeaderValue("L0Test", "0.0");
|
public List<ProductInfoHeaderValue> UserAgents => new List<ProductInfoHeaderValue>() { new ProductInfoHeaderValue("L0Test", "0.0") };
|
||||||
|
|
||||||
public RunnerWebProxy WebProxy => new RunnerWebProxy();
|
public RunnerWebProxy WebProxy => new RunnerWebProxy();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
using GitHub.Runner.Worker;
|
using GitHub.Runner.Worker;
|
||||||
@@ -54,7 +56,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
@@ -109,6 +111,57 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async void PrepareActions_SkipDownloadActionFromGraphWhenCached_OnPremises()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var actions = new List<Pipelines.ActionStep>
|
||||||
|
{
|
||||||
|
new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "actions/no-such-action",
|
||||||
|
Ref = "master",
|
||||||
|
RepositoryType = "GitHub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_configurationStore.Object.GetSettings().IsHostedServer = false;
|
||||||
|
var actionDirectory = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "actions/no-such-action", "master");
|
||||||
|
Directory.CreateDirectory(actionDirectory);
|
||||||
|
var watermarkFile = $"{actionDirectory}.completed";
|
||||||
|
File.WriteAllText(watermarkFile, DateTime.UtcNow.ToString());
|
||||||
|
var actionFile = Path.Combine(actionDirectory, "action.yml");
|
||||||
|
File.WriteAllText(actionFile, @"
|
||||||
|
name: ""no-such-action""
|
||||||
|
runs:
|
||||||
|
using: node12
|
||||||
|
main: no-such-action.js
|
||||||
|
");
|
||||||
|
var testFile = Path.Combine(actionDirectory, "test-file");
|
||||||
|
File.WriteAllText(testFile, "asdf");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(File.Exists(testFile));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -164,7 +217,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
Assert.True(steps.Count == 0);
|
Assert.True(steps.Count == 0);
|
||||||
}
|
}
|
||||||
@@ -203,7 +256,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfile");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfile");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
Assert.Equal(Path.Combine(actionDir, "Dockerfile"), (steps[0].Data as ContainerSetupInfo).Container.Dockerfile);
|
Assert.Equal(Path.Combine(actionDir, "Dockerfile"), (steps[0].Data as ContainerSetupInfo).Container.Dockerfile);
|
||||||
@@ -243,7 +296,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfileinrelativepath");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfileinrelativepath");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
@@ -282,7 +335,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfileinrelativepath");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithdockerfileinrelativepath");
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
@@ -322,7 +375,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionfile_DockerfileRelativePath");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionfile_DockerfileRelativePath");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
@@ -362,7 +415,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionfile_DockerHubImage");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionfile_DockerHubImage");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal("ubuntu:18.04", (steps[0].Data as ContainerSetupInfo).Container.Image);
|
Assert.Equal("ubuntu:18.04", (steps[0].Data as ContainerSetupInfo).Container.Image);
|
||||||
@@ -401,7 +454,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionYamlFile_DockerHubImage");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "RepositoryActionWithActionYamlFile_DockerHubImage");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
Assert.Equal((steps[0].Data as ContainerSetupInfo).StepIds[0], actionId);
|
Assert.Equal((steps[0].Data as ContainerSetupInfo).StepIds[0], actionId);
|
||||||
Assert.Equal("ubuntu:18.04", (steps[0].Data as ContainerSetupInfo).Container.Image);
|
Assert.Equal("ubuntu:18.04", (steps[0].Data as ContainerSetupInfo).Container.Image);
|
||||||
@@ -440,7 +493,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithactionfileanddockerfile");
|
var actionDir = Path.Combine(_hc.GetDirectory(WellKnownDirectory.Actions), "TingluoHuang", "runner_L0", "repositoryactionwithactionfileanddockerfile");
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
Assert.Equal(actionDir, (steps[0].Data as ContainerSetupInfo).Container.WorkingDirectory);
|
||||||
@@ -557,7 +610,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
Assert.Equal(actionId1, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
Assert.Equal(actionId1, (steps[0].Data as ContainerSetupInfo).StepIds[0]);
|
||||||
@@ -618,7 +671,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var steps = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
var steps = (await _actionManager.PrepareActionsAsync(_ec.Object, actions)).ContainerSetupSteps;
|
||||||
|
|
||||||
// node.js based action doesn't need any extra steps to build/pull containers.
|
// node.js based action doesn't need any extra steps to build/pull containers.
|
||||||
Assert.True(steps.Count == 0);
|
Assert.True(steps.Count == 0);
|
||||||
@@ -629,6 +682,104 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async void PrepareActions_RepositoryActionWithInvalidWrapperActionfile_Node()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var actions = new List<Pipelines.ActionStep>
|
||||||
|
{
|
||||||
|
new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "TingluoHuang/runner_L0",
|
||||||
|
Ref = "RepositoryActionWithInvalidWrapperActionfile_Node",
|
||||||
|
RepositoryType = "GitHub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Act
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
var traceFile = Path.GetTempFileName();
|
||||||
|
File.Copy(_hc.TraceFileName, traceFile, true);
|
||||||
|
Assert.Contains("Entry javascript file is not provided.", File.ReadAllText(traceFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async void PrepareActions_RepositoryActionWithWrapperActionfile_PreSteps()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
_hc.EnqueueInstance<IActionRunner>(new Mock<IActionRunner>().Object);
|
||||||
|
_hc.EnqueueInstance<IActionRunner>(new Mock<IActionRunner>().Object);
|
||||||
|
|
||||||
|
var actionId1 = Guid.NewGuid();
|
||||||
|
var actionId2 = Guid.NewGuid();
|
||||||
|
_hc.GetTrace().Info(actionId1);
|
||||||
|
_hc.GetTrace().Info(actionId2);
|
||||||
|
var actions = new List<Pipelines.ActionStep>
|
||||||
|
{
|
||||||
|
new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action1",
|
||||||
|
Id = actionId1,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "TingluoHuang/runner_L0",
|
||||||
|
Ref = "RepositoryActionWithWrapperActionfile_Node",
|
||||||
|
RepositoryType = "GitHub"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action2",
|
||||||
|
Id = actionId2,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "TingluoHuang/runner_L0",
|
||||||
|
Ref = "RepositoryActionWithWrapperActionfile_Docker",
|
||||||
|
RepositoryType = "GitHub"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var preResult = await _actionManager.PrepareActionsAsync(_ec.Object, actions);
|
||||||
|
Assert.Equal(2, preResult.PreStepTracker.Count);
|
||||||
|
Assert.NotNull(preResult.PreStepTracker[actionId1]);
|
||||||
|
Assert.NotNull(preResult.PreStepTracker[actionId2]);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -1373,7 +1524,7 @@ runs:
|
|||||||
|
|
||||||
Assert.NotNull((definition.Data.Execution as NodeJSActionExecutionData));
|
Assert.NotNull((definition.Data.Execution as NodeJSActionExecutionData));
|
||||||
Assert.Equal("task.js", (definition.Data.Execution as NodeJSActionExecutionData).Script);
|
Assert.Equal("task.js", (definition.Data.Execution as NodeJSActionExecutionData).Script);
|
||||||
Assert.Equal("cleanup.js", (definition.Data.Execution as NodeJSActionExecutionData).Cleanup);
|
Assert.Equal("cleanup.js", (definition.Data.Execution as NodeJSActionExecutionData).Post);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -1453,7 +1604,7 @@ runs:
|
|||||||
Assert.NotNull((definition.Data.Execution as ContainerActionExecutionData)); // execution.Node
|
Assert.NotNull((definition.Data.Execution as ContainerActionExecutionData)); // execution.Node
|
||||||
Assert.Equal("image:1234", (definition.Data.Execution as ContainerActionExecutionData).Image);
|
Assert.Equal("image:1234", (definition.Data.Execution as ContainerActionExecutionData).Image);
|
||||||
Assert.Equal("main.sh", (definition.Data.Execution as ContainerActionExecutionData).EntryPoint);
|
Assert.Equal("main.sh", (definition.Data.Execution as ContainerActionExecutionData).EntryPoint);
|
||||||
Assert.Equal("cleanup.sh", (definition.Data.Execution as ContainerActionExecutionData).Cleanup);
|
Assert.Equal("cleanup.sh", (definition.Data.Execution as ContainerActionExecutionData).Post);
|
||||||
|
|
||||||
foreach (var arg in (definition.Data.Execution as ContainerActionExecutionData).Arguments)
|
foreach (var arg in (definition.Data.Execution as ContainerActionExecutionData).Arguments)
|
||||||
{
|
{
|
||||||
@@ -1542,7 +1693,7 @@ runs:
|
|||||||
|
|
||||||
Assert.NotNull((definition.Data.Execution as PluginActionExecutionData));
|
Assert.NotNull((definition.Data.Execution as PluginActionExecutionData));
|
||||||
Assert.Equal("plugin.class, plugin", (definition.Data.Execution as PluginActionExecutionData).Plugin);
|
Assert.Equal("plugin.class, plugin", (definition.Data.Execution as PluginActionExecutionData).Plugin);
|
||||||
Assert.Equal("plugin.cleanup, plugin", (definition.Data.Execution as PluginActionExecutionData).Cleanup);
|
Assert.Equal("plugin.cleanup, plugin", (definition.Data.Execution as PluginActionExecutionData).Post);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -1600,6 +1751,8 @@ runs:
|
|||||||
_ec = new Mock<IExecutionContext>();
|
_ec = new Mock<IExecutionContext>();
|
||||||
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
|
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
||||||
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Worker;
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Expressions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -63,6 +65,52 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void Load_ContainerAction_Dockerfile_Pre()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var actionManifest = new ActionManifestManager();
|
||||||
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction_init.yml"));
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
|
||||||
|
Assert.Equal("Hello World", result.Name);
|
||||||
|
Assert.Equal("Greet the world and record the time", result.Description);
|
||||||
|
Assert.Equal(2, result.Inputs.Count);
|
||||||
|
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
|
||||||
|
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
|
||||||
|
|
||||||
|
Assert.Equal(ActionExecutionType.Container, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var containerAction = result.Execution as ContainerActionExecutionData;
|
||||||
|
|
||||||
|
Assert.Equal("Dockerfile", containerAction.Image);
|
||||||
|
Assert.Equal("main.sh", containerAction.EntryPoint);
|
||||||
|
Assert.Equal("init.sh", containerAction.Pre);
|
||||||
|
Assert.Equal("success()", containerAction.InitCondition);
|
||||||
|
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
|
||||||
|
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
|
||||||
|
Assert.Equal("foo", containerAction.Environment[0].Value.ToString());
|
||||||
|
Assert.Equal("Url", containerAction.Environment[1].Key.ToString());
|
||||||
|
Assert.Equal("bar", containerAction.Environment[1].Value.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -95,7 +143,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
Assert.Equal("Dockerfile", containerAction.Image);
|
Assert.Equal("Dockerfile", containerAction.Image);
|
||||||
Assert.Equal("main.sh", containerAction.EntryPoint);
|
Assert.Equal("main.sh", containerAction.EntryPoint);
|
||||||
Assert.Equal("cleanup.sh", containerAction.Cleanup);
|
Assert.Equal("cleanup.sh", containerAction.Post);
|
||||||
Assert.Equal("failure()", containerAction.CleanupCondition);
|
Assert.Equal("failure()", containerAction.CleanupCondition);
|
||||||
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
|
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
|
||||||
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
|
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
|
||||||
@@ -109,6 +157,52 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void Load_ContainerAction_Dockerfile_Pre_DefaultCondition()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var actionManifest = new ActionManifestManager();
|
||||||
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction_init_default.yml"));
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
|
||||||
|
Assert.Equal("Hello World", result.Name);
|
||||||
|
Assert.Equal("Greet the world and record the time", result.Description);
|
||||||
|
Assert.Equal(2, result.Inputs.Count);
|
||||||
|
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
|
||||||
|
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
|
||||||
|
|
||||||
|
Assert.Equal(ActionExecutionType.Container, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var containerAction = result.Execution as ContainerActionExecutionData;
|
||||||
|
|
||||||
|
Assert.Equal("Dockerfile", containerAction.Image);
|
||||||
|
Assert.Equal("main.sh", containerAction.EntryPoint);
|
||||||
|
Assert.Equal("init.sh", containerAction.Pre);
|
||||||
|
Assert.Equal("always()", containerAction.InitCondition);
|
||||||
|
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
|
||||||
|
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
|
||||||
|
Assert.Equal("foo", containerAction.Environment[0].Value.ToString());
|
||||||
|
Assert.Equal("Url", containerAction.Environment[1].Key.ToString());
|
||||||
|
Assert.Equal("bar", containerAction.Environment[1].Value.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -141,7 +235,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
Assert.Equal("Dockerfile", containerAction.Image);
|
Assert.Equal("Dockerfile", containerAction.Image);
|
||||||
Assert.Equal("main.sh", containerAction.EntryPoint);
|
Assert.Equal("main.sh", containerAction.EntryPoint);
|
||||||
Assert.Equal("cleanup.sh", containerAction.Cleanup);
|
Assert.Equal("cleanup.sh", containerAction.Post);
|
||||||
Assert.Equal("always()", containerAction.CleanupCondition);
|
Assert.Equal("always()", containerAction.CleanupCondition);
|
||||||
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
|
Assert.Equal("bzz", containerAction.Arguments[0].ToString());
|
||||||
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
|
Assert.Equal("Token", containerAction.Environment[0].Key.ToString());
|
||||||
@@ -321,6 +415,94 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void Load_NodeAction_Pre()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var actionManifest = new ActionManifestManager();
|
||||||
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "nodeaction_init.yml"));
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal("Hello World", result.Name);
|
||||||
|
Assert.Equal("Greet the world and record the time", result.Description);
|
||||||
|
Assert.Equal(2, result.Inputs.Count);
|
||||||
|
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
|
||||||
|
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
|
||||||
|
Assert.Equal(1, result.Deprecated.Count);
|
||||||
|
|
||||||
|
Assert.True(result.Deprecated.ContainsKey("greeting"));
|
||||||
|
result.Deprecated.TryGetValue("greeting", out string value);
|
||||||
|
Assert.Equal("This property has been deprecated", value);
|
||||||
|
|
||||||
|
Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
||||||
|
|
||||||
|
Assert.Equal("main.js", nodeAction.Script);
|
||||||
|
Assert.Equal("init.js", nodeAction.Pre);
|
||||||
|
Assert.Equal("cancelled()", nodeAction.InitCondition);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void Load_NodeAction_Init_DefaultCondition()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var actionManifest = new ActionManifestManager();
|
||||||
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "nodeaction_init_default.yml"));
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal("Hello World", result.Name);
|
||||||
|
Assert.Equal("Greet the world and record the time", result.Description);
|
||||||
|
Assert.Equal(2, result.Inputs.Count);
|
||||||
|
Assert.Equal("greeting", result.Inputs[0].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("Hello", result.Inputs[0].Value.AssertString("value").Value);
|
||||||
|
Assert.Equal("entryPoint", result.Inputs[1].Key.AssertString("key").Value);
|
||||||
|
Assert.Equal("", result.Inputs[1].Value.AssertString("value").Value);
|
||||||
|
Assert.Equal(1, result.Deprecated.Count);
|
||||||
|
|
||||||
|
Assert.True(result.Deprecated.ContainsKey("greeting"));
|
||||||
|
result.Deprecated.TryGetValue("greeting", out string value);
|
||||||
|
Assert.Equal("This property has been deprecated", value);
|
||||||
|
|
||||||
|
Assert.Equal(ActionExecutionType.NodeJS, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
||||||
|
|
||||||
|
Assert.Equal("main.js", nodeAction.Script);
|
||||||
|
Assert.Equal("init.js", nodeAction.Pre);
|
||||||
|
Assert.Equal("always()", nodeAction.InitCondition);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -356,7 +538,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
||||||
|
|
||||||
Assert.Equal("main.js", nodeAction.Script);
|
Assert.Equal("main.js", nodeAction.Script);
|
||||||
Assert.Equal("cleanup.js", nodeAction.Cleanup);
|
Assert.Equal("cleanup.js", nodeAction.Post);
|
||||||
Assert.Equal("cancelled()", nodeAction.CleanupCondition);
|
Assert.Equal("cancelled()", nodeAction.CleanupCondition);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -400,7 +582,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
var nodeAction = result.Execution as NodeJSActionExecutionData;
|
||||||
|
|
||||||
Assert.Equal("main.js", nodeAction.Script);
|
Assert.Equal("main.js", nodeAction.Script);
|
||||||
Assert.Equal("cleanup.js", nodeAction.Cleanup);
|
Assert.Equal("cleanup.js", nodeAction.Post);
|
||||||
Assert.Equal("always()", nodeAction.CleanupCondition);
|
Assert.Equal("always()", nodeAction.CleanupCondition);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -533,26 +715,26 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var actionManifest = new ActionManifestManager();
|
var actionManifest = new ActionManifestManager();
|
||||||
actionManifest.Initialize(_hc);
|
actionManifest.Initialize(_hc);
|
||||||
|
|
||||||
var githubContext = new DictionaryContextData();
|
_ec.Object.ExpressionValues["github"] = new DictionaryContextData
|
||||||
githubContext.Add("ref", new StringContextData("refs/heads/master"));
|
{
|
||||||
|
{ "ref", new StringContextData("refs/heads/master") },
|
||||||
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
|
};
|
||||||
evaluateContext["github"] = githubContext;
|
_ec.Object.ExpressionValues["strategy"] = new DictionaryContextData();
|
||||||
evaluateContext["strategy"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["matrix"] = new DictionaryContextData();
|
||||||
evaluateContext["matrix"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["steps"] = new DictionaryContextData();
|
||||||
evaluateContext["steps"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["job"] = new DictionaryContextData();
|
||||||
evaluateContext["job"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["runner"] = new DictionaryContextData();
|
||||||
evaluateContext["runner"] = new DictionaryContextData();
|
_ec.Object.ExpressionValues["env"] = new DictionaryContextData();
|
||||||
evaluateContext["env"] = new DictionaryContextData();
|
_ec.Object.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>("hashFiles", 1, 255));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue"), evaluateContext);
|
var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue"));
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
Assert.Equal("defaultValue", result);
|
Assert.Equal("defaultValue", result);
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new BasicExpressionToken(null, null, null, "github.ref"), evaluateContext);
|
result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new BasicExpressionToken(null, null, null, "github.ref"));
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
Assert.Equal("refs/heads/master", result);
|
Assert.Equal("refs/heads/master", result);
|
||||||
@@ -575,6 +757,8 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec.Setup(x => x.WriteDebug).Returns(true);
|
_ec.Setup(x => x.WriteDebug).Returns(true);
|
||||||
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
|
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
@@ -322,6 +323,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
_ec = new Mock<IExecutionContext>();
|
_ec = new Mock<IExecutionContext>();
|
||||||
_ec.Setup(x => x.ExpressionValues).Returns(_context);
|
_ec.Setup(x => x.ExpressionValues).Returns(_context);
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.IntraActionState).Returns(new Dictionary<string, string>());
|
_ec.Setup(x => x.IntraActionState).Returns(new Dictionary<string, string>());
|
||||||
_ec.Setup(x => x.EnvironmentVariables).Returns(new Dictionary<string, string>());
|
_ec.Setup(x => x.EnvironmentVariables).Returns(new Dictionary<string, string>());
|
||||||
_ec.Setup(x => x.SetGitHubContext(It.IsAny<string>(), It.IsAny<string>()));
|
_ec.Setup(x => x.SetGitHubContext(It.IsAny<string>(), It.IsAny<string>()));
|
||||||
|
|||||||
@@ -199,20 +199,20 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
|
|
||||||
var postRunner1 = hc.CreateService<IActionRunner>();
|
var postRunner1 = hc.CreateService<IActionRunner>();
|
||||||
postRunner1.Action = new Pipelines.ActionStep() { Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
postRunner1.Action = new Pipelines.ActionStep() { Id = Guid.NewGuid(), Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
||||||
postRunner1.Stage = ActionRunStage.Post;
|
postRunner1.Stage = ActionRunStage.Post;
|
||||||
postRunner1.Condition = "always()";
|
postRunner1.Condition = "always()";
|
||||||
postRunner1.DisplayName = "post1";
|
postRunner1.DisplayName = "post1";
|
||||||
|
|
||||||
|
|
||||||
var postRunner2 = hc.CreateService<IActionRunner>();
|
var postRunner2 = hc.CreateService<IActionRunner>();
|
||||||
postRunner2.Action = new Pipelines.ActionStep() { Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
postRunner2.Action = new Pipelines.ActionStep() { Id = Guid.NewGuid(), Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
||||||
postRunner2.Stage = ActionRunStage.Post;
|
postRunner2.Stage = ActionRunStage.Post;
|
||||||
postRunner2.Condition = "always()";
|
postRunner2.Condition = "always()";
|
||||||
postRunner2.DisplayName = "post2";
|
postRunner2.DisplayName = "post2";
|
||||||
|
|
||||||
action1.RegisterPostJobStep("post1", postRunner1);
|
action1.RegisterPostJobStep(postRunner1);
|
||||||
action2.RegisterPostJobStep("post2", postRunner2);
|
action2.RegisterPostJobStep(postRunner2);
|
||||||
|
|
||||||
Assert.NotNull(jobContext.JobSteps);
|
Assert.NotNull(jobContext.JobSteps);
|
||||||
Assert.NotNull(jobContext.PostJobSteps);
|
Assert.NotNull(jobContext.PostJobSteps);
|
||||||
@@ -238,6 +238,91 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void RegisterPostJobAction_NotRegisterPostTwice()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
// Arrange: Create a job request message.
|
||||||
|
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
|
||||||
|
TimelineReference timeline = new TimelineReference();
|
||||||
|
Guid jobId = Guid.NewGuid();
|
||||||
|
string jobName = "some job name";
|
||||||
|
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||||
|
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||||
|
{
|
||||||
|
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||||
|
Id = "github",
|
||||||
|
Version = "sha1"
|
||||||
|
});
|
||||||
|
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||||
|
jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true";
|
||||||
|
|
||||||
|
// Arrange: Setup the paging logger.
|
||||||
|
var pagingLogger1 = new Mock<IPagingLogger>();
|
||||||
|
var pagingLogger2 = new Mock<IPagingLogger>();
|
||||||
|
var pagingLogger3 = new Mock<IPagingLogger>();
|
||||||
|
var pagingLogger4 = new Mock<IPagingLogger>();
|
||||||
|
var pagingLogger5 = new Mock<IPagingLogger>();
|
||||||
|
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
|
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
|
||||||
|
jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.IsAny<string>())).Callback((Guid id, string msg) => { hc.GetTrace().Info(msg); });
|
||||||
|
|
||||||
|
var actionRunner1 = new ActionRunner();
|
||||||
|
actionRunner1.Initialize(hc);
|
||||||
|
var actionRunner2 = new ActionRunner();
|
||||||
|
actionRunner2.Initialize(hc);
|
||||||
|
|
||||||
|
hc.EnqueueInstance(pagingLogger1.Object);
|
||||||
|
hc.EnqueueInstance(pagingLogger2.Object);
|
||||||
|
hc.EnqueueInstance(pagingLogger3.Object);
|
||||||
|
hc.EnqueueInstance(pagingLogger4.Object);
|
||||||
|
hc.EnqueueInstance(pagingLogger5.Object);
|
||||||
|
hc.EnqueueInstance(actionRunner1 as IActionRunner);
|
||||||
|
hc.EnqueueInstance(actionRunner2 as IActionRunner);
|
||||||
|
hc.SetSingleton(jobServerQueue.Object);
|
||||||
|
|
||||||
|
var jobContext = new Runner.Worker.ExecutionContext();
|
||||||
|
jobContext.Initialize(hc);
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
||||||
|
|
||||||
|
var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null);
|
||||||
|
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_1_main", "action_1_main", null, null);
|
||||||
|
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var postRunner1 = hc.CreateService<IActionRunner>();
|
||||||
|
postRunner1.Action = new Pipelines.ActionStep() { Id = actionId, Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
||||||
|
postRunner1.Stage = ActionRunStage.Post;
|
||||||
|
postRunner1.Condition = "always()";
|
||||||
|
postRunner1.DisplayName = "post1";
|
||||||
|
|
||||||
|
|
||||||
|
var postRunner2 = hc.CreateService<IActionRunner>();
|
||||||
|
postRunner2.Action = new Pipelines.ActionStep() { Id = actionId, Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } };
|
||||||
|
postRunner2.Stage = ActionRunStage.Post;
|
||||||
|
postRunner2.Condition = "always()";
|
||||||
|
postRunner2.DisplayName = "post2";
|
||||||
|
|
||||||
|
action1.RegisterPostJobStep(postRunner1);
|
||||||
|
action2.RegisterPostJobStep(postRunner2);
|
||||||
|
|
||||||
|
Assert.NotNull(jobContext.JobSteps);
|
||||||
|
Assert.NotNull(jobContext.PostJobSteps);
|
||||||
|
Assert.Equal(1, jobContext.PostJobSteps.Count);
|
||||||
|
var post1 = jobContext.PostJobSteps.Pop();
|
||||||
|
|
||||||
|
Assert.Equal("post1", (post1 as IActionRunner).Action.Name);
|
||||||
|
|
||||||
|
Assert.Equal(ActionRunStage.Post, (post1 as IActionRunner).Stage);
|
||||||
|
|
||||||
|
Assert.Equal("always()", (post1 as IActionRunner).Condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
var hc = new TestHostContext(this, testName);
|
var hc = new TestHostContext(this, testName);
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Worker;
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Expressions;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.Worker
|
namespace GitHub.Runner.Common.Tests.Worker.Expressions
|
||||||
{
|
{
|
||||||
public sealed class ExpressionManagerL0
|
public sealed class ConditionFunctionsL0
|
||||||
{
|
{
|
||||||
private Mock<IExecutionContext> _ec;
|
private TemplateContext _templateContext;
|
||||||
private ExpressionManager _expressionManager;
|
|
||||||
private DictionaryContextData _expressions;
|
|
||||||
private JobContext _jobContext;
|
private JobContext _jobContext;
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -38,7 +38,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobContext.Status = variableSet.JobStatus;
|
_jobContext.Status = variableSet.JobStatus;
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, "always()").Value;
|
bool actual = Evaluate("always()");
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
Assert.Equal(variableSet.Expected, actual);
|
||||||
@@ -68,7 +68,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobContext.Status = variableSet.JobStatus;
|
_jobContext.Status = variableSet.JobStatus;
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, "cancelled()").Value;
|
bool actual = Evaluate("cancelled()");
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
Assert.Equal(variableSet.Expected, actual);
|
||||||
@@ -97,7 +97,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobContext.Status = variableSet.JobStatus;
|
_jobContext.Status = variableSet.JobStatus;
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, "failure()").Value;
|
bool actual = Evaluate("failure()");
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
Assert.Equal(variableSet.Expected, actual);
|
||||||
@@ -126,37 +126,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobContext.Status = variableSet.JobStatus;
|
_jobContext.Status = variableSet.JobStatus;
|
||||||
|
|
||||||
// Act.
|
// Act.
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, "success()").Value;
|
bool actual = Evaluate("success()");
|
||||||
|
|
||||||
// Assert.
|
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Level", "L0")]
|
|
||||||
[Trait("Category", "Worker")]
|
|
||||||
public void ContextNamedValue()
|
|
||||||
{
|
|
||||||
using (TestHostContext hc = CreateTestContext())
|
|
||||||
{
|
|
||||||
// Arrange.
|
|
||||||
var variableSets = new[]
|
|
||||||
{
|
|
||||||
new { Condition = "github.ref == 'refs/heads/master'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true },
|
|
||||||
new { Condition = "github['ref'] == 'refs/heads/master'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true },
|
|
||||||
new { Condition = "github.nosuch || '' == ''", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true },
|
|
||||||
new { Condition = "github['ref'] == 'refs/heads/release'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = false },
|
|
||||||
new { Condition = "github.ref == 'refs/heads/release'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = false },
|
|
||||||
};
|
|
||||||
foreach (var variableSet in variableSets)
|
|
||||||
{
|
|
||||||
InitializeExecutionContext(hc);
|
|
||||||
_ec.Object.ExpressionValues["github"] = new GitHubContext() { { variableSet.VariableName, new StringContextData(variableSet.VariableValue) } };
|
|
||||||
|
|
||||||
// Act.
|
|
||||||
bool actual = _expressionManager.Evaluate(_ec.Object, variableSet.Condition).Value;
|
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(variableSet.Expected, actual);
|
Assert.Equal(variableSet.Expected, actual);
|
||||||
@@ -166,21 +136,34 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
var hc = new TestHostContext(this, testName);
|
return new TestHostContext(this, testName);
|
||||||
_expressionManager = new ExpressionManager();
|
|
||||||
_expressionManager.Initialize(hc);
|
|
||||||
return hc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeExecutionContext(TestHostContext hc)
|
private void InitializeExecutionContext(TestHostContext hc)
|
||||||
{
|
{
|
||||||
_expressions = new DictionaryContextData();
|
|
||||||
_jobContext = new JobContext();
|
_jobContext = new JobContext();
|
||||||
|
|
||||||
_ec = new Mock<IExecutionContext>();
|
var executionContext = new Mock<IExecutionContext>();
|
||||||
_ec.SetupAllProperties();
|
executionContext.SetupAllProperties();
|
||||||
_ec.Setup(x => x.ExpressionValues).Returns(_expressions);
|
executionContext.Setup(x => x.JobContext).Returns(_jobContext);
|
||||||
_ec.Setup(x => x.JobContext).Returns(_jobContext);
|
|
||||||
|
_templateContext = new TemplateContext();
|
||||||
|
_templateContext.State[nameof(IExecutionContext)] = executionContext.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Evaluate(string expression)
|
||||||
|
{
|
||||||
|
var parser = new ExpressionParser();
|
||||||
|
var functions = new IFunctionInfo[]
|
||||||
|
{
|
||||||
|
new FunctionInfo<AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0),
|
||||||
|
new FunctionInfo<CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0),
|
||||||
|
new FunctionInfo<FailureFunction>(PipelineTemplateConstants.Failure, 0, 0),
|
||||||
|
new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0),
|
||||||
|
};
|
||||||
|
var tree = parser.CreateTree(expression, null, null, functions);
|
||||||
|
var result = tree.Evaluate(null, null, _templateContext, null);
|
||||||
|
return result.IsTruthy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
private Mock<IJobServerQueue> _jobServerQueue;
|
private Mock<IJobServerQueue> _jobServerQueue;
|
||||||
private Mock<IConfigurationStore> _config;
|
private Mock<IConfigurationStore> _config;
|
||||||
private Mock<IPagingLogger> _logger;
|
private Mock<IPagingLogger> _logger;
|
||||||
private Mock<IExpressionManager> _express;
|
|
||||||
private Mock<IContainerOperationProvider> _containerProvider;
|
private Mock<IContainerOperationProvider> _containerProvider;
|
||||||
private Mock<IDiagnosticLogManager> _diagnosticLogManager;
|
private Mock<IDiagnosticLogManager> _diagnosticLogManager;
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_jobServerQueue = new Mock<IJobServerQueue>();
|
_jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
_config = new Mock<IConfigurationStore>();
|
_config = new Mock<IConfigurationStore>();
|
||||||
_logger = new Mock<IPagingLogger>();
|
_logger = new Mock<IPagingLogger>();
|
||||||
_express = new Mock<IExpressionManager>();
|
|
||||||
_containerProvider = new Mock<IContainerOperationProvider>();
|
_containerProvider = new Mock<IContainerOperationProvider>();
|
||||||
_diagnosticLogManager = new Mock<IDiagnosticLogManager>();
|
_diagnosticLogManager = new Mock<IDiagnosticLogManager>();
|
||||||
_directoryManager = new Mock<IPipelineDirectoryManager>();
|
_directoryManager = new Mock<IPipelineDirectoryManager>();
|
||||||
@@ -108,7 +106,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
hc.SetSingleton(_actionManager.Object);
|
hc.SetSingleton(_actionManager.Object);
|
||||||
hc.SetSingleton(_config.Object);
|
hc.SetSingleton(_config.Object);
|
||||||
hc.SetSingleton(_jobServerQueue.Object);
|
hc.SetSingleton(_jobServerQueue.Object);
|
||||||
hc.SetSingleton(_express.Object);
|
|
||||||
hc.SetSingleton(_containerProvider.Object);
|
hc.SetSingleton(_containerProvider.Object);
|
||||||
hc.SetSingleton(_directoryManager.Object);
|
hc.SetSingleton(_directoryManager.Object);
|
||||||
hc.SetSingleton(_diagnosticLogManager.Object);
|
hc.SetSingleton(_diagnosticLogManager.Object);
|
||||||
@@ -144,7 +141,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
jobExtension.Initialize(hc);
|
jobExtension.Initialize(hc);
|
||||||
|
|
||||||
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
|
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
|
||||||
.Returns(Task.FromResult(new List<JobExtensionRunner>()));
|
.Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>(), new Dictionary<Guid, IActionRunner>())));
|
||||||
|
|
||||||
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
|
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
|
||||||
|
|
||||||
@@ -179,7 +176,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
jobExtension.Initialize(hc);
|
jobExtension.Initialize(hc);
|
||||||
|
|
||||||
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
|
_actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.JobStep>>()))
|
||||||
.Returns(Task.FromResult(new List<JobExtensionRunner>() { new JobExtensionRunner(null, "", "prepare1", null), new JobExtensionRunner(null, "", "prepare2", null) }));
|
.Returns(Task.FromResult(new PrepareResult(new List<JobExtensionRunner>() { new JobExtensionRunner(null, "", "prepare1", null), new JobExtensionRunner(null, "", "prepare2", null) }, new Dictionary<Guid, IActionRunner>())));
|
||||||
|
|
||||||
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
|
List<IStep> result = await jobExtension.InitializeJob(_jobEc, _message);
|
||||||
|
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
_tokenSource = new CancellationTokenSource();
|
_tokenSource = new CancellationTokenSource();
|
||||||
var expressionManager = new ExpressionManager();
|
|
||||||
expressionManager.Initialize(hc);
|
|
||||||
hc.SetSingleton<IExpressionManager>(expressionManager);
|
|
||||||
|
|
||||||
_jobRunner = new JobRunner();
|
_jobRunner = new JobRunner();
|
||||||
_jobRunner.Initialize(hc);
|
_jobRunner.Initialize(hc);
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using System;
|
||||||
using GitHub.Runner.Worker;
|
|
||||||
using Moq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common.Tests.Worker
|
namespace GitHub.Runner.Common.Tests.Worker
|
||||||
{
|
{
|
||||||
@@ -27,9 +27,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
|
||||||
{
|
{
|
||||||
var hc = new TestHostContext(this, testName);
|
var hc = new TestHostContext(this, testName);
|
||||||
var expressionManager = new ExpressionManager();
|
|
||||||
expressionManager.Initialize(hc);
|
|
||||||
hc.SetSingleton<IExpressionManager>(expressionManager);
|
|
||||||
Dictionary<string, VariableValue> variablesToCopy = new Dictionary<string, VariableValue>();
|
Dictionary<string, VariableValue> variablesToCopy = new Dictionary<string, VariableValue>();
|
||||||
_variables = new Variables(
|
_variables = new Variables(
|
||||||
hostContext: hc,
|
hostContext: hc,
|
||||||
@@ -49,6 +46,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_contexts["runner"] = new DictionaryContextData();
|
_contexts["runner"] = new DictionaryContextData();
|
||||||
_contexts["job"] = _jobContext;
|
_contexts["job"] = _jobContext;
|
||||||
_ec.Setup(x => x.ExpressionValues).Returns(_contexts);
|
_ec.Setup(x => x.ExpressionValues).Returns(_contexts);
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.JobContext).Returns(_jobContext);
|
_ec.Setup(x => x.JobContext).Returns(_jobContext);
|
||||||
|
|
||||||
_stepContext = new StepsContext();
|
_stepContext = new StepsContext();
|
||||||
@@ -383,16 +381,11 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
using (TestHostContext hc = CreateTestContext())
|
using (TestHostContext hc = CreateTestContext())
|
||||||
{
|
{
|
||||||
var expressionManager = new Mock<IExpressionManager>();
|
|
||||||
expressionManager.Object.Initialize(hc);
|
|
||||||
hc.SetSingleton<IExpressionManager>(expressionManager.Object);
|
|
||||||
expressionManager.Setup(x => x.Evaluate(It.IsAny<IExecutionContext>(), It.IsAny<string>(), It.IsAny<bool>())).Throws(new Exception());
|
|
||||||
|
|
||||||
// Arrange.
|
// Arrange.
|
||||||
var variableSets = new[]
|
var variableSets = new[]
|
||||||
{
|
{
|
||||||
new[] { CreateStep(hc, TaskResult.Succeeded, "success()") },
|
new[] { CreateStep(hc, TaskResult.Succeeded, "fromJson('not json')") },
|
||||||
new[] { CreateStep(hc, TaskResult.Succeeded, "success()") },
|
new[] { CreateStep(hc, TaskResult.Succeeded, "fromJson('not json')") },
|
||||||
};
|
};
|
||||||
foreach (var variableSet in variableSets)
|
foreach (var variableSet in variableSets)
|
||||||
{
|
{
|
||||||
@@ -610,6 +603,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
stepContext.Setup(x => x.Variables).Returns(_variables);
|
stepContext.Setup(x => x.Variables).Returns(_variables);
|
||||||
stepContext.Setup(x => x.EnvironmentVariables).Returns(_env);
|
stepContext.Setup(x => x.EnvironmentVariables).Returns(_env);
|
||||||
stepContext.Setup(x => x.ExpressionValues).Returns(_contexts);
|
stepContext.Setup(x => x.ExpressionValues).Returns(_contexts);
|
||||||
|
stepContext.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
stepContext.Setup(x => x.JobContext).Returns(_jobContext);
|
stepContext.Setup(x => x.JobContext).Returns(_jobContext);
|
||||||
stepContext.Setup(x => x.StepsContext).Returns(_stepContext);
|
stepContext.Setup(x => x.StepsContext).Returns(_stepContext);
|
||||||
stepContext.Setup(x => x.ContextName).Returns(step.Object.Action.ContextName);
|
stepContext.Setup(x => x.ContextName).Returns(step.Object.Action.ContextName);
|
||||||
|
|||||||
27
src/Test/TestData/dockerfileaction_init.yml
Normal file
27
src/Test/TestData/dockerfileaction_init.yml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: 'Hello World'
|
||||||
|
description: 'Greet the world and record the time'
|
||||||
|
author: 'Test Corporation'
|
||||||
|
inputs:
|
||||||
|
greeting: # id of input
|
||||||
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
|
required: true
|
||||||
|
default: 'Hello'
|
||||||
|
entryPoint: # id of input
|
||||||
|
description: 'optional docker entrypoint overwrite.'
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
time: # id of output
|
||||||
|
description: 'The time we did the greeting'
|
||||||
|
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||||
|
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'Dockerfile'
|
||||||
|
args:
|
||||||
|
- 'bzz'
|
||||||
|
entrypoint: 'main.sh'
|
||||||
|
env:
|
||||||
|
Token: foo
|
||||||
|
Url: bar
|
||||||
|
pre-entrypoint: 'init.sh'
|
||||||
|
pre-if: 'success()'
|
||||||
26
src/Test/TestData/dockerfileaction_init_default.yml
Normal file
26
src/Test/TestData/dockerfileaction_init_default.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: 'Hello World'
|
||||||
|
description: 'Greet the world and record the time'
|
||||||
|
author: 'Test Corporation'
|
||||||
|
inputs:
|
||||||
|
greeting: # id of input
|
||||||
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
|
required: true
|
||||||
|
default: 'Hello'
|
||||||
|
entryPoint: # id of input
|
||||||
|
description: 'optional docker entrypoint overwrite.'
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
time: # id of output
|
||||||
|
description: 'The time we did the greeting'
|
||||||
|
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||||
|
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||||
|
runs:
|
||||||
|
using: 'docker'
|
||||||
|
image: 'Dockerfile'
|
||||||
|
args:
|
||||||
|
- 'bzz'
|
||||||
|
entrypoint: 'main.sh'
|
||||||
|
env:
|
||||||
|
Token: foo
|
||||||
|
Url: bar
|
||||||
|
pre-entrypoint: 'init.sh'
|
||||||
22
src/Test/TestData/nodeaction_init.yml
Normal file
22
src/Test/TestData/nodeaction_init.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: 'Hello World'
|
||||||
|
description: 'Greet the world and record the time'
|
||||||
|
author: 'Test Corporation'
|
||||||
|
inputs:
|
||||||
|
greeting: # id of input
|
||||||
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
|
required: true
|
||||||
|
default: 'Hello'
|
||||||
|
deprecationMessage: 'This property has been deprecated'
|
||||||
|
entryPoint: # id of input
|
||||||
|
description: 'optional docker entrypoint overwrite.'
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
time: # id of output
|
||||||
|
description: 'The time we did the greeting'
|
||||||
|
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||||
|
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||||
|
runs:
|
||||||
|
using: 'node12'
|
||||||
|
main: 'main.js'
|
||||||
|
pre: 'init.js'
|
||||||
|
pre-if: 'cancelled()'
|
||||||
21
src/Test/TestData/nodeaction_init_default.yml
Normal file
21
src/Test/TestData/nodeaction_init_default.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: 'Hello World'
|
||||||
|
description: 'Greet the world and record the time'
|
||||||
|
author: 'Test Corporation'
|
||||||
|
inputs:
|
||||||
|
greeting: # id of input
|
||||||
|
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
|
||||||
|
required: true
|
||||||
|
default: 'Hello'
|
||||||
|
deprecationMessage: 'This property has been deprecated'
|
||||||
|
entryPoint: # id of input
|
||||||
|
description: 'optional docker entrypoint overwrite.'
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
time: # id of output
|
||||||
|
description: 'The time we did the greeting'
|
||||||
|
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
|
||||||
|
color: 'green' # optional, decorates the entry in the GitHub Marketplace
|
||||||
|
runs:
|
||||||
|
using: 'node12'
|
||||||
|
main: 'main.js'
|
||||||
|
pre: 'init.js'
|
||||||
@@ -1 +1 @@
|
|||||||
2.165.2
|
2.169.0
|
||||||
|
|||||||
Reference in New Issue
Block a user