mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
95 Commits
users/tihu
...
users/logo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ceb092f7f3 | ||
|
|
e728b8594d | ||
|
|
de4490d06d | ||
|
|
2e800f857e | ||
|
|
312c7668a8 | ||
|
|
eaf39bb058 | ||
|
|
5815819f24 | ||
|
|
1aea046932 | ||
|
|
eda463601c | ||
|
|
f994ae0542 | ||
|
|
3c5aef791c | ||
|
|
c4626d0c3a | ||
|
|
416a7ac4b8 | ||
|
|
11435857e4 | ||
|
|
6f260012a3 | ||
|
|
4fc87ddfc6 | ||
|
|
b45c1b9440 | ||
|
|
73307c0a30 | ||
|
|
cd8e4ddba1 | ||
|
|
abf59bdcb6 | ||
|
|
09cf59c1e0 | ||
|
|
7a65236022 | ||
|
|
462b5117c8 | ||
|
|
6922f3cb86 | ||
|
|
911135e66c | ||
|
|
01c9a8a8af | ||
|
|
33d2d2c328 | ||
|
|
a246b3b29d | ||
|
|
c7768d4a7b | ||
|
|
70729fb3c4 | ||
|
|
1470a3b6e2 | ||
|
|
2fadf430e4 | ||
|
|
f798f5606b | ||
|
|
3f7a01af93 | ||
|
|
d5c54f9819 | ||
|
|
9f78ad3b34 | ||
|
|
97883c8cd5 | ||
|
|
c5fa9fb062 | ||
|
|
b2dcdc21dc | ||
|
|
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 | ||
|
|
88875ca1b0 | ||
|
|
a5eb8cb5c4 | ||
|
|
41f4ca3414 | ||
|
|
aa9f5bf070 | ||
|
|
2d6042421f | ||
|
|
c8890d0f3f | ||
|
|
53fb6297cb | ||
|
|
f9b5d626c5 | ||
|
|
d34afb54b1 | ||
|
|
e291ebc58a | ||
|
|
6bec1e3bb8 | ||
|
|
0cba42590f | ||
|
|
94e7560ccd | ||
|
|
d80ab095a5 | ||
|
|
2efd6f70e2 | ||
|
|
a6f144b014 | ||
|
|
5294a3ee06 | ||
|
|
745b90a8b2 | ||
|
|
0db908da8d | ||
|
|
68de3a94be | ||
|
|
a0a590fb48 | ||
|
|
87a232c477 | ||
|
|
a3c2479a29 | ||
|
|
c45aebc9ab | ||
|
|
b676ab3d33 | ||
|
|
0a6bac355d | ||
|
|
eb78d19b17 | ||
|
|
17970ad1f9 | ||
|
|
2e0e8eb822 | ||
|
|
2a506cc556 |
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -43,14 +43,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
# Set Path workaround for https://github.com/actions/virtual-environments/issues/263
|
|
||||||
- run: |
|
|
||||||
echo "::add-path::C:\Program Files\Git\mingw64\bin"
|
|
||||||
echo "::add-path::C:\Program Files\Git\usr\bin"
|
|
||||||
echo "::add-path::C:\Program Files\Git\bin"
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
name: "Temp step to Set Path for Windows"
|
|
||||||
|
|
||||||
# Build runner layout
|
# Build runner layout
|
||||||
- name: Build & Layout Release
|
- name: Build & Layout Release
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
35
.github/workflows/codeql.yml
vendored
Normal file
35
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: "Code Scanning - Action"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CodeQL-Build:
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
|
||||||
|
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
# Override language selection by uncommenting this and choosing your languages
|
||||||
|
# with:
|
||||||
|
# languages: go, javascript, csharp, python, cpp, java
|
||||||
|
|
||||||
|
- name: Manual build
|
||||||
|
run : |
|
||||||
|
./dev.sh layout Release linux-x64
|
||||||
|
working-directory: src
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
62
docs/adrs/0274-step-outcome-and-conclusion.md
Normal file
62
docs/adrs/0274-step-outcome-and-conclusion.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# ADR 0274: Step outcome and conclusion
|
||||||
|
|
||||||
|
**Date**: 2020-01-13
|
||||||
|
|
||||||
|
**Status**: Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
This ADR proposes adding `steps.<id>.outcome` and `steps.<id>.conclusion` to the steps context.
|
||||||
|
|
||||||
|
This allows downstream a step to run based on whether a previous step succeeded or failed.
|
||||||
|
|
||||||
|
Reminder, currently the steps contains `steps.<id>.outputs`.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
For steps that have completed, populate `steps.<id>.outcome` and `steps.<id>.conclusion` with one of the following values:
|
||||||
|
|
||||||
|
- `success`
|
||||||
|
- `failure`
|
||||||
|
- `cancelled`
|
||||||
|
- `skipped`
|
||||||
|
|
||||||
|
When a continue-on-error step fails, the outcome will be `failure` even though the final conclusion is `success`.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- id: experimental
|
||||||
|
continue-on-error: true
|
||||||
|
run: ./build.sh experimental
|
||||||
|
|
||||||
|
- if: ${{ steps.experimental.outcome == 'success' }}
|
||||||
|
run: ./publish.sh experimental
|
||||||
|
```
|
||||||
|
|
||||||
|
### Terminology
|
||||||
|
|
||||||
|
The runs API uses the term `conclusion`.
|
||||||
|
|
||||||
|
Therefore we use a different term `outcome` for the value prior to continue-on-error.
|
||||||
|
|
||||||
|
The following is a snippet from the runs API response payload:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"name": "Set up job",
|
||||||
|
"status": "completed",
|
||||||
|
"conclusion": "success",
|
||||||
|
"number": 1,
|
||||||
|
"started_at": "2020-01-09T11:06:16.000-05:00",
|
||||||
|
"completed_at": "2020-01-09T11:06:18.000-05:00"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- Update runner
|
||||||
|
- Update [docs](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#steps-context)
|
||||||
@@ -15,6 +15,7 @@ User wants to reference workflow variables defined in workflow yaml file for act
|
|||||||
Runner will create and populate the `env` context for every job execution using following logic:
|
Runner will create and populate the `env` context for every job execution using following logic:
|
||||||
1. On job start, create `env` context with any environment variables in the job message, these are env defined in customer's YAML file's job/workflow level `env` section.
|
1. On job start, create `env` context with any environment variables in the job message, these are env defined in customer's YAML file's job/workflow level `env` section.
|
||||||
2. Update `env` context when customer use `::set-env::` to set env at the runner level.
|
2. Update `env` context when customer use `::set-env::` to set env at the runner level.
|
||||||
|
3. Update `env` context with step's `env` block before each step runs.
|
||||||
|
|
||||||
The `env` context is only available in the runner, customer can't use the `env` context in any server evaluation part, just like the `runner` context
|
The `env` context is only available in the runner, customer can't use the `env` context in any server evaluation part, just like the `runner` context
|
||||||
|
|
||||||
@@ -22,17 +23,25 @@ Example yaml:
|
|||||||
```yaml
|
```yaml
|
||||||
|
|
||||||
env:
|
env:
|
||||||
env1: 100
|
env1: 10
|
||||||
|
env2: 20
|
||||||
|
env3: 30
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
env:
|
env:
|
||||||
|
env1: 100
|
||||||
env2: 200
|
env2: 200
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
echo ${{ env.env1 }}
|
echo ${{ env.env1 }} // 1000
|
||||||
if: env.env2 == 200
|
echo $env1 // 1000
|
||||||
name: ${{ env.env1 }}_${{ env.env2 }}
|
echo $env2 // 200
|
||||||
|
echo $env3 // 30
|
||||||
|
if: env.env2 == 200 // true
|
||||||
|
name: ${{ env.env1 }}_${{ env.env2 }} //1000_200
|
||||||
|
env:
|
||||||
|
env1: 1000
|
||||||
```
|
```
|
||||||
|
|
||||||
### Don't populate the `env` context with environment variables from runner machine.
|
### Don't populate the `env` context with environment variables from runner machine.
|
||||||
@@ -48,4 +57,4 @@ build:
|
|||||||
- uses: docker://ubuntu:18.04
|
- uses: docker://ubuntu:18.04
|
||||||
with:
|
with:
|
||||||
args: echo ${{env.USER}} <- what should customer expect this output? runner/root
|
args: echo ${{env.USER}} <- what should customer expect this output? runner/root
|
||||||
```
|
```
|
||||||
|
|||||||
48
docs/adrs/0297-base64-masking-trailing-characters.md
Normal file
48
docs/adrs/0297-base64-masking-trailing-characters.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# ADR 0297: Base64 Masking Trailing Characters
|
||||||
|
|
||||||
|
**Date** 2020-01-21
|
||||||
|
|
||||||
|
**Status** Proposed
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The Runner registers a number of Value Encoders, which mask various encodings of a provided secret. Currently, we register a 3 base64 Encoders:
|
||||||
|
- The base64 encoded secret
|
||||||
|
- The secret with the first character removed then base64 encoded
|
||||||
|
- The secret with the first two characters removed then base64 encoded
|
||||||
|
|
||||||
|
This gives us good coverage across the board for secrets and secrets with a prefix (i.e. `base64($user:$pass)`).
|
||||||
|
|
||||||
|
However, we don't have great coverage for cases where the secret has a string appended to it before it is base64 encoded (i.e.: `base64($pass\n))`).
|
||||||
|
|
||||||
|
Most notably we've seen this as a result of user error where a user accidentially appends a newline or space character before encoding their secret in base64.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### Trim end characters
|
||||||
|
|
||||||
|
We are going to modify all existing base64 encoders to trim information before registering as a secret.
|
||||||
|
We will trim:
|
||||||
|
- `=` from the end of all base64 strings. This is a padding character that contains no information.
|
||||||
|
- Based on the number of `=`'s at the end of a base64 string, a malicious user could predict the length of the original secret modulo 3.
|
||||||
|
- If a user saw `***==`, they would know the secret could be 1,4,7,10... characters.
|
||||||
|
- If a string contains `=` we will also trim the last non-padding character from the base64 secret.
|
||||||
|
- This character can change if a string is appended to the secret before the encoding.
|
||||||
|
|
||||||
|
|
||||||
|
### Register a fourth encoder
|
||||||
|
|
||||||
|
We will also add back in the original base64 encoded secret encoder for four total encoders:
|
||||||
|
- The base64 encoded secret
|
||||||
|
- The base64 encoded secret trimmed
|
||||||
|
- The secret with the first character removed then base64 encoded and trimmed
|
||||||
|
- The secret with the first two characters removed then base64 encoded and trimmed
|
||||||
|
|
||||||
|
This allows us to fully cover the most common scenario where a user base64 encodes their secret and expects the entire thing to be masked.
|
||||||
|
This will result in us only revealing length or bit information when a prefix or suffix is added to a secret before encoding.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- In the case where a secret has a prefix or suffix added before base64 encoding, we may now reveal up to 20 bits of information and the length of the original string modulo 3, rather then the original 16 bits and no length information
|
||||||
|
- Secrets with a suffix appended before encoding will now be masked across the board. Previously it was only masked if it was a multiple of 3 characters
|
||||||
|
- Performance will suffer in a neglible way
|
||||||
35
docs/adrs/0354-runner-machine-info.md
Normal file
35
docs/adrs/0354-runner-machine-info.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# ADR 354: Expose runner machine info
|
||||||
|
|
||||||
|
**Date**: 2020-03-02
|
||||||
|
|
||||||
|
**Status**: Pending
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- Provide a mechanism in the runner to include extra information in `Set up job` step's log.
|
||||||
|
Ex: Include OS/Software info from Hosted image.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
The runner will look for a file `.setup_info` under the runner's root directory, The file can be a JSON with a simple schema.
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"group": "OS Detail",
|
||||||
|
"detail": "........"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Software Detail",
|
||||||
|
"detail": "........"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
The runner will use `##[group]` and `##[endgroup]` to fold all detail info into an expandable group.
|
||||||
|
|
||||||
|
Both [virtual-environments](https://github.com/actions/virtual-environments) and self-hosted runners can use this mechanism to add extra logging info to the `Set up job` step's log.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
1. Change the runner to best effort read/parse `.extra_setup_info` file under runner root directory.
|
||||||
|
2. [virtual-environments](https://github.com/actions/virtual-environments) generate the file during image generation.
|
||||||
|
3. Change MMS provisioner to properly copy the file to runner root directory at runtime.
|
||||||
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
|
||||||
|
```
|
||||||
@@ -23,7 +23,7 @@ An ADR is an Architectural Decision Record. This allows consensus on the direct
|
|||||||
|
|
||||||
### Required Dev Dependencies
|
### Required Dev Dependencies
|
||||||
|
|
||||||
 Git for Windows [Install Here](https://git-scm.com/downloads) (needed for dev sh script)
|
  Git for Windows and Linux [Install Here](https://git-scm.com/downloads) (needed for dev sh script)
|
||||||
|
|
||||||
### To Build, Test, Layout
|
### To Build, Test, Layout
|
||||||
|
|
||||||
@@ -43,8 +43,9 @@ Sample developer flow:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/actions/runner
|
git clone https://github.com/actions/runner
|
||||||
|
cd runner
|
||||||
cd ./src
|
cd ./src
|
||||||
./dev.(sh/cmd) layout # the runner that build from source is in {root}/_layout
|
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
|
||||||
<make code changes>
|
<make code changes>
|
||||||
./dev.(sh/cmd) build # {root}/_layout will get updated
|
./dev.(sh/cmd) build # {root}/_layout will get updated
|
||||||
./dev.(sh/cmd) test # run all unit tests before git commit/push
|
./dev.(sh/cmd) test # run all unit tests before git commit/push
|
||||||
@@ -53,7 +54,7 @@ cd ./src
|
|||||||
### Editors
|
### Editors
|
||||||
|
|
||||||
[Using Visual Studio Code](https://code.visualstudio.com/)
|
[Using Visual Studio Code](https://code.visualstudio.com/)
|
||||||
[Using Visual Studio 2019](https://www.visualstudio.com/vs/)
|
[Using Visual Studio](https://code.visualstudio.com/docs)
|
||||||
|
|
||||||
### Styling
|
### Styling
|
||||||
|
|
||||||
|
|||||||
61
docs/design/auth.md
Normal file
61
docs/design/auth.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Runner Authentication and Authorization
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
- Support runner installs in untrusted domains.
|
||||||
|
- The account that configures or runs the runner process is not relevant for accessing GitHub resources.
|
||||||
|
- Accessing GitHub resources is done with a per-job token which expires when job completes.
|
||||||
|
- The token is granted to trusted parts of the system including the runner, actions and script steps specified by the workflow author as trusted.
|
||||||
|
- All OAuth tokens that come from the Token Service that the runner uses to access Actions Service resources are the same. It's just the scope and expiration of the token that may vary.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Configuring a self-hosted runner is [covered here in the documentation](https://help.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners).
|
||||||
|
|
||||||
|
Configuration is done with the user being authenticated via a time-limited, GitHub runner registration token.
|
||||||
|
|
||||||
|
*Your credentials are never used for registering the runner with the service.*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
During configuration, an RSA public/private key pair is created, the private key is stored in file on disk. On Windows, the content is protected with DPAPI (machine level encrypted - runner only valid on that machine) and on Linux/OSX with `chmod` permissions.
|
||||||
|
|
||||||
|
Using your credentials, the runner is registered with the service by sending the public key to the service which adds that runner to the pool and stores the public key, the Token Service will generate a `clientId` associated with the public key.
|
||||||
|
|
||||||
|
## Start and Listen
|
||||||
|
|
||||||
|
After configuring the runner, the runner can be started interactively (`./run.cmd` or `./run.sh`) or as a service.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
On start, the runner listener process loads the RSA private key (on Windows decrypting with machine key DPAPI), and asks the Token Service for an OAuth token which is signed with the RSA private key.
|
||||||
|
The server then responds with an OAuth token that grants permission to access the message queue (HTTP long poll), allowing the runner to acquire the messages it will eventually run.
|
||||||
|
|
||||||
|
## Run a workflow
|
||||||
|
|
||||||
|
When a workflow is run, its labels are evaluated, it is matched to a runner and a message is placed in a queue of messages for that runner.
|
||||||
|
The runner then starts listening for jobs via the message queue HTTP long poll.
|
||||||
|
The message is encrypted with the runner's public key, stored during runner configuration.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A workflow is queued as a result of a triggered [event](https://help.github.com/en/actions/reference/events-that-trigger-workflows). Workflows can be scheduled to [run at specific UTC times](https://help.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events-schedule) using POSIX `cron` syntax.
|
||||||
|
An [OAuth token](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) is generated, granting limited access to the host in Actions Service associated with the github.com repository/organization.
|
||||||
|
The lifetime of the OAuth token is the lifetime of the run or at most the [job timeout (default: 6 hours)](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes), plus 10 additional minutes.
|
||||||
|
|
||||||
|
## Accessing GitHub resources
|
||||||
|
|
||||||
|
The job message sent to the runner contains the OAuth token to talk back to the Actions Service.
|
||||||
|
The runner listener parent process will spawn a runner worker process for that job and send it the job message over IPC.
|
||||||
|
The token is never persisted.
|
||||||
|
|
||||||
|
Each action is run as a unique subprocess.
|
||||||
|
The encrypted access token will be provided as an environment variable in each action subprocess.
|
||||||
|
The token is registered with the runner as a secret and scrubbed from the logs as they are written.
|
||||||
|
|
||||||
|
Authentication in a workflow run to github.com can be accomplished by using the [`GITHUB_TOKEN`](https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#about-the-github_token-secret)) secret. This token expires after 60 minutes. Please note that this token is different from the OAuth token that the runner uses to talk to the Actions Service.
|
||||||
|
|
||||||
|
## Hosted runner authentication
|
||||||
|
|
||||||
|
Hosted runner authentication differs from self-hosted authentication in that runners do not undergo a registration process, but instead, the hosted runners get the OAuth token directly by reading the `.credentials` file. The scope of this particular token is limited for a given workflow job execution, and the token is revoked as soon as the job is finished.
|
||||||
|
|
||||||
|

|
||||||
BIN
docs/res/hosted-config-start.png
Normal file
BIN
docs/res/hosted-config-start.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
52
docs/res/runner-auth-diags.txt
Normal file
52
docs/res/runner-auth-diags.txt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Markup used to generate the runner auth diagrams: https://websequencediagrams.com
|
||||||
|
|
||||||
|
title Runner Configuration (self-hosted only)
|
||||||
|
|
||||||
|
note left of Runner: GitHub repo URL as input
|
||||||
|
Runner->github.com: Retrieve Actions Service access using runner registration token
|
||||||
|
github.com->Runner: Access token for Actions Service
|
||||||
|
note left of Runner: Generate RSA key pair
|
||||||
|
note left of Runner: Store encrypted RSA private key on disk
|
||||||
|
Runner->Actions Service: Register runner using Actions Service access token
|
||||||
|
note right of Runner: Runner name, RSA public key sent
|
||||||
|
note right of Actions Service: Public key stored
|
||||||
|
Actions Service->Token Service: Register runner as an app along with the RSA public key
|
||||||
|
note right of Token Service: Public key stored
|
||||||
|
Token Service->Actions Service: Client Id for the runner application
|
||||||
|
Actions Service->Runner: Client Id and Token Endpoint URL
|
||||||
|
note left of Runner: Store runner configuration info into .runner file
|
||||||
|
note left of Runner: Store Token registration info into .credentials file
|
||||||
|
|
||||||
|
title Runner Start and Running (self-hosted only)
|
||||||
|
|
||||||
|
Runner.Listener->Runner.Listener: Start
|
||||||
|
note left of Runner.Listener: Load config info from .runner
|
||||||
|
note left of Runner.Listener: Load token registration from .credentials
|
||||||
|
Runner.Listener->Token Service: Exchange OAuth token (happens every 50 mins)
|
||||||
|
note right of Runner.Listener: Construct JWT token, use Client Id signed by RSA private key
|
||||||
|
note left of Actions Service: Find corresponding RSA public key, use Client Id\nVerify JWT token's signature
|
||||||
|
Token Service->Runner.Listener: OAuth token with limited permission and valid for 50 mins
|
||||||
|
Runner.Listener->Actions Service: Connect to Actions Service with OAuth token
|
||||||
|
Actions Service->Runner.Listener: Workflow job
|
||||||
|
|
||||||
|
title Running workflow
|
||||||
|
|
||||||
|
Runner.Listener->Service (Message Queue): Get message
|
||||||
|
note right of Runner.Listener: Authenticate with exchanged OAuth token
|
||||||
|
Event->Actions Service: Queue workflow
|
||||||
|
Actions Service->Actions Service: Generate OAuth token per job
|
||||||
|
Actions Service->Actions Service: Build job message with the OAuth token
|
||||||
|
Actions Service->Actions Service: Encrypt job message with the target runner's public key
|
||||||
|
Actions Service->Service (Message Queue): Send encrypted job message to runner
|
||||||
|
Service (Message Queue)->Runner.Listener: Send job
|
||||||
|
note right of Runner.Listener: Decrypt message with runner's private key
|
||||||
|
Runner.Listener->Runner.Worker: Create worker process per job and run the job
|
||||||
|
|
||||||
|
title Runner Configuration, Start and Running (hosted only)
|
||||||
|
|
||||||
|
Machine Management Service->Runner.Listener: Construct .runner configuration file, store token in .credentials
|
||||||
|
Runner.Listener->Runner.Listener: Start
|
||||||
|
note left of Runner.Listener: Load config info from .runner
|
||||||
|
note left of Runner.Listener: Load OAuth token from .credentials
|
||||||
|
Runner.Listener->Actions Service: Connect to Actions Service with OAuth token in .credentials
|
||||||
|
Actions Service->Runner.Listener: Workflow job
|
||||||
BIN
docs/res/self-hosted-config.png
Normal file
BIN
docs/res/self-hosted-config.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
BIN
docs/res/self-hosted-start.png
Normal file
BIN
docs/res/self-hosted-start.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/res/workflow-run.png
Normal file
BIN
docs/res/workflow-run.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -40,7 +40,7 @@ Debian based OS (Debian, Ubuntu, Linux Mint)
|
|||||||
- libssl1.1, libssl1.0.2 or libssl1.0.0
|
- libssl1.1, libssl1.0.2 or libssl1.0.0
|
||||||
- libicu63, libicu60, libicu57 or libicu55
|
- libicu63, libicu60, libicu57 or libicu55
|
||||||
|
|
||||||
Fedora based OS (Fedora, Redhat, Centos, Oracle Linux 7)
|
Fedora based OS (Fedora, Red Hat Enterprise Linux, CentOS, Oracle Linux 7)
|
||||||
|
|
||||||
- lttng-ust
|
- lttng-ust
|
||||||
- openssl-libs
|
- openssl-libs
|
||||||
|
|||||||
@@ -1,68 +1,70 @@
|
|||||||
## Features
|
## Features
|
||||||
- Remove runner flow: Change from PAT to "deletion token" in prompt (#225)
|
- N/A
|
||||||
- Expose github.run_id and github.run_number to action runtime env. (#224)
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- Clean up error messages for container scenarios (#221)
|
- Handle `jq` returns "null" if the field does not exist in create-latest-svc.sh (#478)
|
||||||
- Pick shell from prependpath (#231)
|
- Switch GITHUB_URL to GITHUB_SERVER_URL (#482)
|
||||||
|
- Fix problem matcher for GHES (#488)
|
||||||
|
- Fix container action inputs validation warning (#490)
|
||||||
|
- Fix post step display name (#490)
|
||||||
|
- Fix worker crash due to exception from evaluating step.env (#490)
|
||||||
## Misc
|
## Misc
|
||||||
- Runner code cleanup (#218 #227, #228, #229, #230)
|
- N/A
|
||||||
- Consume dotnet core 3.1 in runner. (#213)
|
|
||||||
|
|
||||||
## Windows x64
|
## Windows x64
|
||||||
We recommend configuring the runner under "<DRIVE>:\actions-runner". This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows
|
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||||
```
|
|
||||||
// Create a folder under the drive root
|
The following snipped needs to be run on `powershell`:
|
||||||
|
``` powershell
|
||||||
|
# Create a folder under the drive root
|
||||||
mkdir \actions-runner ; cd \actions-runner
|
mkdir \actions-runner ; cd \actions-runner
|
||||||
// Download the latest runner package
|
# Download the latest runner package
|
||||||
Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip -OutFile actions-runner-win-x64-<RUNNER_VERSION>.zip
|
Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip -OutFile actions-runner-win-x64-<RUNNER_VERSION>.zip
|
||||||
// Extract the installer
|
# Extract the installer
|
||||||
Add-Type -AssemblyName System.IO.Compression.FileSystem ;
|
Add-Type -AssemblyName System.IO.Compression.FileSystem ;
|
||||||
[System.IO.Compression.ZipFile]::ExtractToDirectory("$HOME\Downloads\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
|
[System.IO.Compression.ZipFile]::ExtractToDirectory("$PWD\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
|
||||||
```
|
```
|
||||||
|
|
||||||
## OSX
|
## OSX
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
// Create a folder
|
# Create a folder
|
||||||
mkdir actions-runner && cd actions-runner
|
mkdir actions-runner && cd actions-runner
|
||||||
// Download the latest runner package
|
# Download the latest runner package
|
||||||
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
|
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
|
||||||
// Extract the installer
|
# Extract the installer
|
||||||
tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
|
tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
## Linux x64
|
## Linux x64
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
// Create a folder
|
# Create a folder
|
||||||
mkdir actions-runner && cd actions-runner
|
mkdir actions-runner && cd actions-runner
|
||||||
// Download the latest runner package
|
# Download the latest runner package
|
||||||
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
|
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
|
||||||
// Extract the installer
|
# Extract the installer
|
||||||
tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
|
tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
## Linux arm64 (Pre-release)
|
## Linux arm64 (Pre-release)
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
// Create a folder
|
# Create a folder
|
||||||
mkdir actions-runner && cd actions-runner
|
mkdir actions-runner && cd actions-runner
|
||||||
// Download the latest runner package
|
# Download the latest runner package
|
||||||
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
|
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
|
||||||
// Extract the installer
|
# Extract the installer
|
||||||
tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
|
tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
## Linux arm (Pre-release)
|
## Linux arm (Pre-release)
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
// Create a folder
|
# Create a folder
|
||||||
mkdir actions-runner && cd actions-runner
|
mkdir actions-runner && cd actions-runner
|
||||||
// Download the latest runner package
|
# Download the latest runner package
|
||||||
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
|
curl -O -L https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
|
||||||
// Extract the installer
|
# Extract the installer
|
||||||
tar xzf ./actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
|
tar xzf ./actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.164.0
|
<Update to ./src/runnerversion when creating release>
|
||||||
|
|||||||
4
scripts/README.md
Normal file
4
scripts/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Sample scripts for self-hosted runners
|
||||||
|
|
||||||
|
Here are some examples to work from if you'd like to automate your use of self-hosted runners.
|
||||||
|
See the docs [here](../docs/automate.md).
|
||||||
147
scripts/create-latest-svc.sh
Executable file
147
scripts/create-latest-svc.sh
Executable file
@@ -0,0 +1,147 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
#
|
||||||
|
# Downloads latest releases (not pre-release) runner
|
||||||
|
# Configures as a service
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myuser/myrepo my.ghe.deployment.net
|
||||||
|
# RUNNER_CFG_PAT=<yourPAT> ./create-latest-svc.sh myorg my.ghe.deployment.net
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# export RUNNER_CFG_PAT=<yourPAT>
|
||||||
|
# ./create-latest-svc scope [ghe_domain] [name] [user]
|
||||||
|
#
|
||||||
|
# scope required repo (:owner/:repo) or org (:organization)
|
||||||
|
# ghe_domain optional the fully qualified domain name of your GitHub Enterprise Server deployment
|
||||||
|
# name optional defaults to hostname
|
||||||
|
# user optional user svc will run as. defaults to current
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# PATS over envvars are more secure
|
||||||
|
# Should be used on VMs and not containers
|
||||||
|
# Works on OSX and Linux
|
||||||
|
# Assumes x64 arch
|
||||||
|
#
|
||||||
|
|
||||||
|
runner_scope=${1}
|
||||||
|
ghe_hostname=${2}
|
||||||
|
runner_name=${3:-$(hostname)}
|
||||||
|
svc_user=${4:-$USER}
|
||||||
|
|
||||||
|
echo "Configuring runner @ ${runner_scope}"
|
||||||
|
sudo echo
|
||||||
|
|
||||||
|
#---------------------------------------
|
||||||
|
# Validate Environment
|
||||||
|
#---------------------------------------
|
||||||
|
runner_plat=linux
|
||||||
|
[ ! -z "$(which sw_vers)" ] && runner_plat=osx;
|
||||||
|
|
||||||
|
function fatal()
|
||||||
|
{
|
||||||
|
echo "error: $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "${runner_scope}" ]; then fatal "supply scope as argument 1"; fi
|
||||||
|
if [ -z "${RUNNER_CFG_PAT}" ]; then fatal "RUNNER_CFG_PAT must be set before calling"; fi
|
||||||
|
|
||||||
|
which curl || fatal "curl required. Please install in PATH with apt-get, brew, etc"
|
||||||
|
which jq || fatal "jq required. Please install in PATH with apt-get, brew, etc"
|
||||||
|
|
||||||
|
# bail early if there's already a runner there. also sudo early
|
||||||
|
if [ -d ./runner ]; then
|
||||||
|
fatal "Runner already exists. Use a different directory or delete ./runner"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo -u ${svc_user} mkdir runner
|
||||||
|
|
||||||
|
# TODO: validate not in a container
|
||||||
|
# TODO: validate systemd or osx svc installer
|
||||||
|
|
||||||
|
#--------------------------------------
|
||||||
|
# Get a config token
|
||||||
|
#--------------------------------------
|
||||||
|
echo
|
||||||
|
echo "Generating a registration token..."
|
||||||
|
|
||||||
|
base_api_url="https://api.github.com"
|
||||||
|
if [ -n "${ghe_hostname}" ]; then
|
||||||
|
base_api_url="https://${ghe_hostname}/api/v3"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if the scope has a slash, it's a repo runner
|
||||||
|
orgs_or_repos="orgs"
|
||||||
|
if [[ "$runner_scope" == *\/* ]]; then
|
||||||
|
orgs_or_repos="repos"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export RUNNER_TOKEN=$(curl -s -X POST ${base_api_url}/${orgs_or_repos}/${runner_scope}/actions/runners/registration-token -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" | jq -r '.token')
|
||||||
|
|
||||||
|
if [ "null" == "$RUNNER_TOKEN" -o -z "$RUNNER_TOKEN" ]; then fatal "Failed to get a token"; fi
|
||||||
|
|
||||||
|
#---------------------------------------
|
||||||
|
# Download latest released and extract
|
||||||
|
#---------------------------------------
|
||||||
|
echo
|
||||||
|
echo "Downloading latest runner ..."
|
||||||
|
|
||||||
|
# For the GHES Alpha, download the runner from github.com
|
||||||
|
latest_version_label=$(curl -s -X GET 'https://api.github.com/repos/actions/runner/releases/latest' | jq -r '.tag_name')
|
||||||
|
latest_version=$(echo ${latest_version_label:1})
|
||||||
|
runner_file="actions-runner-${runner_plat}-x64-${latest_version}.tar.gz"
|
||||||
|
|
||||||
|
if [ -f "${runner_file}" ]; then
|
||||||
|
echo "${runner_file} exists. skipping download."
|
||||||
|
else
|
||||||
|
runner_url="https://github.com/actions/runner/releases/download/${latest_version_label}/${runner_file}"
|
||||||
|
|
||||||
|
echo "Downloading ${latest_version_label} for ${runner_plat} ..."
|
||||||
|
echo $runner_url
|
||||||
|
|
||||||
|
curl -O -L ${runner_url}
|
||||||
|
fi
|
||||||
|
|
||||||
|
ls -la *.tar.gz
|
||||||
|
|
||||||
|
#---------------------------------------------------
|
||||||
|
# extract to runner directory in this directory
|
||||||
|
#---------------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "Extracting ${runner_file} to ./runner"
|
||||||
|
|
||||||
|
tar xzf "./${runner_file}" -C runner
|
||||||
|
|
||||||
|
# export of pass
|
||||||
|
sudo chown -R $svc_user ./runner
|
||||||
|
|
||||||
|
pushd ./runner
|
||||||
|
|
||||||
|
#---------------------------------------
|
||||||
|
# Unattend config
|
||||||
|
#---------------------------------------
|
||||||
|
runner_url="https://github.com/${runner_scope}"
|
||||||
|
if [ -n "${ghe_hostname}" ]; then
|
||||||
|
runner_url="https://${ghe_hostname}/${runner_scope}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Configuring ${runner_name} @ $runner_url"
|
||||||
|
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name"
|
||||||
|
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name
|
||||||
|
|
||||||
|
#---------------------------------------
|
||||||
|
# Configuring as a service
|
||||||
|
#---------------------------------------
|
||||||
|
echo
|
||||||
|
echo "Configuring as a service ..."
|
||||||
|
prefix=""
|
||||||
|
if [ "${runner_plat}" == "linux" ]; then
|
||||||
|
prefix="sudo "
|
||||||
|
fi
|
||||||
|
|
||||||
|
${prefix}./svc.sh install ${svc_user}
|
||||||
|
${prefix}./svc.sh start
|
||||||
83
scripts/delete.sh
Executable file
83
scripts/delete.sh
Executable file
@@ -0,0 +1,83 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
#
|
||||||
|
# Force deletes a runner from the service
|
||||||
|
# The caller should have already ensured the runner is gone and/or stopped
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# RUNNER_CFG_PAT=<yourPAT> ./delete.sh myuser/myrepo myname
|
||||||
|
# RUNNER_CFG_PAT=<yourPAT> ./delete.sh myorg
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# export RUNNER_CFG_PAT=<yourPAT>
|
||||||
|
# ./delete.sh scope name
|
||||||
|
#
|
||||||
|
# scope required repo (:owner/:repo) or org (:organization)
|
||||||
|
# name optional defaults to hostname. name to delete
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# PATS over envvars are more secure
|
||||||
|
# Works on OSX and Linux
|
||||||
|
# Assumes x64 arch
|
||||||
|
#
|
||||||
|
|
||||||
|
runner_scope=${1}
|
||||||
|
runner_name=${2}
|
||||||
|
|
||||||
|
echo "Deleting runner ${runner_name} @ ${runner_scope}"
|
||||||
|
|
||||||
|
function fatal()
|
||||||
|
{
|
||||||
|
echo "error: $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "${runner_scope}" ]; then fatal "supply scope as argument 1"; fi
|
||||||
|
if [ -z "${runner_name}" ]; then fatal "supply name as argument 2"; fi
|
||||||
|
if [ -z "${RUNNER_CFG_PAT}" ]; then fatal "RUNNER_CFG_PAT must be set before calling"; fi
|
||||||
|
|
||||||
|
which curl || fatal "curl required. Please install in PATH with apt-get, brew, etc"
|
||||||
|
which jq || fatal "jq required. Please install in PATH with apt-get, brew, etc"
|
||||||
|
|
||||||
|
base_api_url="https://api.github.com/orgs"
|
||||||
|
if [[ "$runner_scope" == *\/* ]]; then
|
||||||
|
base_api_url="https://api.github.com/repos"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
#--------------------------------------
|
||||||
|
# Ensure offline
|
||||||
|
#--------------------------------------
|
||||||
|
runner_status=$(curl -s -X GET ${base_api_url}/${runner_scope}/actions/runners?per_page=100 -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" \
|
||||||
|
| jq -M -j ".runners | .[] | [select(.name == \"${runner_name}\")] | .[0].status")
|
||||||
|
|
||||||
|
if [ -z "${runner_status}" ]; then
|
||||||
|
fatal "Could not find runner with name ${runner_name}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Status: ${runner_status}"
|
||||||
|
|
||||||
|
if [ "${runner_status}" != "offline" ]; then
|
||||||
|
fatal "Runner should be offline before removing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#--------------------------------------
|
||||||
|
# Get id of runner to remove
|
||||||
|
#--------------------------------------
|
||||||
|
runner_id=$(curl -s -X GET ${base_api_url}/${runner_scope}/actions/runners?per_page=100 -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" \
|
||||||
|
| jq -M -j ".runners | .[] | [select(.name == \"${runner_name}\")] | .[0].id")
|
||||||
|
|
||||||
|
if [ -z "${runner_id}" ]; then
|
||||||
|
fatal "Could not find runner with name ${runner_name}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Removing id ${runner_id}"
|
||||||
|
|
||||||
|
#--------------------------------------
|
||||||
|
# Remove the runner
|
||||||
|
#--------------------------------------
|
||||||
|
curl -s -X DELETE ${base_api_url}/${runner_scope}/actions/runners/${runner_id} -H "authorization: token ${RUNNER_CFG_PAT}"
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
76
scripts/remove-svc.sh
Executable file
76
scripts/remove-svc.sh
Executable file
@@ -0,0 +1,76 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
#
|
||||||
|
# Removes a runner running as a service
|
||||||
|
# Must be run on the machine where the service is run
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# RUNNER_CFG_PAT=<yourPAT> ./remove-svc.sh myuser/myrepo
|
||||||
|
# RUNNER_CFG_PAT=<yourPAT> ./remove-svc.sh myorg
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# export RUNNER_CFG_PAT=<yourPAT>
|
||||||
|
# ./remove-svc scope name
|
||||||
|
#
|
||||||
|
# scope required repo (:owner/:repo) or org (:organization)
|
||||||
|
# name optional defaults to hostname. name to uninstall and remove
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# PATS over envvars are more secure
|
||||||
|
# Should be used on VMs and not containers
|
||||||
|
# Works on OSX and Linux
|
||||||
|
# Assumes x64 arch
|
||||||
|
#
|
||||||
|
|
||||||
|
runner_scope=${1}
|
||||||
|
runner_name=${2:-$(hostname)}
|
||||||
|
|
||||||
|
echo "Uninstalling runner ${runner_name} @ ${runner_scope}"
|
||||||
|
sudo echo
|
||||||
|
|
||||||
|
function fatal()
|
||||||
|
{
|
||||||
|
echo "error: $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "${runner_scope}" ]; then fatal "supply scope as argument 1"; fi
|
||||||
|
if [ -z "${RUNNER_CFG_PAT}" ]; then fatal "RUNNER_CFG_PAT must be set before calling"; fi
|
||||||
|
|
||||||
|
which curl || fatal "curl required. Please install in PATH with apt-get, brew, etc"
|
||||||
|
which jq || fatal "jq required. Please install in PATH with apt-get, brew, etc"
|
||||||
|
|
||||||
|
runner_plat=linux
|
||||||
|
[ ! -z "$(which sw_vers)" ] && runner_plat=osx;
|
||||||
|
|
||||||
|
#--------------------------------------
|
||||||
|
# Get a remove token
|
||||||
|
#--------------------------------------
|
||||||
|
echo
|
||||||
|
echo "Generating a removal token..."
|
||||||
|
|
||||||
|
# if the scope has a slash, it's an repo runner
|
||||||
|
base_api_url="https://api.github.com/orgs"
|
||||||
|
if [[ "$runner_scope" == *\/* ]]; then
|
||||||
|
base_api_url="https://api.github.com/repos"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export REMOVE_TOKEN=$(curl -s -X POST ${base_api_url}/${runner_scope}/actions/runners/remove-token -H "accept: application/vnd.github.everest-preview+json" -H "authorization: token ${RUNNER_CFG_PAT}" | jq -r '.token')
|
||||||
|
|
||||||
|
if [ -z "$REMOVE_TOKEN" ]; then fatal "Failed to get a token"; fi
|
||||||
|
|
||||||
|
#---------------------------------------
|
||||||
|
# Stop and uninstall the service
|
||||||
|
#---------------------------------------
|
||||||
|
echo
|
||||||
|
echo "Uninstall the service ..."
|
||||||
|
pushd ./runner
|
||||||
|
prefix=""
|
||||||
|
if [ "${runner_plat}" == "linux" ]; then
|
||||||
|
prefix="sudo "
|
||||||
|
fi
|
||||||
|
${prefix}./svc.sh stop
|
||||||
|
${prefix}./svc.sh uninstall
|
||||||
|
${prefix}./config.sh remove --token $REMOVE_TOKEN
|
||||||
193
src/Misc/dotnet-install.ps1
vendored
193
src/Misc/dotnet-install.ps1
vendored
@@ -684,3 +684,196 @@ Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath
|
|||||||
|
|
||||||
Say "Installation finished"
|
Say "Installation finished"
|
||||||
exit 0
|
exit 0
|
||||||
|
|
||||||
|
# SIG # Begin signature block
|
||||||
|
# MIIjkQYJKoZIhvcNAQcCoIIjgjCCI34CAQExDzANBglghkgBZQMEAgEFADB5Bgor
|
||||||
|
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
|
||||||
|
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAwp4UsNdAkvwY3
|
||||||
|
# VhbuN9D6NGOz+qNqW2+62YubWa4qJaCCDYEwggX/MIID56ADAgECAhMzAAABh3IX
|
||||||
|
# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
|
||||||
|
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
|
||||||
|
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
|
||||||
|
# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw
|
||||||
|
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
|
||||||
|
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
|
||||||
|
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||||
|
# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB
|
||||||
|
# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH
|
||||||
|
# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d
|
||||||
|
# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ
|
||||||
|
# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV
|
||||||
|
# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
|
||||||
|
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw
|
||||||
|
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
|
||||||
|
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu
|
||||||
|
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
|
||||||
|
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
|
||||||
|
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
|
||||||
|
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
|
||||||
|
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy
|
||||||
|
# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K
|
||||||
|
# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV
|
||||||
|
# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr
|
||||||
|
# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx
|
||||||
|
# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe
|
||||||
|
# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g
|
||||||
|
# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf
|
||||||
|
# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI
|
||||||
|
# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5
|
||||||
|
# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea
|
||||||
|
# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS
|
||||||
|
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
|
||||||
|
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
|
||||||
|
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
|
||||||
|
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
|
||||||
|
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
|
||||||
|
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
|
||||||
|
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
|
||||||
|
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
|
||||||
|
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
|
||||||
|
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
|
||||||
|
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
|
||||||
|
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
|
||||||
|
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
|
||||||
|
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
|
||||||
|
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
|
||||||
|
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
|
||||||
|
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
|
||||||
|
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
|
||||||
|
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
|
||||||
|
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
|
||||||
|
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
|
||||||
|
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
|
||||||
|
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
|
||||||
|
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
|
||||||
|
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
|
||||||
|
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
|
||||||
|
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
|
||||||
|
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
|
||||||
|
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
|
||||||
|
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
|
||||||
|
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
|
||||||
|
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
|
||||||
|
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
|
||||||
|
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
|
||||||
|
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
|
||||||
|
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
|
||||||
|
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
|
||||||
|
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
|
||||||
|
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
|
||||||
|
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZjCCFWICAQEwgZUwfjELMAkG
|
||||||
|
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
|
||||||
|
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
|
||||||
|
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN
|
||||||
|
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
|
||||||
|
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQga11B1DE+
|
||||||
|
# y9z0lmEO+MC+bhXPKfWALB7Snkn7G/wCUncwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
|
||||||
|
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
|
||||||
|
# BgkqhkiG9w0BAQEFAASCAQBIgx+sFXkLXf7Xbx7opCD3uhpQGEQ4x/LsqTax0bu1
|
||||||
|
# GC/cxiI+dodUz+T4hKj1ZQyUH0Zlce32GutY048O9tkr7fQyuohoFUgChdIATEOY
|
||||||
|
# qAIESFbDT07i7khJfO2pewlhgM+A5ClvBa8HAvV0wOd+2IVgv3pgow1LEJm0/5NB
|
||||||
|
# E3IFA+hFrqiWALOY0uUep4H20EHMrbqw3YoV3EodIkTj3fC76q4K/bF84EZLUgjY
|
||||||
|
# e4rmXac8n7A9qR18QzGl8usEJej4OHU4nlUT1J734m+AWIFmfb/Zr2MyXED0V4q4
|
||||||
|
# Vbmw3O7xD9STeNYrn5RjPmGPEN04akHxhNUSqLIc9vxQoYIS8DCCEuwGCisGAQQB
|
||||||
|
# gjcDAwExghLcMIIS2AYJKoZIhvcNAQcCoIISyTCCEsUCAQMxDzANBglghkgBZQME
|
||||||
|
# AgEFADCCAVQGCyqGSIb3DQEJEAEEoIIBQwSCAT8wggE7AgEBBgorBgEEAYRZCgMB
|
||||||
|
# MDEwDQYJYIZIAWUDBAIBBQAEIPPK1A0D1n7ZEdgTjKPY4sWiOMtohMqGpFvG55NY
|
||||||
|
# SFHeAgZepuJh/dEYEjIwMjAwNTI5MTYyNzE1LjMxWjAEgAIB9KCB1KSB0TCBzjEL
|
||||||
|
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
|
||||||
|
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWlj
|
||||||
|
# cm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBU
|
||||||
|
# U1MgRVNOOjYwQkMtRTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T
|
||||||
|
# dGFtcCBTZXJ2aWNloIIORDCCBPUwggPdoAMCAQICEzMAAAEm37pLIrmCggcAAAAA
|
||||||
|
# ASYwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
|
||||||
|
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
|
||||||
|
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw
|
||||||
|
# HhcNMTkxMjE5MDExNDU5WhcNMjEwMzE3MDExNDU5WjCBzjELMAkGA1UEBhMCVVMx
|
||||||
|
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
|
||||||
|
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJh
|
||||||
|
# dGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjYwQkMt
|
||||||
|
# RTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
|
||||||
|
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnjC+hpxO8w2VdBO18X8L
|
||||||
|
# Hk6XdfR9yNQ0y+MuBOY7n5YdgkVunvbk/f6q8UoNFAdYQjVLPSAHbi6tUMiNeMGH
|
||||||
|
# k1U0lUxAkja2W2/szj/ghuFklvfHNBbsuiUShlhRlqcFNS7KXL2iwKDijmOhWJPY
|
||||||
|
# a2bLEr4W/mQLbSXail5p6m138Ttx4MAVEzzuGI0Kwr8ofIL7z6zCeWDiBM57LrNC
|
||||||
|
# qHOA2wboeuMsG4O0Oz2LMAzBLbJZPRPnZAD2HdD4HUL2mzZ8wox74Mekb7RzrUP3
|
||||||
|
# hiHpxXZceJvhIEKfAgVkB5kTZQnio8A1JijMjw8f4TmsJPdJWpi8ei73sexe8/Yj
|
||||||
|
# cwIDAQABo4IBGzCCARcwHQYDVR0OBBYEFEmrrB8XsH6YQo3RWKZfxqM0DmFBMB8G
|
||||||
|
# A1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeG
|
||||||
|
# RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Rp
|
||||||
|
# bVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH
|
||||||
|
# MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3Rh
|
||||||
|
# UENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB
|
||||||
|
# BQUHAwgwDQYJKoZIhvcNAQELBQADggEBAECW+51o6W/0J/O/npudfjVzMXq0u0cs
|
||||||
|
# HjqXpdRyH6o03jlmY5MXAui3cmPBKufijJxD2pMRPVMUNh3VA0PQuJeYrP06oFdq
|
||||||
|
# LpLxd3IJARm98vzaMgCz2nCwBDpe9X2M3Js9K1GAX+w4Az8N7J+Z6P1OD0VxHBdq
|
||||||
|
# eTaqDN1lk1vwagTN7t/WitxMXRDz0hRdYiWbATBAVgXXCOfzs3hnEv1n/EDab9HX
|
||||||
|
# OLMXKVY/+alqYKdV9lkuRp8Us1Q1WZy9z72Azu9x4mzft3fJ1puTjBHo5tHfixZo
|
||||||
|
# ummbI+WwjVCrku7pskJahfNi5amSgrqgR6nWAwvpJELccpVLdSxxmG0wggZxMIIE
|
||||||
|
# WaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
|
||||||
|
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
|
||||||
|
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v
|
||||||
|
# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0y
|
||||||
|
# NTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
|
||||||
|
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
|
||||||
|
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjAN
|
||||||
|
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RU
|
||||||
|
# ENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBE
|
||||||
|
# D/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50
|
||||||
|
# YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd
|
||||||
|
# /XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaR
|
||||||
|
# togINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB
|
||||||
|
# o4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8
|
||||||
|
# RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB
|
||||||
|
# hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO
|
||||||
|
# mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w
|
||||||
|
# a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr
|
||||||
|
# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
|
||||||
|
# bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSAB
|
||||||
|
# Af8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3
|
||||||
|
# dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEF
|
||||||
|
# BQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBt
|
||||||
|
# AGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Eh
|
||||||
|
# b7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7
|
||||||
|
# uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqR
|
||||||
|
# UgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9
|
||||||
|
# Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8
|
||||||
|
# +n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+
|
||||||
|
# Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh
|
||||||
|
# 2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRy
|
||||||
|
# zR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoo
|
||||||
|
# uLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx
|
||||||
|
# 16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341
|
||||||
|
# Hgi62jbb01+P3nSISRKhggLSMIICOwIBATCB/KGB1KSB0TCBzjELMAkGA1UEBhMC
|
||||||
|
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
|
||||||
|
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w
|
||||||
|
# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjYw
|
||||||
|
# QkMtRTM4My0yNjM1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
|
||||||
|
# aWNloiMKAQEwBwYFKw4DAhoDFQAKZzI5aZnESumrToHx3Lqgxnr//KCBgzCBgKR+
|
||||||
|
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
|
||||||
|
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
|
||||||
|
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA
|
||||||
|
# 4nuQTDAiGA8yMDIwMDUyOTE3NDQ0NFoYDzIwMjAwNTMwMTc0NDQ0WjB3MD0GCisG
|
||||||
|
# AQQBhFkKBAExLzAtMAoCBQDie5BMAgEAMAoCAQACAiZJAgH/MAcCAQACAhEjMAoC
|
||||||
|
# BQDifOHMAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA
|
||||||
|
# AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAprmyJTXdH9FmQZ0I
|
||||||
|
# mRSJdjc/RrSqDm8DUEq/h3FL73G/xvg9MbQj1J/h3hdlSIPcQXjrhL8hud/vyF0j
|
||||||
|
# IFaTK5YOcixkX++9t7Vz3Mn0KkQo8F4DNSyZEPpz682AyKKwLMJDy52pFFFKNP5l
|
||||||
|
# NpOz6YY1Od1xvk4nyN1WwfLnGswxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJV
|
||||||
|
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
|
||||||
|
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
|
||||||
|
# ZS1TdGFtcCBQQ0EgMjAxMAITMwAAASbfuksiuYKCBwAAAAABJjANBglghkgBZQME
|
||||||
|
# AgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJ
|
||||||
|
# BDEiBCB0IE0Q6P23RQlh8TFyp57UQQUF/sbui7mOMStRgTFZxTCB+gYLKoZIhvcN
|
||||||
|
# AQkQAi8xgeowgecwgeQwgb0EIDb9z++evV5wDO9qk5ZnbEZ8CTOuR+kZyu8xbTsJ
|
||||||
|
# CXUPMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
|
||||||
|
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
|
||||||
|
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEm
|
||||||
|
# 37pLIrmCggcAAAAAASYwIgQgtwi02bvsGAOdpAxEF607G6g9PlyS8vc2bAUSHovH
|
||||||
|
# /IIwDQYJKoZIhvcNAQELBQAEggEAEMCfsXNudrjztjI6JNyNDVpdF1axRVcGiNy6
|
||||||
|
# 67pgb1EePsjA2EaBB+5ZjgO/73JxuiVgsoXgH7em8tKG5RQJtcm5obVDb+jKksK4
|
||||||
|
# qcFLA1f7seQRGfE06UAPnSFh2GqMtTNJGCXWwqWLH2LduTjOqPt8Nupo16ABFIT2
|
||||||
|
# akTzBSJ81EHBkEU0Et6CgeaZiBYrCCXUtD+ASvLDkPSrjweQGu3Zk1SSROEzxMY9
|
||||||
|
# jdlGfMkK2krMd9ub9UZ13RcQDijJqo+h6mz76pAuiFFvuQl6wMoSGFaaUQwfd+WQ
|
||||||
|
# gXlVVX/A9JFBihrxnDVglEPlsIOxCHkTeIxLfnAkCbax+9pevA==
|
||||||
|
# SIG # End signature block
|
||||||
|
|||||||
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"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SVC_NAME="{{SvcNameVar}}"
|
SVC_NAME="{{SvcNameVar}}"
|
||||||
|
SVC_NAME=${SVC_NAME// /_}
|
||||||
SVC_DESCRIPTION="{{SvcDescription}}"
|
SVC_DESCRIPTION="{{SvcDescription}}"
|
||||||
|
|
||||||
user_id=`id -u`
|
user_id=`id -u`
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ fi
|
|||||||
|
|
||||||
# Determine OS type
|
# Determine OS type
|
||||||
# Debian based OS (Debian, Ubuntu, Linux Mint) has /etc/debian_version
|
# Debian based OS (Debian, Ubuntu, Linux Mint) has /etc/debian_version
|
||||||
# Fedora based OS (Fedora, Redhat, Centos, Oracle Linux 7) has /etc/redhat-release
|
# Fedora based OS (Fedora, Red Hat Enterprise Linux, CentOS, Oracle Linux 7) has /etc/redhat-release
|
||||||
# SUSE based OS (OpenSUSE, SUSE Enterprise) has ID_LIKE=suse in /etc/os-release
|
# SUSE based OS (OpenSUSE, SUSE Enterprise) has ID_LIKE=suse in /etc/os-release
|
||||||
|
|
||||||
function print_errormessage()
|
function print_errormessage()
|
||||||
@@ -70,8 +70,8 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# libicu version prefer: libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
|
# libicu version prefer: libicu66 -> libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
|
||||||
apt install -y libicu63 || apt install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
|
apt install -y libicu66 || apt install -y libicu63 || apt install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
echo "'apt' failed with exit code '$?'"
|
echo "'apt' failed with exit code '$?'"
|
||||||
@@ -99,8 +99,8 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# libicu version prefer: libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
|
# libicu version prefer: libicu66 -> libicu63 -> libicu60 -> libicu57 -> libicu55 -> libicu52
|
||||||
apt-get install -y libicu63 || apt-get install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
|
apt-get install -y libicu66 || apt-get install -y libicu63 || apt-get install -y libicu60 || apt install -y libicu57 || apt install -y libicu55 || apt install -y libicu52
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
echo "'apt-get' failed with exit code '$?'"
|
echo "'apt-get' failed with exit code '$?'"
|
||||||
@@ -116,12 +116,12 @@ then
|
|||||||
elif [ -e /etc/redhat-release ]
|
elif [ -e /etc/redhat-release ]
|
||||||
then
|
then
|
||||||
echo "The current OS is Fedora based"
|
echo "The current OS is Fedora based"
|
||||||
echo "--------Redhat Version--------"
|
echo "--Fedora/RHEL/CentOS Version--"
|
||||||
cat /etc/redhat-release
|
cat /etc/redhat-release
|
||||||
echo "------------------------------"
|
echo "------------------------------"
|
||||||
|
|
||||||
# use dnf on fedora
|
# use dnf on fedora
|
||||||
# use yum on centos and redhat
|
# use yum on centos and rhel
|
||||||
if [ -e /etc/fedora-release ]
|
if [ -e /etc/fedora-release ]
|
||||||
then
|
then
|
||||||
command -v dnf
|
command -v dnf
|
||||||
@@ -191,7 +191,7 @@ then
|
|||||||
redhatRelease=$(</etc/redhat-release)
|
redhatRelease=$(</etc/redhat-release)
|
||||||
if [[ $redhatRelease == "CentOS release 6."* || $redhatRelease == "Red Hat Enterprise Linux Server release 6."* ]]
|
if [[ $redhatRelease == "CentOS release 6."* || $redhatRelease == "Red Hat Enterprise Linux Server release 6."* ]]
|
||||||
then
|
then
|
||||||
echo "The current OS is Red Hat Enterprise Linux 6 or Centos 6"
|
echo "The current OS is Red Hat Enterprise Linux 6 or CentOS 6"
|
||||||
|
|
||||||
# Install known dependencies, as a best effort.
|
# Install known dependencies, as a best effort.
|
||||||
# The remaining dependencies are covered by the GitHub doc that will be shown by `print_rhel6message`
|
# The remaining dependencies are covered by the GitHub doc that will be shown by `print_rhel6message`
|
||||||
|
|||||||
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SVC_NAME="{{SvcNameVar}}"
|
SVC_NAME="{{SvcNameVar}}"
|
||||||
|
SVC_NAME=${SVC_NAME// /_}
|
||||||
SVC_DESCRIPTION="{{SvcDescription}}"
|
SVC_DESCRIPTION="{{SvcDescription}}"
|
||||||
|
|
||||||
SVC_CMD=$1
|
SVC_CMD=$1
|
||||||
@@ -62,12 +63,25 @@ function install()
|
|||||||
|
|
||||||
sed "s/{{User}}/${run_as_user}/g; s/{{Description}}/$(echo ${SVC_DESCRIPTION} | sed -e 's/[\/&]/\\&/g')/g; s/{{RunnerRoot}}/$(echo ${RUNNER_ROOT} | sed -e 's/[\/&]/\\&/g')/g;" "${TEMPLATE_PATH}" > "${TEMP_PATH}" || failed "failed to create replacement temp file"
|
sed "s/{{User}}/${run_as_user}/g; s/{{Description}}/$(echo ${SVC_DESCRIPTION} | sed -e 's/[\/&]/\\&/g')/g; s/{{RunnerRoot}}/$(echo ${RUNNER_ROOT} | sed -e 's/[\/&]/\\&/g')/g;" "${TEMPLATE_PATH}" > "${TEMP_PATH}" || failed "failed to create replacement temp file"
|
||||||
mv "${TEMP_PATH}" "${UNIT_PATH}" || failed "failed to copy unit file"
|
mv "${TEMP_PATH}" "${UNIT_PATH}" || failed "failed to copy unit file"
|
||||||
|
|
||||||
|
# Recent Fedora based Linux (CentOS/Redhat) has SELinux enabled by default
|
||||||
|
# We need to restore security context on the unit file we added otherwise SystemD have no access to it.
|
||||||
|
command -v getenforce > /dev/null
|
||||||
|
if [ $? -eq 0 ]
|
||||||
|
then
|
||||||
|
selinuxEnabled=$(getenforce)
|
||||||
|
if [[ $selinuxEnabled == "Enforcing" ]]
|
||||||
|
then
|
||||||
|
# SELinux is enabled, we will need to Restore SELinux Context for the service file
|
||||||
|
restorecon -r -v "${UNIT_PATH}" || failed "failed to restore SELinux context on ${UNIT_PATH}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# unit file should not be executable and world writable
|
# unit file should not be executable and world writable
|
||||||
chmod 664 ${UNIT_PATH} || failed "failed to set permissions on ${UNIT_PATH}"
|
chmod 664 "${UNIT_PATH}" || failed "failed to set permissions on ${UNIT_PATH}"
|
||||||
systemctl daemon-reload || failed "failed to reload daemons"
|
systemctl daemon-reload || failed "failed to reload daemons"
|
||||||
|
|
||||||
# Since we started with sudo, runsvc.sh will be owned by root. Change this to current login user.
|
# Since we started with sudo, runsvc.sh will be owned by root. Change this to current login user.
|
||||||
cp ./bin/runsvc.sh ./runsvc.sh || failed "failed to copy runsvc.sh"
|
cp ./bin/runsvc.sh ./runsvc.sh || failed "failed to copy runsvc.sh"
|
||||||
chown ${run_as_uid}:${run_as_gid} ./runsvc.sh || failed "failed to set owner for runsvc.sh"
|
chown ${run_as_uid}:${run_as_gid} ./runsvc.sh || failed "failed to set owner for runsvc.sh"
|
||||||
chmod 755 ./runsvc.sh || failed "failed to set permission for runsvc.sh"
|
chmod 755 ./runsvc.sh || failed "failed to set permission for runsvc.sh"
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
<packageSources>
|
<packageSources>
|
||||||
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
|
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
|
||||||
<clear />
|
<clear />
|
||||||
<add key="dotnet-core" value="https://www.myget.org/F/dotnet-core/api/v3/index.json" />
|
|
||||||
<add key="dotnet-buildtools" value="https://www.myget.org/F/dotnet-buildtools/api/v3/index.json" />
|
|
||||||
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
|
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -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))]
|
||||||
@@ -78,10 +105,12 @@ namespace GitHub.Runner.Common
|
|||||||
bool IsServiceConfigured();
|
bool IsServiceConfigured();
|
||||||
bool HasCredentials();
|
bool HasCredentials();
|
||||||
CredentialData GetCredentials();
|
CredentialData GetCredentials();
|
||||||
|
CredentialData GetMigratedCredentials();
|
||||||
RunnerSettings GetSettings();
|
RunnerSettings GetSettings();
|
||||||
void SaveCredential(CredentialData credential);
|
void SaveCredential(CredentialData credential);
|
||||||
void SaveSettings(RunnerSettings settings);
|
void SaveSettings(RunnerSettings settings);
|
||||||
void DeleteCredential();
|
void DeleteCredential();
|
||||||
|
void DeleteMigratedCredential();
|
||||||
void DeleteSettings();
|
void DeleteSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,9 +119,11 @@ namespace GitHub.Runner.Common
|
|||||||
private string _binPath;
|
private string _binPath;
|
||||||
private string _configFilePath;
|
private string _configFilePath;
|
||||||
private string _credFilePath;
|
private string _credFilePath;
|
||||||
|
private string _migratedCredFilePath;
|
||||||
private string _serviceConfigFilePath;
|
private string _serviceConfigFilePath;
|
||||||
|
|
||||||
private CredentialData _creds;
|
private CredentialData _creds;
|
||||||
|
private CredentialData _migratedCreds;
|
||||||
private RunnerSettings _settings;
|
private RunnerSettings _settings;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
@@ -114,6 +145,9 @@ namespace GitHub.Runner.Common
|
|||||||
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
_credFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Credentials);
|
||||||
Trace.Info("CredFilePath: {0}", _credFilePath);
|
Trace.Info("CredFilePath: {0}", _credFilePath);
|
||||||
|
|
||||||
|
_migratedCredFilePath = hostContext.GetConfigFile(WellKnownConfigFile.MigratedCredentials);
|
||||||
|
Trace.Info("MigratedCredFilePath: {0}", _migratedCredFilePath);
|
||||||
|
|
||||||
_serviceConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Service);
|
_serviceConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Service);
|
||||||
Trace.Info("ServiceConfigFilePath: {0}", _serviceConfigFilePath);
|
Trace.Info("ServiceConfigFilePath: {0}", _serviceConfigFilePath);
|
||||||
}
|
}
|
||||||
@@ -123,7 +157,7 @@ namespace GitHub.Runner.Common
|
|||||||
public bool HasCredentials()
|
public bool HasCredentials()
|
||||||
{
|
{
|
||||||
Trace.Info("HasCredentials()");
|
Trace.Info("HasCredentials()");
|
||||||
bool credsStored = (new FileInfo(_credFilePath)).Exists;
|
bool credsStored = (new FileInfo(_credFilePath)).Exists || (new FileInfo(_migratedCredFilePath)).Exists;
|
||||||
Trace.Info("stored {0}", credsStored);
|
Trace.Info("stored {0}", credsStored);
|
||||||
return credsStored;
|
return credsStored;
|
||||||
}
|
}
|
||||||
@@ -154,6 +188,16 @@ namespace GitHub.Runner.Common
|
|||||||
return _creds;
|
return _creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CredentialData GetMigratedCredentials()
|
||||||
|
{
|
||||||
|
if (_migratedCreds == null && File.Exists(_migratedCredFilePath))
|
||||||
|
{
|
||||||
|
_migratedCreds = IOUtil.LoadObject<CredentialData>(_migratedCredFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _migratedCreds;
|
||||||
|
}
|
||||||
|
|
||||||
public RunnerSettings GetSettings()
|
public RunnerSettings GetSettings()
|
||||||
{
|
{
|
||||||
if (_settings == null)
|
if (_settings == null)
|
||||||
@@ -206,6 +250,12 @@ namespace GitHub.Runner.Common
|
|||||||
public void DeleteCredential()
|
public void DeleteCredential()
|
||||||
{
|
{
|
||||||
IOUtil.Delete(_credFilePath, default(CancellationToken));
|
IOUtil.Delete(_credFilePath, default(CancellationToken));
|
||||||
|
IOUtil.Delete(_migratedCredFilePath, default(CancellationToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteMigratedCredential()
|
||||||
|
{
|
||||||
|
IOUtil.Delete(_migratedCredFilePath, default(CancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteSettings()
|
public void DeleteSettings()
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
Runner,
|
Runner,
|
||||||
Credentials,
|
Credentials,
|
||||||
|
MigratedCredentials,
|
||||||
RSACredentials,
|
RSACredentials,
|
||||||
Service,
|
Service,
|
||||||
CredentialStore,
|
CredentialStore,
|
||||||
Certificates,
|
Certificates,
|
||||||
Options,
|
Options,
|
||||||
|
SetupInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Constants
|
public static class Constants
|
||||||
@@ -85,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";
|
||||||
@@ -134,6 +137,15 @@ namespace GitHub.Runner.Common
|
|||||||
public const int RunnerUpdating = 3;
|
public const int RunnerUpdating = 3;
|
||||||
public const int RunOnceRunnerUpdating = 4;
|
public const int RunOnceRunnerUpdating = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||||
|
public static readonly string WorkerCrash = "WORKER_CRASH";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RunnerEvent
|
||||||
|
{
|
||||||
|
public static readonly string Register = "register";
|
||||||
|
public static readonly string Remove = "remove";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Pipeline
|
public static class Pipeline
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
using GitHub.Runner.Common.Util;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.Tracing;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Loader;
|
using System.Runtime.Loader;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Diagnostics.Tracing;
|
|
||||||
using GitHub.DistributedTask.Logging;
|
using GitHub.DistributedTask.Logging;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
namespace GitHub.Runner.Common
|
namespace GitHub.Runner.Common
|
||||||
@@ -24,7 +23,7 @@ namespace GitHub.Runner.Common
|
|||||||
CancellationToken RunnerShutdownToken { get; }
|
CancellationToken RunnerShutdownToken { get; }
|
||||||
ShutdownReason RunnerShutdownReason { get; }
|
ShutdownReason RunnerShutdownReason { get; }
|
||||||
ISecretMasker SecretMasker { get; }
|
ISecretMasker SecretMasker { get; }
|
||||||
ProductInfoHeaderValue UserAgent { get; }
|
List<ProductInfoHeaderValue> UserAgents { get; }
|
||||||
RunnerWebProxy WebProxy { get; }
|
RunnerWebProxy WebProxy { get; }
|
||||||
string GetDirectory(WellKnownDirectory directory);
|
string GetDirectory(WellKnownDirectory directory);
|
||||||
string GetConfigFile(WellKnownConfigFile configFile);
|
string GetConfigFile(WellKnownConfigFile configFile);
|
||||||
@@ -54,7 +53,7 @@ namespace GitHub.Runner.Common
|
|||||||
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new ConcurrentDictionary<Type, object>();
|
private readonly ConcurrentDictionary<Type, object> _serviceInstances = new ConcurrentDictionary<Type, object>();
|
||||||
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new ConcurrentDictionary<Type, Type>();
|
private readonly ConcurrentDictionary<Type, Type> _serviceTypes = new ConcurrentDictionary<Type, Type>();
|
||||||
private readonly ISecretMasker _secretMasker = new SecretMasker();
|
private readonly ISecretMasker _secretMasker = new SecretMasker();
|
||||||
private readonly ProductInfoHeaderValue _userAgent = new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version);
|
private readonly List<ProductInfoHeaderValue> _userAgents = new List<ProductInfoHeaderValue>() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) };
|
||||||
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
|
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
|
||||||
private object _perfLock = new object();
|
private object _perfLock = new object();
|
||||||
private Tracing _trace;
|
private Tracing _trace;
|
||||||
@@ -72,7 +71,7 @@ namespace GitHub.Runner.Common
|
|||||||
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token;
|
||||||
public ShutdownReason RunnerShutdownReason { get; private set; }
|
public ShutdownReason RunnerShutdownReason { get; private set; }
|
||||||
public ISecretMasker SecretMasker => _secretMasker;
|
public ISecretMasker SecretMasker => _secretMasker;
|
||||||
public ProductInfoHeaderValue UserAgent => _userAgent;
|
public List<ProductInfoHeaderValue> UserAgents => _userAgents;
|
||||||
public RunnerWebProxy WebProxy => _webProxy;
|
public RunnerWebProxy WebProxy => _webProxy;
|
||||||
public HostContext(string hostType, string logFile = null)
|
public HostContext(string hostType, string logFile = null)
|
||||||
{
|
{
|
||||||
@@ -89,6 +88,7 @@ namespace GitHub.Runner.Common
|
|||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.JsonStringEscape);
|
this.SecretMasker.AddValueEncoder(ValueEncoders.JsonStringEscape);
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
|
this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.XmlDataEscape);
|
this.SecretMasker.AddValueEncoder(ValueEncoders.XmlDataEscape);
|
||||||
|
this.SecretMasker.AddValueEncoder(ValueEncoders.TrimDoubleQuotes);
|
||||||
|
|
||||||
// Create the trace manager.
|
// Create the trace manager.
|
||||||
if (string.IsNullOrEmpty(logFile))
|
if (string.IsNullOrEmpty(logFile))
|
||||||
@@ -189,6 +189,17 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
|
_trace.Info($"No proxy settings were found based on environmental variables (http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var credFile = GetConfigFile(WellKnownConfigFile.Credentials);
|
||||||
|
if (File.Exists(credFile))
|
||||||
|
{
|
||||||
|
var credData = IOUtil.LoadObject<CredentialData>(credFile);
|
||||||
|
if (credData != null &&
|
||||||
|
credData.Data.TryGetValue("clientId", out var clientId))
|
||||||
|
{
|
||||||
|
_userAgents.Add(new ProductInfoHeaderValue($"RunnerId", clientId));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetDirectory(WellKnownDirectory directory)
|
public string GetDirectory(WellKnownDirectory directory)
|
||||||
@@ -281,6 +292,12 @@ namespace GitHub.Runner.Common
|
|||||||
".credentials");
|
".credentials");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.MigratedCredentials:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
|
".credentials_migrated");
|
||||||
|
break;
|
||||||
|
|
||||||
case WellKnownConfigFile.RSACredentials:
|
case WellKnownConfigFile.RSACredentials:
|
||||||
path = Path.Combine(
|
path = Path.Combine(
|
||||||
GetDirectory(WellKnownDirectory.Root),
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
@@ -316,6 +333,13 @@ namespace GitHub.Runner.Common
|
|||||||
GetDirectory(WellKnownDirectory.Root),
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
".options");
|
".options");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WellKnownConfigFile.SetupInfo:
|
||||||
|
path = Path.Combine(
|
||||||
|
GetDirectory(WellKnownDirectory.Root),
|
||||||
|
".setup_info");
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
|
||||||
}
|
}
|
||||||
@@ -590,9 +614,8 @@ namespace GitHub.Runner.Common
|
|||||||
{
|
{
|
||||||
public static HttpClientHandler CreateHttpClientHandler(this IHostContext context)
|
public static HttpClientHandler CreateHttpClientHandler(this IHostContext context)
|
||||||
{
|
{
|
||||||
HttpClientHandler clientHandler = new HttpClientHandler();
|
var handlerFactory = context.GetService<IHttpClientHandlerFactory>();
|
||||||
clientHandler.Proxy = context.WebProxy;
|
return handlerFactory.CreateClientHandler(context.WebProxy);
|
||||||
return clientHandler;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
src/Runner.Common/HttpClientHandlerFactory.cs
Normal file
19
src/Runner.Common/HttpClientHandlerFactory.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common
|
||||||
|
{
|
||||||
|
[ServiceLocator(Default = typeof(HttpClientHandlerFactory))]
|
||||||
|
public interface IHttpClientHandlerFactory : IRunnerService
|
||||||
|
{
|
||||||
|
HttpClientHandler CreateClientHandler(RunnerWebProxy webProxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HttpClientHandlerFactory : RunnerService, IHttpClientHandlerFactory
|
||||||
|
{
|
||||||
|
public HttpClientHandler CreateClientHandler(RunnerWebProxy webProxy)
|
||||||
|
{
|
||||||
|
return new HttpClientHandler() { Proxy = webProxy };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ namespace GitHub.Runner.Common
|
|||||||
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
|
||||||
Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent;
|
Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent;
|
||||||
Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
|
||||||
|
Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class JobServer : RunnerService, IJobServer
|
public sealed class JobServer : RunnerService, IJobServer
|
||||||
@@ -113,5 +114,14 @@ namespace GitHub.Runner.Common
|
|||||||
CheckConnection();
|
CheckConnection();
|
||||||
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
|
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
// Action download info
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
public Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
CheckConnection();
|
||||||
|
return _taskClient.ResolveActionDownloadInfoAsync(scopeIdentifier, hubName, planId, actions, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -296,10 +296,10 @@ namespace GitHub.Runner.Common
|
|||||||
// JobRequest
|
// JobRequest
|
||||||
//-----------------------------------------------------------------
|
//-----------------------------------------------------------------
|
||||||
|
|
||||||
public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, CancellationToken cancellationToken = default(CancellationToken))
|
public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, string orchestrationId = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
{
|
{
|
||||||
CheckConnection(RunnerConnectionType.JobRequest);
|
CheckConnection(RunnerConnectionType.JobRequest);
|
||||||
return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, cancellationToken: cancellationToken);
|
return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, orchestrationId: orchestrationId, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken))
|
public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
@@ -334,5 +334,20 @@ namespace GitHub.Runner.Common
|
|||||||
CheckConnection(RunnerConnectionType.Generic);
|
CheckConnection(RunnerConnectionType.Generic);
|
||||||
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState);
|
return _genericTaskAgentClient.UpdateAgentUpdateStateAsync(agentPoolId, agentId, currentState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
// Runner Auth Url
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
public Task<string> GetRunnerAuthUrlAsync(int runnerPoolId, int runnerId)
|
||||||
|
{
|
||||||
|
CheckConnection(RunnerConnectionType.MessageQueue);
|
||||||
|
return _messageTaskAgentClient.GetAgentAuthUrlAsync(runnerPoolId, runnerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ReportRunnerAuthUrlErrorAsync(int runnerPoolId, int runnerId, string error)
|
||||||
|
{
|
||||||
|
CheckConnection(RunnerConnectionType.MessageQueue);
|
||||||
|
return _messageTaskAgentClient.ReportAgentAuthUrlMigrationErrorAsync(runnerPoolId, runnerId, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,13 +96,14 @@ namespace GitHub.Runner.Common
|
|||||||
Trace.Info($"WRITE: {message}");
|
Trace.Info($"WRITE: {message}");
|
||||||
if (!Silent)
|
if (!Silent)
|
||||||
{
|
{
|
||||||
if(colorCode != null)
|
if (colorCode != null)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = colorCode.Value;
|
Console.ForegroundColor = colorCode.Value;
|
||||||
Console.Write(message);
|
Console.Write(message);
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
Console.Write(message);
|
Console.Write(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,13 +121,14 @@ namespace GitHub.Runner.Common
|
|||||||
Trace.Info($"WRITE LINE: {line}");
|
Trace.Info($"WRITE LINE: {line}");
|
||||||
if (!Silent)
|
if (!Silent)
|
||||||
{
|
{
|
||||||
if(colorCode != null)
|
if (colorCode != null)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = colorCode.Value;
|
Console.ForegroundColor = colorCode.Value;
|
||||||
Console.WriteLine(line);
|
Console.WriteLine(line);
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
Console.WriteLine(line);
|
Console.WriteLine(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -190,7 +191,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
return GetArgOrPrompt(
|
return GetArgOrPrompt(
|
||||||
name: Constants.Runner.CommandLine.Args.Token,
|
name: Constants.Runner.CommandLine.Args.Token,
|
||||||
description: "Enter runner deletion token:",
|
description: "Enter runner remove token:",
|
||||||
defaultValue: string.Empty,
|
defaultValue: string.Empty,
|
||||||
validator: Validators.NonEmptyValidator);
|
validator: Validators.NonEmptyValidator);
|
||||||
}
|
}
|
||||||
@@ -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));
|
||||||
@@ -291,7 +311,7 @@ namespace GitHub.Runner.Listener
|
|||||||
if (!string.IsNullOrEmpty(result))
|
if (!string.IsNullOrEmpty(result))
|
||||||
{
|
{
|
||||||
// After read the arg from input commandline args, remove it from Arg dictionary,
|
// After read the arg from input commandline args, remove it from Arg dictionary,
|
||||||
// This will help if bad arg value passed through CommandLine arg, when ConfigurationManager ask CommandSetting the second time,
|
// This will help if bad arg value passed through CommandLine arg, when ConfigurationManager ask CommandSetting the second time,
|
||||||
// It will prompt for input instead of continue use the bad input.
|
// It will prompt for input instead of continue use the bad input.
|
||||||
_trace.Info($"Remove {name} from Arg dictionary.");
|
_trace.Info($"Remove {name} from Arg dictionary.");
|
||||||
RemoveArg(name);
|
RemoveArg(name);
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using GitHub.Runner.Common;
|
|
||||||
using GitHub.Runner.Sdk;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Configuration
|
namespace GitHub.Runner.Listener.Configuration
|
||||||
{
|
{
|
||||||
@@ -87,17 +86,17 @@ 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;
|
||||||
_term.WriteSection("Authentication");
|
_term.WriteSection("Authentication");
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Get the URL
|
// When testing against a dev deployment of Actions Service, set this environment variable
|
||||||
|
var useDevActionsServiceUrl = Environment.GetEnvironmentVariable("USE_DEV_ACTIONS_SERVICE_URL");
|
||||||
var inputUrl = command.GetUrl();
|
var inputUrl = command.GetUrl();
|
||||||
if (!inputUrl.Contains("github.com", StringComparison.OrdinalIgnoreCase) &&
|
if (inputUrl.Contains("codedev.ms", StringComparison.OrdinalIgnoreCase)
|
||||||
!inputUrl.Contains("github.localhost", StringComparison.OrdinalIgnoreCase))
|
|| useDevActionsServiceUrl != null)
|
||||||
{
|
{
|
||||||
runnerSettings.ServerUrl = inputUrl;
|
runnerSettings.ServerUrl = inputUrl;
|
||||||
// Get the credentials
|
// Get the credentials
|
||||||
@@ -109,7 +108,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
runnerSettings.GitHubUrl = inputUrl;
|
runnerSettings.GitHubUrl = inputUrl;
|
||||||
var githubToken = command.GetRunnerRegisterToken();
|
var githubToken = command.GetRunnerRegisterToken();
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, githubToken);
|
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, githubToken, Constants.RunnerEvent.Register);
|
||||||
runnerSettings.ServerUrl = authResult.TenantUrl;
|
runnerSettings.ServerUrl = authResult.TenantUrl;
|
||||||
creds = authResult.ToVssCredentials();
|
creds = authResult.ToVssCredentials();
|
||||||
Trace.Info("cred retrieved via GitHub auth");
|
Trace.Info("cred retrieved via GitHub auth");
|
||||||
@@ -118,7 +117,20 @@ 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));
|
||||||
|
|
||||||
|
// Warn if the Actions server url and GHES server url has different Host
|
||||||
|
if (!runnerSettings.IsHostedServer)
|
||||||
|
{
|
||||||
|
// Example actionsServerUrl is https://my-ghes/_services/pipelines/[...]
|
||||||
|
// Example githubServerUrl is https://my-ghes
|
||||||
|
var actionsServerUrl = new Uri(runnerSettings.ServerUrl);
|
||||||
|
var githubServerUrl = new Uri(runnerSettings.GitHubUrl);
|
||||||
|
if (!string.Equals(actionsServerUrl.Authority, githubServerUrl.Authority, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"GitHub Actions is not properly configured in GHES. GHES url: {runnerSettings.GitHubUrl}, Actions url: {runnerSettings.ServerUrl}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate can connect.
|
// Validate can connect.
|
||||||
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), creds);
|
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), creds);
|
||||||
@@ -169,6 +181,9 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
|
|
||||||
|
var userLabels = command.GetLabels();
|
||||||
|
_term.WriteLine();
|
||||||
|
|
||||||
var agents = await _runnerServer.GetAgentsAsync(runnerSettings.PoolId, runnerSettings.AgentName);
|
var agents = await _runnerServer.GetAgentsAsync(runnerSettings.PoolId, runnerSettings.AgentName);
|
||||||
Trace.Verbose("Returns {0} agents", agents.Count);
|
Trace.Verbose("Returns {0} agents", agents.Count);
|
||||||
agent = agents.FirstOrDefault();
|
agent = agents.FirstOrDefault();
|
||||||
@@ -178,7 +193,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
if (command.GetReplace())
|
if (command.GetReplace())
|
||||||
{
|
{
|
||||||
// Update existing agent with new PublicKey, agent version.
|
// Update existing agent with new PublicKey, agent version.
|
||||||
agent = UpdateExistingAgent(agent, publicKey);
|
agent = UpdateExistingAgent(agent, publicKey, userLabels);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -195,13 +210,13 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
else if (command.Unattended)
|
else if (command.Unattended)
|
||||||
{
|
{
|
||||||
// if not replace and it is unattended config.
|
// if not replace and it is unattended config.
|
||||||
throw new TaskAgentExistsException($"Pool {runnerSettings.PoolId} already contains a runner with name {runnerSettings.AgentName}.");
|
throw new TaskAgentExistsException($"A runner exists with the same name {runnerSettings.AgentName}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create a new agent.
|
// Create a new agent.
|
||||||
agent = CreateNewAgent(runnerSettings.AgentName, publicKey);
|
agent = CreateNewAgent(runnerSettings.AgentName, publicKey, userLabels);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -219,44 +234,11 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// Add Agent Id to settings
|
// Add Agent Id to settings
|
||||||
runnerSettings.AgentId = agent.Id;
|
runnerSettings.AgentId = agent.Id;
|
||||||
|
|
||||||
// respect the serverUrl resolve by server.
|
|
||||||
// in case of agent configured using collection url instead of account url.
|
|
||||||
string agentServerUrl;
|
|
||||||
if (agent.Properties.TryGetValidatedValue<string>("ServerUrl", out agentServerUrl) &&
|
|
||||||
!string.IsNullOrEmpty(agentServerUrl))
|
|
||||||
{
|
|
||||||
Trace.Info($"Agent server url resolve by server: '{agentServerUrl}'.");
|
|
||||||
|
|
||||||
// we need make sure the Schema/Host/Port component of the url remain the same.
|
|
||||||
UriBuilder inputServerUrl = new UriBuilder(runnerSettings.ServerUrl);
|
|
||||||
UriBuilder serverReturnedServerUrl = new UriBuilder(agentServerUrl);
|
|
||||||
if (Uri.Compare(inputServerUrl.Uri, serverReturnedServerUrl.Uri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0)
|
|
||||||
{
|
|
||||||
inputServerUrl.Path = serverReturnedServerUrl.Path;
|
|
||||||
Trace.Info($"Replace server returned url's scheme://host:port component with user input server url's scheme://host:port: '{inputServerUrl.Uri.AbsoluteUri}'.");
|
|
||||||
runnerSettings.ServerUrl = inputServerUrl.Uri.AbsoluteUri;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
runnerSettings.ServerUrl = agentServerUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if the server supports our OAuth key exchange for credentials
|
// See if the server supports our OAuth key exchange for credentials
|
||||||
if (agent.Authorization != null &&
|
if (agent.Authorization != null &&
|
||||||
agent.Authorization.ClientId != Guid.Empty &&
|
agent.Authorization.ClientId != Guid.Empty &&
|
||||||
agent.Authorization.AuthorizationUrl != null)
|
agent.Authorization.AuthorizationUrl != null)
|
||||||
{
|
{
|
||||||
UriBuilder configServerUrl = new UriBuilder(runnerSettings.ServerUrl);
|
|
||||||
UriBuilder oauthEndpointUrlBuilder = new UriBuilder(agent.Authorization.AuthorizationUrl);
|
|
||||||
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,
|
||||||
@@ -264,7 +246,6 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
||||||
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
||||||
{ "oauthEndpointUrl", oauthEndpointUrlBuilder.Uri.AbsoluteUri },
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -277,19 +258,22 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
throw new NotSupportedException("Message queue listen OAuth token.");
|
throw new NotSupportedException("Message queue listen OAuth token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testing agent connection, detect any protential connection issue, like local clock skew that cause OAuth token expired.
|
// Testing agent connection, detect any potential connection issue, like local clock skew that cause OAuth token expired.
|
||||||
var credMgr = HostContext.GetService<ICredentialManager>();
|
var credMgr = HostContext.GetService<ICredentialManager>();
|
||||||
VssCredentials credential = credMgr.LoadCredentials();
|
VssCredentials credential = credMgr.LoadCredentials();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
|
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
|
||||||
|
// ConnectAsync() hits _apis/connectionData which is an anonymous endpoint
|
||||||
|
// Need to hit an authenticate endpoint to trigger OAuth token exchange.
|
||||||
|
await _runnerServer.GetAgentPoolsAsync();
|
||||||
_term.WriteSuccessMessage("Runner connection is good");
|
_term.WriteSuccessMessage("Runner connection is good");
|
||||||
}
|
}
|
||||||
catch (VssOAuthTokenRequestException ex) when (ex.Message.Contains("Current server time is"))
|
catch (VssOAuthTokenRequestException ex) when (ex.Message.Contains("Current server time is"))
|
||||||
{
|
{
|
||||||
// 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.");
|
||||||
@@ -373,13 +357,12 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var githubToken = command.GetRunnerDeletionToken();
|
var githubToken = command.GetRunnerDeletionToken();
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, githubToken);
|
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, githubToken, Constants.RunnerEvent.Remove);
|
||||||
creds = authResult.ToVssCredentials();
|
creds = authResult.ToVssCredentials();
|
||||||
Trace.Info("cred retrieved via GitHub auth");
|
Trace.Info("cred retrieved via GitHub auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -402,7 +385,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)
|
||||||
{
|
{
|
||||||
@@ -416,7 +399,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)
|
||||||
{
|
{
|
||||||
@@ -457,7 +440,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey)
|
private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey, ISet<string> userLabels)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(agent, nameof(agent));
|
ArgUtil.NotNull(agent, nameof(agent));
|
||||||
agent.Authorization = new TaskAgentAuthorization
|
agent.Authorization = new TaskAgentAuthorization
|
||||||
@@ -465,18 +448,25 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus),
|
PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus),
|
||||||
};
|
};
|
||||||
|
|
||||||
// update - update instead of delete so we don't lose labels etc...
|
// update should replace the existing labels
|
||||||
agent.Version = BuildConstants.RunnerPackage.Version;
|
agent.Version = BuildConstants.RunnerPackage.Version;
|
||||||
agent.OSDescription = RuntimeInformation.OSDescription;
|
agent.OSDescription = RuntimeInformation.OSDescription;
|
||||||
|
|
||||||
agent.Labels.Add("self-hosted");
|
agent.Labels.Clear();
|
||||||
agent.Labels.Add(VarUtil.OS);
|
|
||||||
agent.Labels.Add(VarUtil.OSArchitecture);
|
agent.Labels.Add(new AgentLabel("self-hosted", LabelType.System));
|
||||||
|
agent.Labels.Add(new AgentLabel(VarUtil.OS, LabelType.System));
|
||||||
|
agent.Labels.Add(new AgentLabel(VarUtil.OSArchitecture, LabelType.System));
|
||||||
|
|
||||||
|
foreach (var userLabel in userLabels)
|
||||||
|
{
|
||||||
|
agent.Labels.Add(new AgentLabel(userLabel, LabelType.User));
|
||||||
|
}
|
||||||
|
|
||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey)
|
private TaskAgent CreateNewAgent(string agentName, RSAParameters publicKey, ISet<string> userLabels)
|
||||||
{
|
{
|
||||||
TaskAgent agent = new TaskAgent(agentName)
|
TaskAgent agent = new TaskAgent(agentName)
|
||||||
{
|
{
|
||||||
@@ -489,45 +479,51 @@ 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)
|
private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, string githubToken, string runnerEvent)
|
||||||
{
|
{
|
||||||
var gitHubUrl = new UriBuilder(githubUrl);
|
var githubApiUrl = "";
|
||||||
var githubApiUrl = $"https://api.{gitHubUrl.Host}/repos/{gitHubUrl.Path.Trim('/')}/actions-runners/registration";
|
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||||
|
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);
|
||||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.shuri-preview+json"));
|
|
||||||
var response = await httpClient.PostAsync(githubApiUrl, new StringContent("", null, "application/json"));
|
var bodyObject = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"url", githubUrl},
|
||||||
|
{"runner_event", runnerEvent}
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json"));
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,6 +50,18 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
CredentialData credData = store.GetCredentials();
|
CredentialData credData = store.GetCredentials();
|
||||||
|
var migratedCred = store.GetMigratedCredentials();
|
||||||
|
if (migratedCred != null)
|
||||||
|
{
|
||||||
|
credData = migratedCred;
|
||||||
|
|
||||||
|
// Re-write .credentials with Token URL
|
||||||
|
store.SaveCredential(credData);
|
||||||
|
|
||||||
|
// Delete .credentials_migrated
|
||||||
|
store.DeleteMigratedCredential();
|
||||||
|
}
|
||||||
|
|
||||||
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
ICredentialProvider credProv = GetCredentialProvider(credData.Scheme);
|
||||||
credProv.CredentialData = credData;
|
credProv.CredentialData = credData;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.OAuth;
|
using GitHub.Services.OAuth;
|
||||||
@@ -29,7 +28,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null);
|
var authorizationUrl = this.CredentialData.Data.GetValueOrDefault("authorizationUrl", null);
|
||||||
|
|
||||||
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
|
// For back compat with .credential file that doesn't has 'oauthEndpointUrl' section
|
||||||
var oathEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
|
var oauthEndpointUrl = this.CredentialData.Data.GetValueOrDefault("oauthEndpointUrl", authorizationUrl);
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(clientId, nameof(clientId));
|
ArgUtil.NotNullOrEmpty(clientId, nameof(clientId));
|
||||||
ArgUtil.NotNullOrEmpty(authorizationUrl, nameof(authorizationUrl));
|
ArgUtil.NotNullOrEmpty(authorizationUrl, nameof(authorizationUrl));
|
||||||
@@ -39,7 +38,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
var keyManager = context.GetService<IRSAKeyManager>();
|
var keyManager = context.GetService<IRSAKeyManager>();
|
||||||
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
|
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
|
||||||
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
|
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
|
||||||
var agentCredential = new VssOAuthCredential(new Uri(oathEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
|
var agentCredential = new VssOAuthCredential(new Uri(oauthEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
|
||||||
|
|
||||||
// Construct a credentials cache with a single OAuth credential for communication. The windows credential
|
// Construct a credentials cache with a single OAuth credential for communication. The windows credential
|
||||||
// is explicitly set to null to ensure we never do that negotiation.
|
// is explicitly set to null to ensure we never do that negotiation.
|
||||||
|
|||||||
@@ -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,12 +12,14 @@ 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
|
||||||
{
|
{
|
||||||
[ServiceLocator(Default = typeof(JobDispatcher))]
|
[ServiceLocator(Default = typeof(JobDispatcher))]
|
||||||
public interface IJobDispatcher : IRunnerService
|
public interface IJobDispatcher : IRunnerService
|
||||||
{
|
{
|
||||||
|
bool Busy { get; }
|
||||||
TaskCompletionSource<bool> RunOnceJobCompleted { get; }
|
TaskCompletionSource<bool> RunOnceJobCompleted { get; }
|
||||||
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
|
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
|
||||||
bool Cancel(JobCancelMessage message);
|
bool Cancel(JobCancelMessage message);
|
||||||
@@ -69,6 +71,8 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
public TaskCompletionSource<bool> RunOnceJobCompleted => _runOnceJobCompleted;
|
public TaskCompletionSource<bool> RunOnceJobCompleted => _runOnceJobCompleted;
|
||||||
|
|
||||||
|
public bool Busy { get; private set; }
|
||||||
|
|
||||||
public void Run(Pipelines.AgentJobRequestMessage jobRequestMessage, bool runOnce = false)
|
public void Run(Pipelines.AgentJobRequestMessage jobRequestMessage, bool runOnce = false)
|
||||||
{
|
{
|
||||||
Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received.");
|
Trace.Info($"Job request {jobRequestMessage.RequestId} for plan {jobRequestMessage.Plan.PlanId} job {jobRequestMessage.JobId} received.");
|
||||||
@@ -83,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);
|
||||||
@@ -247,7 +266,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Task completedTask = await Task.WhenAny(jobDispatch.WorkerDispatch, Task.Delay(TimeSpan.FromSeconds(45)));
|
Task completedTask = await Task.WhenAny(jobDispatch.WorkerDispatch, Task.Delay(TimeSpan.FromSeconds(45)));
|
||||||
if (completedTask != jobDispatch.WorkerDispatch)
|
if (completedTask != jobDispatch.WorkerDispatch)
|
||||||
{
|
{
|
||||||
// at this point, the job exectuion might encounter some dead lock and even not able to be canclled.
|
// at this point, the job execution might encounter some dead lock and even not able to be cancelled.
|
||||||
// no need to localize the exception string should never happen.
|
// no need to localize the exception string should never happen.
|
||||||
throw new InvalidOperationException($"Job dispatch process for {jobDispatch.JobId} has encountered unexpected error, the dispatch task is not able to be canceled within 45 seconds.");
|
throw new InvalidOperationException($"Job dispatch process for {jobDispatch.JobId} has encountered unexpected error, the dispatch task is not able to be canceled within 45 seconds.");
|
||||||
}
|
}
|
||||||
@@ -281,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
|
||||||
{
|
{
|
||||||
@@ -294,192 +313,292 @@ 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)
|
||||||
{
|
{
|
||||||
if (previousJobDispatch != null)
|
Busy = true;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Trace.Verbose($"Make sure the previous job request {previousJobDispatch.JobId} has successfully finished on worker.");
|
if (previousJobDispatch != null)
|
||||||
await EnsureDispatchFinished(previousJobDispatch);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Trace.Verbose($"This is the first job request.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var term = HostContext.GetService<ITerminal>();
|
|
||||||
term.WriteLine($"{DateTime.UtcNow:u}: Running job: {message.JobDisplayName}");
|
|
||||||
|
|
||||||
// first job request renew succeed.
|
|
||||||
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
|
||||||
var notification = HostContext.GetService<IJobNotification>();
|
|
||||||
|
|
||||||
// lock renew cancellation token.
|
|
||||||
using (var lockRenewalTokenSource = new CancellationTokenSource())
|
|
||||||
using (var workerProcessCancelTokenSource = new CancellationTokenSource())
|
|
||||||
{
|
|
||||||
long requestId = message.RequestId;
|
|
||||||
Guid lockToken = Guid.Empty; // lockToken has never been used, keep this here of compat
|
|
||||||
|
|
||||||
// start renew job request
|
|
||||||
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
|
|
||||||
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
|
||||||
|
|
||||||
// wait till first renew succeed or job request is canceled
|
|
||||||
// not even start worker if the first renew fail
|
|
||||||
await Task.WhenAny(firstJobRequestRenewed.Task, renewJobRequest, Task.Delay(-1, jobRequestCancellationToken));
|
|
||||||
|
|
||||||
if (renewJobRequest.IsCompleted)
|
|
||||||
{
|
{
|
||||||
// renew job request task complete means we run out of retry for the first job request renew.
|
Trace.Verbose($"Make sure the previous job request {previousJobDispatch.JobId} has successfully finished on worker.");
|
||||||
Trace.Info($"Unable to renew job request for job {message.JobId} for the first time, stop dispatching job to worker.");
|
await EnsureDispatchFinished(previousJobDispatch);
|
||||||
return;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Trace.Verbose($"This is the first job request.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jobRequestCancellationToken.IsCancellationRequested)
|
var term = HostContext.GetService<ITerminal>();
|
||||||
|
term.WriteLine($"{DateTime.UtcNow:u}: Running job: {message.JobDisplayName}");
|
||||||
|
|
||||||
|
// first job request renew succeed.
|
||||||
|
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
|
||||||
|
var notification = HostContext.GetService<IJobNotification>();
|
||||||
|
|
||||||
|
// lock renew cancellation token.
|
||||||
|
using (var lockRenewalTokenSource = new CancellationTokenSource())
|
||||||
|
using (var workerProcessCancelTokenSource = new CancellationTokenSource())
|
||||||
{
|
{
|
||||||
Trace.Info($"Stop renew job request for job {message.JobId}.");
|
long requestId = message.RequestId;
|
||||||
// stop renew lock
|
Guid lockToken = Guid.Empty; // lockToken has never been used, keep this here of compat
|
||||||
lockRenewalTokenSource.Cancel();
|
|
||||||
// renew job request should never blows up.
|
|
||||||
await renewJobRequest;
|
|
||||||
|
|
||||||
// complete job request with result Cancelled
|
// start renew job request
|
||||||
await CompleteJobRequestAsync(_poolId, message, lockToken, TaskResult.Canceled);
|
Trace.Info($"Start renew job request {requestId} for job {message.JobId}.");
|
||||||
return;
|
Task renewJobRequest = RenewJobRequestAsync(_poolId, requestId, lockToken, orchestrationId, firstJobRequestRenewed, lockRenewalTokenSource.Token);
|
||||||
}
|
|
||||||
|
|
||||||
HostContext.WritePerfCounter($"JobRequestRenewed_{requestId.ToString()}");
|
// wait till first renew succeed or job request is canceled
|
||||||
|
// not even start worker if the first renew fail
|
||||||
|
await Task.WhenAny(firstJobRequestRenewed.Task, renewJobRequest, Task.Delay(-1, jobRequestCancellationToken));
|
||||||
|
|
||||||
Task<int> workerProcessTask = null;
|
if (renewJobRequest.IsCompleted)
|
||||||
object _outputLock = new object();
|
|
||||||
List<string> workerOutput = new List<string>();
|
|
||||||
using (var processChannel = HostContext.CreateService<IProcessChannel>())
|
|
||||||
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
|
||||||
{
|
|
||||||
// Start the process channel.
|
|
||||||
// It's OK if StartServer bubbles an execption after the worker process has already started.
|
|
||||||
// The worker will shutdown after 30 seconds if it hasn't received the job message.
|
|
||||||
processChannel.StartServer(
|
|
||||||
// Delegate to start the child process.
|
|
||||||
startProcess: (string pipeHandleOut, string pipeHandleIn) =>
|
|
||||||
{
|
|
||||||
// Validate args.
|
|
||||||
ArgUtil.NotNullOrEmpty(pipeHandleOut, nameof(pipeHandleOut));
|
|
||||||
ArgUtil.NotNullOrEmpty(pipeHandleIn, nameof(pipeHandleIn));
|
|
||||||
|
|
||||||
// Save STDOUT from worker, worker will use STDOUT report unhandle exception.
|
|
||||||
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(stdout.Data))
|
|
||||||
{
|
|
||||||
lock (_outputLock)
|
|
||||||
{
|
|
||||||
workerOutput.Add(stdout.Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Save STDERR from worker, worker will use STDERR on crash.
|
|
||||||
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(stderr.Data))
|
|
||||||
{
|
|
||||||
lock (_outputLock)
|
|
||||||
{
|
|
||||||
workerOutput.Add(stderr.Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Start the child process.
|
|
||||||
HostContext.WritePerfCounter("StartingWorkerProcess");
|
|
||||||
var assemblyDirectory = HostContext.GetDirectory(WellKnownDirectory.Bin);
|
|
||||||
string workerFileName = Path.Combine(assemblyDirectory, _workerProcessName);
|
|
||||||
workerProcessTask = processInvoker.ExecuteAsync(
|
|
||||||
workingDirectory: assemblyDirectory,
|
|
||||||
fileName: workerFileName,
|
|
||||||
arguments: "spawnclient " + pipeHandleOut + " " + pipeHandleIn,
|
|
||||||
environment: null,
|
|
||||||
requireExitCodeZero: false,
|
|
||||||
outputEncoding: null,
|
|
||||||
killProcessOnCancel: true,
|
|
||||||
redirectStandardIn: null,
|
|
||||||
inheritConsoleHandler: false,
|
|
||||||
keepStandardInOpen: false,
|
|
||||||
highPriorityProcess: true,
|
|
||||||
cancellationToken: workerProcessCancelTokenSource.Token);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send the job request message.
|
|
||||||
// Kill the worker process if sending the job message times out. The worker
|
|
||||||
// process may have successfully received the job message.
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Trace.Info($"Send job request message to worker for job {message.JobId}.");
|
// renew job request task complete means we run out of retry for the first job request renew.
|
||||||
HostContext.WritePerfCounter($"RunnerSendingJobToWorker_{message.JobId}");
|
Trace.Info($"Unable to renew job request for job {message.JobId} for the first time, stop dispatching job to worker.");
|
||||||
using (var csSendJobRequest = new CancellationTokenSource(_channelTimeout))
|
return;
|
||||||
{
|
|
||||||
await processChannel.SendAsync(
|
|
||||||
messageType: MessageType.NewJobRequest,
|
|
||||||
body: JsonUtility.ToString(message),
|
|
||||||
cancellationToken: csSendJobRequest.Token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// message send been cancelled.
|
|
||||||
// timeout 30 sec. kill worker.
|
|
||||||
Trace.Info($"Job request message sending for job {message.JobId} been cancelled, kill running worker.");
|
|
||||||
workerProcessCancelTokenSource.Cancel();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await workerProcessTask;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
Trace.Info("worker process has been killed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (jobRequestCancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
Trace.Info($"Stop renew job request for job {message.JobId}.");
|
Trace.Info($"Stop renew job request for job {message.JobId}.");
|
||||||
// stop renew lock
|
// stop renew lock
|
||||||
lockRenewalTokenSource.Cancel();
|
lockRenewalTokenSource.Cancel();
|
||||||
// renew job request should never blows up.
|
// renew job request should never blows up.
|
||||||
await renewJobRequest;
|
await renewJobRequest;
|
||||||
|
|
||||||
// not finish the job request since the job haven't run on worker at all, we will not going to set a result to server.
|
// complete job request with result Cancelled
|
||||||
|
await CompleteJobRequestAsync(_poolId, message, lockToken, TaskResult.Canceled);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we get first jobrequest renew succeed and start the worker process with the job message.
|
HostContext.WritePerfCounter($"JobRequestRenewed_{requestId.ToString()}");
|
||||||
// send notification to machine provisioner.
|
|
||||||
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
|
||||||
var accessToken = systemConnection?.Authorization?.Parameters["AccessToken"];
|
|
||||||
notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
|
|
||||||
|
|
||||||
HostContext.WritePerfCounter($"SentJobToWorker_{requestId.ToString()}");
|
Task<int> workerProcessTask = null;
|
||||||
|
object _outputLock = new object();
|
||||||
try
|
List<string> workerOutput = new List<string>();
|
||||||
|
using (var processChannel = HostContext.CreateService<IProcessChannel>())
|
||||||
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
{
|
{
|
||||||
TaskResult resultOnAbandonOrCancel = TaskResult.Succeeded;
|
// Start the process channel.
|
||||||
// wait for renewlock, worker process or cancellation token been fired.
|
// It's OK if StartServer bubbles an execption after the worker process has already started.
|
||||||
var completedTask = await Task.WhenAny(renewJobRequest, workerProcessTask, Task.Delay(-1, jobRequestCancellationToken));
|
// The worker will shutdown after 30 seconds if it hasn't received the job message.
|
||||||
if (completedTask == workerProcessTask)
|
processChannel.StartServer(
|
||||||
{
|
// Delegate to start the child process.
|
||||||
// worker finished successfully, complete job request with result, attach unhandled exception reported by worker, stop renew lock, job has finished.
|
startProcess: (string pipeHandleOut, string pipeHandleIn) =>
|
||||||
int returnCode = await workerProcessTask;
|
|
||||||
Trace.Info($"Worker finished for job {message.JobId}. Code: " + returnCode);
|
|
||||||
|
|
||||||
string detailInfo = null;
|
|
||||||
if (!TaskResultUtil.IsValidReturnCode(returnCode))
|
|
||||||
{
|
{
|
||||||
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
// Validate args.
|
||||||
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
ArgUtil.NotNullOrEmpty(pipeHandleOut, nameof(pipeHandleOut));
|
||||||
await LogWorkerProcessUnhandledException(message, detailInfo);
|
ArgUtil.NotNullOrEmpty(pipeHandleIn, nameof(pipeHandleIn));
|
||||||
|
|
||||||
|
// Save STDOUT from worker, worker will use STDOUT report unhandle exception.
|
||||||
|
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(stdout.Data))
|
||||||
|
{
|
||||||
|
lock (_outputLock)
|
||||||
|
{
|
||||||
|
workerOutput.Add(stdout.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save STDERR from worker, worker will use STDERR on crash.
|
||||||
|
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(stderr.Data))
|
||||||
|
{
|
||||||
|
lock (_outputLock)
|
||||||
|
{
|
||||||
|
workerOutput.Add(stderr.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the child process.
|
||||||
|
HostContext.WritePerfCounter("StartingWorkerProcess");
|
||||||
|
var assemblyDirectory = HostContext.GetDirectory(WellKnownDirectory.Bin);
|
||||||
|
string workerFileName = Path.Combine(assemblyDirectory, _workerProcessName);
|
||||||
|
workerProcessTask = processInvoker.ExecuteAsync(
|
||||||
|
workingDirectory: assemblyDirectory,
|
||||||
|
fileName: workerFileName,
|
||||||
|
arguments: "spawnclient " + pipeHandleOut + " " + pipeHandleIn,
|
||||||
|
environment: null,
|
||||||
|
requireExitCodeZero: false,
|
||||||
|
outputEncoding: null,
|
||||||
|
killProcessOnCancel: true,
|
||||||
|
redirectStandardIn: null,
|
||||||
|
inheritConsoleHandler: false,
|
||||||
|
keepStandardInOpen: false,
|
||||||
|
highPriorityProcess: true,
|
||||||
|
cancellationToken: workerProcessCancelTokenSource.Token);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send the job request message.
|
||||||
|
// Kill the worker process if sending the job message times out. The worker
|
||||||
|
// process may have successfully received the job message.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Trace.Info($"Send job request message to worker for job {message.JobId}.");
|
||||||
|
HostContext.WritePerfCounter($"RunnerSendingJobToWorker_{message.JobId}");
|
||||||
|
using (var csSendJobRequest = new CancellationTokenSource(_channelTimeout))
|
||||||
|
{
|
||||||
|
await processChannel.SendAsync(
|
||||||
|
messageType: MessageType.NewJobRequest,
|
||||||
|
body: JsonUtility.ToString(message),
|
||||||
|
cancellationToken: csSendJobRequest.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// message send been cancelled.
|
||||||
|
// timeout 30 sec. kill worker.
|
||||||
|
Trace.Info($"Job request message sending for job {message.JobId} been cancelled, kill running worker.");
|
||||||
|
workerProcessCancelTokenSource.Cancel();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await workerProcessTask;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Trace.Info("worker process has been killed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
|
Trace.Info($"Stop renew job request for job {message.JobId}.");
|
||||||
Trace.Info($"finish job request for job {message.JobId} with result: {result}");
|
// stop renew lock
|
||||||
term.WriteLine($"{DateTime.UtcNow:u}: Job {message.JobDisplayName} completed with result: {result}");
|
lockRenewalTokenSource.Cancel();
|
||||||
|
// renew job request should never blows up.
|
||||||
|
await renewJobRequest;
|
||||||
|
|
||||||
|
// not finish the job request since the job haven't run on worker at all, we will not going to set a result to server.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we get first jobrequest renew succeed and start the worker process with the job message.
|
||||||
|
// send notification to machine provisioner.
|
||||||
|
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
|
||||||
|
var accessToken = systemConnection?.Authorization?.Parameters["AccessToken"];
|
||||||
|
notification.JobStarted(message.JobId, accessToken, systemConnection.Url);
|
||||||
|
|
||||||
|
HostContext.WritePerfCounter($"SentJobToWorker_{requestId.ToString()}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TaskResult resultOnAbandonOrCancel = TaskResult.Succeeded;
|
||||||
|
// wait for renewlock, worker process or cancellation token been fired.
|
||||||
|
var completedTask = await Task.WhenAny(renewJobRequest, workerProcessTask, Task.Delay(-1, jobRequestCancellationToken));
|
||||||
|
if (completedTask == workerProcessTask)
|
||||||
|
{
|
||||||
|
// worker finished successfully, complete job request with result, attach unhandled exception reported by worker, stop renew lock, job has finished.
|
||||||
|
int returnCode = await workerProcessTask;
|
||||||
|
Trace.Info($"Worker finished for job {message.JobId}. Code: " + returnCode);
|
||||||
|
|
||||||
|
string detailInfo = null;
|
||||||
|
if (!TaskResultUtil.IsValidReturnCode(returnCode))
|
||||||
|
{
|
||||||
|
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
||||||
|
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
||||||
|
await LogWorkerProcessUnhandledException(message, detailInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
|
||||||
|
Trace.Info($"finish job request for job {message.JobId} with result: {result}");
|
||||||
|
term.WriteLine($"{DateTime.UtcNow:u}: Job {message.JobDisplayName} completed with result: {result}");
|
||||||
|
|
||||||
|
Trace.Info($"Stop renew job request for job {message.JobId}.");
|
||||||
|
// stop renew lock
|
||||||
|
lockRenewalTokenSource.Cancel();
|
||||||
|
// renew job request should never blows up.
|
||||||
|
await renewJobRequest;
|
||||||
|
|
||||||
|
// complete job request
|
||||||
|
await CompleteJobRequestAsync(_poolId, message, lockToken, result, detailInfo);
|
||||||
|
|
||||||
|
// print out unhandled exception happened in worker after we complete job request.
|
||||||
|
// when we run out of disk space, report back to server has higher priority.
|
||||||
|
if (!string.IsNullOrEmpty(detailInfo))
|
||||||
|
{
|
||||||
|
Trace.Error("Unhandled exception happened in worker:");
|
||||||
|
Trace.Error(detailInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (completedTask == renewJobRequest)
|
||||||
|
{
|
||||||
|
resultOnAbandonOrCancel = TaskResult.Abandoned;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resultOnAbandonOrCancel = TaskResult.Canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// renew job request completed or job request cancellation token been fired for RunAsync(jobrequestmessage)
|
||||||
|
// cancel worker gracefully first, then kill it after worker cancel timeout
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Trace.Info($"Send job cancellation message to worker for job {message.JobId}.");
|
||||||
|
using (var csSendCancel = new CancellationTokenSource(_channelTimeout))
|
||||||
|
{
|
||||||
|
var messageType = MessageType.CancelRequest;
|
||||||
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
switch (HostContext.RunnerShutdownReason)
|
||||||
|
{
|
||||||
|
case ShutdownReason.UserCancelled:
|
||||||
|
messageType = MessageType.RunnerShutdown;
|
||||||
|
break;
|
||||||
|
case ShutdownReason.OperatingSystemShutdown:
|
||||||
|
messageType = MessageType.OperatingSystemShutdown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await processChannel.SendAsync(
|
||||||
|
messageType: messageType,
|
||||||
|
body: string.Empty,
|
||||||
|
cancellationToken: csSendCancel.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// message send been cancelled.
|
||||||
|
Trace.Info($"Job cancel message sending for job {message.JobId} been cancelled, kill running worker.");
|
||||||
|
workerProcessCancelTokenSource.Cancel();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await workerProcessTask;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Trace.Info("worker process has been killed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait worker to exit
|
||||||
|
// if worker doesn't exit within timeout, then kill worker.
|
||||||
|
completedTask = await Task.WhenAny(workerProcessTask, Task.Delay(-1, workerCancelTimeoutKillToken));
|
||||||
|
|
||||||
|
// worker haven't exit within cancellation timeout.
|
||||||
|
if (completedTask != workerProcessTask)
|
||||||
|
{
|
||||||
|
Trace.Info($"worker process for job {message.JobId} haven't exit within cancellation timout, kill running worker.");
|
||||||
|
workerProcessCancelTokenSource.Cancel();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await workerProcessTask;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Trace.Info("worker process has been killed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// When worker doesn't exit within cancel timeout, the runner will kill the worker process and worker won't finish upload job logs.
|
||||||
|
// The runner will try to upload these logs at this time.
|
||||||
|
await TryUploadUnfinishedLogs(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"finish job request for job {message.JobId} with result: {resultOnAbandonOrCancel}");
|
||||||
|
term.WriteLine($"{DateTime.UtcNow:u}: Job {message.JobDisplayName} completed with result: {resultOnAbandonOrCancel}");
|
||||||
|
// complete job request with cancel result, stop renew lock, job has finished.
|
||||||
|
|
||||||
Trace.Info($"Stop renew job request for job {message.JobId}.");
|
Trace.Info($"Stop renew job request for job {message.JobId}.");
|
||||||
// stop renew lock
|
// stop renew lock
|
||||||
@@ -488,115 +607,23 @@ namespace GitHub.Runner.Listener
|
|||||||
await renewJobRequest;
|
await renewJobRequest;
|
||||||
|
|
||||||
// complete job request
|
// complete job request
|
||||||
await CompleteJobRequestAsync(_poolId, message, lockToken, result, detailInfo);
|
await CompleteJobRequestAsync(_poolId, message, lockToken, resultOnAbandonOrCancel);
|
||||||
|
|
||||||
// print out unhandled exception happened in worker after we complete job request.
|
|
||||||
// when we run out of disk space, report back to server has higher priority.
|
|
||||||
if (!string.IsNullOrEmpty(detailInfo))
|
|
||||||
{
|
|
||||||
Trace.Error("Unhandled exception happened in worker:");
|
|
||||||
Trace.Error(detailInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else if (completedTask == renewJobRequest)
|
finally
|
||||||
{
|
{
|
||||||
resultOnAbandonOrCancel = TaskResult.Abandoned;
|
// This should be the last thing to run so we don't notify external parties until actually finished
|
||||||
|
await notification.JobCompleted(message.JobId);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
resultOnAbandonOrCancel = TaskResult.Canceled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// renew job request completed or job request cancellation token been fired for RunAsync(jobrequestmessage)
|
|
||||||
// cancel worker gracefully first, then kill it after worker cancel timeout
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Trace.Info($"Send job cancellation message to worker for job {message.JobId}.");
|
|
||||||
using (var csSendCancel = new CancellationTokenSource(_channelTimeout))
|
|
||||||
{
|
|
||||||
var messageType = MessageType.CancelRequest;
|
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
switch (HostContext.RunnerShutdownReason)
|
|
||||||
{
|
|
||||||
case ShutdownReason.UserCancelled:
|
|
||||||
messageType = MessageType.RunnerShutdown;
|
|
||||||
break;
|
|
||||||
case ShutdownReason.OperatingSystemShutdown:
|
|
||||||
messageType = MessageType.OperatingSystemShutdown;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await processChannel.SendAsync(
|
|
||||||
messageType: messageType,
|
|
||||||
body: string.Empty,
|
|
||||||
cancellationToken: csSendCancel.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// message send been cancelled.
|
|
||||||
Trace.Info($"Job cancel message sending for job {message.JobId} been cancelled, kill running worker.");
|
|
||||||
workerProcessCancelTokenSource.Cancel();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await workerProcessTask;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
Trace.Info("worker process has been killed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait worker to exit
|
|
||||||
// if worker doesn't exit within timeout, then kill worker.
|
|
||||||
completedTask = await Task.WhenAny(workerProcessTask, Task.Delay(-1, workerCancelTimeoutKillToken));
|
|
||||||
|
|
||||||
// worker haven't exit within cancellation timeout.
|
|
||||||
if (completedTask != workerProcessTask)
|
|
||||||
{
|
|
||||||
Trace.Info($"worker process for job {message.JobId} haven't exit within cancellation timout, kill running worker.");
|
|
||||||
workerProcessCancelTokenSource.Cancel();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await workerProcessTask;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
Trace.Info("worker process has been killed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// When worker doesn't exit within cancel timeout, the runner will kill the worker process and worker won't finish upload job logs.
|
|
||||||
// The runner will try to upload these logs at this time.
|
|
||||||
await TryUploadUnfinishedLogs(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"finish job request for job {message.JobId} with result: {resultOnAbandonOrCancel}");
|
|
||||||
term.WriteLine($"{DateTime.UtcNow:u}: Job {message.JobDisplayName} completed with result: {resultOnAbandonOrCancel}");
|
|
||||||
// complete job request with cancel result, stop renew lock, job has finished.
|
|
||||||
|
|
||||||
Trace.Info($"Stop renew job request for job {message.JobId}.");
|
|
||||||
// stop renew lock
|
|
||||||
lockRenewalTokenSource.Cancel();
|
|
||||||
// renew job request should never blows up.
|
|
||||||
await renewJobRequest;
|
|
||||||
|
|
||||||
// complete job request
|
|
||||||
await CompleteJobRequestAsync(_poolId, message, lockToken, resultOnAbandonOrCancel);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
// This should be the last thing to run so we don't notify external parties until actually finished
|
|
||||||
await notification.JobCompleted(message.JobId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Busy = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -609,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}");
|
||||||
|
|
||||||
@@ -831,7 +858,6 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We need send detailInfo back to DT in order to add an issue for the job
|
|
||||||
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null)
|
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
@@ -925,8 +951,10 @@ namespace GitHub.Runner.Listener
|
|||||||
ArgUtil.NotNull(timeline, nameof(timeline));
|
ArgUtil.NotNull(timeline, nameof(timeline));
|
||||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||||
|
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
||||||
|
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||||
jobRecord.ErrorCount++;
|
jobRecord.ErrorCount++;
|
||||||
jobRecord.Issues.Add(new Issue() { Type = IssueType.Error, Message = errorMessage });
|
jobRecord.Issues.Add(unhandledExceptionIssue);
|
||||||
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
await jobServer.UpdateTimelineRecordsAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, new TimelineRecord[] { jobRecord }, CancellationToken.None);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Info("Connecting to the Runner Server...");
|
Trace.Info("Connecting to the Runner Server...");
|
||||||
await _runnerServer.ConnectAsync(new Uri(serverUrl), creds);
|
await _runnerServer.ConnectAsync(new Uri(serverUrl), creds);
|
||||||
Trace.Info("VssConnection created");
|
Trace.Info("VssConnection created");
|
||||||
|
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
_term.WriteSuccessMessage("Connected to GitHub");
|
_term.WriteSuccessMessage("Connected to GitHub");
|
||||||
_term.WriteLine();
|
_term.WriteLine();
|
||||||
@@ -118,6 +118,20 @@ namespace GitHub.Runner.Listener
|
|||||||
Trace.Error("Catch exception during create session.");
|
Trace.Error("Catch exception during create session.");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
|
|
||||||
|
if (ex is VssOAuthTokenRequestException && creds.Federated is VssOAuthCredential vssOAuthCred)
|
||||||
|
{
|
||||||
|
// Check whether we get 401 because the runner registration already removed by the service.
|
||||||
|
// If the runner registration get deleted, we can't exchange oauth token.
|
||||||
|
Trace.Error("Test oauth app registration.");
|
||||||
|
var oauthTokenProvider = new VssOAuthTokenProvider(vssOAuthCred, new Uri(serverUrl));
|
||||||
|
var authError = await oauthTokenProvider.ValidateCredentialAsync(token);
|
||||||
|
if (string.Equals(authError, "invalid_client", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_term.WriteError("Failed to create a session. The runner registration has been deleted from the server, please re-configure.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsSessionCreationExceptionRetriable(ex))
|
if (!IsSessionCreationExceptionRetriable(ex))
|
||||||
{
|
{
|
||||||
_term.WriteError($"Failed to create session. {ex.Message}");
|
_term.WriteError($"Failed to create session. {ex.Message}");
|
||||||
|
|||||||
@@ -102,7 +102,9 @@ namespace GitHub.Runner.Listener
|
|||||||
IRunner runner = context.GetService<IRunner>();
|
IRunner runner = context.GetService<IRunner>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await runner.ExecuteCommand(command);
|
var returnCode = await runner.ExecuteCommand(command);
|
||||||
|
trace.Info($"Runner execution has finished with return code {returnCode}");
|
||||||
|
return returnCode;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (context.RunnerShutdownToken.IsCancellationRequested)
|
catch (OperationCanceledException) when (context.RunnerShutdownToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy);
|
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||||
|
|
||||||
_inConfigStage = true;
|
_inConfigStage = true;
|
||||||
_completedCommand.Reset();
|
_completedCommand.Reset();
|
||||||
@@ -466,6 +466,7 @@ Config Options:
|
|||||||
--url string Repository to add the runner to. Required if unattended
|
--url string Repository to add the runner to. Required if unattended
|
||||||
--token string Registration token. Required if unattended
|
--token string Registration token. Required if unattended
|
||||||
--name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"})
|
--name string Name of the runner to configure (default {Environment.MachineName ?? "myrunner"})
|
||||||
|
--labels string Extra labels in addition to the default: 'self-hosted,{Constants.Runner.Platform},{Constants.Runner.PlatformArchitecture}'
|
||||||
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
|
--work string Relative runner work directory (default {Constants.Path.WorkDirectory})
|
||||||
--replace Replace any existing runner with the same name (default false)");
|
--replace Replace any existing runner with the same name (default false)");
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
@@ -478,7 +479,9 @@ Examples:
|
|||||||
Configure a runner non-interactively:
|
Configure a runner non-interactively:
|
||||||
.{separator}config.{ext} --unattended --url <url> --token <token>
|
.{separator}config.{ext} --unattended --url <url> --token <token>
|
||||||
Configure a runner non-interactively, replacing any existing runner with the same name:
|
Configure a runner non-interactively, replacing any existing runner with the same name:
|
||||||
.{separator}config.{ext} --unattended --url <url> --token <token> --replace [--name <name>]");
|
.{separator}config.{ext} --unattended --url <url> --token <token> --replace [--name <name>]
|
||||||
|
Configure a runner non-interactively with three extra labels:
|
||||||
|
.{separator}config.{ext} --unattended --url <url> --token <token> --labels L1,L2,L3");
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
_term.WriteLine($@" Configure a runner to run as a service:");
|
_term.WriteLine($@" Configure a runner to run as a service:");
|
||||||
_term.WriteLine($@" .{separator}config.{ext} --url <url> --token <token> --runasservice");
|
_term.WriteLine($@" .{separator}config.{ext} --url <url> --token <token> --runasservice");
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace GitHub.Runner.Listener
|
|||||||
[ServiceLocator(Default = typeof(SelfUpdater))]
|
[ServiceLocator(Default = typeof(SelfUpdater))]
|
||||||
public interface ISelfUpdater : IRunnerService
|
public interface ISelfUpdater : IRunnerService
|
||||||
{
|
{
|
||||||
|
bool Busy { get; }
|
||||||
Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token);
|
Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +32,8 @@ namespace GitHub.Runner.Listener
|
|||||||
private int _poolId;
|
private int _poolId;
|
||||||
private int _agentId;
|
private int _agentId;
|
||||||
|
|
||||||
|
public bool Busy { get; private set; }
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
base.Initialize(hostContext);
|
base.Initialize(hostContext);
|
||||||
@@ -45,52 +48,60 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
public async Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token)
|
public async Task<bool> SelfUpdate(AgentRefreshMessage updateMessage, IJobDispatcher jobDispatcher, bool restartInteractiveRunner, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (!await UpdateNeeded(updateMessage.TargetVersion, token))
|
Busy = true;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Trace.Info($"Can't find available update package.");
|
if (!await UpdateNeeded(updateMessage.TargetVersion, token))
|
||||||
return false;
|
{
|
||||||
}
|
Trace.Info($"Can't find available update package.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Trace.Info($"An update is available.");
|
Trace.Info($"An update is available.");
|
||||||
|
|
||||||
// Print console line that warn user not shutdown runner.
|
// Print console line that warn user not shutdown runner.
|
||||||
await UpdateRunnerUpdateStateAsync("Runner update in progress, do not shutdown runner.");
|
await UpdateRunnerUpdateStateAsync("Runner update in progress, do not shutdown runner.");
|
||||||
await UpdateRunnerUpdateStateAsync($"Downloading {_targetPackage.Version} runner");
|
await UpdateRunnerUpdateStateAsync($"Downloading {_targetPackage.Version} runner");
|
||||||
|
|
||||||
await DownloadLatestRunner(token);
|
await DownloadLatestRunner(token);
|
||||||
Trace.Info($"Download latest runner and unzip into runner root.");
|
Trace.Info($"Download latest runner and unzip into runner root.");
|
||||||
|
|
||||||
// wait till all running job finish
|
// wait till all running job finish
|
||||||
await UpdateRunnerUpdateStateAsync("Waiting for current job finish running.");
|
await UpdateRunnerUpdateStateAsync("Waiting for current job finish running.");
|
||||||
|
|
||||||
await jobDispatcher.WaitAsync(token);
|
await jobDispatcher.WaitAsync(token);
|
||||||
Trace.Info($"All running job has exited.");
|
Trace.Info($"All running job has exited.");
|
||||||
|
|
||||||
// delete runner backup
|
// delete runner backup
|
||||||
DeletePreviousVersionRunnerBackup(token);
|
DeletePreviousVersionRunnerBackup(token);
|
||||||
Trace.Info($"Delete old version runner backup.");
|
Trace.Info($"Delete old version runner backup.");
|
||||||
|
|
||||||
// generate update script from template
|
// generate update script from template
|
||||||
await UpdateRunnerUpdateStateAsync("Generate and execute update script.");
|
await UpdateRunnerUpdateStateAsync("Generate and execute update script.");
|
||||||
|
|
||||||
string updateScript = GenerateUpdateScript(restartInteractiveRunner);
|
string updateScript = GenerateUpdateScript(restartInteractiveRunner);
|
||||||
Trace.Info($"Generate update script into: {updateScript}");
|
Trace.Info($"Generate update script into: {updateScript}");
|
||||||
|
|
||||||
// kick off update script
|
// kick off update script
|
||||||
Process invokeScript = new Process();
|
Process invokeScript = new Process();
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace);
|
invokeScript.StartInfo.FileName = WhichUtil.Which("cmd.exe", trace: Trace);
|
||||||
invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\"";
|
invokeScript.StartInfo.Arguments = $"/c \"{updateScript}\"";
|
||||||
#elif (OS_OSX || OS_LINUX)
|
#elif (OS_OSX || OS_LINUX)
|
||||||
invokeScript.StartInfo.FileName = WhichUtil.Which("bash", trace: Trace);
|
invokeScript.StartInfo.FileName = WhichUtil.Which("bash", trace: Trace);
|
||||||
invokeScript.StartInfo.Arguments = $"\"{updateScript}\"";
|
invokeScript.StartInfo.Arguments = $"\"{updateScript}\"";
|
||||||
#endif
|
#endif
|
||||||
invokeScript.Start();
|
invokeScript.Start();
|
||||||
Trace.Info($"Update script start running");
|
Trace.Info($"Update script start running");
|
||||||
|
|
||||||
await UpdateRunnerUpdateStateAsync("Runner will exit shortly for update, should back online within 10 seconds.");
|
await UpdateRunnerUpdateStateAsync("Runner will exit shortly for update, should back online within 10 seconds.");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Busy = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> UpdateNeeded(string targetVersion, CancellationToken token)
|
private async Task<bool> UpdateNeeded(string targetVersion, CancellationToken token)
|
||||||
|
|||||||
@@ -80,7 +80,12 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
|
|||||||
// Validate args.
|
// Validate args.
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
executionContext.Output($"Syncing repository: {repoFullName}");
|
executionContext.Output($"Syncing repository: {repoFullName}");
|
||||||
Uri repositoryUrl = new Uri($"https://github.com/{repoFullName}");
|
|
||||||
|
// Repository URL
|
||||||
|
var githubUrl = executionContext.GetGitHubContext("server_url");
|
||||||
|
var githubUri = new Uri(!string.IsNullOrEmpty(githubUrl) ? githubUrl : "https://github.com");
|
||||||
|
var portInfo = githubUri.IsDefaultPort ? string.Empty : $":{githubUri.Port}";
|
||||||
|
Uri repositoryUrl = new Uri($"{githubUri.Scheme}://{githubUri.Host}{portInfo}/{repoFullName}");
|
||||||
if (!repositoryUrl.IsAbsoluteUri)
|
if (!repositoryUrl.IsAbsoluteUri)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Repository url need to be an absolute uri.");
|
throw new InvalidOperationException("Repository url need to be an absolute uri.");
|
||||||
|
|||||||
@@ -271,6 +271,14 @@ namespace GitHub.Runner.Sdk
|
|||||||
// Indicate GitHub Actions process.
|
// Indicate GitHub Actions process.
|
||||||
_proc.StartInfo.Environment["GITHUB_ACTIONS"] = "true";
|
_proc.StartInfo.Environment["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 (!_proc.StartInfo.Environment.ContainsKey("CI") &&
|
||||||
|
Environment.GetEnvironmentVariable("CI") == null)
|
||||||
|
{
|
||||||
|
_proc.StartInfo.Environment["CI"] = "true";
|
||||||
|
}
|
||||||
|
|
||||||
// Hook up the events.
|
// Hook up the events.
|
||||||
_proc.EnableRaisingEvents = true;
|
_proc.EnableRaisingEvents = true;
|
||||||
_proc.Exited += ProcessExitedHandler;
|
_proc.Exited += ProcessExitedHandler;
|
||||||
@@ -310,7 +318,12 @@ namespace GitHub.Runner.Sdk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var registration = cancellationToken.Register(async () => await CancelAndKillProcessTree(killProcessOnCancel)))
|
var cancellationFinished = new TaskCompletionSource<bool>();
|
||||||
|
using (var registration = cancellationToken.Register(async () =>
|
||||||
|
{
|
||||||
|
await CancelAndKillProcessTree(killProcessOnCancel);
|
||||||
|
cancellationFinished.TrySetResult(true);
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
Trace.Info($"Process started with process id {_proc.Id}, waiting for process exit.");
|
Trace.Info($"Process started with process id {_proc.Id}, waiting for process exit.");
|
||||||
while (true)
|
while (true)
|
||||||
@@ -333,6 +346,13 @@ namespace GitHub.Runner.Sdk
|
|||||||
// data buffers one last time before returning
|
// data buffers one last time before returning
|
||||||
ProcessOutput();
|
ProcessOutput();
|
||||||
|
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Ensure cancellation also finish on the cancellationToken.Register thread.
|
||||||
|
await cancellationFinished.Task;
|
||||||
|
Trace.Info($"Process Cancellation finished.");
|
||||||
|
}
|
||||||
|
|
||||||
Trace.Info($"Finished process {_proc.Id} with exit code {_proc.ExitCode}, and elapsed time {_stopWatch.Elapsed}.");
|
Trace.Info($"Finished process {_proc.Id} with exit code {_proc.ExitCode}, and elapsed time {_stopWatch.Elapsed}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
private string _httpsProxyAddress;
|
private string _httpsProxyAddress;
|
||||||
private string _httpsProxyUsername;
|
private string _httpsProxyUsername;
|
||||||
private string _httpsProxyPassword;
|
private string _httpsProxyPassword;
|
||||||
|
private string _noProxyString;
|
||||||
|
|
||||||
private readonly List<ByPassInfo> _noProxyList = new List<ByPassInfo>();
|
private readonly List<ByPassInfo> _noProxyList = new List<ByPassInfo>();
|
||||||
private readonly HashSet<string> _noProxyUnique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _noProxyUnique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -33,6 +34,7 @@ namespace GitHub.Runner.Sdk
|
|||||||
public string HttpsProxyAddress => _httpsProxyAddress;
|
public string HttpsProxyAddress => _httpsProxyAddress;
|
||||||
public string HttpsProxyUsername => _httpsProxyUsername;
|
public string HttpsProxyUsername => _httpsProxyUsername;
|
||||||
public string HttpsProxyPassword => _httpsProxyPassword;
|
public string HttpsProxyPassword => _httpsProxyPassword;
|
||||||
|
public string NoProxyString => _noProxyString;
|
||||||
|
|
||||||
public List<ByPassInfo> NoProxyList => _noProxyList;
|
public List<ByPassInfo> NoProxyList => _noProxyList;
|
||||||
|
|
||||||
@@ -72,8 +74,8 @@ namespace GitHub.Runner.Sdk
|
|||||||
_httpProxyAddress = proxyHttpUri.AbsoluteUri;
|
_httpProxyAddress = proxyHttpUri.AbsoluteUri;
|
||||||
|
|
||||||
// Set both environment variables since there are tools support both casing (curl, wget) and tools support only one casing (docker)
|
// Set both environment variables since there are tools support both casing (curl, wget) and tools support only one casing (docker)
|
||||||
Environment.SetEnvironmentVariable("http_proxy", _httpProxyAddress);
|
|
||||||
Environment.SetEnvironmentVariable("HTTP_PROXY", _httpProxyAddress);
|
Environment.SetEnvironmentVariable("HTTP_PROXY", _httpProxyAddress);
|
||||||
|
Environment.SetEnvironmentVariable("http_proxy", _httpProxyAddress);
|
||||||
|
|
||||||
// the proxy url looks like http://[user:pass@]127.0.0.1:8888
|
// the proxy url looks like http://[user:pass@]127.0.0.1:8888
|
||||||
var userInfo = Uri.UnescapeDataString(proxyHttpUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
|
var userInfo = Uri.UnescapeDataString(proxyHttpUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
|
||||||
@@ -102,8 +104,8 @@ namespace GitHub.Runner.Sdk
|
|||||||
_httpsProxyAddress = proxyHttpsUri.AbsoluteUri;
|
_httpsProxyAddress = proxyHttpsUri.AbsoluteUri;
|
||||||
|
|
||||||
// Set both environment variables since there are tools support both casing (curl, wget) and tools support only one casing (docker)
|
// Set both environment variables since there are tools support both casing (curl, wget) and tools support only one casing (docker)
|
||||||
Environment.SetEnvironmentVariable("https_proxy", _httpsProxyAddress);
|
|
||||||
Environment.SetEnvironmentVariable("HTTPS_PROXY", _httpsProxyAddress);
|
Environment.SetEnvironmentVariable("HTTPS_PROXY", _httpsProxyAddress);
|
||||||
|
Environment.SetEnvironmentVariable("https_proxy", _httpsProxyAddress);
|
||||||
|
|
||||||
// the proxy url looks like http://[user:pass@]127.0.0.1:8888
|
// the proxy url looks like http://[user:pass@]127.0.0.1:8888
|
||||||
var userInfo = Uri.UnescapeDataString(proxyHttpsUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
|
var userInfo = Uri.UnescapeDataString(proxyHttpsUri.UserInfo).Split(':', 2, StringSplitOptions.RemoveEmptyEntries);
|
||||||
@@ -129,9 +131,11 @@ namespace GitHub.Runner.Sdk
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(noProxyList))
|
if (!string.IsNullOrEmpty(noProxyList))
|
||||||
{
|
{
|
||||||
|
_noProxyString = noProxyList;
|
||||||
|
|
||||||
// Set both environment variables since there are tools support both casing (curl, wget) and tools support only one casing (docker)
|
// Set both environment variables since there are tools support both casing (curl, wget) and tools support only one casing (docker)
|
||||||
Environment.SetEnvironmentVariable("no_proxy", noProxyList);
|
|
||||||
Environment.SetEnvironmentVariable("NO_PROXY", noProxyList);
|
Environment.SetEnvironmentVariable("NO_PROXY", noProxyList);
|
||||||
|
Environment.SetEnvironmentVariable("no_proxy", noProxyList);
|
||||||
|
|
||||||
var noProxyListSplit = noProxyList.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
var noProxyListSplit = noProxyList.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||||
foreach (string noProxy in noProxyListSplit)
|
foreach (string noProxy in noProxyListSplit)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(command, nameof(command));
|
ArgUtil.NotNullOrEmpty(command, nameof(command));
|
||||||
trace?.Info($"Which: '{command}'");
|
trace?.Info($"Which: '{command}'");
|
||||||
|
if (Path.IsPathFullyQualified(command) && File.Exists(command))
|
||||||
|
{
|
||||||
|
trace?.Info($"Fully qualified path: '{command}'");
|
||||||
|
return command;
|
||||||
|
}
|
||||||
string path = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
string path = Environment.GetEnvironmentVariable(PathUtil.PathVariable);
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common.Util;
|
using GitHub.Runner.Common.Util;
|
||||||
|
using GitHub.Runner.Worker.Container;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -15,14 +16,14 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
void EnablePluginInternalCommand();
|
void EnablePluginInternalCommand();
|
||||||
void DisablePluginInternalCommand();
|
void DisablePluginInternalCommand();
|
||||||
bool TryProcessCommand(IExecutionContext context, string input);
|
bool TryProcessCommand(IExecutionContext context, string input, ContainerInfo container);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ActionCommandManager : RunnerService, IActionCommandManager
|
public sealed class ActionCommandManager : RunnerService, IActionCommandManager
|
||||||
{
|
{
|
||||||
private const string _stopCommand = "stop-commands";
|
private const string _stopCommand = "stop-commands";
|
||||||
private readonly Dictionary<string, IActionCommandExtension> _commandExtensions = new Dictionary<string, IActionCommandExtension>(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, IActionCommandExtension> _commandExtensions = new Dictionary<string, IActionCommandExtension>(StringComparer.OrdinalIgnoreCase);
|
||||||
private HashSet<string> _registeredCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _registeredCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly object _commandSerializeLock = new object();
|
private readonly object _commandSerializeLock = new object();
|
||||||
private bool _stopProcessCommand = false;
|
private bool _stopProcessCommand = false;
|
||||||
private string _stopToken = null;
|
private string _stopToken = null;
|
||||||
@@ -58,7 +59,7 @@ namespace GitHub.Runner.Worker
|
|||||||
_registeredCommands.Remove("internal-set-repo-path");
|
_registeredCommands.Remove("internal-set-repo-path");
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryProcessCommand(IExecutionContext context, string input)
|
public bool TryProcessCommand(IExecutionContext context, string input, ContainerInfo container)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input))
|
if (string.IsNullOrEmpty(input))
|
||||||
{
|
{
|
||||||
@@ -114,7 +115,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
extension.ProcessCommand(context, input, actionCommand);
|
extension.ProcessCommand(context, input, actionCommand, container);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -140,7 +141,7 @@ namespace GitHub.Runner.Worker
|
|||||||
string Command { get; }
|
string Command { get; }
|
||||||
bool OmitEcho { get; }
|
bool OmitEcho { get; }
|
||||||
|
|
||||||
void ProcessCommand(IExecutionContext context, string line, ActionCommand command);
|
void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class InternalPluginSetRepoPathCommandExtension : RunnerService, IActionCommandExtension
|
public sealed class InternalPluginSetRepoPathCommandExtension : RunnerService, IActionCommandExtension
|
||||||
@@ -150,7 +151,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
if (!command.Properties.TryGetValue(SetRepoPathCommandProperties.repoFullName, out string repoFullName) || string.IsNullOrEmpty(repoFullName))
|
if (!command.Properties.TryGetValue(SetRepoPathCommandProperties.repoFullName, out string repoFullName) || string.IsNullOrEmpty(repoFullName))
|
||||||
{
|
{
|
||||||
@@ -180,7 +181,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
if (!command.Properties.TryGetValue(SetEnvCommandProperties.Name, out string envName) || string.IsNullOrEmpty(envName))
|
if (!command.Properties.TryGetValue(SetEnvCommandProperties.Name, out string envName) || string.IsNullOrEmpty(envName))
|
||||||
{
|
{
|
||||||
@@ -205,7 +206,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
|
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
|
||||||
{
|
{
|
||||||
@@ -229,7 +230,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
|
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
|
||||||
{
|
{
|
||||||
@@ -253,7 +254,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(command.Data))
|
if (string.IsNullOrWhiteSpace(command.Data))
|
||||||
{
|
{
|
||||||
@@ -279,7 +280,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(command.Data, "path");
|
ArgUtil.NotNullOrEmpty(command.Data, "path");
|
||||||
context.PrependPath.RemoveAll(x => string.Equals(x, command.Data, StringComparison.CurrentCulture));
|
context.PrependPath.RemoveAll(x => string.Equals(x, command.Data, StringComparison.CurrentCulture));
|
||||||
@@ -294,7 +295,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
var file = command.Data;
|
var file = command.Data;
|
||||||
|
|
||||||
@@ -306,9 +307,9 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Translate file path back from container path
|
// Translate file path back from container path
|
||||||
if (context.Container != null)
|
if (container != null)
|
||||||
{
|
{
|
||||||
file = context.Container.TranslateToHostPath(file);
|
file = container.TranslateToHostPath(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root the path
|
// Root the path
|
||||||
@@ -341,7 +342,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
command.Properties.TryGetValue(RemoveMatcherCommandProperties.Owner, out string owner);
|
command.Properties.TryGetValue(RemoveMatcherCommandProperties.Owner, out string owner);
|
||||||
var file = command.Data;
|
var file = command.Data;
|
||||||
@@ -369,9 +370,9 @@ namespace GitHub.Runner.Worker
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Translate file path back from container path
|
// Translate file path back from container path
|
||||||
if (context.Container != null)
|
if (container != null)
|
||||||
{
|
{
|
||||||
file = context.Container.TranslateToHostPath(file);
|
file = container.TranslateToHostPath(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root the path
|
// Root the path
|
||||||
@@ -409,7 +410,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
context.Debug(command.Data);
|
context.Debug(command.Data);
|
||||||
}
|
}
|
||||||
@@ -437,7 +438,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string inputLine, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
command.Properties.TryGetValue(IssueCommandProperties.File, out string file);
|
command.Properties.TryGetValue(IssueCommandProperties.File, out string file);
|
||||||
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
|
command.Properties.TryGetValue(IssueCommandProperties.Line, out string line);
|
||||||
@@ -454,10 +455,10 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
issue.Category = "Code";
|
issue.Category = "Code";
|
||||||
|
|
||||||
if (context.Container != null)
|
if (container != null)
|
||||||
{
|
{
|
||||||
// Translate file path back from container path
|
// Translate file path back from container path
|
||||||
file = context.Container.TranslateToHostPath(file);
|
file = container.TranslateToHostPath(file);
|
||||||
command.Properties[IssueCommandProperties.File] = file;
|
command.Properties[IssueCommandProperties.File] = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +486,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
foreach (var property in command.Properties)
|
foreach (var property in command.Properties)
|
||||||
{
|
{
|
||||||
issue.Data[property.Key] = property.Value;
|
if (!string.Equals(property.Key, Constants.Runner.InternalTelemetryIssueDataKey, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
issue.Data[property.Key] = property.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AddIssue(issue);
|
context.AddIssue(issue);
|
||||||
@@ -517,7 +521,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
var data = this is GroupCommandExtension ? command.Data : string.Empty;
|
var data = this is GroupCommandExtension ? command.Data : string.Empty;
|
||||||
context.Output($"##[{Command}]{data}");
|
context.Output($"##[{Command}]{data}");
|
||||||
@@ -531,7 +535,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(command.Data, "value");
|
ArgUtil.NotNullOrEmpty(command.Data, "value");
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,43 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.WebApi;
|
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using Newtonsoft.Json;
|
using WebApi = GitHub.DistributedTask.WebApi;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
|
public class PrepareResult
|
||||||
|
{
|
||||||
|
public PrepareResult(List<JobExtensionRunner> containerSetupSteps, Dictionary<Guid, IActionRunner> preStepTracker)
|
||||||
|
{
|
||||||
|
this.ContainerSetupSteps = containerSetupSteps;
|
||||||
|
this.PreStepTracker = preStepTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JobExtensionRunner> ContainerSetupSteps { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<Guid, IActionRunner> PreStepTracker { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(ActionManager))]
|
[ServiceLocator(Default = typeof(ActionManager))]
|
||||||
public interface IActionManager : IRunnerService
|
public interface IActionManager : IRunnerService
|
||||||
{
|
{
|
||||||
Dictionary<Guid, ContainerInfo> CachedActionContainers { get; }
|
Dictionary<Guid, ContainerInfo> CachedActionContainers { get; }
|
||||||
Task<List<JobExtensionRunner>> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps);
|
Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps);
|
||||||
Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action);
|
Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,11 +47,11 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
//81920 is the default used by System.IO.Stream.CopyTo and is under the large object heap threshold (85k).
|
||||||
private const int _defaultCopyBufferSize = 81920;
|
private const int _defaultCopyBufferSize = 81920;
|
||||||
|
private const string _dotcomApiUrl = "https://api.github.com";
|
||||||
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
private readonly Dictionary<Guid, ContainerInfo> _cachedActionContainers = new Dictionary<Guid, ContainerInfo>();
|
||||||
|
|
||||||
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
||||||
public async Task<List<JobExtensionRunner>> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
ArgUtil.NotNull(steps, nameof(steps));
|
ArgUtil.NotNull(steps, nameof(steps));
|
||||||
@@ -49,18 +61,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);
|
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
||||||
|
|
||||||
|
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
||||||
|
var newActionMetadata = executionContext.Variables.GetBoolean("DistributedTask.NewActionMetadata") ?? false;
|
||||||
|
|
||||||
|
var repositoryActions = new List<Pipelines.ActionStep>();
|
||||||
|
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
{
|
{
|
||||||
if (action.Reference.Type == Pipelines.ActionSourceType.ContainerRegistry)
|
if (action.Reference.Type == Pipelines.ActionSourceType.ContainerRegistry)
|
||||||
@@ -78,7 +96,8 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Action {action.Name} ({action.Id}) needs to pull image '{containerReference.Image}'");
|
Trace.Info($"Action {action.Name} ({action.Id}) needs to pull image '{containerReference.Image}'");
|
||||||
imagesToPull[containerReference.Image].Add(action.Id);
|
imagesToPull[containerReference.Image].Add(action.Id);
|
||||||
}
|
}
|
||||||
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository)
|
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
||||||
|
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository && !newActionMetadata)
|
||||||
{
|
{
|
||||||
// only download the repository archive
|
// only download the repository archive
|
||||||
await DownloadRepositoryActionAsync(executionContext, action);
|
await DownloadRepositoryActionAsync(executionContext, action);
|
||||||
@@ -111,6 +130,97 @@ namespace GitHub.Runner.Worker
|
|||||||
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
|
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
||||||
|
{
|
||||||
|
var definition = LoadAction(executionContext, action);
|
||||||
|
if (definition.Data.Execution.HasPre)
|
||||||
|
{
|
||||||
|
var actionRunner = HostContext.CreateService<IActionRunner>();
|
||||||
|
actionRunner.Action = action;
|
||||||
|
actionRunner.Stage = ActionRunStage.Pre;
|
||||||
|
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
||||||
|
|
||||||
|
Trace.Info($"Add 'pre' execution for {action.Id}");
|
||||||
|
preStepTracker[action.Id] = actionRunner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository && newActionMetadata)
|
||||||
|
{
|
||||||
|
repositoryActions.Add(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repositoryActions.Count > 0)
|
||||||
|
{
|
||||||
|
// Get the download info
|
||||||
|
var downloadInfos = await GetDownloadInfoAsync(executionContext, repositoryActions);
|
||||||
|
|
||||||
|
// Download each action
|
||||||
|
foreach (var action in repositoryActions)
|
||||||
|
{
|
||||||
|
var lookupKey = GetDownloadInfoLookupKey(action);
|
||||||
|
if (string.IsNullOrEmpty(lookupKey))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!downloadInfos.TryGetValue(lookupKey, out var downloadInfo))
|
||||||
|
{
|
||||||
|
throw new Exception($"Missing download info for {lookupKey}");
|
||||||
|
}
|
||||||
|
|
||||||
|
await DownloadRepositoryActionAsync(executionContext, downloadInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// More preparation based on content in the repository (action.yml)
|
||||||
|
foreach (var action in repositoryActions)
|
||||||
|
{
|
||||||
|
var setupInfo = PrepareRepositoryActionAsync(executionContext, action);
|
||||||
|
if (setupInfo != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(setupInfo.Image))
|
||||||
|
{
|
||||||
|
if (!imagesToPull.ContainsKey(setupInfo.Image))
|
||||||
|
{
|
||||||
|
imagesToPull[setupInfo.Image] = new List<Guid>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to pull image '{setupInfo.Image}'");
|
||||||
|
imagesToPull[setupInfo.Image].Add(action.Id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ArgUtil.NotNullOrEmpty(setupInfo.ActionRepository, nameof(setupInfo.ActionRepository));
|
||||||
|
|
||||||
|
if (!imagesToBuild.ContainsKey(setupInfo.ActionRepository))
|
||||||
|
{
|
||||||
|
imagesToBuild[setupInfo.ActionRepository] = new List<Guid>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to build image '{setupInfo.Dockerfile}'");
|
||||||
|
imagesToBuild[setupInfo.ActionRepository].Add(action.Id);
|
||||||
|
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
|
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
||||||
|
{
|
||||||
|
var definition = LoadAction(executionContext, action);
|
||||||
|
if (definition.Data.Execution.HasPre)
|
||||||
|
{
|
||||||
|
var actionRunner = HostContext.CreateService<IActionRunner>();
|
||||||
|
actionRunner.Action = action;
|
||||||
|
actionRunner.Stage = ActionRunStage.Pre;
|
||||||
|
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
||||||
|
|
||||||
|
Trace.Info($"Add 'pre' execution for {action.Id}");
|
||||||
|
preStepTracker[action.Id] = actionRunner;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +257,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 +349,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 +373,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 +391,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}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,7 +512,12 @@ namespace GitHub.Runner.Worker
|
|||||||
var imageName = $"{dockerManger.DockerInstanceLabel}:{Guid.NewGuid().ToString("N")}";
|
var imageName = $"{dockerManger.DockerInstanceLabel}:{Guid.NewGuid().ToString("N")}";
|
||||||
while (retryCount < 3)
|
while (retryCount < 3)
|
||||||
{
|
{
|
||||||
buildExitCode = await dockerManger.DockerBuild(executionContext, setupInfo.Container.WorkingDirectory, Directory.GetParent(setupInfo.Container.Dockerfile).FullName, imageName);
|
buildExitCode = await dockerManger.DockerBuild(
|
||||||
|
executionContext,
|
||||||
|
setupInfo.Container.WorkingDirectory,
|
||||||
|
setupInfo.Container.Dockerfile,
|
||||||
|
Directory.GetParent(setupInfo.Container.Dockerfile).FullName,
|
||||||
|
imageName);
|
||||||
if (buildExitCode == 0)
|
if (buildExitCode == 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -425,6 +546,143 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This implementation is temporary and will be removed when we switch to a REST API call to the service to resolve the download info
|
||||||
|
private async Task<bool> RepoExistsAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo actionDownloadInfo, string token)
|
||||||
|
{
|
||||||
|
var apiUrl = GetApiUrl(executionContext);
|
||||||
|
var repoUrl = $"{apiUrl}/repos/{actionDownloadInfo.NameWithOwner}";
|
||||||
|
for (var attempt = 1; attempt <= 3; attempt++)
|
||||||
|
{
|
||||||
|
executionContext.Debug($"Checking whether repo exists: {repoUrl}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(token);
|
||||||
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
|
using (var response = await httpClient.GetAsync(repoUrl))
|
||||||
|
{
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Throw
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (attempt < 3)
|
||||||
|
{
|
||||||
|
executionContext.Debug($"Failed checking whether repo '{actionDownloadInfo.NameWithOwner}' exists: {ex.Message}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
executionContext.Error($"Failed checking whether repo '{actionDownloadInfo.NameWithOwner}' exists: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Never reaches here
|
||||||
|
}
|
||||||
|
|
||||||
|
// This implementation is temporary and will be replaced with a REST API call to the service to resolve
|
||||||
|
private async Task<IDictionary<string, WebApi.ActionDownloadInfo>> GetDownloadInfoAsync(IExecutionContext executionContext, List<Pipelines.ActionStep> actions)
|
||||||
|
{
|
||||||
|
executionContext.Output("Getting action download info");
|
||||||
|
|
||||||
|
// Convert to action reference
|
||||||
|
var actionReferences = actions
|
||||||
|
.GroupBy(x => GetDownloadInfoLookupKey(x))
|
||||||
|
.Where(x => !string.IsNullOrEmpty(x.Key))
|
||||||
|
.Select(x =>
|
||||||
|
{
|
||||||
|
var action = x.First();
|
||||||
|
var repositoryReference = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
|
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
|
||||||
|
return new WebApi.ActionReference
|
||||||
|
{
|
||||||
|
NameWithOwner = repositoryReference.Name,
|
||||||
|
Ref = repositoryReference.Ref,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Nothing to resolve?
|
||||||
|
if (actionReferences.Count == 0)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, WebApi.ActionDownloadInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve download info
|
||||||
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
|
var actionDownloadInfos = default(WebApi.ActionDownloadInfoCollection);
|
||||||
|
for (var attempt = 1; attempt <= 3; attempt++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Plan.ScopeIdentifier, executionContext.Plan.PlanType, executionContext.Plan.PlanId, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (attempt < 3)
|
||||||
|
{
|
||||||
|
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
||||||
|
executionContext.Debug(ex.ToString());
|
||||||
|
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
|
||||||
|
{
|
||||||
|
var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
|
||||||
|
executionContext.Output($"Retrying in {backoff.TotalSeconds} seconds");
|
||||||
|
await Task.Delay(backoff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgUtil.NotNull(actionDownloadInfos, nameof(actionDownloadInfos));
|
||||||
|
ArgUtil.NotNull(actionDownloadInfos.Actions, nameof(actionDownloadInfos.Actions));
|
||||||
|
var apiUrl = GetApiUrl(executionContext);
|
||||||
|
var defaultAccessToken = executionContext.GetGitHubContext("token");
|
||||||
|
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||||
|
var runnerSettings = configurationStore.GetSettings();
|
||||||
|
|
||||||
|
foreach (var actionDownloadInfo in actionDownloadInfos.Actions.Values)
|
||||||
|
{
|
||||||
|
// Add secret
|
||||||
|
HostContext.SecretMasker.AddValue(actionDownloadInfo.Authentication?.Token);
|
||||||
|
|
||||||
|
// Temporary code: Fix token and download URL
|
||||||
|
if (runnerSettings.IsHostedServer)
|
||||||
|
{
|
||||||
|
actionDownloadInfo.Authentication = new WebApi.ActionDownloadAuthentication { Token = defaultAccessToken };
|
||||||
|
actionDownloadInfo.TarballUrl = actionDownloadInfo.TarballUrl.Replace("<GITHUB_API_URL>", apiUrl);
|
||||||
|
actionDownloadInfo.ZipballUrl = actionDownloadInfo.ZipballUrl.Replace("<GITHUB_API_URL>", apiUrl);
|
||||||
|
}
|
||||||
|
else if (await RepoExistsAsync(executionContext, actionDownloadInfo, defaultAccessToken))
|
||||||
|
{
|
||||||
|
actionDownloadInfo.Authentication = new WebApi.ActionDownloadAuthentication { Token = defaultAccessToken };
|
||||||
|
actionDownloadInfo.TarballUrl = actionDownloadInfo.TarballUrl.Replace("<GITHUB_API_URL>", apiUrl);
|
||||||
|
actionDownloadInfo.ZipballUrl = actionDownloadInfo.ZipballUrl.Replace("<GITHUB_API_URL>", apiUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actionDownloadInfo.TarballUrl = actionDownloadInfo.TarballUrl.Replace("<GITHUB_API_URL>", "https://api.github.com");
|
||||||
|
actionDownloadInfo.ZipballUrl = actionDownloadInfo.ZipballUrl.Replace("<GITHUB_API_URL>", "https://api.github.com");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionDownloadInfos.Actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
||||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
@@ -448,7 +706,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 = GetWatermarkFilePath(destDirectory);
|
||||||
|
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;
|
||||||
@@ -461,27 +720,116 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'");
|
executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if OS_WINDOWS
|
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
||||||
string archiveLink = $"https://api.github.com/repos/{repositoryReference.Name}/zipball/{repositoryReference.Ref}";
|
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
|
||||||
#else
|
if (isHostedServer)
|
||||||
string archiveLink = $"https://api.github.com/repos/{repositoryReference.Name}/tarball/{repositoryReference.Ref}";
|
{
|
||||||
#endif
|
string apiUrl = GetApiUrl(executionContext);
|
||||||
Trace.Info($"Download archive '{archiveLink}' to '{destDirectory}'.");
|
string archiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref);
|
||||||
|
var downloadDetails = new ActionDownloadDetails(archiveLink, ConfigureAuthorizationFromContext);
|
||||||
|
await DownloadRepositoryActionAsync(executionContext, downloadDetails, null, destDirectory);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string apiUrl = GetApiUrl(executionContext);
|
||||||
|
|
||||||
|
// URLs to try:
|
||||||
|
var downloadAttempts = new List<ActionDownloadDetails> {
|
||||||
|
// A built-in action or an action the user has created, on their GHES instance
|
||||||
|
// Example: https://my-ghes/api/v3/repos/my-org/my-action/tarball/v1
|
||||||
|
new ActionDownloadDetails(
|
||||||
|
BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
|
||||||
|
ConfigureAuthorizationFromContext),
|
||||||
|
|
||||||
|
// The same action, on GitHub.com
|
||||||
|
// Example: https://api.github.com/repos/my-org/my-action/tarball/v1
|
||||||
|
new ActionDownloadDetails(
|
||||||
|
BuildLinkToActionArchive(_dotcomApiUrl, repositoryReference.Name, repositoryReference.Ref),
|
||||||
|
configureAuthorization: (e,h) => { /* no authorization for dotcom */ })
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var downloadAttempt in downloadAttempts)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await DownloadRepositoryActionAsync(executionContext, downloadAttempt, null, destDirectory);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (ActionNotFoundException)
|
||||||
|
{
|
||||||
|
Trace.Info($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}' at {downloadAttempt.ArchiveLink}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ActionNotFoundException($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}'. Paths attempted: {string.Join(", ", downloadAttempts.Select(d => d.ArchiveLink))}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo)
|
||||||
|
{
|
||||||
|
Trace.Entering();
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
|
ArgUtil.NotNull(downloadInfo, nameof(downloadInfo));
|
||||||
|
ArgUtil.NotNullOrEmpty(downloadInfo.NameWithOwner, nameof(downloadInfo.NameWithOwner));
|
||||||
|
ArgUtil.NotNullOrEmpty(downloadInfo.Ref, nameof(downloadInfo.Ref));
|
||||||
|
|
||||||
|
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), downloadInfo.NameWithOwner.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), downloadInfo.Ref);
|
||||||
|
string watermarkFile = GetWatermarkFilePath(destDirectory);
|
||||||
|
if (File.Exists(watermarkFile))
|
||||||
|
{
|
||||||
|
executionContext.Debug($"Action '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}' already downloaded at '{destDirectory}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// make sure we get a clean folder ready to use.
|
||||||
|
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
|
||||||
|
Directory.CreateDirectory(destDirectory);
|
||||||
|
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
await DownloadRepositoryActionAsync(executionContext, null, downloadInfo, destDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetApiUrl(IExecutionContext executionContext)
|
||||||
|
{
|
||||||
|
string apiUrl = executionContext.GetGitHubContext("api_url");
|
||||||
|
if (!string.IsNullOrEmpty(apiUrl))
|
||||||
|
{
|
||||||
|
return apiUrl;
|
||||||
|
}
|
||||||
|
// Once the api_url is set for hosted, we can remove this fallback (it doesn't make sense for GHES)
|
||||||
|
return _dotcomApiUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildLinkToActionArchive(string apiUrl, string repository, string @ref)
|
||||||
|
{
|
||||||
|
#if OS_WINDOWS
|
||||||
|
return $"{apiUrl}/repos/{repository}/zipball/{@ref}";
|
||||||
|
#else
|
||||||
|
return $"{apiUrl}/repos/{repository}/tarball/{@ref}";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: Remove the parameter "actionDownloadDetails" when feature flag DistributedTask.NewActionMetadata is removed
|
||||||
|
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadDetails actionDownloadDetails, WebApi.ActionDownloadInfo downloadInfo, string destDirectory)
|
||||||
|
{
|
||||||
//download and extract action in a temp folder and rename it on success
|
//download and extract action in a temp folder and rename it on success
|
||||||
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
|
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
|
||||||
Directory.CreateDirectory(tempDirectory);
|
Directory.CreateDirectory(tempDirectory);
|
||||||
|
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip");
|
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip");
|
||||||
|
string link = downloadInfo?.ZipballUrl ?? actionDownloadDetails.ArchiveLink;
|
||||||
#else
|
#else
|
||||||
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
|
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
|
||||||
|
string link = downloadInfo?.TarballUrl ?? actionDownloadDetails.ArchiveLink;
|
||||||
#endif
|
#endif
|
||||||
Trace.Info($"Save archive '{archiveLink}' into {archiveFile}.");
|
|
||||||
|
Trace.Info($"Save archive '{link}' into {archiveFile}.");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
|
|
||||||
// Allow up to 20 * 60s for any action to be downloaded from github graph.
|
// Allow up to 20 * 60s for any action to be downloaded from github graph.
|
||||||
@@ -498,55 +846,67 @@ namespace GitHub.Runner.Worker
|
|||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
{
|
{
|
||||||
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
|
// Legacy
|
||||||
if (string.IsNullOrEmpty(authToken))
|
if (downloadInfo == null)
|
||||||
{
|
{
|
||||||
// TODO: Depreciate the PREVIEW_ACTION_TOKEN
|
actionDownloadDetails.ConfigureAuthorization(executionContext, httpClient);
|
||||||
authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(authToken))
|
|
||||||
{
|
|
||||||
HostContext.SecretMasker.AddValue(authToken);
|
|
||||||
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
|
|
||||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
|
||||||
}
|
}
|
||||||
|
// FF DistributedTask.NewActionMetadata
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var accessToken = executionContext.GetGitHubContext("token");
|
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
||||||
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 response = await httpClient.GetAsync(link))
|
||||||
{
|
{
|
||||||
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
if (response.IsSuccessStatusCode)
|
||||||
await fs.FlushAsync(actionDownloadCancellation.Token);
|
{
|
||||||
|
using (var result = await response.Content.ReadAsStreamAsync())
|
||||||
|
{
|
||||||
|
await result.CopyToAsync(fs, _defaultCopyBufferSize, actionDownloadCancellation.Token);
|
||||||
|
await fs.FlushAsync(actionDownloadCancellation.Token);
|
||||||
|
|
||||||
// download succeed, break out the retry loop.
|
// download succeed, break out the retry loop.
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (response.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
// It doesn't make sense to retry in this case, so just stop
|
||||||
|
throw new ActionNotFoundException(new Uri(link));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Something else bad happened, let's go to our retry logic
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
|
catch (OperationCanceledException) when (executionContext.CancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
Trace.Info($"Action download has been cancelled.");
|
Trace.Info("Action download has been cancelled.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (ActionNotFoundException)
|
||||||
|
{
|
||||||
|
Trace.Info($"The action at '{link}' does not exist");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (retryCount < 2)
|
catch (Exception ex) when (retryCount < 2)
|
||||||
{
|
{
|
||||||
retryCount++;
|
retryCount++;
|
||||||
Trace.Error($"Fail to download archive '{archiveLink}' -- Attempt: {retryCount}");
|
Trace.Error($"Fail to download archive '{link}' -- Attempt: {retryCount}");
|
||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
if (actionDownloadTimeout.Token.IsCancellationRequested)
|
if (actionDownloadTimeout.Token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
// action download didn't finish within timeout
|
// action download didn't finish within timeout
|
||||||
executionContext.Warning($"Action '{archiveLink}' didn't finish download within {timeoutSeconds} seconds.");
|
executionContext.Warning($"Action '{link}' didn't finish download within {timeoutSeconds} seconds.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
executionContext.Warning($"Failed to download action '{archiveLink}'. Error {ex.Message}");
|
executionContext.Warning($"Failed to download action '{link}'. Error: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,7 +920,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile));
|
ArgUtil.NotNullOrEmpty(archiveFile, nameof(archiveFile));
|
||||||
executionContext.Debug($"Download '{archiveLink}' to '{archiveFile}'");
|
executionContext.Debug($"Download '{link}' to '{archiveFile}'");
|
||||||
|
|
||||||
var stagingDirectory = Path.Combine(tempDirectory, "_staging");
|
var stagingDirectory = Path.Combine(tempDirectory, "_staging");
|
||||||
Directory.CreateDirectory(stagingDirectory);
|
Directory.CreateDirectory(stagingDirectory);
|
||||||
@@ -610,7 +970,8 @@ 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());
|
string watermarkFile = GetWatermarkFilePath(destDirectory);
|
||||||
|
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.");
|
||||||
@@ -634,6 +995,32 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
||||||
|
private void ConfigureAuthorizationFromContext(IExecutionContext executionContext, HttpClient httpClient)
|
||||||
|
{
|
||||||
|
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
|
||||||
|
if (string.IsNullOrEmpty(authToken))
|
||||||
|
{
|
||||||
|
// TODO: Deprecate the PREVIEW_ACTION_TOKEN
|
||||||
|
authToken = executionContext.Variables.Get("PREVIEW_ACTION_TOKEN");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(authToken))
|
||||||
|
{
|
||||||
|
HostContext.SecretMasker.AddValue(authToken);
|
||||||
|
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"PAT:{authToken}"));
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var accessToken = executionContext.GetGitHubContext("token");
|
||||||
|
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}"));
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetWatermarkFilePath(string directory) => directory + ".completed";
|
||||||
|
|
||||||
private ActionContainer PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
private ActionContainer PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
||||||
{
|
{
|
||||||
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
|
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
|
||||||
@@ -739,6 +1126,64 @@ namespace GitHub.Runner.Worker
|
|||||||
throw new InvalidOperationException($"Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '{fullPath}'. Did you forget to run actions/checkout before running your local action?");
|
throw new InvalidOperationException($"Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '{fullPath}'. Did you forget to run actions/checkout before running your local action?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetDownloadInfoLookupKey(Pipelines.ActionStep action)
|
||||||
|
{
|
||||||
|
if (action.Reference.Type != Pipelines.ActionSourceType.Repository)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var repositoryReference = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
|
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
|
||||||
|
|
||||||
|
if (string.Equals(repositoryReference.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(repositoryReference.RepositoryType, Pipelines.RepositoryTypes.GitHub, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(repositoryReference.RepositoryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgUtil.NotNullOrEmpty(repositoryReference.Name, nameof(repositoryReference.Name));
|
||||||
|
ArgUtil.NotNullOrEmpty(repositoryReference.Ref, nameof(repositoryReference.Ref));
|
||||||
|
return $"{repositoryReference.Name}@{repositoryReference.Ref}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetDownloadInfoLookupKey(WebApi.ActionDownloadInfo info)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNullOrEmpty(info.NameWithOwner, nameof(info.NameWithOwner));
|
||||||
|
ArgUtil.NotNullOrEmpty(info.Ref, nameof(info.Ref));
|
||||||
|
return $"{info.NameWithOwner}@{info.Ref}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthenticationHeaderValue CreateAuthHeader(string token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{token}"));
|
||||||
|
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
||||||
|
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
||||||
|
private class ActionDownloadDetails
|
||||||
|
{
|
||||||
|
public string ArchiveLink { get; }
|
||||||
|
|
||||||
|
public Action<IExecutionContext, HttpClient> ConfigureAuthorization { get; }
|
||||||
|
|
||||||
|
public ActionDownloadDetails(string archiveLink, Action<IExecutionContext, HttpClient> configureAuthorization)
|
||||||
|
{
|
||||||
|
ArchiveLink = archiveLink;
|
||||||
|
ConfigureAuthorization = configureAuthorization;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Definition
|
public sealed class Definition
|
||||||
@@ -772,7 +1217,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 +1228,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
|
||||||
@@ -863,4 +1324,3 @@ namespace GitHub.Runner.Worker
|
|||||||
public string ActionRepository { get; set; }
|
public string ActionRepository { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,17 @@ 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
|
||||||
{
|
{
|
||||||
private TemplateSchema _actionManifestSchema;
|
private TemplateSchema _actionManifestSchema;
|
||||||
|
private IReadOnlyList<String> _fileTable;
|
||||||
|
|
||||||
public override void Initialize(IHostContext hostContext)
|
public override void Initialize(IHostContext hostContext)
|
||||||
{
|
{
|
||||||
@@ -53,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
|
||||||
{
|
{
|
||||||
@@ -61,6 +62,9 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Get the file ID
|
// Get the file ID
|
||||||
var fileId = context.GetFileId(manifestFile);
|
var fileId = context.GetFileId(manifestFile);
|
||||||
|
_fileTable = context.GetFileTable();
|
||||||
|
|
||||||
|
// Read the file
|
||||||
var fileContent = File.ReadAllText(manifestFile);
|
var fileContent = File.ReadAllText(manifestFile);
|
||||||
using (var stringReader = new StringReader(fileContent))
|
using (var stringReader = new StringReader(fileContent))
|
||||||
{
|
{
|
||||||
@@ -129,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);
|
||||||
@@ -168,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);
|
||||||
@@ -212,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);
|
||||||
@@ -243,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
|
||||||
{
|
{
|
||||||
@@ -257,14 +260,36 @@ 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
|
||||||
|
if (_fileTable?.Count > 0)
|
||||||
|
{
|
||||||
|
for (var i = 0 ; i < _fileTable.Count ; i++)
|
||||||
|
{
|
||||||
|
result.GetFileId(_fileTable[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,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);
|
||||||
@@ -318,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;
|
||||||
@@ -340,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()"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -349,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()"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -415,566 +456,5 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a YAML file into a TemplateToken
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class YamlObjectReader : IObjectReader
|
|
||||||
{
|
|
||||||
internal YamlObjectReader(
|
|
||||||
Int32? fileId,
|
|
||||||
TextReader input)
|
|
||||||
{
|
|
||||||
m_fileId = fileId;
|
|
||||||
m_parser = new Parser(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowLiteral(out LiteralToken value)
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is Scalar scalar)
|
|
||||||
{
|
|
||||||
// Tag specified
|
|
||||||
if (!string.IsNullOrEmpty(scalar.Tag))
|
|
||||||
{
|
|
||||||
// String tag
|
|
||||||
if (string.Equals(scalar.Tag, c_stringTag, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, scalar.Value);
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not plain style
|
|
||||||
if (scalar.Style != ScalarStyle.Plain)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"The scalar style '{scalar.Style}' on line {scalar.Start.Line} and column {scalar.Start.Column} is not valid with the tag '{scalar.Tag}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boolean, Float, Integer, or Null
|
|
||||||
switch (scalar.Tag)
|
|
||||||
{
|
|
||||||
case c_booleanTag:
|
|
||||||
value = ParseBoolean(scalar);
|
|
||||||
break;
|
|
||||||
case c_floatTag:
|
|
||||||
value = ParseFloat(scalar);
|
|
||||||
break;
|
|
||||||
case c_integerTag:
|
|
||||||
value = ParseInteger(scalar);
|
|
||||||
break;
|
|
||||||
case c_nullTag:
|
|
||||||
value = ParseNull(scalar);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NotSupportedException($"Unexpected tag '{scalar.Tag}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Plain style, determine type using YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
if (scalar.Style == ScalarStyle.Plain)
|
|
||||||
{
|
|
||||||
if (MatchNull(scalar, out var nullToken))
|
|
||||||
{
|
|
||||||
value = nullToken;
|
|
||||||
}
|
|
||||||
else if (MatchBoolean(scalar, out var booleanToken))
|
|
||||||
{
|
|
||||||
value = booleanToken;
|
|
||||||
}
|
|
||||||
else if (MatchInteger(scalar, out var numberToken) ||
|
|
||||||
MatchFloat(scalar, out numberToken))
|
|
||||||
{
|
|
||||||
value = numberToken;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, scalar.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise assume string
|
|
||||||
value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, scalar.Value);
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowSequenceStart(out SequenceToken value)
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is SequenceStart sequenceStart)
|
|
||||||
{
|
|
||||||
value = new SequenceToken(m_fileId, sequenceStart.Start.Line, sequenceStart.Start.Column);
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowSequenceEnd()
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is SequenceEnd)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowMappingStart(out MappingToken value)
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is MappingStart mappingStart)
|
|
||||||
{
|
|
||||||
value = new MappingToken(m_fileId, mappingStart.Start.Line, mappingStart.Start.Column);
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean AllowMappingEnd()
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is MappingEnd)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Consumes the last parsing events, which are expected to be DocumentEnd and StreamEnd.
|
|
||||||
/// </summary>
|
|
||||||
public void ValidateEnd()
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() is DocumentEnd)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected document end parse event");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EvaluateCurrent() is StreamEnd)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected stream end parse event");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MoveNext())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected end of parse events");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Consumes the first parsing events, which are expected to be StreamStart and DocumentStart.
|
|
||||||
/// </summary>
|
|
||||||
public void ValidateStart()
|
|
||||||
{
|
|
||||||
if (EvaluateCurrent() != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Unexpected parser state");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!MoveNext())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected a parse event");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EvaluateCurrent() is StreamStart)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected stream start parse event");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EvaluateCurrent() is DocumentStart)
|
|
||||||
{
|
|
||||||
MoveNext();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Expected document start parse event");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ParsingEvent EvaluateCurrent()
|
|
||||||
{
|
|
||||||
if (m_current == null)
|
|
||||||
{
|
|
||||||
m_current = m_parser.Current;
|
|
||||||
if (m_current != null)
|
|
||||||
{
|
|
||||||
if (m_current is Scalar scalar)
|
|
||||||
{
|
|
||||||
// Verify not using achors
|
|
||||||
if (scalar.Anchor != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Anchors are not currently supported. Remove the anchor '{scalar.Anchor}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (m_current is MappingStart mappingStart)
|
|
||||||
{
|
|
||||||
// Verify not using achors
|
|
||||||
if (mappingStart.Anchor != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Anchors are not currently supported. Remove the anchor '{mappingStart.Anchor}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (m_current is SequenceStart sequenceStart)
|
|
||||||
{
|
|
||||||
// Verify not using achors
|
|
||||||
if (sequenceStart.Anchor != null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Anchors are not currently supported. Remove the anchor '{sequenceStart.Anchor}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!(m_current is MappingEnd) &&
|
|
||||||
!(m_current is SequenceEnd) &&
|
|
||||||
!(m_current is DocumentStart) &&
|
|
||||||
!(m_current is DocumentEnd) &&
|
|
||||||
!(m_current is StreamStart) &&
|
|
||||||
!(m_current is StreamEnd))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Unexpected parsing event type: {m_current.GetType().Name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_current;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MoveNext()
|
|
||||||
{
|
|
||||||
m_current = null;
|
|
||||||
return m_parser.MoveNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
private BooleanToken ParseBoolean(Scalar scalar)
|
|
||||||
{
|
|
||||||
if (MatchBoolean(scalar, out var token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThrowInvalidValue(scalar, c_booleanTag); // throws
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NumberToken ParseFloat(Scalar scalar)
|
|
||||||
{
|
|
||||||
if (MatchFloat(scalar, out var token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThrowInvalidValue(scalar, c_floatTag); // throws
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NumberToken ParseInteger(Scalar scalar)
|
|
||||||
{
|
|
||||||
if (MatchInteger(scalar, out var token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NullToken ParseNull(Scalar scalar)
|
|
||||||
{
|
|
||||||
if (MatchNull(scalar, out var token))
|
|
||||||
{
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
ThrowInvalidValue(scalar, c_nullTag); // throws
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MatchBoolean(
|
|
||||||
Scalar scalar,
|
|
||||||
out BooleanToken value)
|
|
||||||
{
|
|
||||||
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
switch (scalar.Value ?? string.Empty)
|
|
||||||
{
|
|
||||||
case "true":
|
|
||||||
case "True":
|
|
||||||
case "TRUE":
|
|
||||||
value = new BooleanToken(m_fileId, scalar.Start.Line, scalar.Start.Column, true);
|
|
||||||
return true;
|
|
||||||
case "false":
|
|
||||||
case "False":
|
|
||||||
case "FALSE":
|
|
||||||
value = new BooleanToken(m_fileId, scalar.Start.Line, scalar.Start.Column, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MatchFloat(
|
|
||||||
Scalar scalar,
|
|
||||||
out NumberToken value)
|
|
||||||
{
|
|
||||||
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
var str = scalar.Value;
|
|
||||||
if (!string.IsNullOrEmpty(str))
|
|
||||||
{
|
|
||||||
// Check for [-+]?(\.inf|\.Inf|\.INF)|\.nan|\.NaN|\.NAN
|
|
||||||
switch (str)
|
|
||||||
{
|
|
||||||
case ".inf":
|
|
||||||
case ".Inf":
|
|
||||||
case ".INF":
|
|
||||||
case "+.inf":
|
|
||||||
case "+.Inf":
|
|
||||||
case "+.INF":
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, Double.PositiveInfinity);
|
|
||||||
return true;
|
|
||||||
case "-.inf":
|
|
||||||
case "-.Inf":
|
|
||||||
case "-.INF":
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, Double.NegativeInfinity);
|
|
||||||
return true;
|
|
||||||
case ".nan":
|
|
||||||
case ".NaN":
|
|
||||||
case ".NAN":
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, Double.NaN);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise check [-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?
|
|
||||||
|
|
||||||
// Skip leading sign
|
|
||||||
var index = str[0] == '-' || str[0] == '+' ? 1 : 0;
|
|
||||||
|
|
||||||
// Check for integer portion
|
|
||||||
var length = str.Length;
|
|
||||||
var hasInteger = false;
|
|
||||||
while (index < length && str[index] >= '0' && str[index] <= '9')
|
|
||||||
{
|
|
||||||
hasInteger = true;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for decimal point
|
|
||||||
var hasDot = false;
|
|
||||||
if (index < length && str[index] == '.')
|
|
||||||
{
|
|
||||||
hasDot = true;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for decimal portion
|
|
||||||
var hasDecimal = false;
|
|
||||||
while (index < length && str[index] >= '0' && str[index] <= '9')
|
|
||||||
{
|
|
||||||
hasDecimal = true;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check [-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)
|
|
||||||
if ((hasDot && hasDecimal) || hasInteger)
|
|
||||||
{
|
|
||||||
// Check for end
|
|
||||||
if (index == length)
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Double.TryParse(str, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var doubleValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, doubleValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Otherwise exceeds range
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ThrowInvalidValue(scalar, c_floatTag); // throws
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check [eE][-+]?[0-9]
|
|
||||||
else if (index < length && (str[index] == 'e' || str[index] == 'E'))
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
|
|
||||||
// Skip sign
|
|
||||||
if (index < length && (str[index] == '-' || str[index] == '+'))
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for exponent
|
|
||||||
var hasExponent = false;
|
|
||||||
while (index < length && str[index] >= '0' && str[index] <= '9')
|
|
||||||
{
|
|
||||||
hasExponent = true;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for end
|
|
||||||
if (hasExponent && index == length)
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Double.TryParse(str, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out var doubleValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, (Double)doubleValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Otherwise exceeds range
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ThrowInvalidValue(scalar, c_floatTag); // throws
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MatchInteger(
|
|
||||||
Scalar scalar,
|
|
||||||
out NumberToken value)
|
|
||||||
{
|
|
||||||
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
var str = scalar.Value;
|
|
||||||
if (!string.IsNullOrEmpty(str))
|
|
||||||
{
|
|
||||||
// Check for [0-9]+
|
|
||||||
var firstChar = str[0];
|
|
||||||
if (firstChar >= '0' && firstChar <= '9' &&
|
|
||||||
str.Skip(1).All(x => x >= '0' && x <= '9'))
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Double.TryParse(str, NumberStyles.None, CultureInfo.InvariantCulture, out var doubleValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, doubleValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise exceeds range
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
}
|
|
||||||
// Check for (-|+)[0-9]+
|
|
||||||
else if ((firstChar == '-' || firstChar == '+') &&
|
|
||||||
str.Length > 1 &&
|
|
||||||
str.Skip(1).All(x => x >= '0' && x <= '9'))
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Double.TryParse(str, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var doubleValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, doubleValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise exceeds range
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
}
|
|
||||||
// Check for 0x[0-9a-fA-F]+
|
|
||||||
else if (firstChar == '0' &&
|
|
||||||
str.Length > 2 &&
|
|
||||||
str[1] == 'x' &&
|
|
||||||
str.Skip(2).All(x => (x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F')))
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
if (Int32.TryParse(str.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out var integerValue))
|
|
||||||
{
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, integerValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise exceeds range
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
}
|
|
||||||
// Check for 0o[0-9]+
|
|
||||||
else if (firstChar == '0' &&
|
|
||||||
str.Length > 2 &&
|
|
||||||
str[1] == 'o' &&
|
|
||||||
str.Skip(2).All(x => x >= '0' && x <= '7'))
|
|
||||||
{
|
|
||||||
// Try parse
|
|
||||||
var integerValue = default(Int32);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
integerValue = Convert.ToInt32(str.Substring(2), 8);
|
|
||||||
}
|
|
||||||
// Otherwise exceeds range
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
ThrowInvalidValue(scalar, c_integerTag); // throws
|
|
||||||
}
|
|
||||||
|
|
||||||
value = new NumberToken(m_fileId, scalar.Start.Line, scalar.Start.Column, integerValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean MatchNull(
|
|
||||||
Scalar scalar,
|
|
||||||
out NullToken value)
|
|
||||||
{
|
|
||||||
// YAML 1.2 "core" schema https://yaml.org/spec/1.2/spec.html#id2804923
|
|
||||||
switch (scalar.Value ?? string.Empty)
|
|
||||||
{
|
|
||||||
case "":
|
|
||||||
case "null":
|
|
||||||
case "Null":
|
|
||||||
case "NULL":
|
|
||||||
case "~":
|
|
||||||
value = new NullToken(m_fileId, scalar.Start.Line, scalar.Start.Column);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThrowInvalidValue(
|
|
||||||
Scalar scalar,
|
|
||||||
String tag)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException($"The value '{scalar.Value}' on line {scalar.Start.Line} and column {scalar.Start.Column} is invalid for the type '{scalar.Tag}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
private const String c_booleanTag = "tag:yaml.org,2002:bool";
|
|
||||||
private const String c_floatTag = "tag:yaml.org,2002:float";
|
|
||||||
private const String c_integerTag = "tag:yaml.org,2002:int";
|
|
||||||
private const String c_nullTag = "tag:yaml.org,2002:null";
|
|
||||||
private const String c_stringTag = "tag:yaml.org,2002:string";
|
|
||||||
private readonly Int32? m_fileId;
|
|
||||||
private readonly Parser m_parser;
|
|
||||||
private ParsingEvent m_current;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
src/Runner.Worker/ActionNotFoundException.cs
Normal file
33
src/Runner.Worker/ActionNotFoundException.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker
|
||||||
|
{
|
||||||
|
public class ActionNotFoundException : Exception
|
||||||
|
{
|
||||||
|
public ActionNotFoundException(Uri actionUri)
|
||||||
|
: base(FormatMessage(actionUri))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionNotFoundException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionNotFoundException(string message, System.Exception inner)
|
||||||
|
: base(message, inner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ActionNotFoundException(SerializationInfo info, StreamingContext context)
|
||||||
|
: base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatMessage(Uri actionUri)
|
||||||
|
{
|
||||||
|
return $"An action could not be found at the URI '{actionUri}'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,25 @@ namespace GitHub.Runner.Worker
|
|||||||
ActionExecutionData handlerData = definition.Data?.Execution;
|
ActionExecutionData handlerData = definition.Data?.Execution;
|
||||||
ArgUtil.NotNull(handlerData, nameof(handlerData));
|
ArgUtil.NotNull(handlerData, nameof(handlerData));
|
||||||
|
|
||||||
|
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))
|
if (Stage == ActionRunStage.Pre &&
|
||||||
|
this.DisplayName.StartsWith("Pre ", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
postDisplayName = $"Post {this.DisplayName.Substring(PipelineTemplateConstants.RunDisplayPrefix.Length)}";
|
// Trim the leading `Pre ` from the display name.
|
||||||
|
// Otherwise, we will get `Post Pre xxx` as DisplayName for the Post step.
|
||||||
|
postDisplayName = $"Post {this.DisplayName.Substring("Pre ".Length)}";
|
||||||
}
|
}
|
||||||
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 +114,7 @@ namespace GitHub.Runner.Worker
|
|||||||
actionRunner.Condition = handlerData.CleanupCondition;
|
actionRunner.Condition = handlerData.CleanupCondition;
|
||||||
actionRunner.DisplayName = postDisplayName;
|
actionRunner.DisplayName = postDisplayName;
|
||||||
|
|
||||||
ExecutionContext.RegisterPostJobStep($"{actionRunner.Action.Name}_post", actionRunner);
|
ExecutionContext.RegisterPostJobStep(actionRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
|
IStepHost stepHost = HostContext.CreateService<IDefaultStepHost>();
|
||||||
@@ -141,13 +147,13 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Load the inputs.
|
// Load the inputs.
|
||||||
ExecutionContext.Debug("Loading inputs");
|
ExecutionContext.Debug("Loading inputs");
|
||||||
var templateTrace = ExecutionContext.ToTemplateTraceWriter();
|
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
|
||||||
var templateEvaluator = new PipelineTemplateEvaluator(templateTrace, schema);
|
|
||||||
var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues);
|
|
||||||
|
|
||||||
|
var userInputs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (KeyValuePair<string, string> input in inputs)
|
foreach (KeyValuePair<string, string> input in inputs)
|
||||||
{
|
{
|
||||||
|
userInputs.Add(input.Key);
|
||||||
string message = "";
|
string message = "";
|
||||||
if (definition.Data?.Deprecated?.TryGetValue(input.Key, out message) == true)
|
if (definition.Data?.Deprecated?.TryGetValue(input.Key, out message) == true)
|
||||||
{
|
{
|
||||||
@@ -155,41 +161,60 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var validInputs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (handlerData.ExecutionType == ActionExecutionType.Container)
|
||||||
|
{
|
||||||
|
// container action always accept 'entryPoint' and 'args' as inputs
|
||||||
|
// https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswithargs
|
||||||
|
validInputs.Add("entryPoint");
|
||||||
|
validInputs.Add("args");
|
||||||
|
}
|
||||||
// Merge the default inputs from the definition
|
// Merge the default inputs from the definition
|
||||||
if (definition.Data?.Inputs != null)
|
if (definition.Data?.Inputs != null)
|
||||||
{
|
{
|
||||||
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
var manifestManager = HostContext.GetService<IActionManifestManager>();
|
||||||
foreach (var input in (definition.Data?.Inputs))
|
foreach (var input in definition.Data.Inputs)
|
||||||
{
|
{
|
||||||
string key = input.Key.AssertString("action input name").Value;
|
string key = input.Key.AssertString("action input name").Value;
|
||||||
|
validInputs.Add(key);
|
||||||
if (!inputs.ContainsKey(key))
|
if (!inputs.ContainsKey(key))
|
||||||
{
|
{
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate inputs only for actions with action.yml
|
||||||
|
if (Action.Reference.Type == Pipelines.ActionSourceType.Repository)
|
||||||
|
{
|
||||||
|
var unexpectedInputs = new List<string>();
|
||||||
|
foreach (var input in userInputs)
|
||||||
|
{
|
||||||
|
if (!validInputs.Contains(input))
|
||||||
|
{
|
||||||
|
unexpectedInputs.Add(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unexpectedInputs.Count > 0)
|
||||||
|
{
|
||||||
|
ExecutionContext.Warning($"Unexpected input(s) '{string.Join("', '", unexpectedInputs)}', valid inputs are ['{string.Join("', '", validInputs)}']");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load the action environment.
|
// Load the action environment.
|
||||||
ExecutionContext.Debug("Loading env");
|
ExecutionContext.Debug("Loading env");
|
||||||
var environment = new Dictionary<String, String>(VarUtil.EnvironmentVariableKeyComparer);
|
var environment = new Dictionary<String, String>(VarUtil.EnvironmentVariableKeyComparer);
|
||||||
|
|
||||||
// Apply environment set using ##[set-env] first since these are job level env
|
#if OS_WINDOWS
|
||||||
foreach (var env in ExecutionContext.EnvironmentVariables)
|
var envContext = ExecutionContext.ExpressionValues["env"] as DictionaryContextData;
|
||||||
|
#else
|
||||||
|
var envContext = ExecutionContext.ExpressionValues["env"] as CaseSensitiveDictionaryContextData;
|
||||||
|
#endif
|
||||||
|
// Apply environment from env context, env context contains job level env and action's evn block
|
||||||
|
foreach (var env in envContext)
|
||||||
{
|
{
|
||||||
environment[env.Key] = env.Value ?? string.Empty;
|
environment[env.Key] = env.Value.ToString();
|
||||||
}
|
|
||||||
|
|
||||||
// Apply action's env block later.
|
|
||||||
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(Action.Environment, ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer);
|
|
||||||
foreach (var env in actionEnvironment)
|
|
||||||
{
|
|
||||||
environment[env.Key] = env.Value ?? string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply action's intra-action state at last
|
// Apply action's intra-action state at last
|
||||||
@@ -297,11 +322,14 @@ namespace GitHub.Runner.Worker
|
|||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
// Try evaluating fully
|
// Try evaluating fully
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
|
||||||
var templateEvaluator = new PipelineTemplateEvaluator(context.ToTemplateTraceWriter(), schema);
|
|
||||||
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,8 +61,11 @@ 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateWebProxyEnv(hostContext.WebProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ContainerId { get; set; }
|
public string ContainerId { get; set; }
|
||||||
@@ -222,6 +225,26 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
{
|
{
|
||||||
_pathMappings.Insert(0, new PathMapping(hostCommonPath, containerCommonPath));
|
_pathMappings.Insert(0, new PathMapping(hostCommonPath, containerCommonPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateWebProxyEnv(RunnerWebProxy webProxy)
|
||||||
|
{
|
||||||
|
// Set common forms of proxy variables if configured in Runner and not set directly by container.env
|
||||||
|
if (!String.IsNullOrEmpty(webProxy.HttpProxyAddress))
|
||||||
|
{
|
||||||
|
ContainerEnvironmentVariables.TryAdd("HTTP_PROXY", webProxy.HttpProxyAddress);
|
||||||
|
ContainerEnvironmentVariables.TryAdd("http_proxy", webProxy.HttpProxyAddress);
|
||||||
|
}
|
||||||
|
if (!String.IsNullOrEmpty(webProxy.HttpsProxyAddress))
|
||||||
|
{
|
||||||
|
ContainerEnvironmentVariables.TryAdd("HTTPS_PROXY", webProxy.HttpsProxyAddress);
|
||||||
|
ContainerEnvironmentVariables.TryAdd("https_proxy", webProxy.HttpsProxyAddress);
|
||||||
|
}
|
||||||
|
if (!String.IsNullOrEmpty(webProxy.NoProxyString))
|
||||||
|
{
|
||||||
|
ContainerEnvironmentVariables.TryAdd("NO_PROXY", webProxy.NoProxyString);
|
||||||
|
ContainerEnvironmentVariables.TryAdd("no_proxy", webProxy.NoProxyString);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MountVolume
|
public class MountVolume
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
string DockerInstanceLabel { get; }
|
string DockerInstanceLabel { get; }
|
||||||
Task<DockerVersion> DockerVersion(IExecutionContext context);
|
Task<DockerVersion> DockerVersion(IExecutionContext context);
|
||||||
Task<int> DockerPull(IExecutionContext context, string image);
|
Task<int> DockerPull(IExecutionContext context, string image);
|
||||||
Task<int> DockerBuild(IExecutionContext context, string workingDirectory, string dockerFile, string tag);
|
Task<int> DockerBuild(IExecutionContext context, string workingDirectory, string dockerFile, string dockerContext, string tag);
|
||||||
Task<string> DockerCreate(IExecutionContext context, ContainerInfo container);
|
Task<string> DockerCreate(IExecutionContext context, ContainerInfo container);
|
||||||
Task<int> DockerRun(IExecutionContext context, ContainerInfo container, EventHandler<ProcessDataReceivedEventArgs> stdoutDataReceived, EventHandler<ProcessDataReceivedEventArgs> stderrDataReceived);
|
Task<int> DockerRun(IExecutionContext context, ContainerInfo container, EventHandler<ProcessDataReceivedEventArgs> stdoutDataReceived, EventHandler<ProcessDataReceivedEventArgs> stderrDataReceived);
|
||||||
Task<int> DockerStart(IExecutionContext context, string containerId);
|
Task<int> DockerStart(IExecutionContext context, string containerId);
|
||||||
@@ -87,9 +87,9 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
return await ExecuteDockerCommandAsync(context, "pull", image, context.CancellationToken);
|
return await ExecuteDockerCommandAsync(context, "pull", image, context.CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> DockerBuild(IExecutionContext context, string workingDirectory, string dockerFile, string tag)
|
public async Task<int> DockerBuild(IExecutionContext context, string workingDirectory, string dockerFile, string dockerContext, string tag)
|
||||||
{
|
{
|
||||||
return await ExecuteDockerCommandAsync(context, "build", $"-t {tag} \"{dockerFile}\"", workingDirectory, context.CancellationToken);
|
return await ExecuteDockerCommandAsync(context, "build", $"-t {tag} -f \"{dockerFile}\" \"{dockerContext}\"", workingDirectory, context.CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> DockerCreate(IExecutionContext context, ContainerInfo container)
|
public async Task<string> DockerCreate(IExecutionContext context, ContainerInfo container)
|
||||||
@@ -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,26 +1,27 @@
|
|||||||
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.WebApi;
|
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
|
||||||
{
|
{
|
||||||
@@ -38,19 +39,24 @@ namespace GitHub.Runner.Worker
|
|||||||
string ContextName { get; }
|
string ContextName { get; }
|
||||||
Task ForceCompleted { get; }
|
Task ForceCompleted { get; }
|
||||||
TaskResult? Result { get; set; }
|
TaskResult? Result { get; set; }
|
||||||
|
TaskResult? Outcome { get; set; }
|
||||||
string ResultCode { get; set; }
|
string ResultCode { get; set; }
|
||||||
TaskResult? CommandResult { get; set; }
|
TaskResult? CommandResult { get; set; }
|
||||||
CancellationToken CancellationToken { get; }
|
CancellationToken CancellationToken { get; }
|
||||||
List<ServiceEndpoint> Endpoints { get; }
|
List<ServiceEndpoint> Endpoints { get; }
|
||||||
|
TaskOrchestrationPlanReference Plan { get; }
|
||||||
|
|
||||||
PlanFeatures Features { get; }
|
PlanFeatures Features { get; }
|
||||||
Variables Variables { get; }
|
Variables Variables { get; }
|
||||||
Dictionary<string, string> IntraActionState { get; }
|
Dictionary<string, string> IntraActionState { get; }
|
||||||
HashSet<string> OutputVariables { get; }
|
IDictionary<String, IDictionary<String, String>> JobDefaults { get; }
|
||||||
|
Dictionary<string, VariableValue> JobOutputs { get; }
|
||||||
IDictionary<String, String> EnvironmentVariables { get; }
|
IDictionary<String, String> EnvironmentVariables { get; }
|
||||||
IDictionary<String, ContextScope> Scopes { get; }
|
IDictionary<String, ContextScope> Scopes { 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; }
|
||||||
@@ -98,17 +104,17 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// others
|
// others
|
||||||
void ForceTaskComplete();
|
void ForceTaskComplete();
|
||||||
void RegisterPostJobStep(string refName, IStep step);
|
void RegisterPostJobStep(IStep step);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
||||||
{
|
{
|
||||||
private const int _maxIssueCount = 10;
|
private const int _maxIssueCount = 10;
|
||||||
|
private const int _throttlingDelayReportThreshold = 10 * 1000; // Don't report throttling with less than 10 seconds delay
|
||||||
|
|
||||||
private readonly TimelineRecord _record = new TimelineRecord();
|
private readonly TimelineRecord _record = new TimelineRecord();
|
||||||
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new Dictionary<Guid, TimelineRecord>();
|
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new Dictionary<Guid, TimelineRecord>();
|
||||||
private readonly object _loggerLock = new object();
|
private readonly object _loggerLock = new object();
|
||||||
private readonly HashSet<string> _outputvariables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
private readonly object _matchersLock = new object();
|
private readonly object _matchersLock = new object();
|
||||||
|
|
||||||
private event OnMatcherChanged _onMatcherChanged;
|
private event OnMatcherChanged _onMatcherChanged;
|
||||||
@@ -136,13 +142,17 @@ namespace GitHub.Runner.Worker
|
|||||||
public Task ForceCompleted => _forceCompleted.Task;
|
public Task ForceCompleted => _forceCompleted.Task;
|
||||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||||
public List<ServiceEndpoint> Endpoints { get; private set; }
|
public List<ServiceEndpoint> Endpoints { get; private set; }
|
||||||
|
public TaskOrchestrationPlanReference Plan { get; private set; }
|
||||||
public Variables Variables { get; private set; }
|
public Variables Variables { get; private set; }
|
||||||
public Dictionary<string, string> IntraActionState { get; private set; }
|
public Dictionary<string, string> IntraActionState { get; private set; }
|
||||||
public HashSet<string> OutputVariables => _outputvariables;
|
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; }
|
||||||
|
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
|
||||||
public IDictionary<String, String> EnvironmentVariables { get; private set; }
|
public IDictionary<String, String> EnvironmentVariables { get; private set; }
|
||||||
public IDictionary<String, ContextScope> Scopes { get; private set; }
|
public IDictionary<String, ContextScope> Scopes { 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; }
|
||||||
@@ -154,6 +164,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; }
|
||||||
|
|
||||||
|
|
||||||
@@ -169,6 +182,8 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TaskResult? Outcome { get; set; }
|
||||||
|
|
||||||
public TaskResult? CommandResult { get; set; }
|
public TaskResult? CommandResult { get; set; }
|
||||||
|
|
||||||
private string ContextType => _record.RecordType;
|
private string ContextType => _record.RecordType;
|
||||||
@@ -239,9 +254,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,6 +277,7 @@ namespace GitHub.Runner.Worker
|
|||||||
child.Features = Features;
|
child.Features = Features;
|
||||||
child.Variables = Variables;
|
child.Variables = Variables;
|
||||||
child.Endpoints = Endpoints;
|
child.Endpoints = Endpoints;
|
||||||
|
child.Plan = Plan;
|
||||||
if (intraActionState == null)
|
if (intraActionState == null)
|
||||||
{
|
{
|
||||||
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -265,12 +287,18 @@ namespace GitHub.Runner.Worker
|
|||||||
child.IntraActionState = intraActionState;
|
child.IntraActionState = intraActionState;
|
||||||
}
|
}
|
||||||
child.EnvironmentVariables = EnvironmentVariables;
|
child.EnvironmentVariables = EnvironmentVariables;
|
||||||
|
child.JobDefaults = JobDefaults;
|
||||||
child.Scopes = Scopes;
|
child.Scopes = Scopes;
|
||||||
|
child.FileTable = FileTable;
|
||||||
child.StepsContext = StepsContext;
|
child.StepsContext = StepsContext;
|
||||||
foreach (var pair in ExpressionValues)
|
foreach (var pair in ExpressionValues)
|
||||||
{
|
{
|
||||||
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;
|
||||||
@@ -311,7 +339,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
// report total delay caused by server throttling.
|
// report total delay caused by server throttling.
|
||||||
if (_totalThrottlingDelayInMilliseconds > 0)
|
if (_totalThrottlingDelayInMilliseconds > _throttlingDelayReportThreshold)
|
||||||
{
|
{
|
||||||
this.Warning($"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling.");
|
this.Warning($"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling.");
|
||||||
}
|
}
|
||||||
@@ -339,10 +367,20 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_cancellationTokenSource?.Dispose();
|
if (Root != this)
|
||||||
|
{
|
||||||
|
// only dispose TokenSource for step level ExecutionContext
|
||||||
|
_cancellationTokenSource?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
_logger.End();
|
_logger.End();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(ContextName))
|
||||||
|
{
|
||||||
|
StepsContext.SetOutcome(ScopeName, ContextName, (Outcome ?? Result ?? TaskResult.Succeeded).ToActionResult());
|
||||||
|
StepsContext.SetConclusion(ScopeName, ContextName, (Result ?? TaskResult.Succeeded).ToActionResult());
|
||||||
|
}
|
||||||
|
|
||||||
return Result.Value;
|
return Result.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,7 +579,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
|
||||||
|
|
||||||
// Features
|
// Plan
|
||||||
|
Plan = message.Plan;
|
||||||
Features = PlanUtil.GetFeatures(message.Plan);
|
Features = PlanUtil.GetFeatures(message.Plan);
|
||||||
|
|
||||||
// Endpoints
|
// Endpoints
|
||||||
@@ -553,6 +592,12 @@ namespace GitHub.Runner.Worker
|
|||||||
// Environment variables shared across all actions
|
// Environment variables shared across all actions
|
||||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer);
|
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer);
|
||||||
|
|
||||||
|
// Job defaults shared across all actions
|
||||||
|
JobDefaults = new Dictionary<string, IDictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Job Outputs
|
||||||
|
JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
// Service container info
|
// Service container info
|
||||||
ServiceContainers = new List<ContainerInfo>();
|
ServiceContainers = new List<ContainerInfo>();
|
||||||
|
|
||||||
@@ -569,11 +614,8 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expression functions
|
// File table
|
||||||
if (Variables.GetBoolean("System.HashFilesV2") == true)
|
FileTable = new List<String>(message.FileTable ?? new string[0]);
|
||||||
{
|
|
||||||
ExpressionConstants.UpdateFunction<Handlers.HashFiles>("hashFiles", 1, byte.MaxValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expression values
|
// Expression values
|
||||||
if (message.ContextData?.Count > 0)
|
if (message.ContextData?.Count > 0)
|
||||||
@@ -592,8 +634,13 @@ namespace GitHub.Runner.Worker
|
|||||||
var githubAccessToken = new StringContextData(Variables.Get("system.github.token"));
|
var githubAccessToken = new StringContextData(Variables.Get("system.github.token"));
|
||||||
var base64EncodedToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{githubAccessToken}"));
|
var base64EncodedToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{githubAccessToken}"));
|
||||||
HostContext.SecretMasker.AddValue(base64EncodedToken);
|
HostContext.SecretMasker.AddValue(base64EncodedToken);
|
||||||
|
var githubJob = Variables.Get("system.github.job");
|
||||||
var githubContext = new GitHubContext();
|
var githubContext = new GitHubContext();
|
||||||
githubContext["token"] = githubAccessToken;
|
githubContext["token"] = githubAccessToken;
|
||||||
|
if (!string.IsNullOrEmpty(githubJob))
|
||||||
|
{
|
||||||
|
githubContext["job"] = new StringContextData(githubJob);
|
||||||
|
}
|
||||||
var githubDictionary = ExpressionValues["github"].AssertDictionary("github");
|
var githubDictionary = ExpressionValues["github"].AssertDictionary("github");
|
||||||
foreach (var pair in githubDictionary)
|
foreach (var pair in githubDictionary)
|
||||||
{
|
{
|
||||||
@@ -618,6 +665,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,
|
||||||
@@ -729,7 +779,7 @@ namespace GitHub.Runner.Worker
|
|||||||
var owners = config.Matchers.Select(x => $"'{x.Owner}'");
|
var owners = config.Matchers.Select(x => $"'{x.Owner}'");
|
||||||
var joinedOwners = string.Join(", ", owners);
|
var joinedOwners = string.Join(", ", owners);
|
||||||
// todo: loc
|
// todo: loc
|
||||||
this.Output($"Added matchers: {joinedOwners}. Problem matchers scan action output for known warning or error strings and report these inline.");
|
this.Debug($"Added matchers: {joinedOwners}. Problem matchers scan action output for known warning or error strings and report these inline.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -771,7 +821,7 @@ namespace GitHub.Runner.Worker
|
|||||||
owners = removedMatchers.Select(x => $"'{x.Owner}'");
|
owners = removedMatchers.Select(x => $"'{x.Owner}'");
|
||||||
var joinedOwners = string.Join(", ", owners);
|
var joinedOwners = string.Join(", ", owners);
|
||||||
// todo: loc
|
// todo: loc
|
||||||
this.Output($"Removed matchers: {joinedOwners}");
|
this.Debug($"Removed matchers: {joinedOwners}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -810,7 +860,8 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
Interlocked.Add(ref _totalThrottlingDelayInMilliseconds, Convert.ToInt64(data.Delay.TotalMilliseconds));
|
Interlocked.Add(ref _totalThrottlingDelayInMilliseconds, Convert.ToInt64(data.Delay.TotalMilliseconds));
|
||||||
|
|
||||||
if (!_throttlingReported)
|
if (!_throttlingReported &&
|
||||||
|
_totalThrottlingDelayInMilliseconds > _throttlingDelayReportThreshold)
|
||||||
{
|
{
|
||||||
this.Warning(string.Format("The job is currently being throttled by the server. You may experience delays in console line output, job status reporting, and action log uploads."));
|
this.Warning(string.Format("The job is currently being throttled by the server. You may experience delays in console line output, job status reporting, and action log uploads."));
|
||||||
|
|
||||||
@@ -818,7 +869,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)
|
||||||
{
|
{
|
||||||
@@ -827,7 +878,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,6 +938,21 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<KeyValuePair<string, object>> ToExpressionState(this IExecutionContext context)
|
||||||
|
{
|
||||||
|
return new[] { new KeyValuePair<string, object>(nameof(IExecutionContext), context) };
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
return new TemplateTraceWriter(context);
|
return new TemplateTraceWriter(context);
|
||||||
@@ -898,6 +965,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,29 +8,12 @@ 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;
|
private const int _hashFileTimeoutSeconds = 120;
|
||||||
|
|
||||||
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,
|
||||||
out ResultMemory resultMemory)
|
out ResultMemory resultMemory)
|
||||||
@@ -82,7 +65,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__"))
|
||||||
@@ -108,19 +91,48 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
env["patterns"] = string.Join(Environment.NewLine, patterns);
|
env["patterns"] = string.Join(Environment.NewLine, patterns);
|
||||||
|
|
||||||
int exitCode = p.ExecuteAsync(workingDirectory: githubWorkspace,
|
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(_hashFileTimeoutSeconds)))
|
||||||
fileName: node,
|
|
||||||
arguments: $"\"{hashFilesScript.Replace("\"", "\\\"")}\"",
|
|
||||||
environment: env,
|
|
||||||
requireExitCodeZero: false,
|
|
||||||
cancellationToken: new CancellationTokenSource(TimeSpan.FromSeconds(120)).Token).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
if (exitCode != 0)
|
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"hashFiles('{ExpressionUtility.StringEscape(string.Join(", ", patterns))}') failed. Fail to hash files under directory '{githubWorkspace}'");
|
try
|
||||||
|
{
|
||||||
|
int exitCode = p.ExecuteAsync(workingDirectory: githubWorkspace,
|
||||||
|
fileName: node,
|
||||||
|
arguments: $"\"{hashFilesScript.Replace("\"", "\\\"")}\"",
|
||||||
|
environment: env,
|
||||||
|
requireExitCodeZero: false,
|
||||||
|
cancellationToken: tokenSource.Token).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"hashFiles('{ExpressionUtility.StringEscape(string.Join(", ", patterns))}') failed. Fail to hash files under directory '{githubWorkspace}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) when (tokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
throw new TimeoutException($"hashFiles('{ExpressionUtility.StringEscape(string.Join(", ", patterns))}') couldn't finish within {_hashFileTimeoutSeconds} seconds.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class HashFilesTrace : ITraceWriter
|
||||||
|
{
|
||||||
|
private GitHub.DistributedTask.Expressions2.ITraceWriter _trace;
|
||||||
|
|
||||||
|
public HashFilesTrace(GitHub.DistributedTask.Expressions2.ITraceWriter trace)
|
||||||
|
{
|
||||||
|
_trace = trace;
|
||||||
|
}
|
||||||
|
public void Info(string message)
|
||||||
|
{
|
||||||
|
_trace.Info(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashResult;
|
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,14 +10,19 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
"action",
|
"action",
|
||||||
"actor",
|
"actor",
|
||||||
|
"api_url",
|
||||||
"base_ref",
|
"base_ref",
|
||||||
"event_name",
|
"event_name",
|
||||||
"event_path",
|
"event_path",
|
||||||
|
"graphql_url",
|
||||||
"head_ref",
|
"head_ref",
|
||||||
|
"job",
|
||||||
"ref",
|
"ref",
|
||||||
"repository",
|
"repository",
|
||||||
|
"repository_owner",
|
||||||
"run_id",
|
"run_id",
|
||||||
"run_number",
|
"run_number",
|
||||||
|
"server_url",
|
||||||
"sha",
|
"sha",
|
||||||
"workflow",
|
"workflow",
|
||||||
"workspace",
|
"workspace",
|
||||||
|
|||||||
@@ -52,7 +52,12 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
ExecutionContext.Output($"Dockerfile for action: '{dockerFile}'.");
|
ExecutionContext.Output($"Dockerfile for action: '{dockerFile}'.");
|
||||||
|
|
||||||
var imageName = $"{dockerManger.DockerInstanceLabel}:{ExecutionContext.Id.ToString("N")}";
|
var imageName = $"{dockerManger.DockerInstanceLabel}:{ExecutionContext.Id.ToString("N")}";
|
||||||
var buildExitCode = await dockerManger.DockerBuild(ExecutionContext, ExecutionContext.GetGitHubContext("workspace"), Directory.GetParent(dockerFile).FullName, imageName);
|
var buildExitCode = await dockerManger.DockerBuild(
|
||||||
|
ExecutionContext,
|
||||||
|
ExecutionContext.GetGitHubContext("workspace"),
|
||||||
|
dockerFile,
|
||||||
|
Directory.GetParent(dockerFile).FullName,
|
||||||
|
imageName);
|
||||||
if (buildExitCode != 0)
|
if (buildExitCode != 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Docker build failed with exit code {buildExitCode}");
|
throw new InvalidOperationException($"Docker build failed with exit code {buildExitCode}");
|
||||||
@@ -82,9 +87,13 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint");
|
container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (stage == ActionRunStage.Pre)
|
||||||
|
{
|
||||||
|
container.ContainerEntryPoint = Data.Pre;
|
||||||
|
}
|
||||||
else if (stage == ActionRunStage.Post)
|
else if (stage == ActionRunStage.Post)
|
||||||
{
|
{
|
||||||
container.ContainerEntryPoint = Data.Cleanup;
|
container.ContainerEntryPoint = Data.Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create inputs context for template evaluation
|
// create inputs context for template evaluation
|
||||||
@@ -97,14 +106,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 +133,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));
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
// This does not need to be inside of a critical section.
|
// This does not need to be inside of a critical section.
|
||||||
// The logging queues and command handlers are thread-safe.
|
// The logging queues and command handlers are thread-safe.
|
||||||
if (_commandManager.TryProcessCommand(_executionContext, line))
|
if (_commandManager.TryProcessCommand(_executionContext, line, _container))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -352,15 +352,24 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
if (File.Exists(gitConfigPath))
|
if (File.Exists(gitConfigPath))
|
||||||
{
|
{
|
||||||
// Check if the config contains the workflow repository url
|
// Check if the config contains the workflow repository url
|
||||||
var qualifiedRepository = _executionContext.GetGitHubContext("repository");
|
var serverUrl = _executionContext.GetGitHubContext("server_url");
|
||||||
var configMatch = $"url = https://github.com/{qualifiedRepository}";
|
serverUrl = !string.IsNullOrEmpty(serverUrl) ? serverUrl : "https://github.com";
|
||||||
|
var host = new Uri(serverUrl, UriKind.Absolute).Host;
|
||||||
|
var nameWithOwner = _executionContext.GetGitHubContext("repository");
|
||||||
|
var patterns = new[] {
|
||||||
|
$"url = {serverUrl}/{nameWithOwner}",
|
||||||
|
$"url = git@{host}:{nameWithOwner}.git",
|
||||||
|
};
|
||||||
var content = File.ReadAllText(gitConfigPath);
|
var content = File.ReadAllText(gitConfigPath);
|
||||||
foreach (var line in content.Split("\n").Select(x => x.Trim()))
|
foreach (var line in content.Split("\n").Select(x => x.Trim()))
|
||||||
{
|
{
|
||||||
if (String.Equals(line, configMatch, StringComparison.OrdinalIgnoreCase))
|
foreach (var pattern in patterns)
|
||||||
{
|
{
|
||||||
repositoryPath = directoryPath;
|
if (String.Equals(line, pattern, StringComparison.OrdinalIgnoreCase))
|
||||||
break;
|
{
|
||||||
|
repositoryPath = directoryPath;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -58,12 +58,21 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
string shellCommandPath = null;
|
string shellCommandPath = null;
|
||||||
bool validateShellOnHost = !(StepHost is ContainerStepHost);
|
bool validateShellOnHost = !(StepHost is ContainerStepHost);
|
||||||
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
||||||
Inputs.TryGetValue("shell", out var shell);
|
string shell = null;
|
||||||
|
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
|
||||||
|
{
|
||||||
|
// TODO: figure out how defaults interact with template later
|
||||||
|
// for now, we won't check job.defaults if we are inside a template.
|
||||||
|
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||||
|
{
|
||||||
|
runDefaults.TryGetValue("shell", out shell);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (string.IsNullOrEmpty(shell))
|
if (string.IsNullOrEmpty(shell))
|
||||||
{
|
{
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
shellCommand = "pwsh";
|
shellCommand = "pwsh";
|
||||||
if(validateShellOnHost)
|
if (validateShellOnHost)
|
||||||
{
|
{
|
||||||
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||||
if (string.IsNullOrEmpty(shellCommandPath))
|
if (string.IsNullOrEmpty(shellCommandPath))
|
||||||
@@ -139,11 +148,36 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
Inputs.TryGetValue("script", out var contents);
|
Inputs.TryGetValue("script", out var contents);
|
||||||
contents = contents ?? string.Empty;
|
contents = contents ?? string.Empty;
|
||||||
|
|
||||||
Inputs.TryGetValue("workingDirectory", out var workingDirectory);
|
string workingDirectory = null;
|
||||||
|
if (!Inputs.TryGetValue("workingDirectory", out workingDirectory))
|
||||||
|
{
|
||||||
|
// TODO: figure out how defaults interact with template later
|
||||||
|
// for now, we won't check job.defaults if we are inside a template.
|
||||||
|
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||||
|
{
|
||||||
|
if (runDefaults.TryGetValue("working-directory", out workingDirectory))
|
||||||
|
{
|
||||||
|
ExecutionContext.Debug("Overwrite 'working-directory' base on job defaults.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var workspaceDir = githubContext["workspace"] as StringContextData;
|
var workspaceDir = githubContext["workspace"] as StringContextData;
|
||||||
workingDirectory = Path.Combine(workspaceDir, workingDirectory ?? string.Empty);
|
workingDirectory = Path.Combine(workspaceDir, workingDirectory ?? string.Empty);
|
||||||
|
|
||||||
Inputs.TryGetValue("shell", out var shell);
|
string shell = null;
|
||||||
|
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
|
||||||
|
{
|
||||||
|
// TODO: figure out how defaults interact with template later
|
||||||
|
// for now, we won't check job.defaults if we are inside a template.
|
||||||
|
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||||
|
{
|
||||||
|
if (runDefaults.TryGetValue("shell", out shell))
|
||||||
|
{
|
||||||
|
ExecutionContext.Debug("Overwrite 'shell' base on job defaults.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var isContainerStepHost = StepHost is ContainerStepHost;
|
var isContainerStepHost = StepHost is ContainerStepHost;
|
||||||
|
|
||||||
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
||||||
@@ -225,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);
|
||||||
@@ -149,14 +149,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
throw new NotSupportedException(msg);
|
throw new NotSupportedException(msg);
|
||||||
}
|
}
|
||||||
nodeExternal = "node12_alpine";
|
nodeExternal = "node12_alpine";
|
||||||
executionContext.Output($"Container distribution is alpine. Running JavaScript Action with external tool: {nodeExternal}");
|
executionContext.Debug($"Container distribution is alpine. Running JavaScript Action with external tool: {nodeExternal}");
|
||||||
return nodeExternal;
|
return nodeExternal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Optimistically use the default
|
// Optimistically use the default
|
||||||
nodeExternal = "node12";
|
nodeExternal = "node12";
|
||||||
executionContext.Output($"Running JavaScript Action with default external tool: {nodeExternal}");
|
executionContext.Debug($"Running JavaScript Action with default external tool: {nodeExternal}");
|
||||||
return nodeExternal;
|
return nodeExternal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
this["status"] = new StringContextData(value.ToString());
|
this["status"] = new StringContextData(value.ToString().ToLowerInvariant());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
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.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
@@ -14,6 +18,16 @@ using Pipelines = GitHub.DistributedTask.Pipelines;
|
|||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
|
[DataContract]
|
||||||
|
public class SetupInfo
|
||||||
|
{
|
||||||
|
[DataMember]
|
||||||
|
public string Group { get; set; }
|
||||||
|
|
||||||
|
[DataMember]
|
||||||
|
public string Detail { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(JobExtension))]
|
[ServiceLocator(Default = typeof(JobExtension))]
|
||||||
|
|
||||||
public interface IJobExtension : IRunnerService
|
public interface IJobExtension : IRunnerService
|
||||||
@@ -49,6 +63,44 @@ namespace GitHub.Runner.Worker
|
|||||||
context.Start();
|
context.Start();
|
||||||
context.Debug($"Starting: Set up job");
|
context.Debug($"Starting: Set up job");
|
||||||
context.Output($"Current runner version: '{BuildConstants.RunnerPackage.Version}'");
|
context.Output($"Current runner version: '{BuildConstants.RunnerPackage.Version}'");
|
||||||
|
|
||||||
|
var setupInfoFile = HostContext.GetConfigFile(WellKnownConfigFile.SetupInfo);
|
||||||
|
if (File.Exists(setupInfoFile))
|
||||||
|
{
|
||||||
|
Trace.Info($"Load machine setup info from {setupInfoFile}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var setupInfo = IOUtil.LoadObject<List<SetupInfo>>(setupInfoFile);
|
||||||
|
if (setupInfo?.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var info in setupInfo)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(info?.Detail))
|
||||||
|
{
|
||||||
|
var groupName = info.Group;
|
||||||
|
if (string.IsNullOrEmpty(groupName))
|
||||||
|
{
|
||||||
|
groupName = "Machine Setup Info";
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Output($"##[group]{groupName}");
|
||||||
|
var multiLines = info.Detail.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
|
||||||
|
foreach (var line in multiLines)
|
||||||
|
{
|
||||||
|
context.Output(line);
|
||||||
|
}
|
||||||
|
context.Output("##[endgroup]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
context.Output($"Fail to load and print machine setup info: {ex.Message}");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var repoFullName = context.GetGitHubContext("repository");
|
var repoFullName = context.GetGitHubContext("repository");
|
||||||
ArgUtil.NotNull(repoFullName, nameof(repoFullName));
|
ArgUtil.NotNull(repoFullName, nameof(repoFullName));
|
||||||
context.Debug($"Primary repository: {repoFullName}");
|
context.Debug($"Primary repository: {repoFullName}");
|
||||||
@@ -76,14 +128,24 @@ 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 (string.IsNullOrEmpty(context.GetGitHubContext("server_url")) && !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("server_url", $"{url.Scheme}://{url.Host}{portInfo}");
|
||||||
|
context.SetGitHubContext("api_url", $"{url.Scheme}://{url.Host}{portInfo}/api/v3");
|
||||||
|
context.SetGitHubContext("graphql_url", $"{url.Scheme}://{url.Host}{portInfo}/api/graphql");
|
||||||
|
}
|
||||||
|
|
||||||
// Evaluate the job-level environment variables
|
// Evaluate the job-level environment variables
|
||||||
context.Debug("Evaluating job-level environment variables");
|
context.Debug("Evaluating job-level environment variables");
|
||||||
var templateTrace = context.ToTemplateTraceWriter();
|
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
|
||||||
var templateEvaluator = new PipelineTemplateEvaluator(templateTrace, schema);
|
|
||||||
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;
|
||||||
@@ -93,7 +155,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);
|
||||||
@@ -101,7 +163,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)
|
||||||
@@ -112,12 +174,32 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Evaluate the job defaults
|
||||||
|
context.Debug("Evaluating job defaults");
|
||||||
|
foreach (var token in message.Defaults)
|
||||||
|
{
|
||||||
|
var defaults = token.AssertMapping("defaults");
|
||||||
|
if (defaults.Any(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.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 jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues, jobContext.ExpressionFunctions);
|
||||||
|
foreach (var pair in jobDefaults)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(pair.Value))
|
||||||
|
{
|
||||||
|
context.JobDefaults["run"][pair.Key] = pair.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build up 2 lists of steps, pre-job, job
|
// Build up 2 lists of steps, pre-job, job
|
||||||
// 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)
|
||||||
@@ -158,9 +240,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)
|
||||||
{
|
{
|
||||||
@@ -171,6 +267,12 @@ namespace GitHub.Runner.Worker
|
|||||||
Guid stepId = Guid.NewGuid();
|
Guid stepId = Guid.NewGuid();
|
||||||
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"));
|
extensionStep.ExecutionContext = jobContext.CreateChild(stepId, extensionStep.DisplayName, null, null, stepId.ToString("N"));
|
||||||
}
|
}
|
||||||
|
else if (step is IActionRunner actionStep)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(actionStep, step.DisplayName);
|
||||||
|
Guid stepId = Guid.NewGuid();
|
||||||
|
actionStep.ExecutionContext = jobContext.CreateChild(stepId, actionStep.DisplayName, stepId.ToString("N"), null, null, intraActionStates[actionStep.Action.Id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create execution context for job steps
|
// Create execution context for job steps
|
||||||
@@ -179,7 +281,8 @@ namespace GitHub.Runner.Worker
|
|||||||
if (step is IActionRunner actionStep)
|
if (step is IActionRunner actionStep)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(actionStep, step.DisplayName);
|
ArgUtil.NotNull(actionStep, step.DisplayName);
|
||||||
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName);
|
intraActionStates.TryGetValue(actionStep.Action.Id, out var intraActionState);
|
||||||
|
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName, intraActionState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +347,58 @@ namespace GitHub.Runner.Worker
|
|||||||
context.Start();
|
context.Start();
|
||||||
context.Debug("Starting: Complete job");
|
context.Debug("Starting: Complete job");
|
||||||
|
|
||||||
|
// Evaluate job outputs
|
||||||
|
if (message.JobOutputs != null && message.JobOutputs.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
context.Output($"Evaluate and set job outputs");
|
||||||
|
|
||||||
|
// Populate env context for each step
|
||||||
|
Trace.Info("Initialize Env context for evaluating job outputs");
|
||||||
|
#if OS_WINDOWS
|
||||||
|
var envContext = new DictionaryContextData();
|
||||||
|
#else
|
||||||
|
var envContext = new CaseSensitiveDictionaryContextData();
|
||||||
|
#endif
|
||||||
|
context.ExpressionValues["env"] = envContext;
|
||||||
|
foreach (var pair in context.EnvironmentVariables)
|
||||||
|
{
|
||||||
|
envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info("Initialize steps context for evaluating job outputs");
|
||||||
|
context.ExpressionValues["steps"] = context.StepsContext.GetScope(context.ScopeName);
|
||||||
|
|
||||||
|
var templateEvaluator = context.ToPipelineTemplateEvaluator();
|
||||||
|
var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues, context.ExpressionFunctions);
|
||||||
|
foreach (var output in outputs)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(output.Value))
|
||||||
|
{
|
||||||
|
context.Debug($"Skip output '{output.Key}' since it's empty");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(output.Value, HostContext.SecretMasker.MaskSecrets(output.Value)))
|
||||||
|
{
|
||||||
|
context.Warning($"Skip output '{output.Key}' since it may contain secret.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Output($"Set output '{output.Key}'");
|
||||||
|
jobContext.JobOutputs[output.Key] = output.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
context.Result = TaskResult.Failed;
|
||||||
|
context.Error($"Fail to evaluate job outputs");
|
||||||
|
context.Error(ex);
|
||||||
|
jobContext.Result = TaskResultUtil.MergeTaskResults(jobContext.Result, TaskResult.Failed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (context.Variables.GetBoolean(Constants.Variables.Actions.RunnerDebug) ?? false)
|
if (context.Variables.GetBoolean(Constants.Variables.Actions.RunnerDebug) ?? false)
|
||||||
{
|
{
|
||||||
Trace.Info("Support log upload starting.");
|
Trace.Info("Support log upload starting.");
|
||||||
|
|||||||
@@ -5,21 +5,13 @@ using GitHub.Services.Common;
|
|||||||
using GitHub.Services.WebApi;
|
using GitHub.Services.WebApi;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
|
||||||
using GitHub.DistributedTask.ObjectTemplating;
|
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -122,13 +114,6 @@ namespace GitHub.Runner.Worker
|
|||||||
_tempDirectoryManager = HostContext.GetService<ITempDirectoryManager>();
|
_tempDirectoryManager = HostContext.GetService<ITempDirectoryManager>();
|
||||||
_tempDirectoryManager.InitializeTempDirectory(jobContext);
|
_tempDirectoryManager.InitializeTempDirectory(jobContext);
|
||||||
|
|
||||||
// // Expand container properties
|
|
||||||
// jobContext.Container?.ExpandProperties(jobContext.Variables);
|
|
||||||
// foreach (var sidecar in jobContext.SidecarContainers)
|
|
||||||
// {
|
|
||||||
// sidecar.ExpandProperties(jobContext.Variables);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Get the job extension.
|
// Get the job extension.
|
||||||
Trace.Info("Getting job extension.");
|
Trace.Info("Getting job extension.");
|
||||||
IJobExtension jobExtension = HostContext.CreateService<IJobExtension>();
|
IJobExtension jobExtension = HostContext.CreateService<IJobExtension>();
|
||||||
@@ -231,7 +216,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info("Raising job completed event.");
|
Trace.Info("Raising job completed event.");
|
||||||
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result);
|
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result, jobContext.JobOutputs);
|
||||||
|
|
||||||
var completeJobRetryLimit = 5;
|
var completeJobRetryLimit = 5;
|
||||||
var exceptions = new List<Exception>();
|
var exceptions = new List<Exception>();
|
||||||
@@ -254,6 +239,12 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Error(ex);
|
Trace.Error(ex);
|
||||||
return TaskResult.Failed;
|
return TaskResult.Failed;
|
||||||
}
|
}
|
||||||
|
catch (TaskOrchestrationPlanTerminatedException ex)
|
||||||
|
{
|
||||||
|
Trace.Error($"TaskOrchestrationPlanTerminatedException received, while attempting to raise JobCompletedEvent for job {message.JobId}.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
return TaskResult.Failed;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Trace.Error($"Catch exception while attempting to raise JobCompletedEvent for job {message.JobId}, job request {message.RequestId}.");
|
Trace.Error($"Catch exception while attempting to raise JobCompletedEvent for job {message.JobId}, job request {message.RequestId}.");
|
||||||
|
|||||||
@@ -56,13 +56,22 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetResult(
|
public void SetConclusion(
|
||||||
string scopeName,
|
string scopeName,
|
||||||
string stepName,
|
string stepName,
|
||||||
string result)
|
ActionResult conclusion)
|
||||||
{
|
{
|
||||||
var step = GetStep(scopeName, stepName);
|
var step = GetStep(scopeName, stepName);
|
||||||
step["result"] = new StringContextData(result);
|
step["conclusion"] = new StringContextData(conclusion.ToString().ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetOutcome(
|
||||||
|
string scopeName,
|
||||||
|
string stepName,
|
||||||
|
ActionResult outcome)
|
||||||
|
{
|
||||||
|
var step = GetStep(scopeName, stepName);
|
||||||
|
step["outcome"] = new StringContextData(outcome.ToString().ToLowerInvariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
private DictionaryContextData GetStep(string scopeName, string stepName)
|
private DictionaryContextData GetStep(string scopeName, string stepName)
|
||||||
|
|||||||
@@ -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,118 +75,162 @@ namespace GitHub.Runner.Worker
|
|||||||
// Start
|
// Start
|
||||||
step.ExecutionContext.Start();
|
step.ExecutionContext.Start();
|
||||||
|
|
||||||
// Set GITHUB_ACTION
|
// Expression functions
|
||||||
if (step is IActionRunner actionStep)
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<AlwaysFunction>(PipelineTemplateConstants.Always, 0, 0));
|
||||||
{
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<CancelledFunction>(PipelineTemplateConstants.Cancelled, 0, 0));
|
||||||
step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name);
|
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))
|
||||||
{
|
{
|
||||||
var expressionManager = HostContext.GetService<IExpressionManager>();
|
// Populate env context for each step
|
||||||
try
|
Trace.Info("Initialize Env context for step");
|
||||||
|
#if OS_WINDOWS
|
||||||
|
var envContext = new DictionaryContextData();
|
||||||
|
#else
|
||||||
|
var envContext = new CaseSensitiveDictionaryContextData();
|
||||||
|
#endif
|
||||||
|
step.ExecutionContext.ExpressionValues["env"] = envContext;
|
||||||
|
foreach (var pair in step.ExecutionContext.EnvironmentVariables)
|
||||||
{
|
{
|
||||||
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
|
||||||
if (!jobContext.CancellationToken.IsCancellationRequested)
|
}
|
||||||
{
|
|
||||||
// Test the condition again. The job was canceled after the condition was originally evaluated.
|
|
||||||
jobCancelRegister = jobContext.CancellationToken.Register(() =>
|
|
||||||
{
|
|
||||||
// mark job as cancelled
|
|
||||||
jobContext.Result = TaskResult.Canceled;
|
|
||||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
|
||||||
|
|
||||||
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
bool evaluateStepEnvFailed = false;
|
||||||
ConditionResult conditionReTestResult;
|
if (step is IActionRunner actionStep)
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
{
|
||||||
{
|
// Set GITHUB_ACTION
|
||||||
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name);
|
||||||
conditionReTestResult = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
conditionReTestResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition, hostTracingOnly: true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Cancel the step since we get exception while re-evaluate step condition.
|
|
||||||
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
|
||||||
step.ExecutionContext.Error(ex);
|
|
||||||
conditionReTestResult = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!conditionReTestResult.Value)
|
try
|
||||||
{
|
|
||||||
// Cancel the step.
|
|
||||||
Trace.Info("Cancel current running step.");
|
|
||||||
step.ExecutionContext.CancelToken();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (jobContext.Result != TaskResult.Canceled)
|
// Evaluate and merge action's env block to env context
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
|
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
|
||||||
|
foreach (var env in actionEnvironment)
|
||||||
{
|
{
|
||||||
// mark job as cancelled
|
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
||||||
jobContext.Result = TaskResult.Canceled;
|
|
||||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
// Evaluate condition.
|
|
||||||
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
|
||||||
Exception conditionEvaluateError = null;
|
|
||||||
ConditionResult conditionResult;
|
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
|
||||||
conditionResult = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
conditionResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Info("Caught exception from expression.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
conditionResult = false;
|
|
||||||
conditionEvaluateError = ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no evaluate error but condition is false
|
|
||||||
if (!conditionResult.Value && conditionEvaluateError == null)
|
|
||||||
{
|
|
||||||
// Condition == false
|
|
||||||
Trace.Info("Skipping step due to condition evaluation.");
|
|
||||||
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionResult.Trace);
|
|
||||||
}
|
|
||||||
else if (conditionEvaluateError != null)
|
|
||||||
{
|
{
|
||||||
// fail the step since there is an evaluate error.
|
// fail the step since there is an evaluate error.
|
||||||
step.ExecutionContext.Error(conditionEvaluateError);
|
Trace.Info("Caught exception from expression for step.env");
|
||||||
|
evaluateStepEnvFailed = true;
|
||||||
|
step.ExecutionContext.Error(ex);
|
||||||
CompleteStep(step, nextStep, TaskResult.Failed);
|
CompleteStep(step, nextStep, TaskResult.Failed);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Run the step.
|
|
||||||
await RunStepAsync(step, jobContext.CancellationToken);
|
|
||||||
CompleteStep(step, nextStep);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
if (!evaluateStepEnvFailed)
|
||||||
{
|
{
|
||||||
if (jobCancelRegister != null)
|
try
|
||||||
{
|
{
|
||||||
jobCancelRegister?.Dispose();
|
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
||||||
jobCancelRegister = null;
|
if (!jobContext.CancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Test the condition again. The job was canceled after the condition was originally evaluated.
|
||||||
|
jobCancelRegister = jobContext.CancellationToken.Register(() =>
|
||||||
|
{
|
||||||
|
// mark job as cancelled
|
||||||
|
jobContext.Result = TaskResult.Canceled;
|
||||||
|
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||||
|
|
||||||
|
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
||||||
|
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
|
||||||
|
var conditionReTestResult = false;
|
||||||
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Cancel the step since we get exception while re-evaluate step condition.
|
||||||
|
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
||||||
|
step.ExecutionContext.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conditionReTestResult)
|
||||||
|
{
|
||||||
|
// Cancel the step.
|
||||||
|
Trace.Info("Cancel current running step.");
|
||||||
|
step.ExecutionContext.CancelToken();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (jobContext.Result != TaskResult.Canceled)
|
||||||
|
{
|
||||||
|
// mark job as cancelled
|
||||||
|
jobContext.Result = TaskResult.Canceled;
|
||||||
|
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate condition.
|
||||||
|
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
||||||
|
var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext);
|
||||||
|
var conditionResult = false;
|
||||||
|
var conditionEvaluateError = default(Exception);
|
||||||
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Info("Caught exception from expression.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
conditionEvaluateError = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no evaluate error but condition is false
|
||||||
|
if (!conditionResult && conditionEvaluateError == null)
|
||||||
|
{
|
||||||
|
// Condition == false
|
||||||
|
Trace.Info("Skipping step due to condition evaluation.");
|
||||||
|
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace);
|
||||||
|
}
|
||||||
|
else if (conditionEvaluateError != null)
|
||||||
|
{
|
||||||
|
// fail the step since there is an evaluate error.
|
||||||
|
step.ExecutionContext.Error(conditionEvaluateError);
|
||||||
|
CompleteStep(step, nextStep, TaskResult.Failed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Run the step.
|
||||||
|
await RunStepAsync(step, jobContext.CancellationToken);
|
||||||
|
CompleteStep(step, nextStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (jobCancelRegister != null)
|
||||||
|
{
|
||||||
|
jobCancelRegister?.Dispose();
|
||||||
|
jobCancelRegister = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,10 +267,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// Set the timeout
|
// Set the timeout
|
||||||
var timeoutMinutes = 0;
|
var timeoutMinutes = 0;
|
||||||
var templateEvaluator = CreateTemplateEvaluator(step.ExecutionContext);
|
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)
|
||||||
{
|
{
|
||||||
@@ -318,7 +361,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)
|
||||||
{
|
{
|
||||||
@@ -330,6 +373,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
if (continueOnError)
|
if (continueOnError)
|
||||||
{
|
{
|
||||||
|
step.ExecutionContext.Outcome = step.ExecutionContext.Result;
|
||||||
step.ExecutionContext.Result = TaskResult.Succeeded;
|
step.ExecutionContext.Result = TaskResult.Succeeded;
|
||||||
Trace.Info($"Updated step result (continue on error)");
|
Trace.Info($"Updated step result (continue on error)");
|
||||||
}
|
}
|
||||||
@@ -366,11 +410,11 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Debug($"Initializing scope '{scope.Name}'");
|
executionContext.Debug($"Initializing scope '{scope.Name}'");
|
||||||
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.ParentName);
|
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.ParentName);
|
||||||
executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null;
|
executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null;
|
||||||
var templateEvaluator = CreateTemplateEvaluator(executionContext);
|
var templateEvaluator = executionContext.ToPipelineTemplateEvaluator();
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -422,11 +466,11 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Debug($"Finalizing scope '{scope.Name}'");
|
executionContext.Debug($"Finalizing scope '{scope.Name}'");
|
||||||
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.Name);
|
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.Name);
|
||||||
executionContext.ExpressionValues["inputs"] = null;
|
executionContext.ExpressionValues["inputs"] = null;
|
||||||
var templateEvaluator = CreateTemplateEvaluator(executionContext);
|
var templateEvaluator = executionContext.ToPipelineTemplateEvaluator();
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -455,11 +499,42 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Complete(result, resultCode: resultCode);
|
executionContext.Complete(result, resultCode: resultCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PipelineTemplateEvaluator CreateTemplateEvaluator(IExecutionContext executionContext)
|
private sealed class ConditionTraceWriter : ObjectTemplating::ITraceWriter
|
||||||
{
|
{
|
||||||
var templateTrace = executionContext.ToTemplateTraceWriter();
|
private readonly IExecutionContext _executionContext;
|
||||||
var schema = new PipelineTemplateSchemaFactory().CreateSchema();
|
private readonly Tracing _trace;
|
||||||
return new PipelineTemplateEvaluator(templateTrace, schema);
|
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"
|
||||||
}
|
}
|
||||||
@@ -90,10 +94,9 @@
|
|||||||
"github",
|
"github",
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"steps",
|
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env"
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -162,8 +162,8 @@ namespace GitHub.Services.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
IssuedToken token = null;
|
IssuedToken token = null;
|
||||||
IssuedTokenProvider provider = null;
|
IssuedTokenProvider provider;
|
||||||
if (this.Credentials != null && this.Credentials.TryGetTokenProvider(request.RequestUri, out provider))
|
if (this.Credentials.TryGetTokenProvider(request.RequestUri, out provider))
|
||||||
{
|
{
|
||||||
token = provider.CurrentToken;
|
token = provider.CurrentToken;
|
||||||
}
|
}
|
||||||
@@ -227,7 +227,7 @@ namespace GitHub.Services.Common
|
|||||||
|
|
||||||
responseWrapper = new HttpResponseMessageWrapper(response);
|
responseWrapper = new HttpResponseMessageWrapper(response);
|
||||||
|
|
||||||
if (this.Credentials != null && !this.Credentials.IsAuthenticationChallenge(responseWrapper))
|
if (!this.Credentials.IsAuthenticationChallenge(responseWrapper))
|
||||||
{
|
{
|
||||||
// Validate the token after it has been successfully authenticated with the server.
|
// Validate the token after it has been successfully authenticated with the server.
|
||||||
if (provider != null)
|
if (provider != null)
|
||||||
@@ -259,10 +259,7 @@ namespace GitHub.Services.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we have an appropriate token provider for the current challenge
|
// Ensure we have an appropriate token provider for the current challenge
|
||||||
if (this.Credentials != null)
|
provider = this.Credentials.CreateTokenProvider(request.RequestUri, responseWrapper, token);
|
||||||
{
|
|
||||||
provider = this.Credentials.CreateTokenProvider(request.RequestUri, responseWrapper, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we don't invoke the provider in an invalid state
|
// Make sure we don't invoke the provider in an invalid state
|
||||||
if (provider == null)
|
if (provider == null)
|
||||||
@@ -311,7 +308,7 @@ namespace GitHub.Services.Common
|
|||||||
|
|
||||||
// We're out of retries and the response was an auth challenge -- then the request was unauthorized
|
// We're out of retries and the response was an auth challenge -- then the request was unauthorized
|
||||||
// and we will throw a strongly-typed exception with a friendly error message.
|
// and we will throw a strongly-typed exception with a friendly error message.
|
||||||
if (!succeeded && response != null && (this.Credentials != null && this.Credentials.IsAuthenticationChallenge(responseWrapper)))
|
if (!succeeded && response != null && this.Credentials.IsAuthenticationChallenge(responseWrapper))
|
||||||
{
|
{
|
||||||
String message = null;
|
String message = null;
|
||||||
IEnumerable<String> serviceError;
|
IEnumerable<String> serviceError;
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,7 @@ namespace GitHub.DistributedTask.Expressions2
|
|||||||
AddFunction<Join>("join", 1, 2);
|
AddFunction<Join>("join", 1, 2);
|
||||||
AddFunction<StartsWith>("startsWith", 2, 2);
|
AddFunction<StartsWith>("startsWith", 2, 2);
|
||||||
AddFunction<ToJson>("toJson", 1, 1);
|
AddFunction<ToJson>("toJson", 1, 1);
|
||||||
AddFunction<HashFiles>("hashFiles", 1, 1);
|
AddFunction<FromJson>("fromJson", 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
|
private static void AddFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
|
||||||
@@ -24,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;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace GitHub.DistributedTask.Expressions2.Sdk.Functions
|
||||||
|
{
|
||||||
|
internal sealed class FromJson : Function
|
||||||
|
{
|
||||||
|
protected sealed override Object EvaluateCore(
|
||||||
|
EvaluationContext context,
|
||||||
|
out ResultMemory resultMemory)
|
||||||
|
{
|
||||||
|
resultMemory = null;
|
||||||
|
var json = Parameters[0].Evaluate(context).ConvertToString();
|
||||||
|
using (var stringReader = new StringReader(json))
|
||||||
|
using (var jsonReader = new JsonTextReader(stringReader) { DateParseHandling = DateParseHandling.None, FloatParseHandling = FloatParseHandling.Double })
|
||||||
|
{
|
||||||
|
var token = JToken.ReadFrom(jsonReader);
|
||||||
|
return token.ToPipelineContextData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
@@ -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);
|
||||||
@@ -779,5 +779,65 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [Preview API]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="poolId"></param>
|
||||||
|
/// <param name="agentId"></param>
|
||||||
|
/// <param name="userState"></param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||||
|
public Task<String> GetAgentAuthUrlAsync(
|
||||||
|
int poolId,
|
||||||
|
int agentId,
|
||||||
|
object userState = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
HttpMethod httpMethod = new HttpMethod("GET");
|
||||||
|
Guid locationId = new Guid("a82a119c-1e46-44b6-8d75-c82a79cf975b");
|
||||||
|
object routeValues = new { poolId = poolId, agentId = agentId };
|
||||||
|
|
||||||
|
return SendAsync<String>(
|
||||||
|
httpMethod,
|
||||||
|
locationId,
|
||||||
|
routeValues: routeValues,
|
||||||
|
version: new ApiResourceVersion(6.0, 1),
|
||||||
|
userState: userState,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [Preview API]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="poolId"></param>
|
||||||
|
/// <param name="agentId"></param>
|
||||||
|
/// <param name="error"></param>
|
||||||
|
/// <param name="userState"></param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||||
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||||
|
public virtual async Task ReportAgentAuthUrlMigrationErrorAsync(
|
||||||
|
int poolId,
|
||||||
|
int agentId,
|
||||||
|
string error,
|
||||||
|
object userState = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
|
Guid locationId = new Guid("a82a119c-1e46-44b6-8d75-c82a79cf975b");
|
||||||
|
object routeValues = new { poolId = poolId, agentId = agentId };
|
||||||
|
HttpContent content = new ObjectContent<string>(error, new VssJsonMediaTypeFormatter(true));
|
||||||
|
|
||||||
|
using (HttpResponseMessage response = await SendAsync(
|
||||||
|
httpMethod,
|
||||||
|
locationId,
|
||||||
|
routeValues: routeValues,
|
||||||
|
version: new ApiResourceVersion(6.0, 1),
|
||||||
|
userState: userState,
|
||||||
|
cancellationToken: cancellationToken,
|
||||||
|
content: content).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,5 +317,37 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
userState: userState,
|
userState: userState,
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [Preview API] Resolves information required to download actions (URL, token) defined in an orchestration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
|
||||||
|
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
|
||||||
|
/// <param name="planId"></param>
|
||||||
|
/// <param name="actionReferenceList"></param>
|
||||||
|
/// <param name="userState"></param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||||
|
public virtual Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(
|
||||||
|
Guid scopeIdentifier,
|
||||||
|
string hubName,
|
||||||
|
Guid planId,
|
||||||
|
ActionReferenceList actionReferenceList,
|
||||||
|
object userState = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
HttpMethod httpMethod = new HttpMethod("POST");
|
||||||
|
Guid locationId = new Guid("27d7f831-88c1-4719-8ca1-6a061dad90eb");
|
||||||
|
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId };
|
||||||
|
HttpContent content = new ObjectContent<ActionReferenceList>(actionReferenceList, new VssJsonMediaTypeFormatter(true));
|
||||||
|
|
||||||
|
return SendAsync<ActionDownloadInfoCollection>(
|
||||||
|
httpMethod,
|
||||||
|
locationId,
|
||||||
|
routeValues: routeValues,
|
||||||
|
version: new ApiResourceVersion(6.0, 1),
|
||||||
|
userState: userState,
|
||||||
|
cancellationToken: cancellationToken,
|
||||||
|
content: content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,20 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
return SecurityElement.Escape(value);
|
return SecurityElement.Escape(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String TrimDoubleQuotes(String value)
|
||||||
|
{
|
||||||
|
var trimmed = string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(value) &&
|
||||||
|
value.Length > 8 &&
|
||||||
|
value.StartsWith('"') &&
|
||||||
|
value.EndsWith('"'))
|
||||||
|
{
|
||||||
|
trimmed = value.Substring(1, value.Length - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
private static string Base64StringEscapeShift(String value, int shift)
|
private static string Base64StringEscapeShift(String value, int shift)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.UTF8.GetBytes(value);
|
var bytes = Encoding.UTF8.GetBytes(value);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
foreach (var propertiesPair in properties)
|
foreach (var propertiesPair in properties)
|
||||||
{
|
{
|
||||||
var propertyName = propertiesPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} key");
|
var propertyName = propertiesPair.Key.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} key");
|
||||||
var propertyValue = propertiesPair.Value.AssertString($"{TemplateConstants.Definition} {TemplateConstants.Mapping} {TemplateConstants.Properties} value");
|
Properties.Add(propertyName.Value, new PropertyValue(propertiesPair.Value));
|
||||||
Properties.Add(propertyName.Value, new PropertyValue(propertyValue.Value));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Property '{TemplateConstants.LooseKeyType}' is defined but '{TemplateConstants.LooseValueType}' is not defined");
|
throw new ArgumentException($"Property '{TemplateConstants.LooseKeyType}' is defined but '{TemplateConstants.LooseValueType}' is not defined on '{name}'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise validate loose value type not be defined
|
// Otherwise validate loose value type not be defined
|
||||||
@@ -95,16 +94,21 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lookup each property
|
// Lookup each property
|
||||||
foreach (var property in Properties.Values)
|
foreach (var property in Properties)
|
||||||
{
|
{
|
||||||
schema.GetDefinition(property.Type);
|
if (String.IsNullOrEmpty(property.Value.Type))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Type not specified for the '{property.Key}' property on the '{name}' type");
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.GetDefinition(property.Value.Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(Inherits))
|
if (!String.IsNullOrEmpty(Inherits))
|
||||||
{
|
{
|
||||||
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.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,40 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
||||||
{
|
{
|
||||||
internal sealed class PropertyValue
|
internal sealed class PropertyValue
|
||||||
{
|
{
|
||||||
internal PropertyValue()
|
internal PropertyValue(TemplateToken token)
|
||||||
{
|
{
|
||||||
}
|
if (token is StringToken stringToken)
|
||||||
|
{
|
||||||
internal PropertyValue(String type)
|
Type = stringToken.Value;
|
||||||
{
|
}
|
||||||
Type = type;
|
else
|
||||||
|
{
|
||||||
|
var mapping = token.AssertMapping($"{TemplateConstants.MappingPropertyValue}");
|
||||||
|
foreach (var mappingPair in mapping)
|
||||||
|
{
|
||||||
|
var mappingKey = mappingPair.Key.AssertString($"{TemplateConstants.MappingPropertyValue} key");
|
||||||
|
switch (mappingKey.Value)
|
||||||
|
{
|
||||||
|
case TemplateConstants.Type:
|
||||||
|
Type = mappingPair.Value.AssertString($"{TemplateConstants.MappingPropertyValue} {TemplateConstants.Type}").Value;
|
||||||
|
break;
|
||||||
|
case TemplateConstants.Required:
|
||||||
|
Required = mappingPair.Value.AssertBoolean($"{TemplateConstants.MappingPropertyValue} {TemplateConstants.Required}").Value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mappingKey.AssertUnexpectedValue($"{TemplateConstants.MappingPropertyValue} key");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal String Type { get; set; }
|
internal String Type { get; set; }
|
||||||
|
|
||||||
|
internal Boolean Required { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,8 +312,8 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// template-schema
|
// template-schema
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Version, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Version, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyValue(TemplateConstants.Definitions));
|
mappingDefinition.Properties.Add(TemplateConstants.Definitions, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Definitions)));
|
||||||
schema.Definitions.Add(TemplateConstants.TemplateSchema, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.TemplateSchema, mappingDefinition);
|
||||||
|
|
||||||
// definitions
|
// definitions
|
||||||
@@ -335,9 +335,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// null-definition
|
// null-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyValue(TemplateConstants.NullDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Null, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NullDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.NullDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.NullDefinition, mappingDefinition);
|
||||||
|
|
||||||
// null-definition-properties
|
// null-definition-properties
|
||||||
@@ -346,9 +346,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// boolean-definition
|
// boolean-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyValue(TemplateConstants.BooleanDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Boolean, new PropertyValue(new StringToken(null, null, null, TemplateConstants.BooleanDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.BooleanDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.BooleanDefinition, mappingDefinition);
|
||||||
|
|
||||||
// boolean-definition-properties
|
// boolean-definition-properties
|
||||||
@@ -357,9 +357,9 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// number-definition
|
// number-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyValue(TemplateConstants.NumberDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Number, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NumberDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.NumberDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.NumberDefinition, mappingDefinition);
|
||||||
|
|
||||||
// number-definition-properties
|
// number-definition-properties
|
||||||
@@ -368,55 +368,68 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
|
|
||||||
// string-definition
|
// string-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyValue(TemplateConstants.StringDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.String, new PropertyValue(new StringToken(null, null, null, TemplateConstants.StringDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.StringDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.StringDefinition, mappingDefinition);
|
||||||
|
|
||||||
// string-definition-properties
|
// string-definition-properties
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Constant, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Constant, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyValue(TemplateConstants.Boolean));
|
mappingDefinition.Properties.Add(TemplateConstants.IgnoreCase, new PropertyValue(new StringToken(null, null, null,TemplateConstants.Boolean)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyValue(TemplateConstants.Boolean));
|
mappingDefinition.Properties.Add(TemplateConstants.RequireNonEmpty, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||||
schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.StringDefinitionProperties, mappingDefinition);
|
||||||
|
|
||||||
// sequence-definition
|
// sequence-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyValue(TemplateConstants.SequenceDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Sequence, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.SequenceDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.SequenceDefinition, mappingDefinition);
|
||||||
|
|
||||||
// sequence-definition-properties
|
// sequence-definition-properties
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.ItemType, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.ItemType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.SequenceDefinitionProperties, mappingDefinition);
|
||||||
|
|
||||||
// mapping-definition
|
// mapping-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyValue(TemplateConstants.MappingDefinitionProperties));
|
mappingDefinition.Properties.Add(TemplateConstants.Mapping, new PropertyValue(new StringToken(null, null, null, TemplateConstants.MappingDefinitionProperties)));
|
||||||
schema.Definitions.Add(TemplateConstants.MappingDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.MappingDefinition, mappingDefinition);
|
||||||
|
|
||||||
// mapping-definition-properties
|
// mapping-definition-properties
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Properties, new PropertyValue(TemplateConstants.Properties));
|
mappingDefinition.Properties.Add(TemplateConstants.Properties, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Properties)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.LooseKeyType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyValue(TemplateConstants.NonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.LooseValueType, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
schema.Definitions.Add(TemplateConstants.MappingDefinitionProperties, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.MappingDefinitionProperties, mappingDefinition);
|
||||||
|
|
||||||
// properties
|
// properties
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
|
mappingDefinition.LooseKeyType = TemplateConstants.NonEmptyString;
|
||||||
mappingDefinition.LooseValueType = TemplateConstants.NonEmptyString;
|
mappingDefinition.LooseValueType = TemplateConstants.PropertyValue;
|
||||||
schema.Definitions.Add(TemplateConstants.Properties, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.Properties, mappingDefinition);
|
||||||
|
|
||||||
|
// property-value
|
||||||
|
oneOfDefinition = new OneOfDefinition();
|
||||||
|
oneOfDefinition.OneOf.Add(TemplateConstants.NonEmptyString);
|
||||||
|
oneOfDefinition.OneOf.Add(TemplateConstants.MappingPropertyValue);
|
||||||
|
schema.Definitions.Add(TemplateConstants.PropertyValue, oneOfDefinition);
|
||||||
|
|
||||||
|
// mapping-property-value
|
||||||
|
mappingDefinition = new MappingDefinition();
|
||||||
|
mappingDefinition.Properties.Add(TemplateConstants.Type, new PropertyValue(new StringToken(null, null, null, TemplateConstants.NonEmptyString)));
|
||||||
|
mappingDefinition.Properties.Add(TemplateConstants.Required, new PropertyValue(new StringToken(null, null, null, TemplateConstants.Boolean)));
|
||||||
|
schema.Definitions.Add(TemplateConstants.MappingPropertyValue, mappingDefinition);
|
||||||
|
|
||||||
|
|
||||||
// one-of-definition
|
// one-of-definition
|
||||||
mappingDefinition = new MappingDefinition();
|
mappingDefinition = new MappingDefinition();
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(TemplateConstants.String));
|
mappingDefinition.Properties.Add(TemplateConstants.Description, new PropertyValue(new StringToken(null, null, null, TemplateConstants.String)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.Context, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyValue(TemplateConstants.SequenceOfNonEmptyString));
|
mappingDefinition.Properties.Add(TemplateConstants.OneOf, new PropertyValue(new StringToken(null, null, null, TemplateConstants.SequenceOfNonEmptyString)));
|
||||||
schema.Definitions.Add(TemplateConstants.OneOfDefinition, mappingDefinition);
|
schema.Definitions.Add(TemplateConstants.OneOfDefinition, mappingDefinition);
|
||||||
|
|
||||||
// non-empty-string
|
// non-empty-string
|
||||||
@@ -477,4 +490,4 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema
|
|||||||
private static readonly Regex s_definitionNameRegex = new Regex("^[a-zA-Z_][a-zA-Z0-9_-]*$", RegexOptions.Compiled);
|
private static readonly Regex s_definitionNameRegex = new Regex("^[a-zA-Z_][a-zA-Z0-9_-]*$", RegexOptions.Compiled);
|
||||||
private static TemplateSchema s_schema;
|
private static TemplateSchema s_schema;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,9 +22,11 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
internal const String ItemType = "item-type";
|
internal const String ItemType = "item-type";
|
||||||
internal const String LooseKeyType = "loose-key-type";
|
internal const String LooseKeyType = "loose-key-type";
|
||||||
internal const String LooseValueType = "loose-value-type";
|
internal const String LooseValueType = "loose-value-type";
|
||||||
|
internal const String MaxConstant = "MAX";
|
||||||
internal const String Mapping = "mapping";
|
internal const String Mapping = "mapping";
|
||||||
internal const String MappingDefinition = "mapping-definition";
|
internal const String MappingDefinition = "mapping-definition";
|
||||||
internal const String MappingDefinitionProperties = "mapping-definition-properties";
|
internal const String MappingDefinitionProperties = "mapping-definition-properties";
|
||||||
|
internal const String MappingPropertyValue = "mapping-property-value";
|
||||||
internal const String NonEmptyString = "non-empty-string";
|
internal const String NonEmptyString = "non-empty-string";
|
||||||
internal const String Null = "null";
|
internal const String Null = "null";
|
||||||
internal const String NullDefinition = "null-definition";
|
internal const String NullDefinition = "null-definition";
|
||||||
@@ -35,7 +37,9 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
internal const String OneOf = "one-of";
|
internal const String OneOf = "one-of";
|
||||||
internal const String OneOfDefinition = "one-of-definition";
|
internal const String OneOfDefinition = "one-of-definition";
|
||||||
internal const String OpenExpression = "${{";
|
internal const String OpenExpression = "${{";
|
||||||
|
internal const String PropertyValue = "property-value";
|
||||||
internal const String Properties = "properties";
|
internal const String Properties = "properties";
|
||||||
|
internal const String Required = "required";
|
||||||
internal const String RequireNonEmpty = "require-non-empty";
|
internal const String RequireNonEmpty = "require-non-empty";
|
||||||
internal const String Scalar = "scalar";
|
internal const String Scalar = "scalar";
|
||||||
internal const String ScalarDefinition = "scalar-definition";
|
internal const String ScalarDefinition = "scalar-definition";
|
||||||
@@ -43,6 +47,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
internal const String Sequence = "sequence";
|
internal const String Sequence = "sequence";
|
||||||
internal const String SequenceDefinition = "sequence-definition";
|
internal const String SequenceDefinition = "sequence-definition";
|
||||||
internal const String SequenceDefinitionProperties = "sequence-definition-properties";
|
internal const String SequenceDefinitionProperties = "sequence-definition-properties";
|
||||||
|
internal const String Type = "type";
|
||||||
internal const String SequenceOfNonEmptyString = "sequence-of-non-empty-string";
|
internal const String SequenceOfNonEmptyString = "sequence-of-non-empty-string";
|
||||||
internal const String String = "string";
|
internal const String String = "string";
|
||||||
internal const String StringDefinition = "string-definition";
|
internal const String StringDefinition = "string-definition";
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
id = FileIds.Count + 1;
|
id = FileIds.Count + 1;
|
||||||
FileIds.Add(file, id);
|
FileIds.Add(file, id);
|
||||||
FileNames.Add(file);
|
FileNames.Add(file);
|
||||||
|
Memory.AddBytes(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
@@ -191,7 +192,12 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
|
|
||||||
internal String GetFileName(Int32 fileId)
|
internal String GetFileName(Int32 fileId)
|
||||||
{
|
{
|
||||||
return FileNames[fileId - 1];
|
return FileNames.Count >= fileId ? FileNames[fileId - 1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IReadOnlyList<String> GetFileTable()
|
||||||
|
{
|
||||||
|
return FileNames.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String GetErrorPrefix(
|
private String GetErrorPrefix(
|
||||||
@@ -199,9 +205,9 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
Int32? line,
|
Int32? line,
|
||||||
Int32? column)
|
Int32? column)
|
||||||
{
|
{
|
||||||
if (fileId != null)
|
var fileName = fileId.HasValue ? GetFileName(fileId.Value) : null;
|
||||||
|
if (!String.IsNullOrEmpty(fileName))
|
||||||
{
|
{
|
||||||
var fileName = GetFileName(fileId.Value);
|
|
||||||
if (line != null && column != null)
|
if (line != null && column != null)
|
||||||
{
|
{
|
||||||
return $"{fileName} {TemplateStrings.LineColumn(line, column)}:";
|
return $"{fileName} {TemplateStrings.LineColumn(line, column)}:";
|
||||||
|
|||||||
@@ -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);
|
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);
|
||||||
|
|
||||||
@@ -182,12 +191,14 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
}
|
}
|
||||||
|
|
||||||
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
var keys = new HashSet<String>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var hasExpressionKey = false;
|
||||||
|
|
||||||
while (m_unraveler.AllowScalar(definition.Expand, out ScalarToken nextKeyScalar))
|
while (m_unraveler.AllowScalar(definition.Expand, out ScalarToken nextKeyScalar))
|
||||||
{
|
{
|
||||||
// Expression
|
// Expression
|
||||||
if (nextKeyScalar is ExpressionToken)
|
if (nextKeyScalar is ExpressionToken)
|
||||||
{
|
{
|
||||||
|
hasExpressionKey = true;
|
||||||
var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any);
|
var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any);
|
||||||
mapping.Add(nextKeyScalar, Evaluate(anyDefinition));
|
mapping.Add(nextKeyScalar, Evaluate(anyDefinition));
|
||||||
continue;
|
continue;
|
||||||
@@ -268,6 +279,19 @@ namespace GitHub.DistributedTask.ObjectTemplating
|
|||||||
String listToDeDuplicate = String.Join(", ", nonDuplicates);
|
String listToDeDuplicate = String.Join(", ", nonDuplicates);
|
||||||
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
|
m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
|
||||||
}
|
}
|
||||||
|
else if (mappingDefinitions.Count == 1 && !hasExpressionKey)
|
||||||
|
{
|
||||||
|
foreach (var property in mappingDefinitions[0].Properties)
|
||||||
|
{
|
||||||
|
if (property.Value.Required)
|
||||||
|
{
|
||||||
|
if (!keys.Contains(property.Key))
|
||||||
|
{
|
||||||
|
m_context.Error(mapping, $"Required property is missing: {property.Key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_unraveler.ReadMappingEnd();
|
m_unraveler.ReadMappingEnd();
|
||||||
}
|
}
|
||||||
@@ -378,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,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)
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user