mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5effa808be | ||
|
|
88098a6705 | ||
|
|
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,11 @@
|
|||||||
## Features
|
## Features
|
||||||
- Support environment URL parsing (#762, #778)
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
- Fixes #759 doesn't change proxy environment variables (#760)
|
- Downgrade runner to .NET 3 to address an issue with broken pipes in Ubuntu (#928)
|
||||||
|
- Fixed an issue where FIPS Cryptography broke back-compat scenarios (#928)
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
- Add .editorconfig (#768)
|
- Updated dotnet install scripts (#928)
|
||||||
|
|
||||||
## 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.1
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
313
src/Misc/dotnet-install.ps1
vendored
313
src/Misc/dotnet-install.ps1
vendored
@@ -23,8 +23,6 @@
|
|||||||
Default: latest
|
Default: latest
|
||||||
Represents a build version on specific channel. Possible values:
|
Represents a build version on specific channel. Possible values:
|
||||||
- latest - most latest build on specific channel
|
- latest - most latest build on specific channel
|
||||||
- coherent - most latest coherent build on specific channel
|
|
||||||
coherent applies only to SDK downloads
|
|
||||||
- 3-part version in a format A.B.C - represents specific version of build
|
- 3-part version in a format A.B.C - represents specific version of build
|
||||||
examples: 2.0.0-preview2-006120, 1.1.0
|
examples: 2.0.0-preview2-006120, 1.1.0
|
||||||
.PARAMETER InstallDir
|
.PARAMETER InstallDir
|
||||||
@@ -122,24 +120,42 @@ $VersionRegEx="/\d+\.\d+[^/]+/"
|
|||||||
$OverrideNonVersionedFiles = !$SkipNonVersionedFiles
|
$OverrideNonVersionedFiles = !$SkipNonVersionedFiles
|
||||||
|
|
||||||
function Say($str) {
|
function Say($str) {
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
Write-Host "dotnet-install: $str"
|
Write-Host "dotnet-install: $str"
|
||||||
}
|
}
|
||||||
catch
|
catch {
|
||||||
{
|
|
||||||
# Some platforms cannot utilize Write-Host (Azure Functions, for instance). Fall back to Write-Output
|
# Some platforms cannot utilize Write-Host (Azure Functions, for instance). Fall back to Write-Output
|
||||||
Write-Output "dotnet-install: $str"
|
Write-Output "dotnet-install: $str"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Say-Warning($str) {
|
||||||
|
try {
|
||||||
|
Write-Warning "dotnet-install: $str"
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Some platforms cannot utilize Write-Warning (Azure Functions, for instance). Fall back to Write-Output
|
||||||
|
Write-Output "dotnet-install: Warning: $str"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Writes a line with error style settings.
|
||||||
|
# Use this function to show a human-readable comment along with an exception.
|
||||||
|
function Say-Error($str) {
|
||||||
|
try {
|
||||||
|
# Write-Error is quite oververbose for the purpose of the function, let's write one line with error style settings.
|
||||||
|
$Host.UI.WriteErrorLine("dotnet-install: $str")
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Output "dotnet-install: Error: $str"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Say-Verbose($str) {
|
function Say-Verbose($str) {
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
Write-Verbose "dotnet-install: $str"
|
Write-Verbose "dotnet-install: $str"
|
||||||
}
|
}
|
||||||
catch
|
catch {
|
||||||
{
|
|
||||||
# Some platforms cannot utilize Write-Verbose (Azure Functions, for instance). Fall back to Write-Output
|
# Some platforms cannot utilize Write-Verbose (Azure Functions, for instance). Fall back to Write-Output
|
||||||
Write-Output "dotnet-install: $str"
|
Write-Output "dotnet-install: $str"
|
||||||
}
|
}
|
||||||
@@ -156,7 +172,7 @@ function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [in
|
|||||||
|
|
||||||
while ($true) {
|
while ($true) {
|
||||||
try {
|
try {
|
||||||
return $ScriptBlock.Invoke()
|
return & $ScriptBlock
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
$Attempts++
|
$Attempts++
|
||||||
@@ -270,18 +286,41 @@ function GetHTTPResponse([Uri] $Uri)
|
|||||||
# Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out
|
# Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out
|
||||||
# 20 minutes allows it to work over much slower connections.
|
# 20 minutes allows it to work over much slower connections.
|
||||||
$HttpClient.Timeout = New-TimeSpan -Minutes 20
|
$HttpClient.Timeout = New-TimeSpan -Minutes 20
|
||||||
$Response = $HttpClient.GetAsync("${Uri}${FeedCredential}").Result
|
$Task = $HttpClient.GetAsync("${Uri}${FeedCredential}").ConfigureAwait("false");
|
||||||
if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode))) {
|
$Response = $Task.GetAwaiter().GetResult();
|
||||||
# The feed credential is potentially sensitive info. Do not log FeedCredential to console output.
|
|
||||||
$ErrorMsg = "Failed to download $Uri."
|
if (($null -eq $Response) -or (-not ($Response.IsSuccessStatusCode))) {
|
||||||
if ($Response -ne $null) {
|
# The feed credential is potentially sensitive info. Do not log FeedCredential to console output.
|
||||||
$ErrorMsg += " $Response"
|
$DownloadException = [System.Exception] "Unable to download $Uri."
|
||||||
|
|
||||||
|
if ($null -ne $Response) {
|
||||||
|
$DownloadException.Data["StatusCode"] = [int] $Response.StatusCode
|
||||||
|
$DownloadException.Data["ErrorMessage"] = "Unable to download $Uri. Returned HTTP status code: " + $DownloadException.Data["StatusCode"]
|
||||||
}
|
}
|
||||||
|
|
||||||
throw $ErrorMsg
|
throw $DownloadException
|
||||||
}
|
}
|
||||||
|
|
||||||
return $Response
|
return $Response
|
||||||
|
}
|
||||||
|
catch [System.Net.Http.HttpRequestException] {
|
||||||
|
$DownloadException = [System.Exception] "Unable to download $Uri."
|
||||||
|
|
||||||
|
# Pick up the exception message and inner exceptions' messages if they exist
|
||||||
|
$CurrentException = $PSItem.Exception
|
||||||
|
$ErrorMsg = $CurrentException.Message + "`r`n"
|
||||||
|
while ($CurrentException.InnerException) {
|
||||||
|
$CurrentException = $CurrentException.InnerException
|
||||||
|
$ErrorMsg += $CurrentException.Message + "`r`n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if there is an issue concerning TLS.
|
||||||
|
if ($ErrorMsg -like "*SSL/TLS*") {
|
||||||
|
$ErrorMsg += "Ensure that TLS 1.2 or higher is enabled to use this script.`r`n"
|
||||||
|
}
|
||||||
|
|
||||||
|
$DownloadException.Data["ErrorMessage"] = $ErrorMsg
|
||||||
|
throw $DownloadException
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if ($HttpClient -ne $null) {
|
if ($HttpClient -ne $null) {
|
||||||
@@ -291,7 +330,7 @@ function GetHTTPResponse([Uri] $Uri)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) {
|
function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel) {
|
||||||
Say-Invocation $MyInvocation
|
Say-Invocation $MyInvocation
|
||||||
|
|
||||||
$VersionFileUrl = $null
|
$VersionFileUrl = $null
|
||||||
@@ -306,12 +345,7 @@ function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Co
|
|||||||
$VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version"
|
$VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version"
|
||||||
}
|
}
|
||||||
elseif (-not $Runtime) {
|
elseif (-not $Runtime) {
|
||||||
if ($Coherent) {
|
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version"
|
||||||
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw "Invalid value for `$Runtime"
|
throw "Invalid value for `$Runtime"
|
||||||
@@ -320,7 +354,8 @@ function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Co
|
|||||||
$Response = GetHTTPResponse -Uri $VersionFileUrl
|
$Response = GetHTTPResponse -Uri $VersionFileUrl
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
throw "Could not resolve version information."
|
Say-Error "Could not resolve version information."
|
||||||
|
throw
|
||||||
}
|
}
|
||||||
$StringContent = $Response.Content.ReadAsStringAsync().Result
|
$StringContent = $Response.Content.ReadAsStringAsync().Result
|
||||||
|
|
||||||
@@ -346,7 +381,8 @@ function Parse-Jsonfile-For-Version([string]$JSonFile) {
|
|||||||
$JSonContent = Get-Content($JSonFile) -Raw | ConvertFrom-Json | Select-Object -expand "sdk" -ErrorAction SilentlyContinue
|
$JSonContent = Get-Content($JSonFile) -Raw | ConvertFrom-Json | Select-Object -expand "sdk" -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
throw "Json file unreadable: '$JSonFile'"
|
Say-Error "Json file unreadable: '$JSonFile'"
|
||||||
|
throw
|
||||||
}
|
}
|
||||||
if ($JSonContent) {
|
if ($JSonContent) {
|
||||||
try {
|
try {
|
||||||
@@ -359,7 +395,8 @@ function Parse-Jsonfile-For-Version([string]$JSonFile) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
throw "Unable to parse the SDK node in '$JSonFile'"
|
Say-Error "Unable to parse the SDK node in '$JSonFile'"
|
||||||
|
throw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -375,16 +412,12 @@ function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel,
|
|||||||
Say-Invocation $MyInvocation
|
Say-Invocation $MyInvocation
|
||||||
|
|
||||||
if (-not $JSonFile) {
|
if (-not $JSonFile) {
|
||||||
switch ($Version.ToLower()) {
|
if ($Version.ToLower() -eq "latest") {
|
||||||
{ $_ -eq "latest" } {
|
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel
|
||||||
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False
|
return $LatestVersionInfo.Version
|
||||||
return $LatestVersionInfo.Version
|
}
|
||||||
}
|
else {
|
||||||
{ $_ -eq "coherent" } {
|
return $Version
|
||||||
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $True
|
|
||||||
return $LatestVersionInfo.Version
|
|
||||||
}
|
|
||||||
default { return $Version }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -619,6 +652,23 @@ function DownloadFile($Source, [string]$OutPath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SafeRemoveFile($Path) {
|
||||||
|
try {
|
||||||
|
if (Test-Path $Path) {
|
||||||
|
Remove-Item $Path
|
||||||
|
Say-Verbose "The temporary file `"$Path`" was removed."
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Say-Verbose "The temporary file `"$Path`" does not exist, therefore is not removed."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Say-Warning "Failed to remove the temporary file: `"$Path`", remove it manually."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) {
|
function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) {
|
||||||
$BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath)
|
$BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath)
|
||||||
if (-Not $NoPath) {
|
if (-Not $NoPath) {
|
||||||
@@ -635,6 +685,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
|
||||||
@@ -668,7 +723,7 @@ if ($DryRun) {
|
|||||||
Say "NOTE: Due to finding a version manifest with this runtime, it would actually install with version '$EffectiveVersion'"
|
Say "NOTE: Due to finding a version manifest with this runtime, it would actually install with version '$EffectiveVersion'"
|
||||||
}
|
}
|
||||||
|
|
||||||
exit 0
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($Runtime -eq "dotnet") {
|
if ($Runtime -eq "dotnet") {
|
||||||
@@ -702,7 +757,7 @@ $isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -Relat
|
|||||||
if ($isAssetInstalled) {
|
if ($isAssetInstalled) {
|
||||||
Say "$assetName version $SpecificVersion is already installed."
|
Say "$assetName version $SpecificVersion is already installed."
|
||||||
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
|
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
|
||||||
exit 0
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
|
New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
|
||||||
@@ -710,30 +765,69 @@ New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
|
|||||||
$installDrive = $((Get-Item $InstallRoot).PSDrive.Name);
|
$installDrive = $((Get-Item $InstallRoot).PSDrive.Name);
|
||||||
$diskInfo = Get-PSDrive -Name $installDrive
|
$diskInfo = Get-PSDrive -Name $installDrive
|
||||||
if ($diskInfo.Free / 1MB -le 100) {
|
if ($diskInfo.Free / 1MB -le 100) {
|
||||||
Say "There is not enough disk space on drive ${installDrive}:"
|
throw "There is not enough disk space on drive ${installDrive}:"
|
||||||
exit 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
|
$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
|
||||||
Say-Verbose "Zip path: $ZipPath"
|
Say-Verbose "Zip path: $ZipPath"
|
||||||
|
|
||||||
$DownloadFailed = $false
|
$DownloadFailed = $false
|
||||||
Say "Downloading link: $DownloadLink"
|
|
||||||
|
$PrimaryDownloadStatusCode = 0
|
||||||
|
$LegacyDownloadStatusCode = 0
|
||||||
|
|
||||||
|
$PrimaryDownloadFailedMsg = ""
|
||||||
|
$LegacyDownloadFailedMsg = ""
|
||||||
|
|
||||||
|
Say "Downloading primary link $DownloadLink"
|
||||||
try {
|
try {
|
||||||
DownloadFile -Source $DownloadLink -OutPath $ZipPath
|
DownloadFile -Source $DownloadLink -OutPath $ZipPath
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Say "Cannot download: $DownloadLink"
|
if ($PSItem.Exception.Data.Contains("StatusCode")) {
|
||||||
|
$PrimaryDownloadStatusCode = $PSItem.Exception.Data["StatusCode"]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($PSItem.Exception.Data.Contains("ErrorMessage")) {
|
||||||
|
$PrimaryDownloadFailedMsg = $PSItem.Exception.Data["ErrorMessage"]
|
||||||
|
} else {
|
||||||
|
$PrimaryDownloadFailedMsg = $PSItem.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($PrimaryDownloadStatusCode -eq 404) {
|
||||||
|
Say "The resource at $DownloadLink is not available."
|
||||||
|
} else {
|
||||||
|
Say $PSItem.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
SafeRemoveFile -Path $ZipPath
|
||||||
|
|
||||||
if ($LegacyDownloadLink) {
|
if ($LegacyDownloadLink) {
|
||||||
$DownloadLink = $LegacyDownloadLink
|
$DownloadLink = $LegacyDownloadLink
|
||||||
$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
|
$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
|
||||||
Say-Verbose "Legacy zip path: $ZipPath"
|
Say-Verbose "Legacy zip path: $ZipPath"
|
||||||
Say "Downloading legacy link: $DownloadLink"
|
Say "Downloading legacy link $DownloadLink"
|
||||||
try {
|
try {
|
||||||
DownloadFile -Source $DownloadLink -OutPath $ZipPath
|
DownloadFile -Source $DownloadLink -OutPath $ZipPath
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Say "Cannot download: $DownloadLink"
|
if ($PSItem.Exception.Data.Contains("StatusCode")) {
|
||||||
|
$LegacyDownloadStatusCode = $PSItem.Exception.Data["StatusCode"]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($PSItem.Exception.Data.Contains("ErrorMessage")) {
|
||||||
|
$LegacyDownloadFailedMsg = $PSItem.Exception.Data["ErrorMessage"]
|
||||||
|
} else {
|
||||||
|
$LegacyDownloadFailedMsg = $PSItem.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($LegacyDownloadStatusCode -eq 404) {
|
||||||
|
Say "The resource at $DownloadLink is not available."
|
||||||
|
} else {
|
||||||
|
Say $PSItem.Exception.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
SafeRemoveFile -Path $ZipPath
|
||||||
$DownloadFailed = $true
|
$DownloadFailed = $true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -743,7 +837,19 @@ catch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($DownloadFailed) {
|
if ($DownloadFailed) {
|
||||||
throw "Could not find/download: `"$assetName`" with version = $SpecificVersion`nRefer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support"
|
if (($PrimaryDownloadStatusCode -eq 404) -and ((-not $LegacyDownloadLink) -or ($LegacyDownloadStatusCode -eq 404))) {
|
||||||
|
throw "Could not find `"$assetName`" with version = $SpecificVersion`nRefer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support"
|
||||||
|
} else {
|
||||||
|
# 404-NotFound is an expected response if it goes from only one of the links, do not show that error.
|
||||||
|
# If primary path is available (not 404-NotFound) then show the primary error else show the legacy error.
|
||||||
|
if ($PrimaryDownloadStatusCode -ne 404) {
|
||||||
|
throw "Could not download `"$assetName`" with version = $SpecificVersion`r`n$PrimaryDownloadFailedMsg"
|
||||||
|
}
|
||||||
|
if (($LegacyDownloadLink) -and ($LegacyDownloadStatusCode -ne 404)) {
|
||||||
|
throw "Could not download `"$assetName`" with version = $SpecificVersion`r`n$LegacyDownloadFailedMsg"
|
||||||
|
}
|
||||||
|
throw "Could not download `"$assetName`" with version = $SpecificVersion"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Say "Extracting zip from $DownloadLink"
|
Say "Extracting zip from $DownloadLink"
|
||||||
@@ -765,21 +871,24 @@ if (!$isAssetInstalled) {
|
|||||||
$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion
|
$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm.
|
||||||
if (!$isAssetInstalled) {
|
if (!$isAssetInstalled) {
|
||||||
|
Say-Error "Failed to verify the version of installed `"$assetName`".`nInstallation source: $DownloadLink.`nInstallation location: $InstallRoot.`nReport the bug at https://github.com/dotnet/install-scripts/issues."
|
||||||
throw "`"$assetName`" with version = $SpecificVersion failed to install with an unknown error."
|
throw "`"$assetName`" with version = $SpecificVersion failed to install with an unknown error."
|
||||||
}
|
}
|
||||||
|
|
||||||
Remove-Item $ZipPath
|
SafeRemoveFile -Path $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
|
|
||||||
# SIG # Begin signature block
|
# SIG # Begin signature block
|
||||||
# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor
|
# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor
|
||||||
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
|
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
|
||||||
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAdMJOqDPFy5F1i
|
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD2c707qnCLOLIC
|
||||||
# HBXPyOE4hGkUv5EGyQzmS901lRr+baCCDYEwggX/MIID56ADAgECAhMzAAABh3IX
|
# n6Mu5Gr4+Xp68foyZlGlTycnycc5l6CCDYEwggX/MIID56ADAgECAhMzAAABh3IX
|
||||||
# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
|
# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
|
||||||
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
|
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
|
||||||
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
|
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
|
||||||
@@ -856,50 +965,50 @@ exit 0
|
|||||||
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
|
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
|
||||||
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN
|
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN
|
||||||
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
|
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
|
||||||
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgGfshXxhl
|
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgE/MRhWyu
|
||||||
# 7+O9cl90lOU62gZCBmJzcomUxEL8+XyoDYQwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
|
# Zg+EA2WKcxYC31nHVCTE6guHppZppc70RtkwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
|
||||||
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
|
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
|
||||||
# BgkqhkiG9w0BAQEFAASCAQCPVhcZxxdIzkFdrv/FCW737QgR8fCO1/oRXwhigOyQ
|
# BgkqhkiG9w0BAQEFAASCAQBvcYCjRDXUYEIz9j2j0r4GFI2Y3g/CoNxDDBaeQ+gV
|
||||||
# P2MF39fIYsVXuzVnO8pYZZOeW04kMECcWf9420okd4lXP7Xc5m+5UrqPuN1UgNle
|
# khO0fK0oLh18RbV271Mg6SF7X7+mXB5MnL68voVQDqHnsCYrIAuMF/AEpv9YuDDp
|
||||||
# hhwLBiXuZaAfllBMWMeQi7DZmg7XW8Yay9TAbc2XSTGQ8foDxPllKFbdPvvQ2DRy
|
# ZRJuqN7Vwg3HM02l/FyATBIMgf/V79aYzJL3jjtt9bRIyxk6aPU4XcwMeA4usnUQ
|
||||||
# VRLyNNQQEo3IuHHa0nnVNaL2PUYJf0udMCdGkxIMbApAYcitJLSwMLqMzrMkrvS9
|
# rMhIiQz07DgfSrcQWe4AvGFAIvqTAKE4P944EZWWVnWI/10rvatEAefqJZX3XljW
|
||||||
# ubm7CgigsKRJ3cZtCtFFMUkMsstoVuKLFtu69OvOfgLy1qmKotE6EnF7xudV+qAA
|
# sK/6NY/0MyAyiILOuXbvVS0YFbHaR2qd1jUXbrY79fS+H4Ts6qnbufOkHQvmcDxs
|
||||||
# a+UxGVT715tK5kgb5eTr1K2NdWRj517oANQNOjR/m6OPoYIS8TCCEu0GCisGAQQB
|
# 801wKLHumMdPTtMVzfVMCwPvrHP0wtzsFlmCcKjBbGpvoYIS8TCCEu0GCisGAQQB
|
||||||
# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME
|
# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME
|
||||||
# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB
|
# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB
|
||||||
# MDEwDQYJYIZIAWUDBAIBBQAEIHYNJoLIl+IWj/Npb6r479Guw3UW/q0/jJhqKgHm
|
# MDEwDQYJYIZIAWUDBAIBBQAEINdeoXtuzW+Dihw6n+VdG+91si0f6TvWhJXaPtvW
|
||||||
# xq1NAgZfdIY1B90YEzIwMjAxMDE0MTcxOTIwLjg5NVowBIACAfSggdSkgdEwgc4x
|
# oF4cAgZfu+i3IT8YEzIwMjAxMjE3MDYzMDM2LjU0M1owBIACAfSggdSkgdEwgc4x
|
||||||
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
|
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
|
||||||
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p
|
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p
|
||||||
# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg
|
# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg
|
||||||
# VFNTIEVTTjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
|
# VFNTIEVTTjo4OTdBLUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
|
||||||
# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJt+6SyK5goIHAAAA
|
# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABLCKvRZd1+RvuAAAA
|
||||||
# AAEmMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
|
# AAEsMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
|
||||||
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
|
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
|
||||||
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
|
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
|
||||||
# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT
|
# MB4XDTE5MTIxOTAxMTUwM1oXDTIxMDMxNzAxMTUwM1owgc4xCzAJBgNVBAYTAlVT
|
||||||
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
|
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
|
||||||
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
|
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
|
||||||
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2MEJD
|
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4OTdB
|
||||||
# LUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
|
# LUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
|
||||||
# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ4wvoacTvMNlXQTtfF/
|
# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPK1zgSSq+MxAYo3qpCt
|
||||||
# Cx5Ol3X0fcjUNMvjLgTmO5+WHYJFbp725P3+qvFKDRQHWEI1Sz0gB24urVDIjXjB
|
# QDxSMPPJy6mm/wfEJNjNUnYtLFBwl1BUS5trEk/t41ldxITKehs+ABxYqo4Qxsg3
|
||||||
# h5NVNJVMQJI2tltv7M4/4IbhZJb3xzQW7LolEoZYUZanBTUuyly9osCg4o5joViT
|
# Gy1ugKiwHAnYiiekfC+ZhptNFgtnDZIn45zC0AlVr/6UfLtsLcHCh1XElLUHfEC0
|
||||||
# 2GtmyxK+Fv5kC20l2opeaeptd/E7ceDAFRM87hiNCsK/KHyC+8+swnlg4gTOey6z
|
# nBuQcM/SpYo9e3l1qY5NdMgDGxCsmCKdiZfYXIu+U0UYIBhdzmSHnB3fxZOBVcr5
|
||||||
# QqhzgNsG6HrjLBuDtDs9izAMwS2yWT0T52QA9h3Q+B1C9ps2fMKMe+DHpG+0c61D
|
# htFHEBBNt/rFJlm/A4yb8oBsp+Uf0p5QwmO/bCcdqB15JpylOhZmWs0sUfJKlK9E
|
||||||
# 94Yh6cV2XHib4SBCnwIFZAeZE2UJ4qPANSYozI8PH+E5rCT3SVqYvHou97HsXvP2
|
# rAhBwGki2eIRFKsQBdkXS9PWpF1w2gIJRvSkDEaCf+lbGTPdSzHSbfREWOF9wY3i
|
||||||
# I3MCAwEAAaOCARswggEXMB0GA1UdDgQWBBRJq6wfF7B+mEKN0VimX8ajNA5hQTAf
|
# Yj8CAwEAAaOCARswggEXMB0GA1UdDgQWBBRRahZSGfrCQhCyIyGH9DkiaW7L0zAf
|
||||||
# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH
|
# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH
|
||||||
# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU
|
# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU
|
||||||
# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF
|
# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF
|
||||||
# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0
|
# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0
|
||||||
# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG
|
# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG
|
||||||
# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBAlvudaOlv9Cfzv56bnX41czF6tLtH
|
# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBPFxHIwi4vAH49w9Svmz6K3tM55RlW
|
||||||
# LB46l6XUch+qNN45ZmOTFwLot3JjwSrn4oycQ9qTET1TFDYd1QND0LiXmKz9OqBX
|
# 5pPeULXdut2Rqy6Ys0+VpZsbuaEoxs6Z1C3hMbkiqZFxxyltxJpuHTyGTg61zfNI
|
||||||
# ai6S8XdyCQEZvfL82jIAs9pwsAQ6XvV9jNybPStRgF/sOAM/Deyfmej9Tg9FcRwX
|
# F5n6RsYF3s7IElDXNfZznF1/2iWc6uRPZK8rxxUJ/7emYXZCYwuUY0XjsCpP9pbR
|
||||||
# ank2qgzdZZNb8GoEze7f1orcTF0Q89IUXWIlmwEwQFYF1wjn87N4ZxL9Z/xA2m/R
|
# RKeJi6r5arSyI+NfKxvgoM21JNt1BcdlXuAecdd/k8UjxCscffanoK2n6LFw1PcZ
|
||||||
# 1zizFylWP/mpamCnVfZZLkafFLNUNVmcvc+9gM7vceJs37d3ydabk4wR6ObR34sW
|
# lEO7NId7o+soM2C0QY5BYdghpn7uqopB6ixyFIIkDXFub+1E7GmAEwfU6VwEHL7y
|
||||||
# aLppmyPlsI1Qq5Lu6bJCWoXzYuWpkoK6oEep1gML6SRC3HKVS3UscZhtMIIGcTCC
|
# 9rNE8bd+JrQs+yAtkkHy9FmXg/PsGq1daVzX1So7CJ6nyphpuHSN3VfTMIIGcTCC
|
||||||
# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC
|
# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC
|
||||||
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
|
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
|
||||||
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv
|
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv
|
||||||
@@ -937,33 +1046,33 @@ exit 0
|
|||||||
# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT
|
# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT
|
||||||
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
|
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
|
||||||
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP
|
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP
|
||||||
# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2
|
# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4
|
||||||
# MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
|
# OTdBLUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
|
||||||
# dmljZaIjCgEBMAcGBSsOAwIaAxUACmcyOWmZxErpq06B8dy6oMZ6//yggYMwgYCk
|
# dmljZaIjCgEBMAcGBSsOAwIaAxUADE5OKSMoNx/mYxYWap1RTOohbJ2ggYMwgYCk
|
||||||
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
|
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
|
||||||
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
|
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
|
||||||
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
|
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
|
||||||
# AOMxeOgwIhgPMjAyMDEwMTQxNzE3MjhaGA8yMDIwMTAxNTE3MTcyOFowdzA9Bgor
|
# AOOFYaowIhgPMjAyMDEyMTcwODQ4NDJaGA8yMDIwMTIxODA4NDg0MlowdzA9Bgor
|
||||||
# BgEEAYRZCgQBMS8wLTAKAgUA4zF46AIBADAKAgEAAgIQPAIB/zAHAgEAAgIRZDAK
|
# BgEEAYRZCgQBMS8wLTAKAgUA44VhqgIBADAKAgEAAgIoWgIB/zAHAgEAAgISJTAK
|
||||||
# AgUA4zLKaAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
|
# AgUA44azKgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
|
||||||
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBALEDKhtH6no+VBWb
|
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAB53NDoDDF4vqFWY
|
||||||
# KHscN3Q0bphy1tgMhLZ0UBYpPSgcrPnF36tX3nswRAci3gLdgc77hjn2Zc6UyVJk
|
# fwUnSvAy3z0CtqSFeA9RzDKGklPRwVkya5DtmVBDTZUbVQ2ST9hvRAVxhktfyVBZ
|
||||||
# WhFguWv6KoyTunGPejS/fPIGKm1CXQnEV/JUvt1EAf7YRpHImfjZBhNXbVyV61gy
|
# ewapGJsvwMhg7nnEqBOumt6TvueIZpbs+p5z//3+iFYGkT3YFQI0Gd2JkvgBxfs5
|
||||||
# fEGA6fNNgbI+57xQJCZqdKBYX3EFMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC
|
# +GptO6JKtiyA+zkKijxqXZvMqMxBMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC
|
||||||
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
|
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
|
||||||
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
|
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
|
||||||
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEm37pLIrmCggcAAAAAASYwDQYJYIZIAWUD
|
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEsIq9Fl3X5G+4AAAAAASwwDQYJYIZIAWUD
|
||||||
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
|
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
|
||||||
# CQQxIgQgmfmj5y7wRFTyeI0TaXaljaCJoRQMvGBEAXsAQuY3ZOcwgfoGCyqGSIb3
|
# CQQxIgQg3wEUtEvxwCp3aAFB2vGXOOqg/AXHyXZh9P9J+0uArDMwgfoGCyqGSIb3
|
||||||
# DQEJEAIvMYHqMIHnMIHkMIG9BCA2/c/vnr1ecAzvapOWZ2xGfAkzrkfpGcrvMW07
|
# DQEJEAIvMYHqMIHnMIHkMIG9BCBbn/0uFFh42hTM5XOoKdXevBaiSxmYK9Ilcn9n
|
||||||
# CQl1DzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
|
# u5ZH4TCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
|
||||||
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
|
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
|
||||||
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
|
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
|
||||||
# Jt+6SyK5goIHAAAAAAEmMCIEIJ7sOcZ9sNFABAvIMRs2kk0cZhB239DZbXCLYMT8
|
# LCKvRZd1+RvuAAAAAAEsMCIEINBRtGID6jvA2ptfwIuPyG7qPcLRYb9YrJ8aKfVg
|
||||||
# frPMMA0GCSqGSIb3DQEBCwUABIIBAEiXebYdQ9DIz74YpfQ9FBaLHiSfD3s+jO7x
|
# TulFMA0GCSqGSIb3DQEBCwUABIIBACQQpFGWW6JmH5MTKwhaE/8+gyzI2bT8XJnA
|
||||||
# 1noNe0HIdZaX/Asow0OqsEMzZanOpa3yO8BJskKoDJW9pU//xqCzV1W5FzoOT4Qs
|
# t8k7PHFvEGA7whgp9eNgW+wWJm1gnsmswjx2l7FW4DLg9lghM8FK77JRCg7CJfse
|
||||||
# ZJpG0R5f/eHqMMeRBVUPn1FfT4pQVcHfRHOW/I3hWC0G4SeVwU/L9d8JLSQKzl39
|
# dSbnTv81/4VhSXOAO0jMP2dALP7DF59vQmlDh50u8/Wu61ActMOt6cArkoUhBRXO
|
||||||
# 8bMFbtLJWxUJMM4Vp8Tf+cR7ShZdsK9w88QokR9xbuQgn6jsqhOuyw+dUGrwEI7h
|
# LnqOQCOEEku5Xy2ES9g9eUfLUvTvlWo6HiAq+cJnNV08QRBOnGWRxdwy8YJ5vwNW
|
||||||
# GCdUmsT614oSgdnuUBf/g1aew0e3ulmZYYQ2QLKqnDXuqUIFnPtWFB90h++mdlFg
|
# Pwx0ZG3rTvMtGzOaW6Ve5O36H2ynoEdzCmpakeDaF2sZ86/LNERKyIXiykV/Uig1
|
||||||
# fvIEusNgYkb2kl5xQfxm3wynbxtP249vWF4GACZtqqSj3tcQ+xQ=
|
# SZh2VLY/Yni9SCVHbYgvTOCh5ZZE5eOi6BwLf0T4xl5alHUx+AA=
|
||||||
# SIG # End signature block
|
# SIG # End signature block
|
||||||
|
|||||||
276
src/Misc/dotnet-install.sh
vendored
276
src/Misc/dotnet-install.sh
vendored
@@ -40,7 +40,7 @@ if [ -t 1 ] && command -v tput > /dev/null; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
say_warning() {
|
say_warning() {
|
||||||
printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}"
|
printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3
|
||||||
}
|
}
|
||||||
|
|
||||||
say_err() {
|
say_err() {
|
||||||
@@ -183,6 +183,9 @@ get_current_os_name() {
|
|||||||
elif is_musl_based_distro; then
|
elif is_musl_based_distro; then
|
||||||
echo "linux-musl"
|
echo "linux-musl"
|
||||||
return 0
|
return 0
|
||||||
|
elif [ "$linux_platform_name" = "linux-musl" ]; then
|
||||||
|
echo "linux-musl"
|
||||||
|
return 0
|
||||||
else
|
else
|
||||||
echo "linux"
|
echo "linux"
|
||||||
return 0
|
return 0
|
||||||
@@ -241,42 +244,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() {
|
||||||
@@ -332,7 +299,7 @@ get_machine_architecture() {
|
|||||||
if command -v uname > /dev/null; then
|
if command -v uname > /dev/null; then
|
||||||
CPUName=$(uname -m)
|
CPUName=$(uname -m)
|
||||||
case $CPUName in
|
case $CPUName in
|
||||||
armv7l)
|
armv*l)
|
||||||
echo "arm"
|
echo "arm"
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
@@ -377,6 +344,30 @@ get_normalized_architecture_from_architecture() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# args:
|
||||||
|
# user_defined_os - $1
|
||||||
|
get_normalized_os() {
|
||||||
|
eval $invocation
|
||||||
|
|
||||||
|
local osname="$(to_lowercase "$1")"
|
||||||
|
if [ ! -z "$osname" ]; then
|
||||||
|
case "$osname" in
|
||||||
|
osx | freebsd | rhel.6 | linux-musl | linux)
|
||||||
|
echo "$osname"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
osname="$(get_current_os_name)" || return 1
|
||||||
|
fi
|
||||||
|
echo "$osname"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# The version text returned from the feeds is a 1-line or 2-line string:
|
# The version text returned from the feeds is a 1-line or 2-line string:
|
||||||
# For the SDK and the dotnet runtime (2 lines):
|
# For the SDK and the dotnet runtime (2 lines):
|
||||||
# Line 1: # commit_hash
|
# Line 1: # commit_hash
|
||||||
@@ -418,14 +409,12 @@ is_dotnet_package_installed() {
|
|||||||
# azure_feed - $1
|
# azure_feed - $1
|
||||||
# channel - $2
|
# channel - $2
|
||||||
# normalized_architecture - $3
|
# normalized_architecture - $3
|
||||||
# coherent - $4
|
|
||||||
get_latest_version_info() {
|
get_latest_version_info() {
|
||||||
eval $invocation
|
eval $invocation
|
||||||
|
|
||||||
local azure_feed="$1"
|
local azure_feed="$1"
|
||||||
local channel="$2"
|
local channel="$2"
|
||||||
local normalized_architecture="$3"
|
local normalized_architecture="$3"
|
||||||
local coherent="$4"
|
|
||||||
|
|
||||||
local version_file_url=null
|
local version_file_url=null
|
||||||
if [[ "$runtime" == "dotnet" ]]; then
|
if [[ "$runtime" == "dotnet" ]]; then
|
||||||
@@ -433,11 +422,7 @@ get_latest_version_info() {
|
|||||||
elif [[ "$runtime" == "aspnetcore" ]]; then
|
elif [[ "$runtime" == "aspnetcore" ]]; then
|
||||||
version_file_url="$uncached_feed/aspnetcore/Runtime/$channel/latest.version"
|
version_file_url="$uncached_feed/aspnetcore/Runtime/$channel/latest.version"
|
||||||
elif [ -z "$runtime" ]; then
|
elif [ -z "$runtime" ]; then
|
||||||
if [ "$coherent" = true ]; then
|
version_file_url="$uncached_feed/Sdk/$channel/latest.version"
|
||||||
version_file_url="$uncached_feed/Sdk/$channel/latest.coherent.version"
|
|
||||||
else
|
|
||||||
version_file_url="$uncached_feed/Sdk/$channel/latest.version"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
say_err "Invalid value for \$runtime"
|
say_err "Invalid value for \$runtime"
|
||||||
return 1
|
return 1
|
||||||
@@ -468,7 +453,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
|
||||||
@@ -505,26 +489,16 @@ get_specific_version_from_version() {
|
|||||||
local json_file="$5"
|
local json_file="$5"
|
||||||
|
|
||||||
if [ -z "$json_file" ]; then
|
if [ -z "$json_file" ]; then
|
||||||
case "$version" in
|
if [[ "$version" == "latest" ]]; then
|
||||||
latest)
|
local version_info
|
||||||
local version_info
|
version_info="$(get_latest_version_info "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1
|
||||||
version_info="$(get_latest_version_info "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1
|
say_verbose "get_specific_version_from_version: version_info=$version_info"
|
||||||
say_verbose "get_specific_version_from_version: version_info=$version_info"
|
echo "$version_info" | get_version_from_version_info
|
||||||
echo "$version_info" | get_version_from_version_info
|
return 0
|
||||||
return 0
|
else
|
||||||
;;
|
echo "$version"
|
||||||
coherent)
|
return 0
|
||||||
local version_info
|
fi
|
||||||
version_info="$(get_latest_version_info "$azure_feed" "$channel" "$normalized_architecture" true)" || return 1
|
|
||||||
say_verbose "get_specific_version_from_version: version_info=$version_info"
|
|
||||||
echo "$version_info" | get_version_from_version_info
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "$version"
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
else
|
else
|
||||||
local version_info
|
local version_info
|
||||||
version_info="$(parse_jsonfile_for_version "$json_file")" || return 1
|
version_info="$(parse_jsonfile_for_version "$json_file")" || return 1
|
||||||
@@ -538,6 +512,7 @@ get_specific_version_from_version() {
|
|||||||
# channel - $2
|
# channel - $2
|
||||||
# normalized_architecture - $3
|
# normalized_architecture - $3
|
||||||
# specific_version - $4
|
# specific_version - $4
|
||||||
|
# normalized_os - $5
|
||||||
construct_download_link() {
|
construct_download_link() {
|
||||||
eval $invocation
|
eval $invocation
|
||||||
|
|
||||||
@@ -546,10 +521,8 @@ construct_download_link() {
|
|||||||
local normalized_architecture="$3"
|
local normalized_architecture="$3"
|
||||||
local specific_version="${4//[$'\t\r\n']}"
|
local specific_version="${4//[$'\t\r\n']}"
|
||||||
local specific_product_version="$(get_specific_product_version "$1" "$4")"
|
local specific_product_version="$(get_specific_product_version "$1" "$4")"
|
||||||
|
local osname="$5"
|
||||||
local osname
|
|
||||||
osname="$(get_current_os_name)" || return 1
|
|
||||||
|
|
||||||
local download_link=null
|
local download_link=null
|
||||||
if [[ "$runtime" == "dotnet" ]]; then
|
if [[ "$runtime" == "dotnet" ]]; then
|
||||||
download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
|
download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
|
||||||
@@ -588,14 +561,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']}"
|
||||||
|
|
||||||
@@ -723,11 +702,31 @@ extract_dotnet_package() {
|
|||||||
find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files"
|
find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files"
|
||||||
|
|
||||||
rm -rf "$temp_out_path"
|
rm -rf "$temp_out_path"
|
||||||
|
rm -f "$zip_path" && say_verbose "Temporary zip file $zip_path was removed"
|
||||||
|
|
||||||
if [ "$failed" = true ]; then
|
if [ "$failed" = true ]; then
|
||||||
say_err "Extraction failed"
|
say_err "Extraction failed"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get_http_header_curl() {
|
||||||
|
eval $invocation
|
||||||
|
local remote_path="$1"
|
||||||
|
remote_path_with_credential="${remote_path}${feed_credential}"
|
||||||
|
curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 "
|
||||||
|
curl $curl_options "$remote_path_with_credential" || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get_http_header_wget() {
|
||||||
|
eval $invocation
|
||||||
|
local remote_path="$1"
|
||||||
|
remote_path_with_credential="${remote_path}${feed_credential}"
|
||||||
|
wget_options="-q -S --spider --tries 5 --waitretry 2 --connect-timeout 15 "
|
||||||
|
wget $wget_options "$remote_path_with_credential" 2>&1 || return 1
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# args:
|
# args:
|
||||||
@@ -759,44 +758,56 @@ download() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Updates global variables $http_code and $download_error_msg
|
||||||
downloadcurl() {
|
downloadcurl() {
|
||||||
eval $invocation
|
eval $invocation
|
||||||
local remote_path="$1"
|
local remote_path="$1"
|
||||||
local out_path="${2:-}"
|
local out_path="${2:-}"
|
||||||
|
|
||||||
# Append feed_credential as late as possible before calling curl to avoid logging feed_credential
|
# Append feed_credential as late as possible before calling curl to avoid logging feed_credential
|
||||||
remote_path="${remote_path}${feed_credential}"
|
local remote_path_with_credential="${remote_path}${feed_credential}"
|
||||||
|
|
||||||
local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs "
|
local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs "
|
||||||
local failed=false
|
local failed=false
|
||||||
if [ -z "$out_path" ]; then
|
if [ -z "$out_path" ]; then
|
||||||
curl $curl_options "$remote_path" || failed=true
|
curl $curl_options "$remote_path_with_credential" || failed=true
|
||||||
else
|
else
|
||||||
curl $curl_options -o "$out_path" "$remote_path" || failed=true
|
curl $curl_options -o "$out_path" "$remote_path_with_credential" || failed=true
|
||||||
fi
|
fi
|
||||||
if [ "$failed" = true ]; then
|
if [ "$failed" = true ]; then
|
||||||
say_verbose "Curl download failed"
|
local response=$(get_http_header_curl $remote_path_with_credential)
|
||||||
|
http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 )
|
||||||
|
download_error_msg="Unable to download $remote_path."
|
||||||
|
if [[ $http_code != 2* ]]; then
|
||||||
|
download_error_msg+=" Returned HTTP status code: $http_code."
|
||||||
|
fi
|
||||||
|
say_verbose "$download_error_msg"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Updates global variables $http_code and $download_error_msg
|
||||||
downloadwget() {
|
downloadwget() {
|
||||||
eval $invocation
|
eval $invocation
|
||||||
local remote_path="$1"
|
local remote_path="$1"
|
||||||
local out_path="${2:-}"
|
local out_path="${2:-}"
|
||||||
|
|
||||||
# Append feed_credential as late as possible before calling wget to avoid logging feed_credential
|
# Append feed_credential as late as possible before calling wget to avoid logging feed_credential
|
||||||
remote_path="${remote_path}${feed_credential}"
|
local remote_path_with_credential="${remote_path}${feed_credential}"
|
||||||
local wget_options="--tries 20 --waitretry 2 --connect-timeout 15 "
|
local wget_options="--tries 20 --waitretry 2 --connect-timeout 15 "
|
||||||
local failed=false
|
local failed=false
|
||||||
if [ -z "$out_path" ]; then
|
if [ -z "$out_path" ]; then
|
||||||
wget -q $wget_options -O - "$remote_path" || failed=true
|
wget -q $wget_options -O - "$remote_path_with_credential" || failed=true
|
||||||
else
|
else
|
||||||
wget $wget_options -O "$out_path" "$remote_path" || failed=true
|
wget $wget_options -O "$out_path" "$remote_path_with_credential" || failed=true
|
||||||
fi
|
fi
|
||||||
if [ "$failed" = true ]; then
|
if [ "$failed" = true ]; then
|
||||||
say_verbose "Wget download failed"
|
local response=$(get_http_header_wget $remote_path_with_credential)
|
||||||
|
http_code=$( echo "$response" | awk '/^ HTTP/{print $2}' | tail -1 )
|
||||||
|
download_error_msg="Unable to download $remote_path."
|
||||||
|
if [[ $http_code != 2* ]]; then
|
||||||
|
download_error_msg+=" Returned HTTP status code: $http_code."
|
||||||
|
fi
|
||||||
|
say_verbose "$download_error_msg"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
@@ -809,6 +820,9 @@ calculate_vars() {
|
|||||||
normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")"
|
normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")"
|
||||||
say_verbose "normalized_architecture=$normalized_architecture"
|
say_verbose "normalized_architecture=$normalized_architecture"
|
||||||
|
|
||||||
|
normalized_os="$(get_normalized_os "$user_defined_os")"
|
||||||
|
say_verbose "normalized_os=$normalized_os"
|
||||||
|
|
||||||
specific_version="$(get_specific_version_from_version "$azure_feed" "$channel" "$normalized_architecture" "$version" "$json_file")"
|
specific_version="$(get_specific_version_from_version "$azure_feed" "$channel" "$normalized_architecture" "$version" "$json_file")"
|
||||||
specific_product_version="$(get_specific_product_version "$azure_feed" "$specific_version")"
|
specific_product_version="$(get_specific_product_version "$azure_feed" "$specific_version")"
|
||||||
say_verbose "specific_version=$specific_version"
|
say_verbose "specific_version=$specific_version"
|
||||||
@@ -817,7 +831,7 @@ calculate_vars() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
download_link="$(construct_download_link "$azure_feed" "$channel" "$normalized_architecture" "$specific_version")"
|
download_link="$(construct_download_link "$azure_feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")"
|
||||||
say_verbose "Constructed primary named payload URL: $download_link"
|
say_verbose "Constructed primary named payload URL: $download_link"
|
||||||
|
|
||||||
legacy_download_link="$(construct_legacy_download_link "$azure_feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false
|
legacy_download_link="$(construct_legacy_download_link "$azure_feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false
|
||||||
@@ -862,38 +876,76 @@ install_dotnet() {
|
|||||||
zip_path="$(mktemp "$temporary_file_template")"
|
zip_path="$(mktemp "$temporary_file_template")"
|
||||||
say_verbose "Zip path: $zip_path"
|
say_verbose "Zip path: $zip_path"
|
||||||
|
|
||||||
say "Downloading link: $download_link"
|
|
||||||
|
|
||||||
# Failures are normal in the non-legacy case for ultimately legacy downloads.
|
# Failures are normal in the non-legacy case for ultimately legacy downloads.
|
||||||
# Do not output to stderr, since output to stderr is considered an error.
|
# Do not output to stderr, since output to stderr is considered an error.
|
||||||
|
say "Downloading primary link $download_link"
|
||||||
|
|
||||||
|
# The download function will set variables $http_code and $download_error_msg in case of failure.
|
||||||
|
http_code=""; download_error_msg=""
|
||||||
download "$download_link" "$zip_path" 2>&1 || download_failed=true
|
download "$download_link" "$zip_path" 2>&1 || download_failed=true
|
||||||
|
primary_path_http_code="$http_code"; primary_path_download_error_msg="$download_error_msg"
|
||||||
|
|
||||||
# if the download fails, download the legacy_download_link
|
# if the download fails, download the legacy_download_link
|
||||||
if [ "$download_failed" = true ]; then
|
if [ "$download_failed" = true ]; then
|
||||||
say "Cannot download: $download_link"
|
case $primary_path_http_code in
|
||||||
|
404)
|
||||||
|
say "The resource at $download_link is not available."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
say "$primary_path_download_error_msg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed"
|
||||||
if [ "$valid_legacy_download_link" = true ]; then
|
if [ "$valid_legacy_download_link" = true ]; then
|
||||||
download_failed=false
|
download_failed=false
|
||||||
download_link="$legacy_download_link"
|
download_link="$legacy_download_link"
|
||||||
zip_path="$(mktemp "$temporary_file_template")"
|
zip_path="$(mktemp "$temporary_file_template")"
|
||||||
say_verbose "Legacy zip path: $zip_path"
|
say_verbose "Legacy zip path: $zip_path"
|
||||||
say "Downloading legacy link: $download_link"
|
|
||||||
|
say "Downloading legacy link $download_link"
|
||||||
|
|
||||||
|
# The download function will set variables $http_code and $download_error_msg in case of failure.
|
||||||
|
http_code=""; download_error_msg=""
|
||||||
download "$download_link" "$zip_path" 2>&1 || download_failed=true
|
download "$download_link" "$zip_path" 2>&1 || download_failed=true
|
||||||
|
legacy_path_http_code="$http_code"; legacy_path_download_error_msg="$download_error_msg"
|
||||||
|
|
||||||
if [ "$download_failed" = true ]; then
|
if [ "$download_failed" = true ]; then
|
||||||
say "Cannot download: $download_link"
|
case $legacy_path_http_code in
|
||||||
|
404)
|
||||||
|
say "The resource at $download_link is not available."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
say "$legacy_path_download_error_msg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$download_failed" = true ]; then
|
if [ "$download_failed" = true ]; then
|
||||||
say_err "Could not find/download: \`$asset_name\` with version = $specific_version"
|
if [[ "$primary_path_http_code" = "404" && ( "$valid_legacy_download_link" = false || "$legacy_path_http_code" = "404") ]]; then
|
||||||
say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support"
|
say_err "Could not find \`$asset_name\` with version = $specific_version"
|
||||||
|
say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support"
|
||||||
|
else
|
||||||
|
say_err "Could not download: \`$asset_name\` with version = $specific_version"
|
||||||
|
# 404-NotFound is an expected response if it goes from only one of the links, do not show that error.
|
||||||
|
# If primary path is available (not 404-NotFound) then show the primary error else show the legacy error.
|
||||||
|
if [ "$primary_path_http_code" != "404" ]; then
|
||||||
|
say_err "$primary_path_download_error_msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ "$valid_legacy_download_link" = true && "$legacy_path_http_code" != "404" ]]; then
|
||||||
|
say_err "$legacy_path_download_error_msg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
say "Extracting zip from $download_link"
|
say "Extracting zip from $download_link"
|
||||||
extract_dotnet_package "$zip_path" "$install_root"
|
extract_dotnet_package "$zip_path" "$install_root" || return 1
|
||||||
|
|
||||||
# Check if the SDK version is installed; if not, fail the installation.
|
# Check if the SDK version is installed; if not, fail the installation.
|
||||||
# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
|
# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
|
||||||
@@ -914,6 +966,8 @@ install_dotnet() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm.
|
||||||
|
say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues."
|
||||||
say_err "\`$asset_name\` with version = $specific_product_version failed to install with an unknown error."
|
say_err "\`$asset_name\` with version = $specific_product_version failed to install with an unknown error."
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -940,6 +994,7 @@ runtime=""
|
|||||||
runtime_id=""
|
runtime_id=""
|
||||||
override_non_versioned_files=true
|
override_non_versioned_files=true
|
||||||
non_dynamic_parameters=""
|
non_dynamic_parameters=""
|
||||||
|
user_defined_os=""
|
||||||
|
|
||||||
while [ $# -ne 0 ]
|
while [ $# -ne 0 ]
|
||||||
do
|
do
|
||||||
@@ -961,6 +1016,10 @@ do
|
|||||||
shift
|
shift
|
||||||
architecture="$1"
|
architecture="$1"
|
||||||
;;
|
;;
|
||||||
|
--os|-[Oo][SS])
|
||||||
|
shift
|
||||||
|
user_defined_os="$1"
|
||||||
|
;;
|
||||||
--shared-runtime|-[Ss]hared[Rr]untime)
|
--shared-runtime|-[Ss]hared[Rr]untime)
|
||||||
say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'."
|
say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'."
|
||||||
if [ -z "$runtime" ]; then
|
if [ -z "$runtime" ]; then
|
||||||
@@ -1012,6 +1071,7 @@ do
|
|||||||
shift
|
shift
|
||||||
runtime_id="$1"
|
runtime_id="$1"
|
||||||
non_dynamic_parameters+=" $name "\""$1"\"""
|
non_dynamic_parameters+=" $name "\""$1"\"""
|
||||||
|
say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead."
|
||||||
;;
|
;;
|
||||||
--jsonfile|-[Jj][Ss]on[Ff]ile)
|
--jsonfile|-[Jj][Ss]on[Ff]ile)
|
||||||
shift
|
shift
|
||||||
@@ -1044,8 +1104,6 @@ do
|
|||||||
echo " -Version"
|
echo " -Version"
|
||||||
echo " Possible values:"
|
echo " Possible values:"
|
||||||
echo " - latest - most latest build on specific channel"
|
echo " - latest - most latest build on specific channel"
|
||||||
echo " - coherent - most latest coherent build on specific channel"
|
|
||||||
echo " coherent applies only to SDK downloads"
|
|
||||||
echo " - 3-part version in a format A.B.C - represents specific version of build"
|
echo " - 3-part version in a format A.B.C - represents specific version of build"
|
||||||
echo " examples: 2.0.0-preview2-006120; 1.1.0"
|
echo " examples: 2.0.0-preview2-006120; 1.1.0"
|
||||||
echo " -i,--install-dir <DIR> Install under specified location (see Install Location below)"
|
echo " -i,--install-dir <DIR> Install under specified location (see Install Location below)"
|
||||||
@@ -1053,6 +1111,11 @@ do
|
|||||||
echo " --architecture <ARCHITECTURE> Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`."
|
echo " --architecture <ARCHITECTURE> Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`."
|
||||||
echo " --arch,-Architecture,-Arch"
|
echo " --arch,-Architecture,-Arch"
|
||||||
echo " Possible values: x64, arm, and arm64"
|
echo " Possible values: x64, arm, and arm64"
|
||||||
|
echo " --os <system> Specifies operating system to be used when selecting the installer."
|
||||||
|
echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6."
|
||||||
|
echo " In case any other value is provided, the platform will be determined by the script based on machine configuration."
|
||||||
|
echo " Not supported for legacy links. Use --runtime-id to specify platform for legacy links."
|
||||||
|
echo " Refer to: https://aka.ms/dotnet-os-lifecycle for more information."
|
||||||
echo " --runtime <RUNTIME> Installs a shared runtime only, without the SDK."
|
echo " --runtime <RUNTIME> Installs a shared runtime only, without the SDK."
|
||||||
echo " -Runtime"
|
echo " -Runtime"
|
||||||
echo " Possible values:"
|
echo " Possible values:"
|
||||||
@@ -1069,14 +1132,15 @@ do
|
|||||||
echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly."
|
echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly."
|
||||||
echo " --jsonfile <JSONFILE> Determines the SDK version from a user specified global.json file."
|
echo " --jsonfile <JSONFILE> Determines the SDK version from a user specified global.json file."
|
||||||
echo " Note: global.json must have a value for 'SDK:Version'"
|
echo " Note: global.json must have a value for 'SDK:Version'"
|
||||||
echo " --runtime-id Installs the .NET Tools for the given platform (use linux-x64 for portable linux)."
|
|
||||||
echo " -RuntimeId"
|
|
||||||
echo " -?,--?,-h,--help,-Help Shows this help message"
|
echo " -?,--?,-h,--help,-Help Shows this help message"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Obsolete parameters:"
|
echo "Obsolete parameters:"
|
||||||
echo " --shared-runtime The recommended alternative is '--runtime dotnet'."
|
echo " --shared-runtime The recommended alternative is '--runtime dotnet'."
|
||||||
echo " This parameter is obsolete and may be removed in a future version of this script."
|
echo " This parameter is obsolete and may be removed in a future version of this script."
|
||||||
echo " Installs just the shared runtime bits, not the entire SDK."
|
echo " Installs just the shared runtime bits, not the entire SDK."
|
||||||
|
echo " --runtime-id Installs the .NET Tools for the given platform (use linux-x64 for portable linux)."
|
||||||
|
echo " -RuntimeId" The parameter is obsolete and may be removed in a future version of this script. Should be used only for versions below 2.1.
|
||||||
|
echo " For primary links to override OS or/and architecture, use --os and --architecture option instead."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Install Location:"
|
echo "Install Location:"
|
||||||
echo " Location is chosen in following order:"
|
echo " Location is chosen in following order:"
|
||||||
@@ -1098,6 +1162,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")
|
||||||
@@ -1108,7 +1177,7 @@ if [ "$dry_run" = true ]; then
|
|||||||
if [ "$valid_legacy_download_link" = true ]; then
|
if [ "$valid_legacy_download_link" = true ]; then
|
||||||
say "Legacy named payload URL: $legacy_download_link"
|
say "Legacy named payload URL: $legacy_download_link"
|
||||||
fi
|
fi
|
||||||
repeatable_command="./$script_name --version "\""$specific_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"""
|
repeatable_command="./$script_name --version "\""$specific_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\"""
|
||||||
if [[ "$runtime" == "dotnet" ]]; then
|
if [[ "$runtime" == "dotnet" ]]; then
|
||||||
repeatable_command+=" --runtime "\""dotnet"\"""
|
repeatable_command+=" --runtime "\""dotnet"\"""
|
||||||
elif [[ "$runtime" == "aspnetcore" ]]; then
|
elif [[ "$runtime" == "aspnetcore" ]]; then
|
||||||
@@ -1119,7 +1188,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 +1198,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}'");
|
||||||
|
|||||||
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)
|
||||||
@@ -263,6 +263,7 @@ namespace GitHub.Runner.Listener.Configuration
|
|||||||
{
|
{
|
||||||
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
{ "clientId", agent.Authorization.ClientId.ToString("D") },
|
||||||
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
|
||||||
|
{ "requireFipsCryptography", agent.Properties.GetValue("RequireFipsCryptography", false).ToString() }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -373,8 +374,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 +509,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(), StringUtil.ConvertToBoolean(CredentialData.Data.GetValueOrDefault("requireFipsCryptography"), false));
|
||||||
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,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)
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 +1 @@
|
|||||||
2.274.0
|
2.276.1
|
||||||
|
|||||||
Reference in New Issue
Block a user