mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ee7717774 | ||
|
|
c946435010 | ||
|
|
0953ffa62b | ||
|
|
66727f76c8 | ||
|
|
7ee333b5cd | ||
|
|
3b34e203dc | ||
|
|
e808190dd2 | ||
|
|
d2cb9d7685 | ||
|
|
5ba6a2c78d | ||
|
|
fc3ca9bb92 | ||
|
|
a94a19bb36 | ||
|
|
a9be5f6557 | ||
|
|
3600f20cd3 | ||
|
|
81a00fff3e | ||
|
|
31474098ff | ||
|
|
7ff6ff6afa | ||
|
|
56529a1c2f | ||
|
|
510fadf71a | ||
|
|
007ac8138b | ||
|
|
1e12b8909a | ||
|
|
9ceb3d481a | ||
|
|
3bce2eb09c | ||
|
|
80bf68db81 | ||
|
|
a2e32170fd | ||
|
|
35dda19491 |
1
.github/workflows/codeql.yml
vendored
1
.github/workflows/codeql.yml
vendored
@@ -2,6 +2,7 @@ name: "Code Scanning - Action"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * 0'
|
- cron: '0 0 * * 0'
|
||||||
|
|
||||||
|
|||||||
335
.github/workflows/e2etest.yml
vendored
Normal file
335
.github/workflows/e2etest.yml
vendored
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
name: Runner E2E Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- releases/*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
init:
|
||||||
|
name: Initialize workflow ☕
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
unique_runner_label: ${{steps.generator.outputs.runner_label}}
|
||||||
|
steps:
|
||||||
|
- name: Delete all runners
|
||||||
|
uses: actions/github-script@v3
|
||||||
|
with:
|
||||||
|
debug: true
|
||||||
|
script: |
|
||||||
|
var runnersResp = await github.actions.listSelfHostedRunnersForRepo({
|
||||||
|
owner: 'actions',
|
||||||
|
repo: 'runner',
|
||||||
|
per_page: '100'
|
||||||
|
});
|
||||||
|
for(var i=0; i<runnersResp.data.total_count; i++){
|
||||||
|
core.debug(JSON.stringify(runnersResp.data.runners[i]))
|
||||||
|
await github.actions.deleteSelfHostedRunnerFromRepo({
|
||||||
|
owner: 'actions',
|
||||||
|
repo: 'runner',
|
||||||
|
runner_id: runnersResp.data.runners[i].id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
github-token: ${{secrets.PAT}}
|
||||||
|
- name: Generate Unique Runner label
|
||||||
|
id: generator
|
||||||
|
run: |
|
||||||
|
label=$(openssl rand -hex 16)
|
||||||
|
echo ::set-output name=runner_label::$label
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build runner packages 🏗 📦
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64 ]
|
||||||
|
include:
|
||||||
|
- runtime: linux-x64
|
||||||
|
os: ubuntu-latest
|
||||||
|
devScript: ./dev.sh
|
||||||
|
|
||||||
|
- runtime: linux-arm64
|
||||||
|
os: ubuntu-latest
|
||||||
|
devScript: ./dev.sh
|
||||||
|
|
||||||
|
- runtime: linux-arm
|
||||||
|
os: ubuntu-latest
|
||||||
|
devScript: ./dev.sh
|
||||||
|
|
||||||
|
- runtime: osx-x64
|
||||||
|
os: macOS-latest
|
||||||
|
devScript: ./dev.sh
|
||||||
|
|
||||||
|
- runtime: win-x64
|
||||||
|
os: windows-latest
|
||||||
|
devScript: ./dev
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
# Build runner layout
|
||||||
|
- name: Build & Layout Release
|
||||||
|
run: |
|
||||||
|
${{ matrix.devScript }} layout Release ${{ matrix.runtime }}
|
||||||
|
working-directory: src
|
||||||
|
|
||||||
|
# Create runner package tar.gz/zip
|
||||||
|
- name: Package Release
|
||||||
|
run: |
|
||||||
|
${{ matrix.devScript }} package Release ${{ matrix.runtime }}
|
||||||
|
working-directory: src
|
||||||
|
|
||||||
|
# Upload runner package tar.gz/zip as artifact
|
||||||
|
- name: Publish Artifact
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: runner-package-${{ matrix.runtime }}
|
||||||
|
path: _package
|
||||||
|
|
||||||
|
dispatch_workflow:
|
||||||
|
name: Dispatch workflow to runners 🚨
|
||||||
|
needs: [init, build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Dispatch workflow
|
||||||
|
timeout-minutes: 10
|
||||||
|
uses: actions/github-script@v3
|
||||||
|
with:
|
||||||
|
debug: true
|
||||||
|
script: |
|
||||||
|
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
|
||||||
|
async function dispatchWorkflow(runner) {
|
||||||
|
await github.actions.createWorkflowDispatch({
|
||||||
|
owner: 'actions',
|
||||||
|
repo: 'runner',
|
||||||
|
workflow_id: 'runner-basic-e2e-test-case.yml',
|
||||||
|
ref: 'main',
|
||||||
|
inputs: {target_runner: runner}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var runWin64 = false, runLinux64 = false, runOsx64 = false, runLinuxARM64 = false;
|
||||||
|
while (true) {
|
||||||
|
core.info(`------------- Waiting for runners to be configured --------------`)
|
||||||
|
await sleep(10000);
|
||||||
|
var runnersResp = await github.actions.listSelfHostedRunnersForRepo({owner: 'actions', repo: 'runner', per_page: '100'});
|
||||||
|
for (var i = 0; i < runnersResp.data.total_count; i++) {
|
||||||
|
core.debug(JSON.stringify(runnersResp.data.runners[i]))
|
||||||
|
var labels = runnersResp.data.runners[i].labels;
|
||||||
|
for (var j = 0; j < labels.length; j++) {
|
||||||
|
core.debug(`Comparing: ${labels[j].name} to win-x64/linux-x64/osx-x64/linux-arm64-${{ needs.init.outputs.unique_runner_label }}`)
|
||||||
|
if (labels[j].name == 'win-x64-${{needs.init.outputs.unique_runner_label}}' && runWin64 == false) {
|
||||||
|
core.info(`------------------- Windows runner is configured, queue Windows Run -------------------------`)
|
||||||
|
runWin64 = true;
|
||||||
|
await dispatchWorkflow('win-x64-${{needs.init.outputs.unique_runner_label}}');
|
||||||
|
break;
|
||||||
|
} else if (labels[j].name == 'linux-x64-${{needs.init.outputs.unique_runner_label}}' && runLinux64 == false) {
|
||||||
|
core.info(`------------------- Linux runner is configured, queue Linux Run -------------------------`)
|
||||||
|
runLinux64 = true;
|
||||||
|
await dispatchWorkflow('linux-x64-${{needs.init.outputs.unique_runner_label}}');
|
||||||
|
break;
|
||||||
|
} else if (labels[j].name == 'osx-x64-${{needs.init.outputs.unique_runner_label}}' && runOsx64 == false) {
|
||||||
|
core.info(`------------------- macOS runner is configured, queue macOS Run -------------------------`)
|
||||||
|
runOsx64 = true;
|
||||||
|
await dispatchWorkflow('osx-x64-${{needs.init.outputs.unique_runner_label}}');
|
||||||
|
break;
|
||||||
|
} else if (labels[j].name == 'linux-arm64-${{needs.init.outputs.unique_runner_label}}' && runLinuxARM64 == false) {
|
||||||
|
core.info(`------------------- Linux ARM64 runner is configured, queue Linux ARM64 Run-------------------------`)
|
||||||
|
runLinuxARM64 = true;
|
||||||
|
await dispatchWorkflow('linux-arm64-${{needs.init.outputs.unique_runner_label}}');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (runWin64 && runLinux64 && runOsx64 && runLinuxARM64) {
|
||||||
|
core.info(`--------------------- ALL runner are running jobs --------------------------`)
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
core.info(`---------- Windows running: ${runWin64} -- Linux running: ${runLinux64} -- macOS running: ${runOsx64} -- Linux ARM64 running: ${runLinuxARM64} -----------`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
github-token: ${{secrets.PAT}}
|
||||||
|
|
||||||
|
LinuxE2E:
|
||||||
|
needs: [build, init]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Runner
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: runner-package-linux-x64
|
||||||
|
- name: Unzip Runner Package
|
||||||
|
run: |
|
||||||
|
tar -xzf *.tar.gz
|
||||||
|
- name: Configure Runner
|
||||||
|
env:
|
||||||
|
unique_runner_name: linux-x64-${{needs.init.outputs.unique_runner_label}}
|
||||||
|
run: |
|
||||||
|
./config.sh --url ${{github.event.repository.html_url}} --unattended --name $unique_runner_name --pat ${{secrets.PAT}} --labels $unique_runner_name --replace
|
||||||
|
- name: Start Runner and Wait for Job
|
||||||
|
timeout-minutes: 5
|
||||||
|
run: |
|
||||||
|
./run.sh --once
|
||||||
|
- name: Remove Runner
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
./config.sh remove --pat ${{secrets.PAT}}
|
||||||
|
- name: Upload Runner Logs
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: linux_x64_logs
|
||||||
|
path: _diag
|
||||||
|
macOSE2E:
|
||||||
|
needs: [build, init]
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Runner
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: runner-package-osx-x64
|
||||||
|
- name: Unzip Runner Package
|
||||||
|
run: |
|
||||||
|
tar -xzf *.tar.gz
|
||||||
|
- name: Configure Runner
|
||||||
|
env:
|
||||||
|
unique_runner_name: osx-x64-${{needs.init.outputs.unique_runner_label}}
|
||||||
|
run: |
|
||||||
|
./config.sh --url ${{github.event.repository.html_url}} --unattended --name $unique_runner_name --pat ${{secrets.PAT}} --labels $unique_runner_name --replace
|
||||||
|
- name: Start Runner and Wait for Job
|
||||||
|
timeout-minutes: 5
|
||||||
|
run: |
|
||||||
|
./run.sh --once
|
||||||
|
- name: Remove Runner
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
./config.sh remove --pat ${{secrets.PAT}}
|
||||||
|
- name: Upload Runner Logs
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: osx_x64_logs
|
||||||
|
path: _diag
|
||||||
|
|
||||||
|
ARM64E2E:
|
||||||
|
needs: [build, init]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Runner
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: runner-package-linux-arm64
|
||||||
|
- name: Unzip Runner Package
|
||||||
|
run: |
|
||||||
|
tar -xzf *.tar.gz
|
||||||
|
- name: Prepare QEMU
|
||||||
|
run: |
|
||||||
|
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||||
|
- name: Configure Runner
|
||||||
|
uses: docker://multiarch/ubuntu-core:arm64-bionic
|
||||||
|
with:
|
||||||
|
args: 'bash -c "apt-get update && apt-get install -y curl && ./bin/installdependencies.sh && ./config.sh --unattended --name $unique_runner_name --url ${{github.event.repository.html_url}} --pat ${{secrets.PAT}} --labels $unique_runner_name --replace"'
|
||||||
|
env:
|
||||||
|
RUNNER_ALLOW_RUNASROOT: 1
|
||||||
|
unique_runner_name: linux-arm64-${{needs.init.outputs.unique_runner_label}}
|
||||||
|
|
||||||
|
- name: Start Runner and Wait for Job
|
||||||
|
timeout-minutes: 5
|
||||||
|
uses: docker://multiarch/ubuntu-core:arm64-bionic
|
||||||
|
with:
|
||||||
|
args: 'bash -c "apt-get update && apt-get install -y curl git && ./bin/installdependencies.sh && ./run.sh --once"'
|
||||||
|
env:
|
||||||
|
RUNNER_ALLOW_RUNASROOT: 1
|
||||||
|
|
||||||
|
- name: Remove Runner
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true
|
||||||
|
uses: docker://multiarch/ubuntu-core:arm64-bionic
|
||||||
|
with:
|
||||||
|
args: 'bash -c "apt-get update && apt-get install -y curl && ./bin/installdependencies.sh && ./config.sh remove --pat ${{secrets.PAT}}"'
|
||||||
|
env:
|
||||||
|
RUNNER_ALLOW_RUNASROOT: 1
|
||||||
|
|
||||||
|
- name: Upload Runner Logs
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: linux_arm64_logs
|
||||||
|
path: _diag
|
||||||
|
|
||||||
|
WindowsE2E:
|
||||||
|
needs: [build, init]
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Runner
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: runner-package-win-x64
|
||||||
|
- name: Unzip Runner Package
|
||||||
|
run: |
|
||||||
|
Get-ChildItem *.zip | Expand-Archive -DestinationPath $PWD
|
||||||
|
- name: Configure Runner
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
config.cmd --unattended --url ${{github.event.repository.html_url}} --name %unique_runner_name% --pat ${{secrets.PAT}} --labels %unique_runner_name% --replace
|
||||||
|
env:
|
||||||
|
unique_runner_name: win-x64-${{needs.init.outputs.unique_runner_label}}
|
||||||
|
|
||||||
|
- name: Start Runner and Wait for Job
|
||||||
|
shell: cmd
|
||||||
|
timeout-minutes: 5
|
||||||
|
run: |
|
||||||
|
run.cmd --once
|
||||||
|
- name: Remove Runner
|
||||||
|
shell: cmd
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
config.cmd remove --pat ${{secrets.PAT}}
|
||||||
|
- name: Upload Runner Logs
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: win_x64_logs
|
||||||
|
path: _diag
|
||||||
|
|
||||||
|
check:
|
||||||
|
name: Check runner logs 🕵️♂️
|
||||||
|
needs: [WindowsE2E, LinuxE2E, macOSE2E, ARM64E2E]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Linux Runner Logs
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: linux_x64_logs
|
||||||
|
path: linux_x64_logs
|
||||||
|
- name: Download macOS Runner Logs
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: osx_x64_logs
|
||||||
|
path: osx_x64_logs
|
||||||
|
- name: Download Linux ARM64 Runner Logs
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: linux_arm64_logs
|
||||||
|
path: linux_arm64_logs
|
||||||
|
- name: Download Windows Runner Logs
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: win_x64_logs
|
||||||
|
path: win_x64_logs
|
||||||
|
- name: Check Runner Logs
|
||||||
|
run: |
|
||||||
|
function failed()
|
||||||
|
{
|
||||||
|
local error=${1:-Undefined error}
|
||||||
|
echo "Failed: $error" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -R "completed with result: Succeeded" ./win_x64_logs || failed "Windows Runner fail to run the job, please check logs"
|
||||||
|
grep -R "completed with result: Succeeded" ./linux_x64_logs || failed "Linux Runner fail to run the job, please check logs"
|
||||||
|
grep -R "completed with result: Succeeded" ./osx_x64_logs || failed "macOS Runner fail to run the job, please check logs"
|
||||||
|
grep -R "completed with result: Succeeded" ./linux_arm64_logs || failed "Linux ARM64 Runner fail to run the job, please check logs"
|
||||||
31
.github/workflows/runner-basic-e2e-test-case.yml
vendored
Normal file
31
.github/workflows/runner-basic-e2e-test-case.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Runner Basics Test Case
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
target_runner:
|
||||||
|
description: 'Self-hosted runner will run the job'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on:
|
||||||
|
- self-hosted
|
||||||
|
- ${{github.event.inputs.target_runner}}
|
||||||
|
|
||||||
|
name: Runner Basic Test 🛠
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Run a one-line script
|
||||||
|
run: echo Hello, world!
|
||||||
|
- name: Run a multi-line script
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
printenv|sort
|
||||||
|
cat $GITHUB_EVENT_PATH
|
||||||
|
- name: Validate GitHub Context
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
declare -a context_vars=("GITHUB_ACTION" "GITHUB_ACTIONS" "GITHUB_REPOSITORY" "GITHUB_WORKSPACE" "GITHUB_SHA" "GITHUB_RUN_ID" "GITHUB_RUN_NUMBER")
|
||||||
|
for var in ${context_vars[@]};
|
||||||
|
do [ -z "${!var}" ] && echo "##[error]$var not found" && exit 1 || echo "$var: ${!var}"; done
|
||||||
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* @actions/actions-runtime
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
# GitHub Actions Runner
|
# GitHub Actions Runner
|
||||||
|
|
||||||
[](https://github.com/actions/runner/actions)
|
[](https://github.com/actions/runner/actions)
|
||||||
|
[](https://github.com/actions/runner/actions)
|
||||||
|
|
||||||
The runner is the application that runs a job from a GitHub Actions workflow. It is used by GitHub Actions in the [hosted virtual environments](https://github.com/actions/virtual-environments), or you can [self-host the runner](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) in your own environment.
|
The runner is the application that runs a job from a GitHub Actions workflow. It is used by GitHub Actions in the [hosted virtual environments](https://github.com/actions/virtual-environments), or you can [self-host the runner](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) in your own environment.
|
||||||
|
|
||||||
|
|||||||
44
docs/checks/actions.md
Normal file
44
docs/checks/actions.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
# Actions Connection Check
|
||||||
|
|
||||||
|
## What is this check for?
|
||||||
|
|
||||||
|
Make sure the runner has access to actions service for GitHub.com or GitHub Enterprise Server
|
||||||
|
|
||||||
|
- For GitHub.com
|
||||||
|
- The runner needs to access https://api.github.com for downloading actions.
|
||||||
|
- The runner needs to access https://vstoken.actions.githubusercontent.com/_apis/.../ for requesting an access token.
|
||||||
|
- The runner needs to access https://pipelines.actions.githubusercontent.com/_apis/.../ for receiving workflow jobs.
|
||||||
|
- For GitHub Enterprise Server
|
||||||
|
- The runner needs to access https://myGHES.com/api/v3 for downloading actions.
|
||||||
|
- The runner needs to access https://myGHES.com/_services/vstoken/_apis/.../ for requesting an access token.
|
||||||
|
- The runner needs to access https://myGHES.com/_services/pipelines/_apis/.../ for receiving workflow jobs.
|
||||||
|
|
||||||
|
## What is checked?
|
||||||
|
|
||||||
|
- DNS lookup for api.github.com or myGHES.com using dotnet
|
||||||
|
- Ping api.github.com or myGHES.com using dotnet
|
||||||
|
- Make HTTP GET to https://api.github.com or https://myGHES.com/api/v3 using dotnet, check response headers contains `X-GitHub-Request-Id`
|
||||||
|
---
|
||||||
|
- DNS lookup for vstoken.actions.githubusercontent.com using dotnet
|
||||||
|
- Ping vstoken.actions.githubusercontent.com using dotnet
|
||||||
|
- Make HTTP GET to https://vstoken.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/vstoken/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
||||||
|
---
|
||||||
|
- DNS lookup for pipelines.actions.githubusercontent.com using dotnet
|
||||||
|
- Ping pipelines.actions.githubusercontent.com using dotnet
|
||||||
|
- Make HTTP GET to https://pipelines.actions.githubusercontent.com/_apis/health or https://myGHES.com/_services/pipelines/_apis/health using dotnet, check response headers contains `x-vss-e2eid`
|
||||||
|
|
||||||
|
## How to fix the issue?
|
||||||
|
|
||||||
|
### 1. Check the common network issue
|
||||||
|
|
||||||
|
> Please check the [network doc](./network.md)
|
||||||
|
|
||||||
|
### 2. SSL certificate related issue
|
||||||
|
|
||||||
|
If you are seeing `System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.` in the log, it means the runner can't connect to Actions service due to SSL handshake failure.
|
||||||
|
> Please check the [SSL cert doc](./sslcert.md)
|
||||||
|
|
||||||
|
## Still not working?
|
||||||
|
|
||||||
|
Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
||||||
34
docs/checks/git.md
Normal file
34
docs/checks/git.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Git Connection Check
|
||||||
|
|
||||||
|
## What is this check for?
|
||||||
|
|
||||||
|
Make sure `git` can access GitHub.com or your GitHub Enterprise Server.
|
||||||
|
|
||||||
|
|
||||||
|
## What is checked?
|
||||||
|
|
||||||
|
The test is done by executing
|
||||||
|
```bash
|
||||||
|
# For GitHub.com
|
||||||
|
git ls-remote --exit-code https://github.com/actions/checkout HEAD
|
||||||
|
|
||||||
|
# For GitHub Enterprise Server
|
||||||
|
git ls-remote --exit-code https://ghes.me/actions/checkout HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
The test also set environment variable `GIT_TRACE=1` and `GIT_CURL_VERBOSE=1` before running `git ls-remote`, this will make `git` to produce debug log for better debug any potential issues.
|
||||||
|
|
||||||
|
## How to fix the issue?
|
||||||
|
|
||||||
|
### 1. Check the common network issue
|
||||||
|
|
||||||
|
> Please check the [network doc](./network.md)
|
||||||
|
|
||||||
|
### 2. SSL certificate related issue
|
||||||
|
|
||||||
|
If you are seeing `SSL Certificate problem:` in the log, it means the `git` can't connect to the GitHub server due to SSL handshake failure.
|
||||||
|
> Please check the [SSL cert doc](./sslcert.md)
|
||||||
|
|
||||||
|
## Still not working?
|
||||||
|
|
||||||
|
Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
||||||
26
docs/checks/internet.md
Normal file
26
docs/checks/internet.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Internet Connection Check
|
||||||
|
|
||||||
|
## What is this check for?
|
||||||
|
|
||||||
|
Make sure the runner has access to https://api.github.com
|
||||||
|
|
||||||
|
The runner needs to access https://api.github.com to download any actions from the marketplace.
|
||||||
|
|
||||||
|
Even the runner is configured to GitHub Enterprise Server, the runner can still download actions from GitHub.com with [GitHub Connect](https://docs.github.com/en/enterprise-server@2.22/admin/github-actions/enabling-automatic-access-to-githubcom-actions-using-github-connect)
|
||||||
|
|
||||||
|
|
||||||
|
## What is checked?
|
||||||
|
|
||||||
|
- DNS lookup for api.github.com using dotnet
|
||||||
|
- Ping api.github.com using dotnet
|
||||||
|
- Make HTTP GET to https://api.github.com using dotnet, check response headers contains `X-GitHub-Request-Id`
|
||||||
|
|
||||||
|
## How to fix the issue?
|
||||||
|
|
||||||
|
### 1. Check the common network issue
|
||||||
|
|
||||||
|
> Please check the [network doc](./network.md)
|
||||||
|
|
||||||
|
## Still not working?
|
||||||
|
|
||||||
|
Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
||||||
29
docs/checks/network.md
Normal file
29
docs/checks/network.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
## Common Network Related Issues
|
||||||
|
|
||||||
|
### Common things that can cause the runner to not working properly
|
||||||
|
|
||||||
|
- Bug in the runner or the dotnet framework that causes actions runner can't make Http request in a certain network environment.
|
||||||
|
|
||||||
|
- Proxy/Firewall block certain HTTP method, like it block all POST and PUT calls which the runner will use to upload logs.
|
||||||
|
|
||||||
|
- Proxy/Firewall only allows requests with certain user-agent to pass through and the actions runner user-agent is not in the allow list.
|
||||||
|
|
||||||
|
- Proxy try to decrypt and exam HTTPS traffic for security purpose but cause the actions-runner to fail to finish SSL handshake due to the lack of trusting proxy's CA.
|
||||||
|
|
||||||
|
- Firewall rules that block action runner from accessing certain hosts, ex: `*.github.com`, `*.actions.githubusercontent.com`, etc.
|
||||||
|
|
||||||
|
|
||||||
|
### Identify and solve these problems
|
||||||
|
|
||||||
|
The key is to figure out where is the problem, the network environment, or the actions runner?
|
||||||
|
|
||||||
|
Use a 3rd party tool to make the same requests as the runner did would be a good start point.
|
||||||
|
|
||||||
|
- Use `nslookup` to check DNS
|
||||||
|
- Use `ping` to check Ping
|
||||||
|
- Use `curl -v` to check the network stack, good for verifying default certificate/proxy settings.
|
||||||
|
- Use `Invoke-WebRequest` from `pwsh` (`PowerShell Core`) to check the dotnet network stack, good for verifying bugs in the dotnet framework.
|
||||||
|
|
||||||
|
If the 3rd party tool is also experiencing the same error as the runner does, then you might want to contact your network administrator for help.
|
||||||
|
|
||||||
|
Otherwise, contact GitHub customer support or log an issue at https://github.com/actions/runner
|
||||||
30
docs/checks/nodejs.md
Normal file
30
docs/checks/nodejs.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Node.js Connection Check
|
||||||
|
|
||||||
|
## What is this check for?
|
||||||
|
|
||||||
|
Make sure the built-in node.js has access to GitHub.com or GitHub Enterprise Server.
|
||||||
|
|
||||||
|
The runner carries it's own copy of node.js executable under `<runner_root>/externals/node12/`.
|
||||||
|
|
||||||
|
All javascript base Actions will get executed by the built-in `node` at `<runner_root>/externals/node12/`.
|
||||||
|
|
||||||
|
> Not the `node` from `$PATH`
|
||||||
|
|
||||||
|
## What is checked?
|
||||||
|
|
||||||
|
- Make HTTPS GET to https://api.github.com or https://myGHES.com/api/v3 using node.js, make sure it gets 200 response code.
|
||||||
|
|
||||||
|
## How to fix the issue?
|
||||||
|
|
||||||
|
### 1. Check the common network issue
|
||||||
|
|
||||||
|
> Please check the [network doc](./network.md)
|
||||||
|
|
||||||
|
### 2. SSL certificate related issue
|
||||||
|
|
||||||
|
If you are seeing `Https request failed due to SSL cert issue` in the log, it means the `node.js` can't connect to the GitHub server due to SSL handshake failure.
|
||||||
|
> Please check the [SSL cert doc](./sslcert.md)
|
||||||
|
|
||||||
|
## Still not working?
|
||||||
|
|
||||||
|
Contact GitHub customer service or log an issue at https://github.com/actions/runner if you think it's a runner issue.
|
||||||
89
docs/checks/sslcert.md
Normal file
89
docs/checks/sslcert.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
## SSL Certificate Related Issues
|
||||||
|
|
||||||
|
You might run into an SSL certificate error when your GitHub Enterprise Server is using a self-signed SSL server certificate or a web proxy within your network is decrypting HTTPS traffic for a security audit.
|
||||||
|
|
||||||
|
As long as your certificate is generated properly, most of the issues should be fixed after your trust the certificate properly on the runner machine.
|
||||||
|
|
||||||
|
> Different OS might have extra requirements on SSL certificate,
|
||||||
|
> Ex: macOS requires `ExtendedKeyUsage` https://support.apple.com/en-us/HT210176
|
||||||
|
|
||||||
|
### Don't skip SSL cert validation
|
||||||
|
|
||||||
|
> !!! DO NOT SKIP SSL CERT VALIDATION !!!
|
||||||
|
> !!! IT IS A BAD SECURITY PRACTICE !!!
|
||||||
|
|
||||||
|
### Download SSL certificate chain
|
||||||
|
|
||||||
|
Depends on how your SSL server certificate gets configured, you might need to download the whole certificate chain from a machine that has trusted the SSL certificate's CA.
|
||||||
|
|
||||||
|
- Approach 1: Download certificate chain using a browser (Chrome, Firefox, IT), you can google for more example, [here is what I found](https://medium.com/@menakajain/export-download-ssl-certificate-from-server-site-url-bcfc41ea46a2)
|
||||||
|
|
||||||
|
- Approach 2: Download certificate chain using OpenSSL, you can google for more example, [here is what I found](https://superuser.com/a/176721)
|
||||||
|
|
||||||
|
- Approach 3: Ask your network administrator or the owner of the CA certificate to send you a copy of it
|
||||||
|
|
||||||
|
### Trust CA certificate for the Runner
|
||||||
|
|
||||||
|
The actions runner is a dotnet core application which will follow how dotnet load SSL CA certificates on each OS.
|
||||||
|
|
||||||
|
You can get full details documentation at [here](https://docs.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography#x509store)
|
||||||
|
|
||||||
|
In short:
|
||||||
|
- Windows: Load from Windows certificate store.
|
||||||
|
- Linux: Load from OpenSSL CA cert bundle.
|
||||||
|
- macOS: Load from macOS KeyChain.
|
||||||
|
|
||||||
|
To let the runner trusts your CA certificate, you will need to:
|
||||||
|
1. Save your SSL certificate chain which includes the root CA and all intermediate CAs into a `.pem` file.
|
||||||
|
2. Use `OpenSSL` to convert `.pem` file to a proper format for different OS, here is some [doc with sample commands](https://www.sslshopper.com/ssl-converter.html)
|
||||||
|
3. Trust CA on different OS:
|
||||||
|
- Windows: https://docs.microsoft.com/en-us/skype-sdk/sdn/articles/installing-the-trusted-root-certificate
|
||||||
|
- macOS: 
|
||||||
|
- Linux: Refer to the distribution documentation
|
||||||
|
1. RedHat: https://www.redhat.com/sysadmin/ca-certificates-cli
|
||||||
|
2. Ubuntu: http://manpages.ubuntu.com/manpages/focal/man8/update-ca-certificates.8.html
|
||||||
|
3. Google search: "trust ca certificate on [linux distribution]"
|
||||||
|
4. If all approaches failed, set environment variable `SSL_CERT_FILE` to the CA bundle `.pem` file we get.
|
||||||
|
> To verity cert gets installed properly on Linux, you can try use `curl -v https://sitewithsslissue.com` and `pwsh -Command \"Invoke-WebRequest -Uri https://sitewithsslissue.com\"`
|
||||||
|
|
||||||
|
### Trust CA certificate for Git CLI
|
||||||
|
|
||||||
|
Git uses various CA bundle file depends on your operation system.
|
||||||
|
- Git packaged the CA bundle file within the Git installation on Windows
|
||||||
|
- Git use OpenSSL certificate CA bundle file on Linux and macOS
|
||||||
|
|
||||||
|
You can check where Git check CA file by running:
|
||||||
|
```bash
|
||||||
|
export GIT_CURL_VERBOSE=1
|
||||||
|
git ls-remote https://github.com/actions/runner HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see something like:
|
||||||
|
```
|
||||||
|
* Couldn't find host github.com in the .netrc file; using defaults
|
||||||
|
* Trying 140.82.114.4...
|
||||||
|
* TCP_NODELAY set
|
||||||
|
* Connected to github.com (140.82.114.4) port 443 (#0)
|
||||||
|
* ALPN, offering h2
|
||||||
|
* ALPN, offering http/1.1
|
||||||
|
* successfully set certificate verify locations:
|
||||||
|
* CAfile: /etc/ssl/cert.pem
|
||||||
|
CApath: none
|
||||||
|
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
|
||||||
|
```
|
||||||
|
This tells me `/etc/ssl/cert.pem` is where it read trusted CA certificates.
|
||||||
|
|
||||||
|
To let Git trusts your CA certificate, you will need to:
|
||||||
|
1. Save your SSL certificate chain which includes the root CA and all intermediate CAs into a `.pem` file.
|
||||||
|
2. Set `http.sslCAInfo` Git config or `GIT_SSL_CAINFO` environment variable to the full path of the `.pem` file [Git Doc](https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpsslCAInfo)
|
||||||
|
> I would recommend using `http.sslCAInfo` since it can be scope to certain hosts that need the extra trusted CA.
|
||||||
|
> Ex: `git config --global http.https://myghes.com/.sslCAInfo /extra/ca/cert.pem`
|
||||||
|
> This will make Git use the `/extra/ca/cert.pem` only when communicates with `https://myghes.com` and keep using the default CA bundle with others.
|
||||||
|
|
||||||
|
### Trust CA certificate for Node.js
|
||||||
|
|
||||||
|
Node.js has compiled a snapshot of the Mozilla CA store that is fixed at each version of Node.js' release time.
|
||||||
|
|
||||||
|
To let Node.js trusts your CA certificate, you will need to:
|
||||||
|
1. Save your SSL certificate chain which includes the root CA and all intermediate CAs into a `.pem` file.
|
||||||
|
2. Set environment variable `NODE_EXTRA_CA_CERTS` which point to the file. ex: `export NODE_EXTRA_CA_CERTS=/full/path/to/cacert.pem` or `set NODE_EXTRA_CA_CERTS=C:\full\path\to\cacert.pem`
|
||||||
BIN
docs/res/macOStrustCA.gif
Normal file
BIN
docs/res/macOStrustCA.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 MiB |
@@ -15,16 +15,16 @@ x64
|
|||||||
- openSUSE 15+
|
- openSUSE 15+
|
||||||
- SUSE Enterprise Linux (SLES) 12 SP2+
|
- SUSE Enterprise Linux (SLES) 12 SP2+
|
||||||
|
|
||||||
## Install .Net Core 3.x Linux Dependencies
|
## Install .Net Core 5 Linux Dependencies
|
||||||
|
|
||||||
The `./config.sh` will check .Net Core 3.x dependencies during runner configuration.
|
The `./config.sh` will check .Net Core 5 dependencies during runner configuration.
|
||||||
You might see something like this which indicate a dependency's missing.
|
You might see something like this which indicate a dependency's missing.
|
||||||
```bash
|
```bash
|
||||||
./config.sh
|
./config.sh
|
||||||
libunwind.so.8 => not found
|
libunwind.so.8 => not found
|
||||||
libunwind-x86_64.so.8 => not found
|
libunwind-x86_64.so.8 => not found
|
||||||
Dependencies is missing for Dotnet Core 3.0
|
Dependencies is missing for Dotnet 5
|
||||||
Execute ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies.
|
Execute ./bin/installdependencies.sh to install any missing Dotnet 5 dependencies.
|
||||||
```
|
```
|
||||||
You can easily correct the problem by executing `./bin/installdependencies.sh`.
|
You can easily correct the problem by executing `./bin/installdependencies.sh`.
|
||||||
The `installdependencies.sh` script should install all required dependencies on all supported Linux versions
|
The `installdependencies.sh` script should install all required dependencies on all supported Linux versions
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
## Features
|
## Features
|
||||||
- Support environment URL parsing (#762, #778)
|
- Support config runner via GitHub PAT. (#874)
|
||||||
|
- Update runner to .NET 5 (#799)
|
||||||
|
- Add new ANDROID_SDK_ROOT environment variable (#892)
|
||||||
|
- Add warning when running out of disk. (#873)
|
||||||
|
- Always use FIPS Cryptography (#896)
|
||||||
|
- Add `--check` to run a serials network test against GitHub or GHES. (#900)
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- Fixes #759 doesn't change proxy environment variables (#760)
|
- Ignore certain scenarios so they are not counted as infra failures (#889)
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
- Add .editorconfig (#768)
|
- Add runner e2e test workflow (#885)
|
||||||
|
- Add on: pull_request trigger to CodeQL workflow (#907)
|
||||||
|
|
||||||
## Windows x64
|
## Windows x64
|
||||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<Update to ./src/runnerversion when creating release>
|
2.276.0
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ set -e
|
|||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# export RUNNER_CFG_PAT=<yourPAT>
|
# export RUNNER_CFG_PAT=<yourPAT>
|
||||||
# ./create-latest-svc scope [ghe_domain] [name] [user]
|
# ./create-latest-svc scope [ghe_domain] [name] [user] [labels]
|
||||||
#
|
#
|
||||||
# scope required repo (:owner/:repo) or org (:organization)
|
# scope required repo (:owner/:repo) or org (:organization)
|
||||||
# ghe_domain optional the fully qualified domain name of your GitHub Enterprise Server deployment
|
# ghe_domain optional the fully qualified domain name of your GitHub Enterprise Server deployment
|
||||||
# name optional defaults to hostname
|
# name optional defaults to hostname
|
||||||
# user optional user svc will run as. defaults to current
|
# user optional user svc will run as. defaults to current
|
||||||
|
# labels optional list of labels (split by comma) applied on the runner
|
||||||
#
|
#
|
||||||
# Notes:
|
# Notes:
|
||||||
# PATS over envvars are more secure
|
# PATS over envvars are more secure
|
||||||
@@ -30,6 +31,7 @@ runner_scope=${1}
|
|||||||
ghe_hostname=${2}
|
ghe_hostname=${2}
|
||||||
runner_name=${3:-$(hostname)}
|
runner_name=${3:-$(hostname)}
|
||||||
svc_user=${4:-$USER}
|
svc_user=${4:-$USER}
|
||||||
|
labels=${5}
|
||||||
|
|
||||||
echo "Configuring runner @ ${runner_scope}"
|
echo "Configuring runner @ ${runner_scope}"
|
||||||
sudo echo
|
sudo echo
|
||||||
@@ -130,8 +132,8 @@ fi
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Configuring ${runner_name} @ $runner_url"
|
echo "Configuring ${runner_name} @ $runner_url"
|
||||||
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name"
|
echo "./config.sh --unattended --url $runner_url --token *** --name $runner_name --labels $labels"
|
||||||
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name
|
sudo -E -u ${svc_user} ./config.sh --unattended --url $runner_url --token $RUNNER_TOKEN --name $runner_name --labels $labels
|
||||||
|
|
||||||
#---------------------------------------
|
#---------------------------------------
|
||||||
# Configuring as a service
|
# Configuring as a service
|
||||||
|
|||||||
373
src/Misc/dotnet-install.ps1
vendored
373
src/Misc/dotnet-install.ps1
vendored
@@ -635,6 +635,11 @@ function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolde
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
|
||||||
|
Say "- The SDK needs to be installed without user interaction and without admin rights."
|
||||||
|
Say "- The SDK installation doesn't need to persist across multiple CI runs."
|
||||||
|
Say "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.`r`n"
|
||||||
|
|
||||||
$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture
|
$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture
|
||||||
$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version -JSonFile $JSonFile
|
$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version -JSonFile $JSonFile
|
||||||
$DownloadLink, $EffectiveVersion = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
|
$DownloadLink, $EffectiveVersion = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
|
||||||
@@ -773,197 +778,199 @@ Remove-Item $ZipPath
|
|||||||
|
|
||||||
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
|
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
|
||||||
|
|
||||||
|
Say "Note that the script does not resolve dependencies during installation."
|
||||||
|
Say "To check the list of dependencies, go to https://docs.microsoft.com/dotnet/core/install/windows#dependencies"
|
||||||
Say "Installation finished"
|
Say "Installation finished"
|
||||||
exit 0
|
exit 0
|
||||||
# SIG # Begin signature block
|
# SIG # Begin signature block
|
||||||
# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor
|
# MIIjlgYJKoZIhvcNAQcCoIIjhzCCI4MCAQExDzANBglghkgBZQMEAgEFADB5Bgor
|
||||||
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
|
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
|
||||||
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAdMJOqDPFy5F1i
|
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA+isugNMwZSGLd
|
||||||
# HBXPyOE4hGkUv5EGyQzmS901lRr+baCCDYEwggX/MIID56ADAgECAhMzAAABh3IX
|
# kfBd0C2Ud//U2Nbj31s1jg3Yf9gh4KCCDYUwggYDMIID66ADAgECAhMzAAABiK9S
|
||||||
# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
|
# 1rmSbej5AAAAAAGIMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
|
||||||
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
|
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
|
||||||
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
|
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
|
||||||
# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw
|
# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ4WhcNMjEwMzAzMTgzOTQ4WjB0MQsw
|
||||||
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
|
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
|
||||||
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
|
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
|
||||||
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||||
# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB
|
# AQCSCNryE+Cewy2m4t/a74wZ7C9YTwv1PyC4BvM/kSWPNs8n0RTe+FvYfU+E9uf0
|
||||||
# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH
|
# t7nYlAzHjK+plif2BhD+NgdhIUQ8sVwWO39tjvQRHjP2//vSvIfmmkRoML1Ihnjs
|
||||||
# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d
|
# 9kQiZQzYRDYYRp9xSQYmRwQjk5hl8/U7RgOiQDitVHaU7BT1MI92lfZRuIIDDYBd
|
||||||
# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ
|
# vXtbclYJMVOwqZtv0O9zQCret6R+fRSGaDNfEEpcILL+D7RV3M4uaJE4Ta6KAOdv
|
||||||
# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV
|
# V+MVaJp1YXFTZPKtpjHO6d9pHQPZiG7NdC6QbnRGmsa48uNQrb6AfmLKDI1Lp31W
|
||||||
# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
|
# MogTaX5tZf+CZT9PSuvjOCLNAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
|
||||||
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw
|
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUj9RJL9zNrPcL10RZdMQIXZN7MG8w
|
||||||
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
|
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
|
||||||
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu
|
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ1ODM4NjAfBgNVHSMEGDAW
|
||||||
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
|
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
|
||||||
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
|
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
|
||||||
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
|
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
|
||||||
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
|
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
|
||||||
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy
|
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
|
||||||
# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K
|
# ACnXo8hjp7FeT+H6iQlV3CcGnkSbFvIpKYafgzYCFo3UHY1VHYJVb5jHEO8oG26Q
|
||||||
# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV
|
# qBELmak6MTI+ra3WKMTGhE1sEIlowTcp4IAs8a5wpCh6Vf4Z/bAtIppP3p3gXk2X
|
||||||
# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr
|
# 8UXTc+WxjQYsDkFiSzo/OBa5hkdW1g4EpO43l9mjToBdqEPtIXsZ7Hi1/6y4gK0P
|
||||||
# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx
|
# mMiwG8LMpSn0n/oSHGjrUNBgHJPxgs63Slf58QGBznuXiRaXmfTUDdrvhRocdxIM
|
||||||
# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe
|
# i8nXQwWACMiQzJSRzBP5S2wUq7nMAqjaTbeXhJqD2SFVHdUYlKruvtPSwbnqSRWT
|
||||||
# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g
|
# GI8s4FEXt+TL3w5JnwVZmZkUFoioQDMMjFyaKurdJ6pnzbr1h6QW0R97fWc8xEIz
|
||||||
# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf
|
# LIOiU2rjwWAtlQqFO8KNiykjYGyEf5LyAJKAO+rJd9fsYR+VBauIEQoYmjnUbTXM
|
||||||
# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI
|
# SY2Lf5KMluWlDOGVh8q6XjmBccpaT+8tCfxpaVYPi1ncnwTwaPQvVq8RjWDRB7Pa
|
||||||
# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5
|
# 8ruHgj2HJFi69+hcq7mWx5nTUtzzFa7RSZfE5a1a5AuBmGNRr7f8cNfa01+tiWjV
|
||||||
# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea
|
# Kk1a+gJUBSP0sIxecFbVSXTZ7bqeal45XSDIisZBkWb+83TbXdTGMDSUFKTAdtC+
|
||||||
# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS
|
# r35GfsN8QVy59Hb5ZYzAXczhgRmk7NyE6jD0Ym5TKiW5MIIHejCCBWKgAwIBAgIK
|
||||||
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
|
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
|
||||||
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
|
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
|
||||||
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
|
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
|
||||||
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
|
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
|
||||||
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
|
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
|
||||||
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
|
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
|
||||||
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
|
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
|
||||||
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
|
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
|
||||||
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
|
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
|
||||||
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
|
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
|
||||||
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
|
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
|
||||||
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
|
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
|
||||||
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
|
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
|
||||||
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
|
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
|
||||||
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
|
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
|
||||||
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
|
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
|
||||||
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
|
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
|
||||||
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
|
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
|
||||||
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
|
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
|
||||||
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
|
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
|
||||||
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
|
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
|
||||||
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
|
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
|
||||||
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
|
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
|
||||||
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
|
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
|
||||||
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
|
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
|
||||||
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
|
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
|
||||||
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
|
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
|
||||||
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
|
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
|
||||||
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
|
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
|
||||||
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
|
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
|
||||||
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
|
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
|
||||||
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
|
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
|
||||||
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
|
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
|
||||||
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
|
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
|
||||||
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
|
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
|
||||||
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
|
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
|
||||||
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
|
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
|
||||||
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
|
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
|
||||||
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
|
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
|
||||||
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG
|
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFWcwghVjAgEBMIGVMH4x
|
||||||
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
|
|
||||||
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
|
|
||||||
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN
|
|
||||||
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
|
|
||||||
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgGfshXxhl
|
|
||||||
# 7+O9cl90lOU62gZCBmJzcomUxEL8+XyoDYQwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
|
|
||||||
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
|
|
||||||
# BgkqhkiG9w0BAQEFAASCAQCPVhcZxxdIzkFdrv/FCW737QgR8fCO1/oRXwhigOyQ
|
|
||||||
# P2MF39fIYsVXuzVnO8pYZZOeW04kMECcWf9420okd4lXP7Xc5m+5UrqPuN1UgNle
|
|
||||||
# hhwLBiXuZaAfllBMWMeQi7DZmg7XW8Yay9TAbc2XSTGQ8foDxPllKFbdPvvQ2DRy
|
|
||||||
# VRLyNNQQEo3IuHHa0nnVNaL2PUYJf0udMCdGkxIMbApAYcitJLSwMLqMzrMkrvS9
|
|
||||||
# ubm7CgigsKRJ3cZtCtFFMUkMsstoVuKLFtu69OvOfgLy1qmKotE6EnF7xudV+qAA
|
|
||||||
# a+UxGVT715tK5kgb5eTr1K2NdWRj517oANQNOjR/m6OPoYIS8TCCEu0GCisGAQQB
|
|
||||||
# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME
|
|
||||||
# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB
|
|
||||||
# MDEwDQYJYIZIAWUDBAIBBQAEIHYNJoLIl+IWj/Npb6r479Guw3UW/q0/jJhqKgHm
|
|
||||||
# xq1NAgZfdIY1B90YEzIwMjAxMDE0MTcxOTIwLjg5NVowBIACAfSggdSkgdEwgc4x
|
|
||||||
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
|
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
|
||||||
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p
|
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
|
||||||
# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg
|
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAGIr1LWuZJt6PkAAAAA
|
||||||
# VFNTIEVTTjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
|
# AYgwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
|
||||||
# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJt+6SyK5goIHAAAA
|
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIK4I
|
||||||
# AAEmMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
|
# CDH7/r/eeMqTtDETJ67ogfneVRo0/P6ogV2vy4tXMEIGCisGAQQBgjcCAQwxNDAy
|
||||||
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
|
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
|
||||||
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
|
# b20wDQYJKoZIhvcNAQEBBQAEggEAOnmVmILEjI6ZiuuSOvvTvijidkBez61Vz97A
|
||||||
# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT
|
# jV3AOsfmUvLpVaTVa1Mt2iPDuq1QLqRPaT7BD8PAUwr91pYllVgEd8NqivCIaCZg
|
||||||
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
|
# QyIRiTmHQxbozWsLcjxMvX2VxSmNKDw7IOHzUbXtmiEGhygyZpdh/uiCj7ziSxp3
|
||||||
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
|
# lQBR8mUE1NL9dxaxKWLhGeORqAepw6nId9oO+mHRh4JRK7uqZOFAES7/21M9vPZi
|
||||||
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2MEJD
|
# XYilJLgIoyMkvqYSdoouzn6+m74kgzkNkyK9GYz2mmO2BCMnai9Njze2d0+kY+37
|
||||||
# LUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
|
# kt10BmJDw3FHaZ+/fH/TMTgo0ZcAOicP9ccdIh/CzzpU52o+Q6GCEvEwghLtBgor
|
||||||
# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ4wvoacTvMNlXQTtfF/
|
# BgEEAYI3AwMBMYIS3TCCEtkGCSqGSIb3DQEHAqCCEsowghLGAgEDMQ8wDQYJYIZI
|
||||||
# Cx5Ol3X0fcjUNMvjLgTmO5+WHYJFbp725P3+qvFKDRQHWEI1Sz0gB24urVDIjXjB
|
# AWUDBAIBBQAwggFVBgsqhkiG9w0BCRABBKCCAUQEggFAMIIBPAIBAQYKKwYBBAGE
|
||||||
# h5NVNJVMQJI2tltv7M4/4IbhZJb3xzQW7LolEoZYUZanBTUuyly9osCg4o5joViT
|
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCBSbhMJwNER+BICn3iLUnPrP8dptyUphcFC
|
||||||
# 2GtmyxK+Fv5kC20l2opeaeptd/E7ceDAFRM87hiNCsK/KHyC+8+swnlg4gTOey6z
|
# A/NsIgnPLwIGX4hEzP6WGBMyMDIwMTEwOTE0NDY1Mi4yMzNaMASAAgH0oIHUpIHR
|
||||||
# QqhzgNsG6HrjLBuDtDs9izAMwS2yWT0T52QA9h3Q+B1C9ps2fMKMe+DHpG+0c61D
|
# MIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
|
||||||
# 94Yh6cV2XHib4SBCnwIFZAeZE2UJ4qPANSYozI8PH+E5rCT3SVqYvHou97HsXvP2
|
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQL
|
||||||
# I3MCAwEAAaOCARswggEXMB0GA1UdDgQWBBRJq6wfF7B+mEKN0VimX8ajNA5hQTAf
|
# EyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhh
|
||||||
# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH
|
# bGVzIFRTUyBFU046MEE1Ni1FMzI5LTRENEQxJTAjBgNVBAMTHE1pY3Jvc29mdCBU
|
||||||
# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU
|
# aW1lLVN0YW1wIFNlcnZpY2Wggg5EMIIE9TCCA92gAwIBAgITMwAAAScvbqPvkagZ
|
||||||
# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF
|
# qAAAAAABJzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
|
||||||
# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0
|
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
|
||||||
# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG
|
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
|
||||||
# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBAlvudaOlv9Cfzv56bnX41czF6tLtH
|
# MjAxMDAeFw0xOTEyMTkwMTE0NTlaFw0yMTAzMTcwMTE0NTlaMIHOMQswCQYDVQQG
|
||||||
# LB46l6XUch+qNN45ZmOTFwLot3JjwSrn4oycQ9qTET1TFDYd1QND0LiXmKz9OqBX
|
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
|
||||||
# ai6S8XdyCQEZvfL82jIAs9pwsAQ6XvV9jNybPStRgF/sOAM/Deyfmej9Tg9FcRwX
|
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQg
|
||||||
# ank2qgzdZZNb8GoEze7f1orcTF0Q89IUXWIlmwEwQFYF1wjn87N4ZxL9Z/xA2m/R
|
# T3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046
|
||||||
# 1zizFylWP/mpamCnVfZZLkafFLNUNVmcvc+9gM7vceJs37d3ydabk4wR6ObR34sW
|
# MEE1Ni1FMzI5LTRENEQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
|
||||||
# aLppmyPlsI1Qq5Lu6bJCWoXzYuWpkoK6oEep1gML6SRC3HKVS3UscZhtMIIGcTCC
|
# cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD4Ad5xEZ5On0uN
|
||||||
# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC
|
# L71ng9xwoDPRKeMUyEIj5yVxPRPh5GVbU7D3pqDsoXzQMhfeRP61L1zlU1HCRS+1
|
||||||
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
|
# 29eo0yj1zjbAlmPAwosUgyIonesWt9E4hFlXCGUcIg5XMdvQ+Ouzk2r+awNRuk8A
|
||||||
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv
|
# BGOa0I4VBy6zqCYHyX2pGauiB43frJSNP6pcrO0CBmpBZNjgepof5Z/50vBuJDUS
|
||||||
# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN
|
# ug6OIMQ7ZwUhSzX4bEmZUUjAycBb62dhQpGqHsXe6ypVDTgAEnGONdSBKkHiNT8H
|
||||||
# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
|
# 0Zt2lm0vCLwHyTwtgIdi67T/LCp+X2mlPHqXsY3u72X3GYn/3G8YFCkrSc6m3b0w
|
||||||
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
|
# TXPd5/2fAgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQU5fSWVYBfOTEkW2JTiV24WNNt
|
||||||
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw
|
# lfIwHwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBL
|
||||||
# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0
|
# oEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
|
||||||
# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw
|
# TWljVGltU3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggr
|
||||||
# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe
|
# BgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNU
|
||||||
# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx
|
# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAK
|
||||||
# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G
|
# BggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAQEACsqNfNFVxwalZ42cEMuzZc12
|
||||||
# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA
|
# 6Nvluanx8UewDVeUQZEZHRmppMFHAzS/g6RzmxTyR2tKE3mChNGW5dTL730vEbRh
|
||||||
# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7
|
# nYRmBgiX/gT3f4AQrOPnZGXY7zszcrlbgzxpakOX+x0u4rkP3Ashh3B2CdJ11XsB
|
||||||
# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
|
# di5PiZa1spB6U5S8D15gqTUfoIniLT4v1DBdkWExsKI1vsiFcDcjGJ4xRlMRF+fw
|
||||||
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
|
# 7SY0WZoOzwRzKxDTdg4DusAXpaeKbch9iithLFk/vIxQrqCr/niW8tEA+eSzeX/E
|
||||||
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
|
# q1D0ZyvOn4e2lTnwoJUKH6OQAWSBogyK4OCbFeJOqdKAUiBTgHKkQIYh/tbKQjCC
|
||||||
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
|
# BnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNV
|
||||||
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
|
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
|
||||||
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g
|
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29m
|
||||||
# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93
|
# dCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1
|
||||||
# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB
|
# NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
|
||||||
# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA
|
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
|
||||||
# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh
|
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw
|
||||||
# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS
|
# ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/
|
||||||
# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK
|
# aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxh
|
||||||
# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon
|
# MFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhH
|
||||||
# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi
|
# hjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tk
|
||||||
# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/
|
# iVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox
|
||||||
# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII
|
# 8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJN
|
||||||
# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0
|
# AgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIox
|
||||||
# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a
|
# kPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0P
|
||||||
# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ
|
# BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9
|
||||||
# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+
|
# lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQu
|
||||||
# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT
|
# Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3Js
|
||||||
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
|
# MFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3Nv
|
||||||
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP
|
# ZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAG
|
||||||
# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2
|
# A1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRw
|
||||||
# MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
|
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAG
|
||||||
# dmljZaIjCgEBMAcGBSsOAwIaAxUACmcyOWmZxErpq06B8dy6oMZ6//yggYMwgYCk
|
# CCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEA
|
||||||
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
|
# dABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXED
|
||||||
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
|
# PZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgr
|
||||||
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
|
# UYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c
|
||||||
# AOMxeOgwIhgPMjAyMDEwMTQxNzE3MjhaGA8yMDIwMTAxNTE3MTcyOFowdzA9Bgor
|
# 8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFw
|
||||||
# BgEEAYRZCgQBMS8wLTAKAgUA4zF46AIBADAKAgEAAgIQPAIB/zAHAgEAAgIRZDAK
|
# nzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFt
|
||||||
# AgUA4zLKaAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
|
# w5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk
|
||||||
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBALEDKhtH6no+VBWb
|
# 7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9d
|
||||||
# KHscN3Q0bphy1tgMhLZ0UBYpPSgcrPnF36tX3nswRAci3gLdgc77hjn2Zc6UyVJk
|
# dJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zG
|
||||||
# WhFguWv6KoyTunGPejS/fPIGKm1CXQnEV/JUvt1EAf7YRpHImfjZBhNXbVyV61gy
|
# y9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3
|
||||||
# fEGA6fNNgbI+57xQJCZqdKBYX3EFMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC
|
# yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7c
|
||||||
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
|
# RDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wkn
|
||||||
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
|
# HNWzfjUeCLraNtvTX4/edIhJEqGCAtIwggI7AgEBMIH8oYHUpIHRMIHOMQswCQYD
|
||||||
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEm37pLIrmCggcAAAAAASYwDQYJYIZIAWUD
|
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
|
||||||
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
|
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3Nv
|
||||||
# CQQxIgQgmfmj5y7wRFTyeI0TaXaljaCJoRQMvGBEAXsAQuY3ZOcwgfoGCyqGSIb3
|
# ZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBF
|
||||||
# DQEJEAIvMYHqMIHnMIHkMIG9BCA2/c/vnr1ecAzvapOWZ2xGfAkzrkfpGcrvMW07
|
# U046MEE1Ni1FMzI5LTRENEQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w
|
||||||
# CQl1DzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
|
# IFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVALOVuE5sgxzETO4s+poBqI6r1x8zoIGD
|
||||||
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
|
# MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
|
||||||
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
|
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
|
||||||
# Jt+6SyK5goIHAAAAAAEmMCIEIJ7sOcZ9sNFABAvIMRs2kk0cZhB239DZbXCLYMT8
|
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEF
|
||||||
# frPMMA0GCSqGSIb3DQEBCwUABIIBAEiXebYdQ9DIz74YpfQ9FBaLHiSfD3s+jO7x
|
# BQACBQDjU7byMCIYDzIwMjAxMTA5MTYzOTE0WhgPMjAyMDExMTAxNjM5MTRaMHcw
|
||||||
# 1noNe0HIdZaX/Asow0OqsEMzZanOpa3yO8BJskKoDJW9pU//xqCzV1W5FzoOT4Qs
|
# PQYKKwYBBAGEWQoEATEvMC0wCgIFAONTtvICAQAwCgIBAAICIt0CAf8wBwIBAAIC
|
||||||
# ZJpG0R5f/eHqMMeRBVUPn1FfT4pQVcHfRHOW/I3hWC0G4SeVwU/L9d8JLSQKzl39
|
# EcQwCgIFAONVCHICAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAK
|
||||||
# 8bMFbtLJWxUJMM4Vp8Tf+cR7ShZdsK9w88QokR9xbuQgn6jsqhOuyw+dUGrwEI7h
|
# MAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQAQhyIIAC/A
|
||||||
# GCdUmsT614oSgdnuUBf/g1aew0e3ulmZYYQ2QLKqnDXuqUIFnPtWFB90h++mdlFg
|
# P+VJdbhL9IQgm8WTa1DmPPE+BQSuRbBy2MmzC1KostixdEkr2OaNSjcYuZBNIJgv
|
||||||
# fvIEusNgYkb2kl5xQfxm3wynbxtP249vWF4GACZtqqSj3tcQ+xQ=
|
# vE8CWhVDD+sbBpVcOdoSfoBwHXKfvqSTiWvovoexkF0X5aon7yr3PkJ/kEqoLyUM
|
||||||
|
# xRvdWKJdHOL1sT0/aWHn048c6aGin/zc8DGCAw0wggMJAgEBMIGTMHwxCzAJBgNV
|
||||||
|
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
|
||||||
|
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
|
||||||
|
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABJy9uo++RqBmoAAAAAAEnMA0GCWCG
|
||||||
|
# SAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZI
|
||||||
|
# hvcNAQkEMSIEIJZkrbvF4R8oqYYpN6ZPGOj+QEZTQriEi/Yw9gW6zMqRMIH6Bgsq
|
||||||
|
# hkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgG5LoSxKGHWoW/wVMlbMztlQ4upAdzEmq
|
||||||
|
# H//vLu0jPiIwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
|
||||||
|
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
|
||||||
|
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAIT
|
||||||
|
# MwAAAScvbqPvkagZqAAAAAABJzAiBCDwhEViCRvqKwQV3MxociF2iGYrDP4p1BK+
|
||||||
|
# s4tStO4vSDANBgkqhkiG9w0BAQsFAASCAQAkgmDo8lVmar0ZIqTG1it3skG8PZC9
|
||||||
|
# iqEEC1vxcz8OSfsjl2QSkQ5T2+3xWpxWA4uy2+Byv0bi8EsfQEnnn4vtdthS6/kb
|
||||||
|
# vB/LLQiqoMhJ0rasf3/y/4KnQZEtztpg1+cCaNwFUgI6o+E8YEFt1frhLwFs/0WH
|
||||||
|
# 5pyBFx9ECEs0M22SLIpW13gexv9fgk6ZboIfSreAI28DLveeJpkgwggxHRpuVOVD
|
||||||
|
# 4D7QQJAvJ0VU6p+yJlbvQXR9iltwb1REhlsJ5mADJ/FkzPVX/swMSUIoyE2inlxK
|
||||||
|
# LEiPkkZYwiFYCifFYUTnQjWU1Ls0EV+ysosL+jhzCxO8S6oRdp5TAi4F
|
||||||
# SIG # End signature block
|
# SIG # End signature block
|
||||||
|
|||||||
65
src/Misc/dotnet-install.sh
vendored
65
src/Misc/dotnet-install.sh
vendored
@@ -241,42 +241,6 @@ check_min_reqs() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
check_pre_reqs() {
|
|
||||||
eval $invocation
|
|
||||||
|
|
||||||
if [ "${DOTNET_INSTALL_SKIP_PREREQS:-}" = "1" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$(uname)" = "Linux" ]; then
|
|
||||||
if is_musl_based_distro; then
|
|
||||||
if ! command -v scanelf > /dev/null; then
|
|
||||||
say_warning "scanelf not found, please install pax-utils package."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
LDCONFIG_COMMAND="scanelf --ldpath -BF '%f'"
|
|
||||||
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libintl)" ] && say_warning "Unable to locate libintl. Probable prerequisite missing; install libintl (or gettext)."
|
|
||||||
else
|
|
||||||
if [ ! -x "$(command -v ldconfig)" ]; then
|
|
||||||
say_verbose "ldconfig is not in PATH, trying /sbin/ldconfig."
|
|
||||||
LDCONFIG_COMMAND="/sbin/ldconfig"
|
|
||||||
else
|
|
||||||
LDCONFIG_COMMAND="ldconfig"
|
|
||||||
fi
|
|
||||||
local librarypath=${LD_LIBRARY_PATH:-}
|
|
||||||
LDCONFIG_COMMAND="$LDCONFIG_COMMAND -NXv ${librarypath//:/ }"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep zlib)" ] && say_warning "Unable to locate zlib. Probable prerequisite missing; install zlib."
|
|
||||||
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep ssl)" ] && say_warning "Unable to locate libssl. Probable prerequisite missing; install libssl."
|
|
||||||
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libicu)" ] && say_warning "Unable to locate libicu. Probable prerequisite missing; install libicu."
|
|
||||||
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep lttng)" ] && say_warning "Unable to locate liblttng. Probable prerequisite missing; install liblttng."
|
|
||||||
[ -z "$($LDCONFIG_COMMAND 2>/dev/null | grep libcurl)" ] && say_warning "Unable to locate libcurl. Probable prerequisite missing; install libcurl."
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# args:
|
# args:
|
||||||
# input - $1
|
# input - $1
|
||||||
to_lowercase() {
|
to_lowercase() {
|
||||||
@@ -468,7 +432,6 @@ parse_jsonfile_for_version() {
|
|||||||
sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}')
|
sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}')
|
||||||
sdk_list=${sdk_list//[\" ]/}
|
sdk_list=${sdk_list//[\" ]/}
|
||||||
sdk_list=${sdk_list//,/$'\n'}
|
sdk_list=${sdk_list//,/$'\n'}
|
||||||
sdk_list="$(echo -e "${sdk_list}" | tr -d '[[:space:]]')"
|
|
||||||
|
|
||||||
local version_info=""
|
local version_info=""
|
||||||
while read -r line; do
|
while read -r line; do
|
||||||
@@ -588,14 +551,20 @@ get_specific_product_version() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
specific_product_version=$(curl -s --fail "$download_link")
|
if machine_has "curl"
|
||||||
if [ $? -ne 0 ]
|
|
||||||
then
|
then
|
||||||
specific_product_version=$(wget -qO- "$download_link")
|
specific_product_version=$(curl -s --fail "$download_link")
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
specific_product_version=$specific_version
|
specific_product_version=$specific_version
|
||||||
fi
|
fi
|
||||||
|
elif machine_has "wget"
|
||||||
|
then
|
||||||
|
specific_product_version=$(wget -qO- "$download_link")
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
specific_product_version=$specific_version
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
specific_product_version="${specific_product_version//[$'\t\r\n']}"
|
specific_product_version="${specific_product_version//[$'\t\r\n']}"
|
||||||
|
|
||||||
@@ -1098,6 +1067,11 @@ if [ "$no_cdn" = true ]; then
|
|||||||
azure_feed="$uncached_feed"
|
azure_feed="$uncached_feed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
|
||||||
|
say "- The SDK needs to be installed without user interaction and without admin rights."
|
||||||
|
say "- The SDK installation doesn't need to persist across multiple CI runs."
|
||||||
|
say "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n"
|
||||||
|
|
||||||
check_min_reqs
|
check_min_reqs
|
||||||
calculate_vars
|
calculate_vars
|
||||||
script_name=$(basename "$0")
|
script_name=$(basename "$0")
|
||||||
@@ -1119,7 +1093,6 @@ if [ "$dry_run" = true ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
check_pre_reqs
|
|
||||||
install_dotnet
|
install_dotnet
|
||||||
|
|
||||||
bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")"
|
bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")"
|
||||||
@@ -1130,4 +1103,6 @@ else
|
|||||||
say "Binaries of dotnet can be found in $bin_path"
|
say "Binaries of dotnet can be found in $bin_path"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
say "Note that the script does not resolve dependencies during installation."
|
||||||
|
say "To check the list of dependencies, go to https://docs.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section."
|
||||||
say "Installation finished successfully."
|
say "Installation finished successfully."
|
||||||
|
|||||||
115
src/Misc/layoutbin/checkScripts/downloadCert.js
Normal file
115
src/Misc/layoutbin/checkScripts/downloadCert.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
const https = require('https')
|
||||||
|
const fs = require('fs')
|
||||||
|
const http = require('http')
|
||||||
|
const hostname = process.env['HOSTNAME'] || ''
|
||||||
|
const port = process.env['PORT'] || ''
|
||||||
|
const path = process.env['PATH'] || ''
|
||||||
|
const pat = process.env['PAT'] || ''
|
||||||
|
const proxyHost = process.env['PROXYHOST'] || ''
|
||||||
|
const proxyPort = process.env['PROXYPORT'] || ''
|
||||||
|
const proxyUsername = process.env['PROXYUSERNAME'] || ''
|
||||||
|
const proxyPassword = process.env['PROXYPASSWORD'] || ''
|
||||||
|
|
||||||
|
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'
|
||||||
|
|
||||||
|
if (proxyHost === '') {
|
||||||
|
const options = {
|
||||||
|
hostname: hostname,
|
||||||
|
port: port,
|
||||||
|
path: path,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'GitHubActionsRunnerCheck/1.0',
|
||||||
|
'Authorization': `token ${pat}`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const req = https.request(options, res => {
|
||||||
|
console.log(`statusCode: ${res.statusCode}`)
|
||||||
|
console.log(`headers: ${JSON.stringify(res.headers)}`)
|
||||||
|
let cert = socket.getPeerCertificate(true)
|
||||||
|
let certPEM = ''
|
||||||
|
let fingerprints = {}
|
||||||
|
while (cert != null && fingerprints[cert.fingerprint] != '1') {
|
||||||
|
fingerprints[cert.fingerprint] = '1'
|
||||||
|
certPEM = certPEM + '-----BEGIN CERTIFICATE-----\n'
|
||||||
|
let certEncoded = cert.raw.toString('base64')
|
||||||
|
for (let i = 0; i < certEncoded.length; i++) {
|
||||||
|
certPEM = certPEM + certEncoded[i]
|
||||||
|
if (i != certEncoded.length - 1 && (i + 1) % 64 == 0) {
|
||||||
|
certPEM = certPEM + '\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
certPEM = certPEM + '\n-----END CERTIFICATE-----\n'
|
||||||
|
cert = cert.issuerCertificate
|
||||||
|
}
|
||||||
|
console.log(certPEM)
|
||||||
|
fs.writeFileSync('./download_ca_cert.pem', certPEM)
|
||||||
|
res.on('data', d => {
|
||||||
|
process.stdout.write(d)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
req.on('error', error => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
req.end()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const auth = 'Basic ' + Buffer.from(proxyUsername + ':' + proxyPassword).toString('base64')
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
host: proxyHost,
|
||||||
|
port: proxyPort,
|
||||||
|
method: 'CONNECT',
|
||||||
|
path: `${hostname}:${port}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proxyUsername != '' || proxyPassword != '') {
|
||||||
|
options.headers = {
|
||||||
|
'Proxy-Authorization': auth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http.request(options).on('connect', (res, socket) => {
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw new Error(`Proxy returns code: ${res.statusCode}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
https.get({
|
||||||
|
host: hostname,
|
||||||
|
port: port,
|
||||||
|
socket: socket,
|
||||||
|
agent: false,
|
||||||
|
path: '/',
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'GitHubActionsRunnerCheck/1.0',
|
||||||
|
'Authorization': `token ${pat}`
|
||||||
|
}
|
||||||
|
}, (res) => {
|
||||||
|
let cert = res.socket.getPeerCertificate(true)
|
||||||
|
let certPEM = ''
|
||||||
|
let fingerprints = {}
|
||||||
|
while (cert != null && fingerprints[cert.fingerprint] != '1') {
|
||||||
|
fingerprints[cert.fingerprint] = '1'
|
||||||
|
certPEM = certPEM + '-----BEGIN CERTIFICATE-----\n'
|
||||||
|
let certEncoded = cert.raw.toString('base64')
|
||||||
|
for (let i = 0; i < certEncoded.length; i++) {
|
||||||
|
certPEM = certPEM + certEncoded[i]
|
||||||
|
if (i != certEncoded.length - 1 && (i + 1) % 64 == 0) {
|
||||||
|
certPEM = certPEM + '\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
certPEM = certPEM + '\n-----END CERTIFICATE-----\n'
|
||||||
|
cert = cert.issuerCertificate
|
||||||
|
}
|
||||||
|
console.log(certPEM)
|
||||||
|
fs.writeFileSync('./download_ca_cert.pem', certPEM)
|
||||||
|
console.log(`statusCode: ${res.statusCode}`)
|
||||||
|
console.log(`headers: ${JSON.stringify(res.headers)}`)
|
||||||
|
res.on('data', d => {
|
||||||
|
process.stdout.write(d)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).on('error', (err) => {
|
||||||
|
console.error('error', err)
|
||||||
|
}).end()
|
||||||
|
}
|
||||||
75
src/Misc/layoutbin/checkScripts/makeWebRequest.js
Normal file
75
src/Misc/layoutbin/checkScripts/makeWebRequest.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
const https = require('https')
|
||||||
|
const http = require('http')
|
||||||
|
const hostname = process.env['HOSTNAME'] || ''
|
||||||
|
const port = process.env['PORT'] || ''
|
||||||
|
const path = process.env['PATH'] || ''
|
||||||
|
const pat = process.env['PAT'] || ''
|
||||||
|
const proxyHost = process.env['PROXYHOST'] || ''
|
||||||
|
const proxyPort = process.env['PROXYPORT'] || ''
|
||||||
|
const proxyUsername = process.env['PROXYUSERNAME'] || ''
|
||||||
|
const proxyPassword = process.env['PROXYPASSWORD'] || ''
|
||||||
|
|
||||||
|
if (proxyHost === '') {
|
||||||
|
const options = {
|
||||||
|
hostname: hostname,
|
||||||
|
port: port,
|
||||||
|
path: path,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'GitHubActionsRunnerCheck/1.0',
|
||||||
|
'Authorization': `token ${pat}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const req = https.request(options, res => {
|
||||||
|
console.log(`statusCode: ${res.statusCode}`)
|
||||||
|
console.log(`headers: ${JSON.stringify(res.headers)}`)
|
||||||
|
|
||||||
|
res.on('data', d => {
|
||||||
|
process.stdout.write(d)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
req.on('error', error => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
req.end()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const proxyAuth = 'Basic ' + Buffer.from(proxyUsername + ':' + proxyPassword).toString('base64')
|
||||||
|
const options = {
|
||||||
|
hostname: proxyHost,
|
||||||
|
port: proxyPort,
|
||||||
|
method: 'CONNECT',
|
||||||
|
path: `${hostname}:${port}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proxyUsername != '' || proxyPassword != '') {
|
||||||
|
options.headers = {
|
||||||
|
'Proxy-Authorization': proxyAuth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.request(options).on('connect', (res, socket) => {
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw new Error(`Proxy returns code: ${res.statusCode}`)
|
||||||
|
}
|
||||||
|
https.get({
|
||||||
|
host: hostname,
|
||||||
|
port: port,
|
||||||
|
socket: socket,
|
||||||
|
agent: false,
|
||||||
|
path: path,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'GitHubActionsRunnerCheck/1.0',
|
||||||
|
'Authorization': `token ${pat}`,
|
||||||
|
}
|
||||||
|
}, (res) => {
|
||||||
|
console.log(`statusCode: ${res.statusCode}`)
|
||||||
|
console.log(`headers: ${JSON.stringify(res.headers)}`)
|
||||||
|
|
||||||
|
res.on('data', d => {
|
||||||
|
process.stdout.write(d)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).on('error', (err) => {
|
||||||
|
console.error('error', err)
|
||||||
|
}).end()
|
||||||
|
}
|
||||||
@@ -14,14 +14,14 @@ fi
|
|||||||
|
|
||||||
function print_errormessage()
|
function print_errormessage()
|
||||||
{
|
{
|
||||||
echo "Can't install dotnet core dependencies."
|
echo "Can't install dotnet 5 dependencies."
|
||||||
echo "You can manually install all required dependencies based on following documentation"
|
echo "You can manually install all required dependencies based on following documentation"
|
||||||
echo "https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x"
|
echo "https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x"
|
||||||
}
|
}
|
||||||
|
|
||||||
function print_rhel6message()
|
function print_rhel6message()
|
||||||
{
|
{
|
||||||
echo "We did our best effort to install dotnet core dependencies"
|
echo "We did our best effort to install dotnet 5 dependencies"
|
||||||
echo "However, there are some dependencies which require manual installation"
|
echo "However, there are some dependencies which require manual installation"
|
||||||
echo "You can install all remaining required dependencies based on the following documentation"
|
echo "You can install all remaining required dependencies based on the following documentation"
|
||||||
echo "https://github.com/dotnet/core/blob/master/Documentation/build-and-install-rhel6-prerequisites.md"
|
echo "https://github.com/dotnet/core/blob/master/Documentation/build-and-install-rhel6-prerequisites.md"
|
||||||
@@ -29,7 +29,7 @@ function print_rhel6message()
|
|||||||
|
|
||||||
function print_rhel6errormessage()
|
function print_rhel6errormessage()
|
||||||
{
|
{
|
||||||
echo "We couldn't install dotnet core dependencies"
|
echo "We couldn't install dotnet 5 dependencies"
|
||||||
echo "You can manually install all required dependencies based on following documentation"
|
echo "You can manually install all required dependencies based on following documentation"
|
||||||
echo "https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x"
|
echo "https://docs.microsoft.com/en-us/dotnet/core/linux-prerequisites?tabs=netcore2x"
|
||||||
echo "In addition, there are some dependencies which require manual installation. Please follow this documentation"
|
echo "In addition, there are some dependencies which require manual installation. Please follow this documentation"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ if [ $user_id -eq 0 -a -z "$RUNNER_ALLOW_RUNASROOT" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check dotnet core 3.0 dependencies for Linux
|
# Check dotnet 5 dependencies for Linux
|
||||||
if [[ (`uname` == "Linux") ]]
|
if [[ (`uname` == "Linux") ]]
|
||||||
then
|
then
|
||||||
command -v ldd > /dev/null
|
command -v ldd > /dev/null
|
||||||
@@ -18,25 +18,25 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
message="Execute sudo ./bin/installdependencies.sh to install any missing Dotnet Core 3.0 dependencies."
|
message="Execute sudo ./bin/installdependencies.sh to install any missing Dotnet 5 dependencies."
|
||||||
|
|
||||||
ldd ./bin/libcoreclr.so | grep 'not found'
|
ldd ./bin/libcoreclr.so | grep 'not found'
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Dependencies is missing for Dotnet Core 3.0"
|
echo "Dependencies is missing for Dotnet 5"
|
||||||
echo $message
|
echo $message
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ldd ./bin/System.Security.Cryptography.Native.OpenSsl.so | grep 'not found'
|
ldd ./bin/libSystem.Security.Cryptography.Native.OpenSsl.so | grep 'not found'
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Dependencies is missing for Dotnet Core 3.0"
|
echo "Dependencies is missing for Dotnet 5"
|
||||||
echo $message
|
echo $message
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ldd ./bin/System.IO.Compression.Native.so | grep 'not found'
|
ldd ./bin/libSystem.IO.Compression.Native.so | grep 'not found'
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "Dependencies is missing for Dotnet Core 3.0"
|
echo "Dependencies is missing for Dotnet 5"
|
||||||
echo $message
|
echo $message
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -54,7 +54,7 @@ then
|
|||||||
libpath=${LD_LIBRARY_PATH:-}
|
libpath=${LD_LIBRARY_PATH:-}
|
||||||
$LDCONFIG_COMMAND -NXv ${libpath//:/ } 2>&1 | grep libicu >/dev/null 2>&1
|
$LDCONFIG_COMMAND -NXv ${libpath//:/ } 2>&1 | grep libicu >/dev/null 2>&1
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Libicu's dependencies is missing for Dotnet Core 3.0"
|
echo "Libicu's dependencies is missing for Dotnet 5"
|
||||||
echo $message
|
echo $message
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ varCheckList=(
|
|||||||
'ANT_HOME'
|
'ANT_HOME'
|
||||||
'M2_HOME'
|
'M2_HOME'
|
||||||
'ANDROID_HOME'
|
'ANDROID_HOME'
|
||||||
|
'ANDROID_SDK_ROOT'
|
||||||
'GRADLE_HOME'
|
'GRADLE_HOME'
|
||||||
'NVM_BIN'
|
'NVM_BIN'
|
||||||
'NVM_PATH'
|
'NVM_PATH'
|
||||||
|
|||||||
@@ -99,9 +99,11 @@ namespace GitHub.Runner.Common
|
|||||||
|
|
||||||
// Secret args. Must be added to the "Secrets" getter as well.
|
// Secret args. Must be added to the "Secrets" getter as well.
|
||||||
public static readonly string Token = "token";
|
public static readonly string Token = "token";
|
||||||
|
public static readonly string PAT = "pat";
|
||||||
public static readonly string WindowsLogonPassword = "windowslogonpassword";
|
public static readonly string WindowsLogonPassword = "windowslogonpassword";
|
||||||
public static string[] Secrets => new[]
|
public static string[] Secrets => new[]
|
||||||
{
|
{
|
||||||
|
PAT,
|
||||||
Token,
|
Token,
|
||||||
WindowsLogonPassword,
|
WindowsLogonPassword,
|
||||||
};
|
};
|
||||||
@@ -119,6 +121,7 @@ namespace GitHub.Runner.Common
|
|||||||
//validFlags array as well present in the CommandSettings.cs
|
//validFlags array as well present in the CommandSettings.cs
|
||||||
public static class Flags
|
public static class Flags
|
||||||
{
|
{
|
||||||
|
public static readonly string Check = "check";
|
||||||
public static readonly string Commit = "commit";
|
public static readonly string Commit = "commit";
|
||||||
public static readonly string Help = "help";
|
public static readonly string Help = "help";
|
||||||
public static readonly string Replace = "replace";
|
public static readonly string Replace = "replace";
|
||||||
@@ -138,10 +141,15 @@ namespace GitHub.Runner.Common
|
|||||||
public const int RunOnceRunnerUpdating = 4;
|
public const int RunOnceRunnerUpdating = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Features
|
||||||
|
{
|
||||||
|
public static readonly string DiskSpaceWarning = "runner.diskspace.warning";
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry";
|
||||||
public static readonly string WorkerCrash = "WORKER_CRASH";
|
public static readonly string WorkerCrash = "WORKER_CRASH";
|
||||||
|
public static readonly string LowDiskSpace = "LOW_DISK_SPACE";
|
||||||
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
|
public static readonly string UnsupportedCommand = "UNSUPPORTED_COMMAND";
|
||||||
public static readonly string UnsupportedCommandMessage = "The `{0}` command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
|
|
||||||
public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
|
public static readonly string UnsupportedCommandMessageDisabled = "The `{0}` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ namespace GitHub.Runner.Common
|
|||||||
Add<T>(extensions, "GitHub.Runner.Worker.AddPathFileCommand, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.AddPathFileCommand, Runner.Worker");
|
||||||
Add<T>(extensions, "GitHub.Runner.Worker.SetEnvFileCommand, Runner.Worker");
|
Add<T>(extensions, "GitHub.Runner.Worker.SetEnvFileCommand, Runner.Worker");
|
||||||
break;
|
break;
|
||||||
|
case "GitHub.Runner.Listener.Check.ICheckExtension":
|
||||||
|
Add<T>(extensions, "GitHub.Runner.Listener.Check.InternetCheck, Runner.Listener");
|
||||||
|
Add<T>(extensions, "GitHub.Runner.Listener.Check.ActionsCheck, Runner.Listener");
|
||||||
|
Add<T>(extensions, "GitHub.Runner.Listener.Check.GitCheck, Runner.Listener");
|
||||||
|
Add<T>(extensions, "GitHub.Runner.Listener.Check.NodeJsCheck, Runner.Listener");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
throw new NotSupportedException($"Unexpected extension type: '{typeof(T).FullName}'");
|
throw new NotSupportedException($"Unexpected extension type: '{typeof(T).FullName}'");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
90
src/Runner.Listener/Checks/ActionsCheck.cs
Normal file
90
src/Runner.Listener/Checks/ActionsCheck.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Listener.Check
|
||||||
|
{
|
||||||
|
public sealed class ActionsCheck : RunnerService, ICheckExtension
|
||||||
|
{
|
||||||
|
private string _logFile = null;
|
||||||
|
|
||||||
|
public int Order => 2;
|
||||||
|
|
||||||
|
public string CheckName => "GitHub Actions Connection";
|
||||||
|
|
||||||
|
public string CheckDescription => "Make sure the actions runner have access to the GitHub Actions Service.";
|
||||||
|
|
||||||
|
public string CheckLog => _logFile;
|
||||||
|
|
||||||
|
public string HelpLink => "https://github.com/actions/runner/blob/main/docs/checks/actions.md";
|
||||||
|
|
||||||
|
public Type ExtensionType => typeof(ICheckExtension);
|
||||||
|
|
||||||
|
public override void Initialize(IHostContext hostContext)
|
||||||
|
{
|
||||||
|
base.Initialize(hostContext);
|
||||||
|
_logFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), StringUtil.Format("{0}_{1:yyyyMMdd-HHmmss}-utc.log", nameof(ActionsCheck), DateTime.UtcNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
// runner access to actions service
|
||||||
|
public async Task<bool> RunCheck(string url, string pat)
|
||||||
|
{
|
||||||
|
await File.AppendAllLinesAsync(_logFile, HostContext.WarnLog());
|
||||||
|
await File.AppendAllLinesAsync(_logFile, HostContext.CheckProxy());
|
||||||
|
|
||||||
|
var checkTasks = new List<Task<CheckResult>>();
|
||||||
|
string githubApiUrl = null;
|
||||||
|
string actionsTokenServiceUrl = null;
|
||||||
|
string actionsPipelinesServiceUrl = null;
|
||||||
|
var urlBuilder = new UriBuilder(url);
|
||||||
|
if (UrlUtil.IsHostedServer(urlBuilder))
|
||||||
|
{
|
||||||
|
urlBuilder.Host = $"api.{urlBuilder.Host}";
|
||||||
|
urlBuilder.Path = "";
|
||||||
|
githubApiUrl = urlBuilder.Uri.AbsoluteUri;
|
||||||
|
actionsTokenServiceUrl = "https://vstoken.actions.githubusercontent.com/_apis/health";
|
||||||
|
actionsPipelinesServiceUrl = "https://pipelines.actions.githubusercontent.com/_apis/health";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
urlBuilder.Path = "api/v3";
|
||||||
|
githubApiUrl = urlBuilder.Uri.AbsoluteUri;
|
||||||
|
urlBuilder.Path = "_services/vstoken/_apis/health";
|
||||||
|
actionsTokenServiceUrl = urlBuilder.Uri.AbsoluteUri;
|
||||||
|
urlBuilder.Path = "_services/pipelines/_apis/health";
|
||||||
|
actionsPipelinesServiceUrl = urlBuilder.Uri.AbsoluteUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check github api
|
||||||
|
checkTasks.Add(CheckUtil.CheckDns(githubApiUrl));
|
||||||
|
checkTasks.Add(CheckUtil.CheckPing(githubApiUrl));
|
||||||
|
checkTasks.Add(HostContext.CheckHttpsRequests(githubApiUrl, pat, expectedHeader: "X-GitHub-Request-Id"));
|
||||||
|
|
||||||
|
// check actions token service
|
||||||
|
checkTasks.Add(CheckUtil.CheckDns(actionsTokenServiceUrl));
|
||||||
|
checkTasks.Add(CheckUtil.CheckPing(actionsTokenServiceUrl));
|
||||||
|
checkTasks.Add(HostContext.CheckHttpsRequests(actionsTokenServiceUrl, pat, expectedHeader: "x-vss-e2eid"));
|
||||||
|
|
||||||
|
// check actions pipelines service
|
||||||
|
checkTasks.Add(CheckUtil.CheckDns(actionsPipelinesServiceUrl));
|
||||||
|
checkTasks.Add(CheckUtil.CheckPing(actionsPipelinesServiceUrl));
|
||||||
|
checkTasks.Add(HostContext.CheckHttpsRequests(actionsPipelinesServiceUrl, pat, expectedHeader: "x-vss-e2eid"));
|
||||||
|
|
||||||
|
var result = true;
|
||||||
|
while (checkTasks.Count > 0)
|
||||||
|
{
|
||||||
|
var finishedCheckTask = await Task.WhenAny<CheckResult>(checkTasks);
|
||||||
|
var finishedCheck = await finishedCheckTask;
|
||||||
|
result = result && finishedCheck.Pass;
|
||||||
|
await File.AppendAllLinesAsync(_logFile, finishedCheck.Logs);
|
||||||
|
checkTasks.Remove(finishedCheckTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(checkTasks);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
351
src/Runner.Listener/Checks/CheckUtil.cs
Normal file
351
src/Runner.Listener/Checks/CheckUtil.cs
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.Tracing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Services.Common;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Listener.Check
|
||||||
|
{
|
||||||
|
public static class CheckUtil
|
||||||
|
{
|
||||||
|
public static List<string> WarnLog(this IHostContext hostContext)
|
||||||
|
{
|
||||||
|
var logs = new List<string>();
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} **** !!! WARNING !!! ");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} **** DO NOT share the log in public place! The log may contains secrets in plain text. ");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} **** !!! WARNING !!! ");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> CheckProxy(this IHostContext hostContext)
|
||||||
|
{
|
||||||
|
var logs = new List<string>();
|
||||||
|
if (!string.IsNullOrEmpty(hostContext.WebProxy.HttpProxyAddress) ||
|
||||||
|
!string.IsNullOrEmpty(hostContext.WebProxy.HttpsProxyAddress))
|
||||||
|
{
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} **** Runner is behind web proxy {hostContext.WebProxy.HttpsProxyAddress ?? hostContext.WebProxy.HttpProxyAddress} ");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CheckResult> CheckDns(string targetUrl)
|
||||||
|
{
|
||||||
|
var result = new CheckResult();
|
||||||
|
var url = new Uri(targetUrl);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Try DNS lookup for {url.Host} ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
IPHostEntry host = await Dns.GetHostEntryAsync(url.Host);
|
||||||
|
foreach (var address in host.AddressList)
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Resolved DNS for {url.Host} to '{address}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Pass = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Pass = false;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Resolved DNS for {url.Host} failed with error: {ex}");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CheckResult> CheckPing(string targetUrl)
|
||||||
|
{
|
||||||
|
var result = new CheckResult();
|
||||||
|
var url = new Uri(targetUrl);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Try ping {url.Host} ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
using (var ping = new Ping())
|
||||||
|
{
|
||||||
|
var reply = await ping.SendPingAsync(url.Host);
|
||||||
|
if (reply.Status == IPStatus.Success)
|
||||||
|
{
|
||||||
|
result.Pass = true;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Ping {url.Host} ({reply.Address}) succeed within to '{reply.RoundtripTime} ms'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Pass = false;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Ping {url.Host} ({reply.Address}) failed with '{reply.Status}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Pass = false;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Ping api.github.com failed with error: {ex}");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CheckResult> CheckHttpsRequests(this IHostContext hostContext, string url, string pat, string expectedHeader)
|
||||||
|
{
|
||||||
|
var result = new CheckResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Send HTTPS Request to {url} ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
using (var _ = new HttpEventSourceListener(result.Logs))
|
||||||
|
using (var httpClientHandler = hostContext.CreateHttpClientHandler())
|
||||||
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(hostContext.UserAgents);
|
||||||
|
if (!string.IsNullOrEmpty(pat))
|
||||||
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token", pat);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await httpClient.GetAsync(url);
|
||||||
|
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http status code: {response.StatusCode}");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http response headers: {response.Headers}");
|
||||||
|
|
||||||
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http response body: {responseContent}");
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
if (response.Headers.Contains(expectedHeader))
|
||||||
|
{
|
||||||
|
result.Pass = true;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http request 'GET' to {url} succeed");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Pass = false;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http request 'GET' to {url} succeed but doesn't have expected HTTP Header.");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Pass = false;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Http request 'GET' to {url} failed with {response.StatusCode}");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Pass = false;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Https request 'GET' to {url} failed with error: {ex}");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CheckResult> DownloadExtraCA(this IHostContext hostContext, string url, string pat)
|
||||||
|
{
|
||||||
|
var result = new CheckResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Download SSL Certificate from {url} ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
|
||||||
|
var uri = new Uri(url);
|
||||||
|
var env = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "HOSTNAME", uri.Host },
|
||||||
|
{ "PORT", uri.IsDefaultPort ? (uri.Scheme.ToLowerInvariant() == "https" ? "443" : "80") : uri.Port.ToString() },
|
||||||
|
{ "PATH", uri.AbsolutePath },
|
||||||
|
{ "PAT", pat }
|
||||||
|
};
|
||||||
|
|
||||||
|
var proxy = hostContext.WebProxy.GetProxy(uri);
|
||||||
|
if (proxy != null)
|
||||||
|
{
|
||||||
|
env["PROXYHOST"] = proxy.Host;
|
||||||
|
env["PROXYPORT"] = proxy.IsDefaultPort ? (proxy.Scheme.ToLowerInvariant() == "https" ? "443" : "80") : proxy.Port.ToString();
|
||||||
|
if (hostContext.WebProxy.HttpProxyUsername != null ||
|
||||||
|
hostContext.WebProxy.HttpsProxyUsername != null)
|
||||||
|
{
|
||||||
|
env["PROXYUSERNAME"] = hostContext.WebProxy.HttpProxyUsername ?? hostContext.WebProxy.HttpsProxyUsername;
|
||||||
|
env["PROXYPASSWORD"] = hostContext.WebProxy.HttpProxyPassword ?? hostContext.WebProxy.HttpsProxyPassword;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env["PROXYUSERNAME"] = "";
|
||||||
|
env["PROXYPASSWORD"] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env["PROXYHOST"] = "";
|
||||||
|
env["PROXYPORT"] = "";
|
||||||
|
env["PROXYUSERNAME"] = "";
|
||||||
|
env["PROXYPASSWORD"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var processInvoker = hostContext.CreateService<IProcessInvoker>())
|
||||||
|
{
|
||||||
|
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} [STDOUT] {args.Data}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} [STDERR] {args.Data}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var downloadCertScript = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "downloadCert");
|
||||||
|
var node12 = Path.Combine(hostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node12} \"{downloadCertScript}\"' ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
|
||||||
|
await processInvoker.ExecuteAsync(
|
||||||
|
hostContext.GetDirectory(WellKnownDirectory.Root),
|
||||||
|
node12,
|
||||||
|
$"\"{downloadCertScript}\"",
|
||||||
|
env,
|
||||||
|
true,
|
||||||
|
CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Pass = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Pass = false;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Download SSL Certificate from '{url}' failed with error: {ex}");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventSource listener for dotnet debug trace for HTTP and SSL
|
||||||
|
public sealed class HttpEventSourceListener : EventListener
|
||||||
|
{
|
||||||
|
private readonly List<string> _logs;
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
private readonly Dictionary<string, HashSet<string>> _ignoredEvent = new Dictionary<string, HashSet<string>>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"Private.InternalDiagnostics.System.Net.Http",
|
||||||
|
new HashSet<string>
|
||||||
|
{
|
||||||
|
"Info",
|
||||||
|
"Associate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Private.InternalDiagnostics.System.Net.Security",
|
||||||
|
new HashSet<string>
|
||||||
|
{
|
||||||
|
"Info",
|
||||||
|
"SslStreamCtor",
|
||||||
|
"SecureChannelCtor",
|
||||||
|
"NoDelegateNoClientCert",
|
||||||
|
"CertsAfterFiltering",
|
||||||
|
"UsingCachedCredential",
|
||||||
|
"SspiSelectedCipherSuite"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public HttpEventSourceListener(List<string> logs)
|
||||||
|
{
|
||||||
|
_logs = logs;
|
||||||
|
if (Environment.GetEnvironmentVariable("ACTIONS_RUNNER_TRACE_ALL_HTTP_EVENT") == "1")
|
||||||
|
{
|
||||||
|
_ignoredEvent.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEventSourceCreated(EventSource eventSource)
|
||||||
|
{
|
||||||
|
base.OnEventSourceCreated(eventSource);
|
||||||
|
|
||||||
|
if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http" ||
|
||||||
|
eventSource.Name == "Private.InternalDiagnostics.System.Net.Security")
|
||||||
|
{
|
||||||
|
EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEventWritten(EventWrittenEventArgs eventData)
|
||||||
|
{
|
||||||
|
base.OnEventWritten(eventData);
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_ignoredEvent.TryGetValue(eventData.EventSource.Name, out var ignored) &&
|
||||||
|
ignored.Contains(eventData.EventName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logs.Add($"{DateTime.UtcNow.ToString("O")} [START {eventData.EventSource.Name} - {eventData.EventName}]");
|
||||||
|
_logs.AddRange(eventData.Payload.Select(x => string.Join(Environment.NewLine, x.ToString().Split(Environment.NewLine).Select(y => $"{DateTime.UtcNow.ToString("O")} {y}"))));
|
||||||
|
_logs.Add($"{DateTime.UtcNow.ToString("O")} [END {eventData.EventSource.Name} - {eventData.EventName}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
171
src/Runner.Listener/Checks/GitCheck.cs
Normal file
171
src/Runner.Listener/Checks/GitCheck.cs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Listener.Check
|
||||||
|
{
|
||||||
|
public sealed class GitCheck : RunnerService, ICheckExtension
|
||||||
|
{
|
||||||
|
private string _logFile = null;
|
||||||
|
private string _gitPath = null;
|
||||||
|
|
||||||
|
public int Order => 3;
|
||||||
|
|
||||||
|
public string CheckName => "Git Certificate/Proxy Validation";
|
||||||
|
|
||||||
|
public string CheckDescription => "Make sure the git cli can access to GitHub.com or the GitHub Enterprise Server.";
|
||||||
|
|
||||||
|
public string CheckLog => _logFile;
|
||||||
|
|
||||||
|
public string HelpLink => "https://github.com/actions/runner/blob/main/docs/checks/git.md";
|
||||||
|
|
||||||
|
public Type ExtensionType => typeof(ICheckExtension);
|
||||||
|
|
||||||
|
public override void Initialize(IHostContext hostContext)
|
||||||
|
{
|
||||||
|
base.Initialize(hostContext);
|
||||||
|
_logFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), StringUtil.Format("{0}_{1:yyyyMMdd-HHmmss}-utc.log", nameof(GitCheck), DateTime.UtcNow));
|
||||||
|
_gitPath = WhichUtil.Which("git");
|
||||||
|
}
|
||||||
|
|
||||||
|
// git access to ghes/gh
|
||||||
|
public async Task<bool> RunCheck(string url, string pat)
|
||||||
|
{
|
||||||
|
await File.AppendAllLinesAsync(_logFile, HostContext.WarnLog());
|
||||||
|
await File.AppendAllLinesAsync(_logFile, HostContext.CheckProxy());
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_gitPath))
|
||||||
|
{
|
||||||
|
await File.AppendAllLinesAsync(_logFile, new[] { $"{DateTime.UtcNow.ToString("O")} Can't verify git with GitHub.com or GitHub Enterprise Server since git is not installed." });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkGit = await CheckGit(url, pat);
|
||||||
|
var result = checkGit.Pass;
|
||||||
|
await File.AppendAllLinesAsync(_logFile, checkGit.Logs);
|
||||||
|
|
||||||
|
// try fix SSL error by providing extra CA certificate.
|
||||||
|
if (checkGit.SslError)
|
||||||
|
{
|
||||||
|
await File.AppendAllLinesAsync(_logFile, new[] { $"{DateTime.UtcNow.ToString("O")} Try fix SSL error by providing extra CA certificate." });
|
||||||
|
var downloadCert = await HostContext.DownloadExtraCA(url, pat);
|
||||||
|
await File.AppendAllLinesAsync(_logFile, downloadCert.Logs);
|
||||||
|
|
||||||
|
if (downloadCert.Pass)
|
||||||
|
{
|
||||||
|
var recheckGit = await CheckGit(url, pat, extraCA: true);
|
||||||
|
await File.AppendAllLinesAsync(_logFile, recheckGit.Logs);
|
||||||
|
if (recheckGit.Pass)
|
||||||
|
{
|
||||||
|
await File.AppendAllLinesAsync(_logFile, new[] { $"{DateTime.UtcNow.ToString("O")} Fixed SSL error by providing extra CA certs." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CheckResult> CheckGit(string url, string pat, bool extraCA = false)
|
||||||
|
{
|
||||||
|
var result = new CheckResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Validate server cert and proxy configuration with Git ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
var repoUrlBuilder = new UriBuilder(url);
|
||||||
|
repoUrlBuilder.Path = "actions/checkout";
|
||||||
|
repoUrlBuilder.UserName = "gh";
|
||||||
|
repoUrlBuilder.Password = pat;
|
||||||
|
|
||||||
|
var gitProxy = "";
|
||||||
|
var proxy = HostContext.WebProxy.GetProxy(repoUrlBuilder.Uri);
|
||||||
|
if (proxy != null)
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Runner is behind http proxy '{proxy.AbsoluteUri}'");
|
||||||
|
if (HostContext.WebProxy.HttpProxyUsername != null ||
|
||||||
|
HostContext.WebProxy.HttpsProxyUsername != null)
|
||||||
|
{
|
||||||
|
var proxyUrlWithCred = UrlUtil.GetCredentialEmbeddedUrl(
|
||||||
|
proxy,
|
||||||
|
HostContext.WebProxy.HttpProxyUsername ?? HostContext.WebProxy.HttpsProxyUsername,
|
||||||
|
HostContext.WebProxy.HttpProxyPassword ?? HostContext.WebProxy.HttpsProxyPassword);
|
||||||
|
gitProxy = $"-c http.proxy={proxyUrlWithCred}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gitProxy = $"-c http.proxy={proxy.AbsoluteUri}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
|
{
|
||||||
|
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {args.Data}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {args.Data}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var gitArgs = $"{gitProxy} ls-remote --exit-code {repoUrlBuilder.Uri.AbsoluteUri} HEAD";
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run 'git {gitArgs}' ");
|
||||||
|
|
||||||
|
var env = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "GIT_TRACE", "1" },
|
||||||
|
{ "GIT_CURL_VERBOSE", "1" }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (extraCA)
|
||||||
|
{
|
||||||
|
env["GIT_SSL_CAINFO"] = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), "download_ca_cert.pem");
|
||||||
|
}
|
||||||
|
|
||||||
|
await processInvoker.ExecuteAsync(
|
||||||
|
HostContext.GetDirectory(WellKnownDirectory.Root),
|
||||||
|
_gitPath,
|
||||||
|
gitArgs,
|
||||||
|
env,
|
||||||
|
true,
|
||||||
|
CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Pass = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Pass = false;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** git ls-remote failed with error: {ex}");
|
||||||
|
if (result.Logs.Any(x => x.Contains("SSL Certificate problem", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** git ls-remote failed due to SSL cert issue.");
|
||||||
|
result.SslError = true;
|
||||||
|
}
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/Runner.Listener/Checks/ICheckExtension.cs
Normal file
30
src/Runner.Listener/Checks/ICheckExtension.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Listener.Check
|
||||||
|
{
|
||||||
|
public interface ICheckExtension : IExtension
|
||||||
|
{
|
||||||
|
int Order { get; }
|
||||||
|
string CheckName { get; }
|
||||||
|
string CheckDescription { get; }
|
||||||
|
string CheckLog { get; }
|
||||||
|
string HelpLink { get; }
|
||||||
|
Task<bool> RunCheck(string url, string pat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CheckResult
|
||||||
|
{
|
||||||
|
public CheckResult()
|
||||||
|
{
|
||||||
|
Logs = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Pass { get; set; }
|
||||||
|
|
||||||
|
public bool SslError { get; set; }
|
||||||
|
|
||||||
|
public List<string> Logs { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/Runner.Listener/Checks/InternetCheck.cs
Normal file
59
src/Runner.Listener/Checks/InternetCheck.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Listener.Check
|
||||||
|
{
|
||||||
|
public sealed class InternetCheck : RunnerService, ICheckExtension
|
||||||
|
{
|
||||||
|
private string _logFile = null;
|
||||||
|
|
||||||
|
public int Order => 1;
|
||||||
|
|
||||||
|
public string CheckName => "Internet Connection";
|
||||||
|
|
||||||
|
public string CheckDescription => "Make sure the actions runner have access to public internet.";
|
||||||
|
|
||||||
|
public string CheckLog => _logFile;
|
||||||
|
|
||||||
|
public string HelpLink => "https://github.com/actions/runner/blob/main/docs/checks/internet.md";
|
||||||
|
|
||||||
|
public Type ExtensionType => typeof(ICheckExtension);
|
||||||
|
|
||||||
|
public override void Initialize(IHostContext hostContext)
|
||||||
|
{
|
||||||
|
base.Initialize(hostContext);
|
||||||
|
_logFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), StringUtil.Format("{0}_{1:yyyyMMdd-HHmmss}-utc.log", nameof(InternetCheck), DateTime.UtcNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check runner access to api.github.com
|
||||||
|
public async Task<bool> RunCheck(string url, string pat)
|
||||||
|
{
|
||||||
|
await File.AppendAllLinesAsync(_logFile, HostContext.WarnLog());
|
||||||
|
await File.AppendAllLinesAsync(_logFile, HostContext.CheckProxy());
|
||||||
|
|
||||||
|
var checkTasks = new List<Task<CheckResult>>();
|
||||||
|
checkTasks.Add(CheckUtil.CheckDns("https://api.github.com"));
|
||||||
|
checkTasks.Add(CheckUtil.CheckPing("https://api.github.com"));
|
||||||
|
|
||||||
|
// We don't need to pass a PAT since it might be a token for GHES.
|
||||||
|
checkTasks.Add(HostContext.CheckHttpsRequests("https://api.github.com", pat: null, expectedHeader: "X-GitHub-Request-Id"));
|
||||||
|
|
||||||
|
var result = true;
|
||||||
|
while (checkTasks.Count > 0)
|
||||||
|
{
|
||||||
|
var finishedCheckTask = await Task.WhenAny<CheckResult>(checkTasks);
|
||||||
|
var finishedCheck = await finishedCheckTask;
|
||||||
|
result = result && finishedCheck.Pass;
|
||||||
|
await File.AppendAllLinesAsync(_logFile, finishedCheck.Logs);
|
||||||
|
checkTasks.Remove(finishedCheckTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(checkTasks);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
181
src/Runner.Listener/Checks/NodeJsCheck.cs
Normal file
181
src/Runner.Listener/Checks/NodeJsCheck.cs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Listener.Check
|
||||||
|
{
|
||||||
|
public sealed class NodeJsCheck : RunnerService, ICheckExtension
|
||||||
|
{
|
||||||
|
private string _logFile = null;
|
||||||
|
|
||||||
|
public int Order => 4;
|
||||||
|
|
||||||
|
public string CheckName => "Node.js Certificate/Proxy Validation";
|
||||||
|
|
||||||
|
public string CheckDescription => "Make sure the node.js have access to GitHub.com or the GitHub Enterprise Server.";
|
||||||
|
|
||||||
|
public string CheckLog => _logFile;
|
||||||
|
|
||||||
|
public string HelpLink => "https://github.com/actions/runner/blob/main/docs/checks/nodejs.md";
|
||||||
|
|
||||||
|
public Type ExtensionType => typeof(ICheckExtension);
|
||||||
|
|
||||||
|
public override void Initialize(IHostContext hostContext)
|
||||||
|
{
|
||||||
|
base.Initialize(hostContext);
|
||||||
|
_logFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Diag), StringUtil.Format("{0}_{1:yyyyMMdd-HHmmss}-utc.log", nameof(NodeJsCheck), DateTime.UtcNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
// node access to ghes/gh
|
||||||
|
public async Task<bool> RunCheck(string url, string pat)
|
||||||
|
{
|
||||||
|
await File.AppendAllLinesAsync(_logFile, HostContext.WarnLog());
|
||||||
|
await File.AppendAllLinesAsync(_logFile, HostContext.CheckProxy());
|
||||||
|
|
||||||
|
// Request to github.com or ghes server
|
||||||
|
var urlBuilder = new UriBuilder(url);
|
||||||
|
if (UrlUtil.IsHostedServer(urlBuilder))
|
||||||
|
{
|
||||||
|
urlBuilder.Host = $"api.{urlBuilder.Host}";
|
||||||
|
urlBuilder.Path = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
urlBuilder.Path = "api/v3";
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkNode = await CheckNodeJs(urlBuilder.Uri.AbsoluteUri, pat);
|
||||||
|
var result = checkNode.Pass;
|
||||||
|
await File.AppendAllLinesAsync(_logFile, checkNode.Logs);
|
||||||
|
|
||||||
|
// try fix SSL error by providing extra CA certificate.
|
||||||
|
if (checkNode.SslError)
|
||||||
|
{
|
||||||
|
var downloadCert = await HostContext.DownloadExtraCA(urlBuilder.Uri.AbsoluteUri, pat);
|
||||||
|
await File.AppendAllLinesAsync(_logFile, downloadCert.Logs);
|
||||||
|
|
||||||
|
if (downloadCert.Pass)
|
||||||
|
{
|
||||||
|
var recheckNode = await CheckNodeJs(urlBuilder.Uri.AbsoluteUri, pat, extraCA: true);
|
||||||
|
await File.AppendAllLinesAsync(_logFile, recheckNode.Logs);
|
||||||
|
if (recheckNode.Pass)
|
||||||
|
{
|
||||||
|
await File.AppendAllLinesAsync(_logFile, new[] { $"{DateTime.UtcNow.ToString("O")} Fixed SSL error by providing extra CA certs." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CheckResult> CheckNodeJs(string url, string pat, bool extraCA = false)
|
||||||
|
{
|
||||||
|
var result = new CheckResult();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Make Http request to {url} using node.js ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
|
||||||
|
// Request to github.com or ghes server
|
||||||
|
Uri requestUrl = new Uri(url);
|
||||||
|
var env = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "HOSTNAME", requestUrl.Host },
|
||||||
|
{ "PORT", requestUrl.IsDefaultPort ? (requestUrl.Scheme.ToLowerInvariant() == "https" ? "443" : "80") : requestUrl.Port.ToString() },
|
||||||
|
{ "PATH", requestUrl.AbsolutePath },
|
||||||
|
{ "PAT", pat }
|
||||||
|
};
|
||||||
|
|
||||||
|
var proxy = HostContext.WebProxy.GetProxy(requestUrl);
|
||||||
|
if (proxy != null)
|
||||||
|
{
|
||||||
|
env["PROXYHOST"] = proxy.Host;
|
||||||
|
env["PROXYPORT"] = proxy.IsDefaultPort ? (proxy.Scheme.ToLowerInvariant() == "https" ? "443" : "80") : proxy.Port.ToString();
|
||||||
|
if (HostContext.WebProxy.HttpProxyUsername != null ||
|
||||||
|
HostContext.WebProxy.HttpsProxyUsername != null)
|
||||||
|
{
|
||||||
|
env["PROXYUSERNAME"] = HostContext.WebProxy.HttpProxyUsername ?? HostContext.WebProxy.HttpsProxyUsername;
|
||||||
|
env["PROXYPASSWORD"] = HostContext.WebProxy.HttpProxyPassword ?? HostContext.WebProxy.HttpsProxyPassword;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env["PROXYUSERNAME"] = "";
|
||||||
|
env["PROXYPASSWORD"] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
env["PROXYHOST"] = "";
|
||||||
|
env["PROXYPORT"] = "";
|
||||||
|
env["PROXYUSERNAME"] = "";
|
||||||
|
env["PROXYPASSWORD"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extraCA)
|
||||||
|
{
|
||||||
|
env["NODE_EXTRA_CA_CERTS"] = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), "download_ca_cert.pem");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var processInvoker = HostContext.CreateService<IProcessInvoker>())
|
||||||
|
{
|
||||||
|
processInvoker.OutputDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} [STDOUT] {args.Data}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
processInvoker.ErrorDataReceived += new EventHandler<ProcessDataReceivedEventArgs>((sender, args) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(args.Data))
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} [STDERR] {args.Data}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var makeWebRequestScript = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "checkScripts", "makeWebRequest.js");
|
||||||
|
var node12 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} Run '{node12} \"{makeWebRequestScript}\"' ");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} {StringUtil.ConvertToJson(env)}");
|
||||||
|
await processInvoker.ExecuteAsync(
|
||||||
|
HostContext.GetDirectory(WellKnownDirectory.Root),
|
||||||
|
node12,
|
||||||
|
$"\"{makeWebRequestScript}\"",
|
||||||
|
env,
|
||||||
|
true,
|
||||||
|
CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Pass = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Pass = false;
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Make https request to {url} using node.js failed with error: {ex}");
|
||||||
|
if (result.Logs.Any(x => x.Contains("UNABLE_TO_VERIFY_LEAF_SIGNATURE") ||
|
||||||
|
x.Contains("UNABLE_TO_GET_ISSUER_CERT_LOCALLY") ||
|
||||||
|
x.Contains("SELF_SIGNED_CERT_IN_CHAIN")))
|
||||||
|
{
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** Https request failed due to SSL cert issue.");
|
||||||
|
result.SslError = true;
|
||||||
|
}
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} **** ****");
|
||||||
|
result.Logs.Add($"{DateTime.UtcNow.ToString("O")} ***************************************************************************************************************");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ namespace GitHub.Runner.Listener
|
|||||||
|
|
||||||
private readonly string[] validFlags =
|
private readonly string[] validFlags =
|
||||||
{
|
{
|
||||||
|
Constants.Runner.CommandLine.Flags.Check,
|
||||||
Constants.Runner.CommandLine.Flags.Commit,
|
Constants.Runner.CommandLine.Flags.Commit,
|
||||||
Constants.Runner.CommandLine.Flags.Help,
|
Constants.Runner.CommandLine.Flags.Help,
|
||||||
Constants.Runner.CommandLine.Flags.Replace,
|
Constants.Runner.CommandLine.Flags.Replace,
|
||||||
@@ -42,6 +43,7 @@ namespace GitHub.Runner.Listener
|
|||||||
Constants.Runner.CommandLine.Args.Labels,
|
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.PAT,
|
||||||
Constants.Runner.CommandLine.Args.RunnerGroup,
|
Constants.Runner.CommandLine.Args.RunnerGroup,
|
||||||
Constants.Runner.CommandLine.Args.StartupType,
|
Constants.Runner.CommandLine.Args.StartupType,
|
||||||
Constants.Runner.CommandLine.Args.Token,
|
Constants.Runner.CommandLine.Args.Token,
|
||||||
@@ -59,6 +61,7 @@ namespace GitHub.Runner.Listener
|
|||||||
public bool Warmup => TestCommand(Constants.Runner.CommandLine.Commands.Warmup);
|
public bool Warmup => TestCommand(Constants.Runner.CommandLine.Commands.Warmup);
|
||||||
|
|
||||||
// Flags.
|
// Flags.
|
||||||
|
public bool Check => TestFlag(Constants.Runner.CommandLine.Flags.Check);
|
||||||
public bool Commit => TestFlag(Constants.Runner.CommandLine.Flags.Commit);
|
public bool Commit => TestFlag(Constants.Runner.CommandLine.Flags.Commit);
|
||||||
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
public bool Help => TestFlag(Constants.Runner.CommandLine.Flags.Help);
|
||||||
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
|
||||||
@@ -187,6 +190,22 @@ namespace GitHub.Runner.Listener
|
|||||||
validator: Validators.NonEmptyValidator);
|
validator: Validators.NonEmptyValidator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetGitHubPersonalAccessToken(bool required = false)
|
||||||
|
{
|
||||||
|
if (required)
|
||||||
|
{
|
||||||
|
return GetArgOrPrompt(
|
||||||
|
name: Constants.Runner.CommandLine.Args.PAT,
|
||||||
|
description: "What is your GitHub personal access token?",
|
||||||
|
defaultValue: string.Empty,
|
||||||
|
validator: Validators.NonEmptyValidator);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return GetArg(name: Constants.Runner.CommandLine.Args.PAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string GetRunnerRegisterToken()
|
public string GetRunnerRegisterToken()
|
||||||
{
|
{
|
||||||
return GetArgOrPrompt(
|
return GetArgOrPrompt(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ 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;
|
||||||
using GitHub.Services.WebApi;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -12,6 +11,7 @@ using System.Net.Http;
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener.Configuration
|
namespace GitHub.Runner.Listener.Configuration
|
||||||
@@ -107,8 +107,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
runnerSettings.GitHubUrl = inputUrl;
|
runnerSettings.GitHubUrl = inputUrl;
|
||||||
var githubToken = command.GetRunnerRegisterToken();
|
var registerToken = await GetRunnerTokenAsync(command, inputUrl, "registration");
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, githubToken, Constants.RunnerEvent.Register);
|
GitHubAuthResult authResult = await GetTenantCredential(inputUrl, registerToken, 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");
|
||||||
@@ -117,7 +117,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
|
||||||
runnerSettings.IsHostedServer = runnerSettings.GitHubUrl == null || IsHostedServer(new UriBuilder(runnerSettings.GitHubUrl));
|
runnerSettings.IsHostedServer = runnerSettings.GitHubUrl == null || UrlUtil.IsHostedServer(new UriBuilder(runnerSettings.GitHubUrl));
|
||||||
|
|
||||||
// Warn if the Actions server url and GHES server url has different Host
|
// Warn if the Actions server url and GHES server url has different Host
|
||||||
if (!runnerSettings.IsHostedServer)
|
if (!runnerSettings.IsHostedServer)
|
||||||
@@ -373,8 +373,8 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var githubToken = command.GetRunnerDeletionToken();
|
var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove");
|
||||||
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, githubToken, Constants.RunnerEvent.Remove);
|
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove);
|
||||||
creds = authResult.ToVssCredentials();
|
creds = authResult.ToVssCredentials();
|
||||||
Trace.Info("cred retrieved via GitHub auth");
|
Trace.Info("cred retrieved via GitHub auth");
|
||||||
}
|
}
|
||||||
@@ -508,18 +508,107 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsHostedServer(UriBuilder gitHubUrl)
|
private async Task<string> GetRunnerTokenAsync(CommandSettings command, string githubUrl, string tokenType)
|
||||||
{
|
{
|
||||||
return string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
var githubPAT = command.GetGitHubPersonalAccessToken();
|
||||||
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
var runnerToken = string.Empty;
|
||||||
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase);
|
if (!string.IsNullOrEmpty(githubPAT))
|
||||||
|
{
|
||||||
|
Trace.Info($"Retriving runner {tokenType} token using GitHub PAT.");
|
||||||
|
var jitToken = await GetJITRunnerTokenAsync(githubUrl, githubPAT, tokenType);
|
||||||
|
Trace.Info($"Retrived runner {tokenType} token is good to {jitToken.ExpiresAt}.");
|
||||||
|
HostContext.SecretMasker.AddValue(jitToken.Token);
|
||||||
|
runnerToken = jitToken.Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(runnerToken))
|
||||||
|
{
|
||||||
|
if (string.Equals("registration", tokenType, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
runnerToken = command.GetRunnerRegisterToken();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
runnerToken = command.GetRunnerDeletionToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return runnerToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<GitHubRunnerRegisterToken> GetJITRunnerTokenAsync(string githubUrl, string githubToken, string tokenType)
|
||||||
|
{
|
||||||
|
var githubApiUrl = "";
|
||||||
|
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||||
|
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (path.Length == 1)
|
||||||
|
{
|
||||||
|
// org runner
|
||||||
|
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runners/{tokenType}-token";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners/{tokenType}-token";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (path.Length == 2)
|
||||||
|
{
|
||||||
|
// repo or enterprise runner.
|
||||||
|
var repoScope = "repos/";
|
||||||
|
if (string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
repoScope = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{repoScope}{path[0]}/{path[1]}/actions/runners/{tokenType}-token";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{repoScope}{path[0]}/{path[1]}/actions/runners/{tokenType}-token";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"'{githubUrl}' should point to an org or repository.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
|
{
|
||||||
|
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"github:{githubToken}"));
|
||||||
|
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken);
|
||||||
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
|
httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json");
|
||||||
|
|
||||||
|
var response = await httpClient.PostAsync(githubApiUrl, new StringContent(string.Empty));
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Trace.Info($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||||
|
var jsonResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
return StringUtil.ConvertFromJson<GitHubRunnerRegisterToken>(jsonResponse);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_term.WriteError($"Http response code: {response.StatusCode} from 'POST {githubApiUrl}'");
|
||||||
|
var errorResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
_term.WriteError(errorResponse);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, string githubToken, string runnerEvent)
|
private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, string githubToken, string runnerEvent)
|
||||||
{
|
{
|
||||||
var githubApiUrl = "";
|
var githubApiUrl = "";
|
||||||
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
||||||
if (IsHostedServer(gitHubUrlBuilder))
|
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
||||||
{
|
{
|
||||||
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/actions/runner-registration";
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/actions/runner-registration";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,16 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataContract]
|
||||||
|
public sealed class GitHubRunnerRegisterToken
|
||||||
|
{
|
||||||
|
[DataMember(Name = "token")]
|
||||||
|
public string Token { get; set; }
|
||||||
|
|
||||||
|
[DataMember(Name = "expires_at")]
|
||||||
|
public string ExpiresAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[DataContract]
|
[DataContract]
|
||||||
public sealed class GitHubAuthResult
|
public sealed class GitHubAuthResult
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
/// key is returned to the caller.
|
/// key is returned to the caller.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
|
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
|
||||||
RSACryptoServiceProvider CreateKey();
|
RSA CreateKey();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the RSA key managed by the key manager.
|
/// Deletes the RSA key managed by the key manager.
|
||||||
@@ -32,7 +32,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
|
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
|
||||||
/// <exception cref="CryptographicException">No key exists in the store</exception>
|
/// <exception cref="CryptographicException">No key exists in the store</exception>
|
||||||
RSACryptoServiceProvider GetKey();
|
RSA GetKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Newtonsoft 10 is not working properly with dotnet RSAParameters class
|
// Newtonsoft 10 is not working properly with dotnet RSAParameters class
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
// We expect the key to be in the machine store at this point. Configuration should have set all of
|
// We expect the key to be in the machine store at this point. Configuration should have set all of
|
||||||
// this up correctly so we can use the key to generate access tokens.
|
// this up correctly so we can use the key to generate access tokens.
|
||||||
var keyManager = context.GetService<IRSAKeyManager>();
|
var keyManager = context.GetService<IRSAKeyManager>();
|
||||||
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey());
|
var signingCredentials = VssSigningCredentials.Create(() => keyManager.GetKey(), requireFipsCryptography: true);
|
||||||
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
|
var clientCredential = new VssOAuthJwtBearerClientCredential(clientId, authorizationUrl, signingCredentials);
|
||||||
var agentCredential = new VssOAuthCredential(new Uri(oauthEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
|
var agentCredential = new VssOAuthCredential(new Uri(oauthEndpointUrl, UriKind.Absolute), VssOAuthGrant.ClientCredentials, clientCredential);
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
private string _keyFile;
|
private string _keyFile;
|
||||||
private IHostContext _context;
|
private IHostContext _context;
|
||||||
|
|
||||||
public RSACryptoServiceProvider CreateKey()
|
public RSA CreateKey()
|
||||||
{
|
{
|
||||||
RSACryptoServiceProvider rsa = null;
|
RSA rsa = null;
|
||||||
if (!File.Exists(_keyFile))
|
if (!File.Exists(_keyFile))
|
||||||
{
|
{
|
||||||
Trace.Info("Creating new RSA key using 2048-bit key length");
|
Trace.Info("Creating new RSA key using 2048-bit key length");
|
||||||
|
|
||||||
rsa = new RSACryptoServiceProvider(2048);
|
rsa = RSA.Create(2048);
|
||||||
|
|
||||||
// Now write the parameters to disk
|
// Now write the parameters to disk
|
||||||
SaveParameters(rsa.ExportParameters(true));
|
SaveParameters(rsa.ExportParameters(true));
|
||||||
@@ -30,7 +30,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
|
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
|
||||||
|
|
||||||
rsa = new RSACryptoServiceProvider();
|
rsa = RSA.Create();
|
||||||
rsa.ImportParameters(LoadParameters());
|
rsa.ImportParameters(LoadParameters());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RSACryptoServiceProvider GetKey()
|
public RSA GetKey()
|
||||||
{
|
{
|
||||||
if (!File.Exists(_keyFile))
|
if (!File.Exists(_keyFile))
|
||||||
{
|
{
|
||||||
@@ -55,7 +55,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
|
|
||||||
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
|
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
|
||||||
|
|
||||||
var rsa = new RSACryptoServiceProvider();
|
var rsa = RSA.Create();
|
||||||
rsa.ImportParameters(LoadParameters());
|
rsa.ImportParameters(LoadParameters());
|
||||||
return rsa;
|
return rsa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
private string _keyFile;
|
private string _keyFile;
|
||||||
private IHostContext _context;
|
private IHostContext _context;
|
||||||
|
|
||||||
public RSACryptoServiceProvider CreateKey()
|
public RSA CreateKey()
|
||||||
{
|
{
|
||||||
RSACryptoServiceProvider rsa = null;
|
RSA rsa = null;
|
||||||
if (!File.Exists(_keyFile))
|
if (!File.Exists(_keyFile))
|
||||||
{
|
{
|
||||||
Trace.Info("Creating new RSA key using 2048-bit key length");
|
Trace.Info("Creating new RSA key using 2048-bit key length");
|
||||||
|
|
||||||
rsa = new RSACryptoServiceProvider(2048);
|
rsa = RSA.Create(2048);
|
||||||
|
|
||||||
// Now write the parameters to disk
|
// Now write the parameters to disk
|
||||||
IOUtil.SaveObject(new RSAParametersSerializable(rsa.ExportParameters(true)), _keyFile);
|
IOUtil.SaveObject(new RSAParametersSerializable(rsa.ExportParameters(true)), _keyFile);
|
||||||
@@ -54,7 +54,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
|
Trace.Info("Found existing RSA key parameters file {0}", _keyFile);
|
||||||
|
|
||||||
rsa = new RSACryptoServiceProvider();
|
rsa = RSA.Create();
|
||||||
rsa.ImportParameters(IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters);
|
rsa.ImportParameters(IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RSACryptoServiceProvider GetKey()
|
public RSA GetKey()
|
||||||
{
|
{
|
||||||
if (!File.Exists(_keyFile))
|
if (!File.Exists(_keyFile))
|
||||||
{
|
{
|
||||||
@@ -80,7 +80,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
|
Trace.Info("Loading RSA key parameters from file {0}", _keyFile);
|
||||||
|
|
||||||
var parameters = IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters;
|
var parameters = IOUtil.LoadObject<RSAParametersSerializable>(_keyFile).RSAParameters;
|
||||||
var rsa = new RSACryptoServiceProvider();
|
var rsa = RSA.Create();
|
||||||
rsa.ImportParameters(parameters);
|
rsa.ImportParameters(parameters);
|
||||||
return rsa;
|
return rsa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,7 +319,8 @@ namespace GitHub.Runner.Listener
|
|||||||
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
var keyManager = HostContext.GetService<IRSAKeyManager>();
|
||||||
using (var rsa = keyManager.GetKey())
|
using (var rsa = keyManager.GetKey())
|
||||||
{
|
{
|
||||||
return aes.CreateDecryptor(rsa.Decrypt(_session.EncryptionKey.Value, RSAEncryptionPadding.OaepSHA1), message.IV);
|
var padding = _session.UseFipsEncryption ? RSAEncryptionPadding.OaepSHA256 : RSAEncryptionPadding.OaepSHA1;
|
||||||
|
return aes.CreateDecryptor(rsa.Decrypt(_session.EncryptionKey.Value, padding), message.IV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using GitHub.Runner.Common.Util;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -11,6 +10,8 @@ using System.Reflection;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using System.Linq;
|
||||||
|
using GitHub.Runner.Listener.Check;
|
||||||
|
|
||||||
namespace GitHub.Runner.Listener
|
namespace GitHub.Runner.Listener
|
||||||
{
|
{
|
||||||
@@ -72,6 +73,46 @@ namespace GitHub.Runner.Listener
|
|||||||
return Constants.Runner.ReturnCode.Success;
|
return Constants.Runner.ReturnCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command.Check)
|
||||||
|
{
|
||||||
|
var url = command.GetUrl();
|
||||||
|
var pat = command.GetGitHubPersonalAccessToken(required: true);
|
||||||
|
var checkExtensions = HostContext.GetService<IExtensionManager>().GetExtensions<ICheckExtension>();
|
||||||
|
var sortedChecks = checkExtensions.OrderBy(x => x.Order);
|
||||||
|
foreach (var check in sortedChecks)
|
||||||
|
{
|
||||||
|
_term.WriteLine($"**********************************************************************************************************************");
|
||||||
|
_term.WriteLine($"** Check: {check.CheckName}");
|
||||||
|
_term.WriteLine($"** Description: {check.CheckDescription}");
|
||||||
|
_term.WriteLine($"**********************************************************************************************************************");
|
||||||
|
var result = await check.RunCheck(url, pat);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
_term.WriteLine($"** **");
|
||||||
|
_term.WriteLine($"** F A I L **");
|
||||||
|
_term.WriteLine($"** **");
|
||||||
|
_term.WriteLine($"**********************************************************************************************************************");
|
||||||
|
_term.WriteLine($"** Log: {check.CheckLog}");
|
||||||
|
_term.WriteLine($"** Help Doc: {check.HelpLink}");
|
||||||
|
_term.WriteLine($"**********************************************************************************************************************");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_term.WriteLine($"** **");
|
||||||
|
_term.WriteLine($"** P A S S **");
|
||||||
|
_term.WriteLine($"** **");
|
||||||
|
_term.WriteLine($"**********************************************************************************************************************");
|
||||||
|
_term.WriteLine($"** Log: {check.CheckLog}");
|
||||||
|
_term.WriteLine($"**********************************************************************************************************************");
|
||||||
|
}
|
||||||
|
|
||||||
|
_term.WriteLine();
|
||||||
|
_term.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Constants.Runner.ReturnCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
// Configure runner prompt for args if not supplied
|
// Configure runner prompt for args if not supplied
|
||||||
// Unattended configure mode will not prompt for args if not supplied and error on any missing or invalid value.
|
// Unattended configure mode will not prompt for args if not supplied and error on any missing or invalid value.
|
||||||
if (command.Configure)
|
if (command.Configure)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
{
|
{
|
||||||
// We should never
|
// We should never
|
||||||
context.Error($"Error '{ex.Message}' when downloading file '{fileToDownload}'. (Downloader {downloaderId})");
|
context.Error($"Error '{ex.Message}' when downloading file '{fileToDownload}'. (Downloader {downloaderId})");
|
||||||
throw ex;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +528,7 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
context.Output($"File error '{ex.Message}' when uploading file '{fileToUpload}'.");
|
context.Output($"File error '{ex.Message}' when uploading file '{fileToUpload}'.");
|
||||||
throw ex;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,4 +682,4 @@ namespace GitHub.Runner.Plugins.Artifact
|
|||||||
: base(message, inner)
|
: base(message, inner)
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -4,6 +4,13 @@ namespace GitHub.Runner.Sdk
|
|||||||
{
|
{
|
||||||
public static class UrlUtil
|
public static class UrlUtil
|
||||||
{
|
{
|
||||||
|
public static bool IsHostedServer(UriBuilder gitHubUrl)
|
||||||
|
{
|
||||||
|
return string.Equals(gitHubUrl.Host, "github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(gitHubUrl.Host, "www.github.com", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(gitHubUrl.Host, "github.localhost", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
public static Uri GetCredentialEmbeddedUrl(Uri baseUrl, string username, string password)
|
public static Uri GetCredentialEmbeddedUrl(Uri baseUrl, string username, string password)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(baseUrl, nameof(baseUrl));
|
ArgUtil.NotNull(baseUrl, nameof(baseUrl));
|
||||||
|
|||||||
@@ -184,9 +184,6 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
|
||||||
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
|
|
||||||
|
|
||||||
var allowUnsecureCommands = false;
|
var allowUnsecureCommands = false;
|
||||||
bool.TryParse(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowUnsupportedCommands), out allowUnsecureCommands);
|
bool.TryParse(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowUnsupportedCommands), out allowUnsecureCommands);
|
||||||
|
|
||||||
@@ -201,22 +198,10 @@ namespace GitHub.Runner.Worker
|
|||||||
bool.TryParse(envContext[Constants.Variables.Actions.AllowUnsupportedCommands].ToString(), out allowUnsecureCommands);
|
bool.TryParse(envContext[Constants.Variables.Actions.AllowUnsupportedCommands].ToString(), out allowUnsecureCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove isHostedServer and apply this to dotcom customers as well
|
if (!allowUnsecureCommands)
|
||||||
if (!isHostedServer && !allowUnsecureCommands)
|
|
||||||
{
|
{
|
||||||
throw new Exception(String.Format(Constants.Runner.UnsupportedCommandMessageDisabled, this.Command));
|
throw new Exception(String.Format(Constants.Runner.UnsupportedCommandMessageDisabled, this.Command));
|
||||||
}
|
}
|
||||||
else if (!allowUnsecureCommands)
|
|
||||||
{
|
|
||||||
// Log Telemetry and let user know they shouldn't do this
|
|
||||||
var issue = new Issue()
|
|
||||||
{
|
|
||||||
Type = IssueType.Error,
|
|
||||||
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
|
||||||
};
|
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
|
||||||
context.AddIssue(issue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!command.Properties.TryGetValue(SetEnvCommandProperties.Name, out string envName) || string.IsNullOrEmpty(envName))
|
if (!command.Properties.TryGetValue(SetEnvCommandProperties.Name, out string envName) || string.IsNullOrEmpty(envName))
|
||||||
{
|
{
|
||||||
@@ -339,10 +324,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public Type ExtensionType => typeof(IActionCommandExtension);
|
public Type ExtensionType => typeof(IActionCommandExtension);
|
||||||
|
|
||||||
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
public void ProcessCommand(IExecutionContext context, string line, ActionCommand command, ContainerInfo container)
|
||||||
{
|
{
|
||||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
|
||||||
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
|
|
||||||
|
|
||||||
var allowUnsecureCommands = false;
|
var allowUnsecureCommands = false;
|
||||||
bool.TryParse(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowUnsupportedCommands), out allowUnsecureCommands);
|
bool.TryParse(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowUnsupportedCommands), out allowUnsecureCommands);
|
||||||
|
|
||||||
@@ -357,22 +339,10 @@ namespace GitHub.Runner.Worker
|
|||||||
bool.TryParse(envContext[Constants.Variables.Actions.AllowUnsupportedCommands].ToString(), out allowUnsecureCommands);
|
bool.TryParse(envContext[Constants.Variables.Actions.AllowUnsupportedCommands].ToString(), out allowUnsecureCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove isHostedServer and apply this to dotcom customers as well
|
if (!allowUnsecureCommands)
|
||||||
if (!isHostedServer && !allowUnsecureCommands)
|
|
||||||
{
|
{
|
||||||
throw new Exception(String.Format(Constants.Runner.UnsupportedCommandMessageDisabled, this.Command));
|
throw new Exception(String.Format(Constants.Runner.UnsupportedCommandMessageDisabled, this.Command));
|
||||||
}
|
}
|
||||||
else if (!allowUnsecureCommands)
|
|
||||||
{
|
|
||||||
// Log Telemetry and let user know they shouldn't do this
|
|
||||||
var issue = new Issue()
|
|
||||||
{
|
|
||||||
Type = IssueType.Error,
|
|
||||||
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
|
||||||
};
|
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
|
||||||
context.AddIssue(issue);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(command.Data, "path");
|
ArgUtil.NotNullOrEmpty(command.Data, "path");
|
||||||
context.Global.PrependPath.RemoveAll(x => string.Equals(x, command.Data, StringComparison.CurrentCulture));
|
context.Global.PrependPath.RemoveAll(x => string.Equals(x, command.Data, StringComparison.CurrentCulture));
|
||||||
|
|||||||
@@ -594,15 +594,33 @@ namespace GitHub.Runner.Worker
|
|||||||
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Global.Plan.ScopeIdentifier, executionContext.Global.Plan.PlanType, executionContext.Global.Plan.PlanId, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Global.Plan.ScopeIdentifier, executionContext.Global.Plan.PlanType, executionContext.Global.Plan.PlanId, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (attempt < 3)
|
catch (Exception ex) when (!executionContext.CancellationToken.IsCancellationRequested) // Do not retry if the run is canceled.
|
||||||
{
|
{
|
||||||
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
|
if (attempt < 3)
|
||||||
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($"Failed to resolve action download info. Error: {ex.Message}");
|
||||||
executionContext.Output($"Retrying in {backoff.TotalSeconds} seconds");
|
executionContext.Debug(ex.ToString());
|
||||||
await Task.Delay(backoff);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Some possible cases are:
|
||||||
|
// * Repo is rate limited
|
||||||
|
// * Repo or tag doesn't exist, or isn't public
|
||||||
|
if (ex is WebApi.UnresolvableActionDownloadInfoException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This exception will be traced as an infrastructure failure
|
||||||
|
throw new WebApi.FailedToResolveActionDownloadInfoException("Failed to resolve action download info.", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,11 @@ namespace GitHub.Runner.Worker
|
|||||||
ExecutionContext.SetGitHubContext("action_repository", repoPathReferenceAction.Name);
|
ExecutionContext.SetGitHubContext("action_repository", repoPathReferenceAction.Name);
|
||||||
ExecutionContext.SetGitHubContext("action_ref", repoPathReferenceAction.Ref);
|
ExecutionContext.SetGitHubContext("action_ref", repoPathReferenceAction.Ref);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecutionContext.SetGitHubContext("action_repository", null);
|
||||||
|
ExecutionContext.SetGitHubContext("action_ref", null);
|
||||||
|
}
|
||||||
|
|
||||||
// Setup container stephost for running inside the container.
|
// Setup container stephost for running inside the container.
|
||||||
if (ExecutionContext.Global.Container != null)
|
if (ExecutionContext.Global.Container != null)
|
||||||
@@ -250,11 +255,11 @@ namespace GitHub.Runner.Worker
|
|||||||
handler.PrintActionDetails(Stage);
|
handler.PrintActionDetails(Stage);
|
||||||
|
|
||||||
// Run the task.
|
// Run the task.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await handler.RunAsync(Stage);
|
await handler.RunAsync(Stage);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
fileCommandManager.ProcessFiles(ExecutionContext, ExecutionContext.Global.Container);
|
fileCommandManager.ProcessFiles(ExecutionContext, ExecutionContext.Global.Container);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ namespace GitHub.Runner.Worker.Container
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ContainerInfo(IHostContext hostContext)
|
||||||
|
{
|
||||||
|
UpdateWebProxyEnv(hostContext.WebProxy);
|
||||||
|
}
|
||||||
|
|
||||||
public ContainerInfo(IHostContext hostContext, Pipelines.JobContainer container, bool isJobContainer = true, string networkAlias = null)
|
public ContainerInfo(IHostContext hostContext, Pipelines.JobContainer container, bool isJobContainer = true, string networkAlias = null)
|
||||||
{
|
{
|
||||||
this.ContainerName = container.Alias;
|
this.ContainerName = container.Alias;
|
||||||
|
|||||||
@@ -918,6 +918,12 @@ namespace GitHub.Runner.Worker
|
|||||||
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message });
|
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
|
public static void InfrastructureError(this IExecutionContext context, string message)
|
||||||
|
{
|
||||||
|
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true});
|
||||||
|
}
|
||||||
|
|
||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
public static void Warning(this IExecutionContext context, string message)
|
public static void Warning(this IExecutionContext context, string message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run container
|
// run container
|
||||||
var container = new ContainerInfo()
|
var container = new ContainerInfo(HostContext)
|
||||||
{
|
{
|
||||||
ContainerImage = Data.Image,
|
ContainerImage = Data.Image,
|
||||||
ContainerName = ExecutionContext.Id.ToString("N"),
|
ContainerName = ExecutionContext.Id.ToString("N"),
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
@@ -41,6 +42,8 @@ namespace GitHub.Runner.Worker
|
|||||||
private readonly HashSet<string> _existingProcesses = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _existingProcesses = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
private bool _processCleanup;
|
private bool _processCleanup;
|
||||||
private string _processLookupId = $"github_{Guid.NewGuid()}";
|
private string _processLookupId = $"github_{Guid.NewGuid()}";
|
||||||
|
private CancellationTokenSource _diskSpaceCheckToken = new CancellationTokenSource();
|
||||||
|
private Task _diskSpaceCheckTask = null;
|
||||||
|
|
||||||
// Download all required actions.
|
// Download all required actions.
|
||||||
// Make sure all condition inputs are valid.
|
// Make sure all condition inputs are valid.
|
||||||
@@ -325,6 +328,12 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jobContext.Global.EnvironmentVariables.TryGetValue(Constants.Runner.Features.DiskSpaceWarning, out var enableWarning);
|
||||||
|
if (StringUtil.ConvertToBoolean(enableWarning, defaultValue: true))
|
||||||
|
{
|
||||||
|
_diskSpaceCheckTask = CheckDiskSpaceAsync(context, _diskSpaceCheckToken.Token);
|
||||||
|
}
|
||||||
|
|
||||||
return steps;
|
return steps;
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException ex) when (jobContext.CancellationToken.IsCancellationRequested)
|
catch (OperationCanceledException ex) when (jobContext.CancellationToken.IsCancellationRequested)
|
||||||
@@ -335,6 +344,14 @@ namespace GitHub.Runner.Worker
|
|||||||
context.Result = TaskResult.Canceled;
|
context.Result = TaskResult.Canceled;
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
catch (FailedToResolveActionDownloadInfoException ex)
|
||||||
|
{
|
||||||
|
// Log the error and fail the JobExtension Initialization.
|
||||||
|
Trace.Error($"Caught exception from JobExtenion Initialization: {ex}");
|
||||||
|
context.InfrastructureError(ex.Message);
|
||||||
|
context.Result = TaskResult.Failed;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Log the error and fail the JobExtension Initialization.
|
// Log the error and fail the JobExtension Initialization.
|
||||||
@@ -521,6 +538,11 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_diskSpaceCheckTask != null)
|
||||||
|
{
|
||||||
|
_diskSpaceCheckToken.Cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -536,6 +558,39 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CheckDiskSpaceAsync(IExecutionContext context, CancellationToken token)
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Add warning when disk is lower than system.runner.lowdiskspacethreshold from service (default to 100 MB on service side)
|
||||||
|
var lowDiskSpaceThreshold = context.Global.Variables.GetInt(WellKnownDistributedTaskVariables.RunnerLowDiskspaceThreshold);
|
||||||
|
if (lowDiskSpaceThreshold == null)
|
||||||
|
{
|
||||||
|
Trace.Info($"Low diskspace warning is not enabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var workDirRoot = Directory.GetDirectoryRoot(HostContext.GetDirectory(WellKnownDirectory.Work));
|
||||||
|
var driveInfo = new DriveInfo(workDirRoot);
|
||||||
|
var freeSpaceInMB = driveInfo.AvailableFreeSpace / 1024 / 1024;
|
||||||
|
if (freeSpaceInMB < lowDiskSpaceThreshold)
|
||||||
|
{
|
||||||
|
var issue = new Issue() { Type = IssueType.Warning, Message = $"You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: {freeSpaceInMB} MB" };
|
||||||
|
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.LowDiskSpace;
|
||||||
|
context.AddIssue(issue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(10 * 1000, token);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Dictionary<int, Process> SnapshotProcesses()
|
private Dictionary<int, Process> SnapshotProcesses()
|
||||||
{
|
{
|
||||||
Dictionary<int, Process> snapshot = new Dictionary<int, Process>();
|
Dictionary<int, Process> snapshot = new Dictionary<int, Process>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -12,29 +12,9 @@ namespace GitHub.Services.Common
|
|||||||
m_request = request;
|
m_request = request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHttpHeaders Headers
|
public IHttpHeaders Headers => this;
|
||||||
{
|
public Uri RequestUri => m_request.RequestUri;
|
||||||
get
|
public IDictionary<string,object> Properties => m_request.Options;
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri RequestUri
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_request.RequestUri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDictionary<string, object> Properties
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_request.Properties;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<String> IHttpHeaders.GetValues(String name)
|
IEnumerable<String> IHttpHeaders.GetValues(String name)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace GitHub.Services.Common.Diagnostics
|
|||||||
public static VssTraceActivity GetActivity(this HttpRequestMessage message)
|
public static VssTraceActivity GetActivity(this HttpRequestMessage message)
|
||||||
{
|
{
|
||||||
Object traceActivity;
|
Object traceActivity;
|
||||||
if (!message.Properties.TryGetValue(VssTraceActivity.PropertyName, out traceActivity))
|
if (!message.Options.TryGetValue(VssTraceActivity.PropertyName, out traceActivity))
|
||||||
{
|
{
|
||||||
return VssTraceActivity.Empty;
|
return VssTraceActivity.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ namespace GitHub.Services.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add ourselves to the message so the underlying token issuers may use it if necessary
|
// Add ourselves to the message so the underlying token issuers may use it if necessary
|
||||||
request.Properties[VssHttpMessageHandler.PropertyName] = this;
|
request.Options.Set(new HttpRequestOptionsKey<VssHttpMessageHandler>(VssHttpMessageHandler.PropertyName), this);
|
||||||
|
|
||||||
Boolean succeeded = false;
|
Boolean succeeded = false;
|
||||||
Boolean lastResponseDemandedProxyAuth = false;
|
Boolean lastResponseDemandedProxyAuth = false;
|
||||||
@@ -409,7 +409,7 @@ namespace GitHub.Services.Common
|
|||||||
// Read the completion option provided by the caller. If we don't find the property then we
|
// Read the completion option provided by the caller. If we don't find the property then we
|
||||||
// assume it is OK to buffer by default.
|
// assume it is OK to buffer by default.
|
||||||
HttpCompletionOption completionOption;
|
HttpCompletionOption completionOption;
|
||||||
if (!request.Properties.TryGetValue(VssHttpRequestSettings.HttpCompletionOptionPropertyName, out completionOption))
|
if (!request.Options.TryGetValue(VssHttpRequestSettings.HttpCompletionOptionPropertyName, out completionOption))
|
||||||
{
|
{
|
||||||
completionOption = HttpCompletionOption.ResponseContentRead;
|
completionOption = HttpCompletionOption.ResponseContentRead;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,9 +77,9 @@ namespace GitHub.Services.Common
|
|||||||
public static void SetTraceInfo(HttpRequestMessage message, VssHttpMessageHandlerTraceInfo traceInfo)
|
public static void SetTraceInfo(HttpRequestMessage message, VssHttpMessageHandlerTraceInfo traceInfo)
|
||||||
{
|
{
|
||||||
object existingTraceInfo;
|
object existingTraceInfo;
|
||||||
if (!message.Properties.TryGetValue(TfsTraceInfoKey, out existingTraceInfo))
|
if (!message.Options.TryGetValue(TfsTraceInfoKey, out existingTraceInfo))
|
||||||
{
|
{
|
||||||
message.Properties.Add(TfsTraceInfoKey, traceInfo);
|
message.Options.Set(new HttpRequestOptionsKey<VssHttpMessageHandlerTraceInfo>(TfsTraceInfoKey), traceInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ namespace GitHub.Services.Common
|
|||||||
{
|
{
|
||||||
VssHttpMessageHandlerTraceInfo traceInfo = null;
|
VssHttpMessageHandlerTraceInfo traceInfo = null;
|
||||||
|
|
||||||
if (message.Properties.TryGetValue(TfsTraceInfoKey, out object traceInfoObject))
|
if (message.Options.TryGetValue(TfsTraceInfoKey, out object traceInfoObject))
|
||||||
{
|
{
|
||||||
traceInfo = traceInfoObject as VssHttpMessageHandlerTraceInfo;
|
traceInfo = traceInfoObject as VssHttpMessageHandlerTraceInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,12 +291,12 @@ namespace GitHub.Services.Common
|
|||||||
protected internal virtual Boolean ApplyTo(HttpRequestMessage request)
|
protected internal virtual Boolean ApplyTo(HttpRequestMessage request)
|
||||||
{
|
{
|
||||||
// Make sure we only apply the settings to the request once
|
// Make sure we only apply the settings to the request once
|
||||||
if (request.Properties.ContainsKey(PropertyName))
|
if (request.Options.TryGetValue(new HttpRequestOptionsKey<VssHttpRequestSettings>(PropertyName), out _))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Properties.Add(PropertyName, this);
|
request.Options.Set(new HttpRequestOptionsKey<VssHttpRequestSettings>(PropertyName), this);
|
||||||
|
|
||||||
if (this.AcceptLanguages != null && this.AcceptLanguages.Count > 0)
|
if (this.AcceptLanguages != null && this.AcceptLanguages.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace GitHub.Services.Common
|
|||||||
// Allow overriding default retry options per request
|
// Allow overriding default retry options per request
|
||||||
VssHttpRetryOptions retryOptions = m_retryOptions;
|
VssHttpRetryOptions retryOptions = m_retryOptions;
|
||||||
object retryOptionsObject;
|
object retryOptionsObject;
|
||||||
if (request.Properties.TryGetValue(HttpRetryOptionsKey, out retryOptionsObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
if (request.Options.TryGetValue(HttpRetryOptionsKey, out retryOptionsObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
||||||
{
|
{
|
||||||
// Fallback to default options if object of unexpected type was passed
|
// Fallback to default options if object of unexpected type was passed
|
||||||
retryOptions = retryOptionsObject as VssHttpRetryOptions ?? m_retryOptions;
|
retryOptions = retryOptionsObject as VssHttpRetryOptions ?? m_retryOptions;
|
||||||
@@ -66,7 +66,7 @@ namespace GitHub.Services.Common
|
|||||||
|
|
||||||
IVssHttpRetryInfo retryInfo = null;
|
IVssHttpRetryInfo retryInfo = null;
|
||||||
object retryInfoObject;
|
object retryInfoObject;
|
||||||
if (request.Properties.TryGetValue(HttpRetryInfoKey, out retryInfoObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
if (request.Options.TryGetValue(HttpRetryInfoKey, out retryInfoObject)) // NETSTANDARD compliant, TryGetValue<T> is not
|
||||||
{
|
{
|
||||||
retryInfo = retryInfoObject as IVssHttpRetryInfo;
|
retryInfo = retryInfoObject as IVssHttpRetryInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2458,4 +2458,42 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class UnresolvableActionDownloadInfoException : DistributedTaskException
|
||||||
|
{
|
||||||
|
public UnresolvableActionDownloadInfoException(String message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnresolvableActionDownloadInfoException(String message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected UnresolvableActionDownloadInfoException(SerializationInfo info, StreamingContext context)
|
||||||
|
: base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class FailedToResolveActionDownloadInfoException : DistributedTaskException
|
||||||
|
{
|
||||||
|
public FailedToResolveActionDownloadInfoException(String message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FailedToResolveActionDownloadInfoException(String message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private FailedToResolveActionDownloadInfoException(SerializationInfo info, StreamingContext context)
|
||||||
|
: base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
this.Type = issueToBeCloned.Type;
|
this.Type = issueToBeCloned.Type;
|
||||||
this.Category = issueToBeCloned.Category;
|
this.Category = issueToBeCloned.Category;
|
||||||
this.Message = issueToBeCloned.Message;
|
this.Message = issueToBeCloned.Message;
|
||||||
|
this.IsInfrastructureIssue = issueToBeCloned.IsInfrastructureIssue;
|
||||||
|
|
||||||
if (issueToBeCloned.m_data != null)
|
if (issueToBeCloned.m_data != null)
|
||||||
{
|
{
|
||||||
@@ -48,6 +49,13 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DataMember(Order = 4)]
|
||||||
|
public bool? IsInfrastructureIssue
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
public IDictionary<String, String> Data
|
public IDictionary<String, String> Data
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -65,5 +65,15 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether to use FIPS compliant encryption scheme for job message key
|
||||||
|
/// </summary>
|
||||||
|
[DataMember]
|
||||||
|
public bool UseFipsEncryption
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,8 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
|
|
||||||
[EnumMember]
|
[EnumMember]
|
||||||
Completed,
|
Completed,
|
||||||
|
|
||||||
|
[EnumMember]
|
||||||
|
Delayed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
public static class WellKnownDistributedTaskVariables
|
public static class WellKnownDistributedTaskVariables
|
||||||
{
|
{
|
||||||
public static readonly String JobId = "system.jobId";
|
public static readonly String JobId = "system.jobId";
|
||||||
|
public static readonly String RunnerLowDiskspaceThreshold = "system.runner.lowdiskspacethreshold";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
|||||||
@@ -130,55 +130,6 @@ namespace GitHub.Services.WebApi.Jwt
|
|||||||
return credentials.SignatureAlgorithm;
|
return credentials.SignatureAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClaimsPrincipal ValidateToken(this JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
|
||||||
ArgumentUtility.CheckForNull(parameters, nameof(parameters));
|
|
||||||
|
|
||||||
ClaimsIdentity actorIdentity = ValidateActor(token, parameters);
|
|
||||||
ValidateLifetime(token, parameters);
|
|
||||||
ValidateAudience(token, parameters);
|
|
||||||
ValidateSignature(token, parameters);
|
|
||||||
ValidateIssuer(token, parameters);
|
|
||||||
|
|
||||||
ClaimsIdentity identity = new ClaimsIdentity("Federation", parameters.IdentityNameClaimType, ClaimTypes.Role);
|
|
||||||
|
|
||||||
if (actorIdentity != null)
|
|
||||||
{
|
|
||||||
identity.Actor = actorIdentity;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<Claim> claims = token.ExtractClaims();
|
|
||||||
|
|
||||||
foreach (Claim claim in claims)
|
|
||||||
{
|
|
||||||
identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, token.Issuer));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ClaimsPrincipal(identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ClaimsIdentity ValidateActor(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
|
||||||
ArgumentUtility.CheckForNull(parameters, nameof(parameters));
|
|
||||||
|
|
||||||
if (!parameters.ValidateActor)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//this recursive call with check the parameters
|
|
||||||
ClaimsPrincipal principal = token.Actor.ValidateToken(parameters.ActorValidationParameters);
|
|
||||||
|
|
||||||
if (!(principal?.Identity is ClaimsIdentity))
|
|
||||||
{
|
|
||||||
throw new ActorValidationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (ClaimsIdentity)principal.Identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ValidateLifetime(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
private static void ValidateLifetime(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
||||||
{
|
{
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
ArgumentUtility.CheckForNull(token, nameof(token));
|
||||||
@@ -241,59 +192,6 @@ namespace GitHub.Services.WebApi.Jwt
|
|||||||
throw new InvalidAudienceException(); //validation exception;
|
throw new InvalidAudienceException(); //validation exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateSignature(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
|
||||||
ArgumentUtility.CheckForNull(parameters, nameof(parameters));
|
|
||||||
|
|
||||||
if (!parameters.ValidateSignature)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string encodedData = token.EncodedToken;
|
|
||||||
|
|
||||||
string[] parts = encodedData.Split('.');
|
|
||||||
|
|
||||||
if (parts.Length != 3)
|
|
||||||
{
|
|
||||||
throw new InvalidTokenException(JwtResources.EncodedTokenDataMalformed()); //validation exception
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(parts[2]))
|
|
||||||
{
|
|
||||||
throw new InvalidTokenException(JwtResources.SignatureNotFound()); //validation exception
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.Algorithm == JWTAlgorithm.None)
|
|
||||||
{
|
|
||||||
throw new InvalidTokenException(JwtResources.InvalidSignatureAlgorithm()); //validation exception
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgumentUtility.CheckForNull(parameters.SigningCredentials, nameof(parameters.SigningCredentials));
|
|
||||||
|
|
||||||
//ArgumentUtility.CheckEnumerableForNullOrEmpty(parameters.SigningToken.SecurityKeys, nameof(parameters.SigningToken.SecurityKeys));
|
|
||||||
|
|
||||||
byte[] sourceInput = Encoding.UTF8.GetBytes(string.Format("{0}.{1}", parts[0], parts[1]));
|
|
||||||
|
|
||||||
byte[] sourceSignature = parts[2].FromBase64StringNoPadding();
|
|
||||||
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (parameters.SigningCredentials.VerifySignature(sourceInput, sourceSignature))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
//swallow exceptions here, we'll throw if nothing works...
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SignatureValidationException(); //valiation exception
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ValidateIssuer(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
private static void ValidateIssuer(JsonWebToken token, JsonWebTokenValidationParameters parameters)
|
||||||
{
|
{
|
||||||
ArgumentUtility.CheckForNull(token, nameof(token));
|
ArgumentUtility.CheckForNull(token, nameof(token));
|
||||||
|
|||||||
@@ -833,7 +833,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
if (userState != null)
|
if (userState != null)
|
||||||
{
|
{
|
||||||
message.Properties[UserStatePropertyName] = userState;
|
message.Options.Set(new HttpRequestOptionsKey<object>(UserStatePropertyName), userState);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!message.Headers.Contains(Common.Internal.HttpHeaders.VssE2EID))
|
if (!message.Headers.Contains(Common.Internal.HttpHeaders.VssE2EID))
|
||||||
@@ -842,11 +842,11 @@ namespace GitHub.Services.WebApi
|
|||||||
}
|
}
|
||||||
VssHttpEventSource.Log.HttpRequestStart(traceActivity, message);
|
VssHttpEventSource.Log.HttpRequestStart(traceActivity, message);
|
||||||
message.Trace();
|
message.Trace();
|
||||||
message.Properties[VssTraceActivity.PropertyName] = traceActivity;
|
message.Options.Set(new HttpRequestOptionsKey<VssTraceActivity>(VssTraceActivity.PropertyName), traceActivity);
|
||||||
|
|
||||||
// Send the completion option to the inner handler stack so we know when it's safe to buffer
|
// Send the completion option to the inner handler stack so we know when it's safe to buffer
|
||||||
// and when we should avoid buffering.
|
// and when we should avoid buffering.
|
||||||
message.Properties[VssHttpRequestSettings.HttpCompletionOptionPropertyName] = completionOption;
|
message.Options.Set(new HttpRequestOptionsKey<HttpCompletionOption>(VssHttpRequestSettings.HttpCompletionOptionPropertyName), completionOption);
|
||||||
|
|
||||||
//ConfigureAwait(false) enables the continuation to be run outside
|
//ConfigureAwait(false) enables the continuation to be run outside
|
||||||
//any captured SyncronizationContext (such as ASP.NET's) which keeps things
|
//any captured SyncronizationContext (such as ASP.NET's) which keeps things
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
Object tracerObj = null;
|
Object tracerObj = null;
|
||||||
VssRequestTimerTrace tracer = null;
|
VssRequestTimerTrace tracer = null;
|
||||||
if (request.Properties.TryGetValue(tracerKey, out tracerObj))
|
if (request.Options.TryGetValue(tracerKey, out tracerObj))
|
||||||
{
|
{
|
||||||
tracer = tracerObj as VssRequestTimerTrace;
|
tracer = tracerObj as VssRequestTimerTrace;
|
||||||
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
|
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
|
||||||
@@ -26,7 +26,7 @@ namespace GitHub.Services.WebApi
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
tracer = new VssRequestTimerTrace();
|
tracer = new VssRequestTimerTrace();
|
||||||
request.Properties[tracerKey] = tracer;
|
request.Options.Set(new HttpRequestOptionsKey<VssRequestTimerTrace>(tracerKey), tracer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tracer != null)
|
if (tracer != null)
|
||||||
@@ -39,7 +39,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
Object tracerObj = null;
|
Object tracerObj = null;
|
||||||
VssRequestTimerTrace tracer = null;
|
VssRequestTimerTrace tracer = null;
|
||||||
if (response.RequestMessage.Properties.TryGetValue(tracerKey, out tracerObj))
|
if (response.RequestMessage.Options.TryGetValue(tracerKey, out tracerObj))
|
||||||
{
|
{
|
||||||
tracer = tracerObj as VssRequestTimerTrace;
|
tracer = tracerObj as VssRequestTimerTrace;
|
||||||
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
|
Debug.Assert(tracer != null, "Tracer object is the wrong type!");
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using GitHub.Services.Common;
|
using GitHub.Services.Common;
|
||||||
using GitHub.Services.WebApi.Jwt;
|
using GitHub.Services.WebApi.Jwt;
|
||||||
|
|
||||||
@@ -75,7 +74,6 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetSignature(input);
|
return GetSignature(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,48 +84,13 @@ namespace GitHub.Services.WebApi
|
|||||||
/// <returns>A blob of data representing the signature of the input data</returns>
|
/// <returns>A blob of data representing the signature of the input data</returns>
|
||||||
protected abstract Byte[] GetSignature(Byte[] input);
|
protected abstract Byte[] GetSignature(Byte[] input);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verifies the signature of the input data, returning true if the signature is valid.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">The data which should be signed</param>
|
|
||||||
/// <param name="signature">The signature which should be verified</param>
|
|
||||||
/// <returns>True if the provided signature matches the current signing token; otherwise, false</returns>
|
|
||||||
public abstract Boolean VerifySignature(Byte[] input, Byte[] signature);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <c>VssSigningCredentials</c> instance using the specified <paramref name="certificate"/> instance
|
|
||||||
/// as the signing key.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="certificate">The certificate which contains the key used for signing and verification</param>
|
|
||||||
/// <returns>A new <c>VssSigningCredentials</c> instance which uses the specified certificate for signing</returns>
|
|
||||||
public static VssSigningCredentials Create(X509Certificate2 certificate)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(certificate, nameof(certificate));
|
|
||||||
|
|
||||||
if (certificate.HasPrivateKey)
|
|
||||||
{
|
|
||||||
var rsa = certificate.GetRSAPrivateKey();
|
|
||||||
if (rsa == null)
|
|
||||||
{
|
|
||||||
throw new SignatureAlgorithmUnsupportedException(certificate.SignatureAlgorithm.FriendlyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rsa.KeySize < c_minKeySize)
|
|
||||||
{
|
|
||||||
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new X509Certificate2SigningToken(certificate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <c>VssSigningCredentials</c> instance using the specified <paramref name="factory"/>
|
/// Creates a new <c>VssSigningCredentials</c> instance using the specified <paramref name="factory"/>
|
||||||
/// callback function to retrieve the signing key.
|
/// callback function to retrieve the signing key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="factory">The factory which creates <c>RSA</c> keys used for signing and verification</param>
|
/// <param name="factory">The factory which creates <c>RSA</c> keys used for signing and verification</param>
|
||||||
/// <returns>A new <c>VssSigningCredentials</c> instance which uses the specified provider for signing</returns>
|
/// <returns>A new <c>VssSigningCredentials</c> instance which uses the specified provider for signing</returns>
|
||||||
public static VssSigningCredentials Create(Func<RSA> factory)
|
public static VssSigningCredentials Create(Func<RSA> factory, bool requireFipsCryptography)
|
||||||
{
|
{
|
||||||
ArgumentUtility.CheckForNull(factory, nameof(factory));
|
ArgumentUtility.CheckForNull(factory, nameof(factory));
|
||||||
|
|
||||||
@@ -143,80 +106,19 @@ namespace GitHub.Services.WebApi
|
|||||||
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
|
throw new InvalidCredentialsException(JwtResources.SigningTokenKeyTooSmall());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RSASigningToken(factory, rsa.KeySize);
|
if (requireFipsCryptography)
|
||||||
|
{
|
||||||
|
return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pss);
|
||||||
|
}
|
||||||
|
return new RSASigningToken(factory, rsa.KeySize, RSASignaturePadding.Pkcs1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <c>VssSigningCredentials</c> instance using the specified <paramref name="key"/> as the signing
|
|
||||||
/// key. The returned signing token performs symmetric key signing and verification.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rsa">The key used for signing and verification</param>
|
|
||||||
/// <returns>A new <c>VssSigningCredentials</c> instance which uses the specified key for signing</returns>
|
|
||||||
public static VssSigningCredentials Create(Byte[] key)
|
|
||||||
{
|
|
||||||
ArgumentUtility.CheckForNull(key, nameof(key));
|
|
||||||
|
|
||||||
// Probably should have validation here, but there was none previously
|
|
||||||
return new SymmetricKeySigningToken(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private const Int32 c_minKeySize = 2048;
|
private const Int32 c_minKeySize = 2048;
|
||||||
private readonly DateTime m_effectiveDate;
|
private readonly DateTime m_effectiveDate;
|
||||||
|
|
||||||
#region Concrete Implementations
|
#region Concrete Implementations
|
||||||
|
|
||||||
private class SymmetricKeySigningToken : VssSigningCredentials
|
|
||||||
{
|
|
||||||
public SymmetricKeySigningToken(Byte[] key)
|
|
||||||
{
|
|
||||||
m_key = new Byte[key.Length];
|
|
||||||
Buffer.BlockCopy(key, 0, m_key, 0, m_key.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Boolean CanSignData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Int32 KeySize
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_key.Length * 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override JWTAlgorithm SignatureAlgorithm
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return JWTAlgorithm.HS256;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Byte[] GetSignature(Byte[] input)
|
|
||||||
{
|
|
||||||
using (var hash = new HMACSHA256(m_key))
|
|
||||||
{
|
|
||||||
return hash.ComputeHash(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Boolean VerifySignature(
|
|
||||||
Byte[] input,
|
|
||||||
Byte[] signature)
|
|
||||||
{
|
|
||||||
var computedSignature = SignData(input);
|
|
||||||
return SecureCompare.TimeInvariantEquals(computedSignature, signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Byte[] m_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class AsymmetricKeySigningToken : VssSigningCredentials
|
private abstract class AsymmetricKeySigningToken : VssSigningCredentials
|
||||||
{
|
{
|
||||||
protected abstract Boolean HasPrivateKey();
|
protected abstract Boolean HasPrivateKey();
|
||||||
@@ -244,70 +146,14 @@ namespace GitHub.Services.WebApi
|
|||||||
private Boolean? m_hasPrivateKey;
|
private Boolean? m_hasPrivateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class X509Certificate2SigningToken : AsymmetricKeySigningToken, IJsonWebTokenHeaderProvider
|
|
||||||
{
|
|
||||||
public X509Certificate2SigningToken(X509Certificate2 certificate)
|
|
||||||
{
|
|
||||||
m_certificate = certificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Int32 KeySize
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_certificate.GetRSAPublicKey().KeySize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override DateTime ValidFrom
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_certificate.NotBefore;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override DateTime ValidTo
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return m_certificate.NotAfter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Boolean VerifySignature(
|
|
||||||
Byte[] input,
|
|
||||||
Byte[] signature)
|
|
||||||
{
|
|
||||||
var rsa = m_certificate.GetRSAPublicKey();
|
|
||||||
return rsa.VerifyData(input, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Byte[] GetSignature(Byte[] input)
|
|
||||||
{
|
|
||||||
var rsa = m_certificate.GetRSAPrivateKey();
|
|
||||||
return rsa.SignData(input, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Boolean HasPrivateKey()
|
|
||||||
{
|
|
||||||
return m_certificate.HasPrivateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IJsonWebTokenHeaderProvider.SetHeaders(IDictionary<String, Object> headers)
|
|
||||||
{
|
|
||||||
headers[JsonWebTokenHeaderParameters.X509CertificateThumbprint] = m_certificate.GetCertHash().ToBase64StringNoPadding();
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly X509Certificate2 m_certificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RSASigningToken : AsymmetricKeySigningToken
|
private class RSASigningToken : AsymmetricKeySigningToken
|
||||||
{
|
{
|
||||||
public RSASigningToken(
|
public RSASigningToken(
|
||||||
Func<RSA> factory,
|
Func<RSA> factory,
|
||||||
Int32 keySize)
|
Int32 keySize,
|
||||||
|
RSASignaturePadding signaturePadding)
|
||||||
{
|
{
|
||||||
|
m_signaturePadding = signaturePadding;
|
||||||
m_keySize = keySize;
|
m_keySize = keySize;
|
||||||
m_factory = factory;
|
m_factory = factory;
|
||||||
}
|
}
|
||||||
@@ -324,7 +170,7 @@ namespace GitHub.Services.WebApi
|
|||||||
{
|
{
|
||||||
using (var rsa = m_factory())
|
using (var rsa = m_factory())
|
||||||
{
|
{
|
||||||
return rsa.SignData(input, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
return rsa.SignData(input, HashAlgorithmName.SHA256, m_signaturePadding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +181,7 @@ namespace GitHub.Services.WebApi
|
|||||||
// As unfortunate as this is, there is no way to tell from an RSA implementation, based on querying
|
// As unfortunate as this is, there is no way to tell from an RSA implementation, based on querying
|
||||||
// properties alone, if it supports signature operations or has a private key. This is a one-time
|
// properties alone, if it supports signature operations or has a private key. This is a one-time
|
||||||
// hit for the signing credentials implementation, so it shouldn't be a huge deal.
|
// hit for the signing credentials implementation, so it shouldn't be a huge deal.
|
||||||
GetSignature(new Byte[1] { 1 });
|
GetSignature(new Byte[1] { 1 });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (CryptographicException)
|
catch (CryptographicException)
|
||||||
@@ -344,18 +190,9 @@ namespace GitHub.Services.WebApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Boolean VerifySignature(
|
|
||||||
Byte[] input,
|
|
||||||
Byte[] signature)
|
|
||||||
{
|
|
||||||
using (var rsa = m_factory())
|
|
||||||
{
|
|
||||||
return rsa.VerifyData(input, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Int32 m_keySize;
|
private readonly Int32 m_keySize;
|
||||||
private readonly Func<RSA> m_factory;
|
private readonly Func<RSA> m_factory;
|
||||||
|
private readonly RSASignaturePadding m_signaturePadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using GitHub.Runner.Listener;
|
using GitHub.Runner.Listener;
|
||||||
|
using GitHub.Runner.Listener.Check;
|
||||||
using GitHub.Runner.Listener.Configuration;
|
using GitHub.Runner.Listener.Configuration;
|
||||||
using GitHub.Runner.Worker;
|
using GitHub.Runner.Worker;
|
||||||
using GitHub.Runner.Worker.Handlers;
|
using GitHub.Runner.Worker.Handlers;
|
||||||
@@ -21,7 +22,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
// Otherwise, the interface needs to whitelisted.
|
// Otherwise, the interface needs to whitelisted.
|
||||||
var whitelist = new[]
|
var whitelist = new[]
|
||||||
{
|
{
|
||||||
typeof(ICredentialProvider)
|
typeof(ICredentialProvider),
|
||||||
|
typeof(ICheckExtension),
|
||||||
};
|
};
|
||||||
Validate(
|
Validate(
|
||||||
assembly: typeof(IMessageListener).GetTypeInfo().Assembly,
|
assembly: typeof(IMessageListener).GetTypeInfo().Assembly,
|
||||||
@@ -85,7 +87,8 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interfaceTypeInfo.FullName.Contains("IConverter")){
|
if (interfaceTypeInfo.FullName.Contains("IConverter"))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using var stream = File.OpenRead(archiveFile);
|
using var stream = File.OpenRead(archiveFile);
|
||||||
var mockClientHandler = new Mock<HttpClientHandler>();
|
var mockClientHandler = new Mock<HttpClientHandler>();
|
||||||
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(expectedArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(expectedArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
||||||
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
|
.ReturnsAsync(() => new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
|
||||||
|
|
||||||
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
||||||
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
||||||
@@ -205,9 +205,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using var stream = File.OpenRead(archiveFile);
|
using var stream = File.OpenRead(archiveFile);
|
||||||
var mockClientHandler = new Mock<HttpClientHandler>();
|
var mockClientHandler = new Mock<HttpClientHandler>();
|
||||||
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(builtInArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(builtInArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
||||||
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound));
|
.ReturnsAsync(() => new HttpResponseMessage(HttpStatusCode.NotFound));
|
||||||
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(dotcomArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(m => m.RequestUri == new Uri(dotcomArchiveLink)), ItExpr.IsAny<CancellationToken>())
|
||||||
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
|
.ReturnsAsync(() => new HttpResponseMessage(HttpStatusCode.OK) { Content = new StreamContent(stream) });
|
||||||
|
|
||||||
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
||||||
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
||||||
@@ -265,7 +265,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
using var stream = File.OpenRead(archiveFile);
|
using var stream = File.OpenRead(archiveFile);
|
||||||
var mockClientHandler = new Mock<HttpClientHandler>();
|
var mockClientHandler = new Mock<HttpClientHandler>();
|
||||||
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
mockClientHandler.Protected().Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||||
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.NotFound));
|
.ReturnsAsync(() => new HttpResponseMessage(HttpStatusCode.NotFound));
|
||||||
|
|
||||||
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
var mockHandlerFactory = new Mock<IHttpClientHandlerFactory>();
|
||||||
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
mockHandlerFactory.Setup(p => p.CreateClientHandler(It.IsAny<RunnerWebProxy>())).Returns(mockClientHandler.Object);
|
||||||
|
|||||||
@@ -333,6 +333,66 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<string>()), Times.Once);
|
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<string>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public async void SetGitHubContextActionRepoRef()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var actionInputs = new MappingToken(null, null, null);
|
||||||
|
actionInputs.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "test1"));
|
||||||
|
actionInputs.Add(new StringToken(null, null, null, "input2"), new StringToken(null, null, null, "test2"));
|
||||||
|
var action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "actions/test",
|
||||||
|
Ref = "master"
|
||||||
|
},
|
||||||
|
Inputs = actionInputs
|
||||||
|
};
|
||||||
|
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
Dictionary<string, string> finialInputs = new Dictionary<string, string>();
|
||||||
|
_handlerFactory.Setup(x => x.Create(It.IsAny<IExecutionContext>(), It.IsAny<ActionStepDefinitionReference>(), It.IsAny<IStepHost>(), It.IsAny<ActionExecutionData>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Variables>(), It.IsAny<string>()))
|
||||||
|
.Callback((IExecutionContext executionContext, Pipelines.ActionStepDefinitionReference actionReference, IStepHost stepHost, ActionExecutionData data, Dictionary<string, string> inputs, Dictionary<string, string> environment, Variables runtimeVariables, string taskDirectory) =>
|
||||||
|
{
|
||||||
|
finialInputs = inputs;
|
||||||
|
})
|
||||||
|
.Returns(new Mock<IHandler>().Object);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
await _actionRunner.RunAsync();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
_ec.Verify(x => x.SetGitHubContext("action_repository", "actions/test"), Times.Once);
|
||||||
|
_ec.Verify(x => x.SetGitHubContext("action_ref", "master"), Times.Once);
|
||||||
|
|
||||||
|
action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.ScriptReference(),
|
||||||
|
Inputs = actionInputs
|
||||||
|
};
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
_hc.EnqueueInstance<IDefaultStepHost>(_defaultStepHost.Object);
|
||||||
|
_hc.EnqueueInstance(_fileCommandManager.Object);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
await _actionRunner.RunAsync();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
_ec.Verify(x => x.SetGitHubContext("action_repository", null), Times.Once);
|
||||||
|
_ec.Verify(x => x.SetGitHubContext("action_ref", null), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
private void Setup([CallerMemberName] string name = "")
|
private void Setup([CallerMemberName] string name = "")
|
||||||
{
|
{
|
||||||
_ecTokenSource?.Dispose();
|
_ecTokenSource?.Dispose();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
|
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
|||||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||||
DOTNETSDK_VERSION="3.1.302"
|
DOTNETSDK_VERSION="5.0.100"
|
||||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||||
RUNNER_VERSION=$(cat runnerversion)
|
RUNNER_VERSION=$(cat runnerversion)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "3.1.302"
|
"version": "5.0.100"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.274.0
|
2.276.0
|
||||||
|
|||||||
Reference in New Issue
Block a user